Three.JS的第一个三弟(3D)案例

举报
不惑 发表于 2024/12/02 13:30:56 2024/12/02
【摘要】 默认 WebGL 只支持简单的 点、线、三角,Three.js 就是在此 WebGL 基础之上,封装出强大且使用起来简单的 JS 3D 类库。

Three.js简介概述

three.js是世界上最流行的用于在Web上显示3D内容的JavaScript框架。

image.png

Three.js概述

Three.js 是基于 WebGL 技术,用于浏览器中开发 3D 交互场景的 JS 引擎。

默认 WebGL 只支持简单的 点、线、三角,Three.js 就是在此 WebGL 基础之上,封装出强大且使用起来简单的 JS 3D 类库。
目前主流现代浏览器都已支持 WebGL,也意味着支持 Three.js。

Three.js优缺点

🟢 优点

  1. 基于WebGL技术:Three.js建立在WebGL之上,利用了浏览器的硬件加速能力,能够高效地进行3D渲染,实现流畅的交互体验。
  2. 易用性:相比直接使用原始的WebGL,Three.js提供了更高级的抽象和封装,使得开发者能够更轻松地创建复杂的3D场景,降低了学习和使用的门槛。
  3. 跨平台性:Three.js基于Web技术,能够在主流现代浏览器上运行,包括桌面端和移动端,实现了跨平台的兼容性。

🔴 缺点

  1. 不擅长物理碰撞:虽然Three.js能够实现复杂的3D场景渲染,但其并不擅长处理物理碰撞,这使得它在开发3D游戏等需要物理交互的应用时显得力不从心。
  2. 学习曲线:尽管相比原始的WebGL,Three.js提供了更高级的抽象和封装,但仍然需要一定的学习成本,特别是对于新手来说,需要掌握一定的3D图形学知识和API使用方法。
  3. 性能依赖于硬件:由于Three.js是基于WebGL技术的,其性能受限于用户设备的硬件性能,较低配置的设备可能无法实现流畅的渲染效果。
  4. 不适合大规模应用:虽然Three.js能够满足一般的3D场景需求,但在需要处理大规模数据或者复杂计算的情况下,可能会遇到性能瓶颈。

官网示例

image.png

Three.js应用场景

🔍 3D 可视化:Three.js 可以用于创建各种 3D 可视化应用,如数据可视化、科学可视化、工程可视化等。用户可以通过浏览器在线查看和操作 3D 模型,而无需安装任何插件或额外的软件。

🔍 虚拟现实和增强现实:Three.js 可以用于创建虚拟现实(VR)和增强现实(AR)应用,如游戏、教育、培训、设计等。用户可以通过 VR 设备和 AR 设备在 3D 空间中浏览和操作 3D 模型,获得更加沉浸式的体验。

🔍 动画和特效:Three.js 可以用于创建各种 3D 动画和特效,如电影、电视、游戏、广告等。用户可以通过浏览器在线观看和互动 3D 动画和特效,而无需安装任何插件或额外的软件。

🔍 游戏开发:Three.js 可以用于创建各种 3D 游戏,如角色扮演游戏、射击游戏、策略游戏等。用户可以通过浏览器在线玩 3D 游戏,而无需安装任何插件或额外的软件。

🔍 产品展示和演示:Three.js 可以用于创建各种 3D 产品展示和演示,如家具、汽车、电子产品等。用户可以通过浏览器在线查看和操作 3D 模型,了解产品的外观、性能和功能,提高购买决策的准确性。

🔍 建筑和城市规划:Three.js 可以用于创建各种 3D 建筑和城市规划应用,如房地产开发、城市规划、景观设计等。用户可以通过浏览器在线查看和操作 3D 模型,了解项目的设计理念和实施效果,提高决策的准确性。

Three.js技术名词

3大核心关键模块

🐔 场景(Scene)

场景是 Three.js 中的一个核心概念,它是所有 3D 对象的容器。场景可以包含几何体、光源、相机等,它们共同构成了一个完整的 3D 世界。在 Three.js 中,场景是通过 THREE.Scene 类来表示的。

🐔 相机(Camera)

相机是 Three.js 中的另一个核心概念,它负责捕捉 3D 世界中的对象,并将它们渲染到屏幕上。Three.js 提供了多种相机类型,如正交相机(THREE.OrthographicCamera)和透视相机(THREE.PerspectiveCamera),以满足不同的渲染需求。

🐔 渲染器(Renderer)

渲染器是 Three.js 中的另一个核心概念,它负责将 3D 世界中的对象渲染到屏幕上。Three.js 提供了多种渲染器类型,如 WebGL 渲染器(THREE.WebGLRenderer)和 Canvas 渲染器(THREE.CanvasRenderer),以满足不同的渲染需求。

以下是一个简单的 Three.js 示例,展示了如何创建一个场景、相机和渲染器:

// 创建场景
var scene = new THREE.Scene();

// 创建相机
var camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);

// 创建渲染器
var renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

// 渲染场景
function animate() {
    requestAnimationFrame(animate);
    renderer.render(scene, camera);
}
animate();

在这个示例中,我们创建了一个场景、一个透视相机和一个 WebGL 渲染器。然后,我们将渲染器的 DOM 元素添加到页面中,并使用 requestAnimationFrame 函数来实现动画效果。

其他技术关键词

💯 几何体(Geometry)

几何体是 Three.js 中的一个核心概念,它表示 3D 世界中的物体的形状。Three.js 提供了多种几何体类型,如立方体(THREE.BoxGeometry)、球体(THREE.SphereGeometry)、圆锥体(THREE.ConeGeometry)等。

💯 材质(Material)

材质是 Three.js 中的一个核心概念,它表示 3D 世界中的物体的表面特性,如颜色、纹理、光照等。Three.js 提供了多种材质类型,如基本材质(THREE.MeshBasicMaterial)、兰伯特材质(THREE.MeshLambertMaterial)、冯氏材质(THREE.MeshPhongMaterial)等。

💯 光源(Light)

光源是 Three.js 中的一个核心概念,它表示 3D 世界中的光源,可以对物体的表面产生影响。Three.js 提供了多种光源类型,如平行光(THREE.DirectionalLight)、点光源(THREE.PointLight)、聚光灯(THREE.SpotLight)等。

💯 网格(Mesh)

网格是 Three.js 中的一个核心概念,它表示 3D 世界中的物体,由几何体和材质组成。Three.js 提供了 THREE.Mesh 类来表示网格。

💯 纹理(Texture)

纹理是 Three.js 中的一个核心概念,它表示 3D 世界中的物体的表面贴图。Three.js 提供了多种纹理类型,如图片纹理(THREE.Texture)、立方体纹理(THREE.CubeTexture)、视频纹理(THREE.VideoTexture)等。

💯 动画(Animation)

动画是 Three.js 中的一个核心概念,它表示 3D 世界中的物体的运动和变化。Three.js 提供了多种动画类型,如骨骼动画(THREE.Skeleton)、变换动画(THREE.TransformControls)等。

💯 碰撞检测(Collision Detection)

碰撞检测是 Three.js 中的一个核心概念,它表示 3D 世界中的物体之间的碰撞和接触。Three.js 提供了多种碰撞检测算法,如轴对齐边界框(AABB)、球面边界框(Sphere)等。

以下是一个简单的 Three.js 示例,展示了如何创建一个几何体、材质和网格,并将其添加到场景中:

// 创建几何体
var geometry = new THREE.BoxGeometry(1, 1, 1);

// 创建材质
var material = new THREE.MeshBasicMaterial({ color: 0xff0000 });

// 创建网格
var mesh = new THREE.Mesh(geometry, material);

// 将网格添加到场景中
scene.add(mesh);

在这个示例中,我们创建了一个立方体几何体、一个基本材质和一个网格,并将其添加到场景中。然后,我们使用 requestAnimationFrame 函数来实现动画效果。

以上提到的所有关键词和概念,在后续学习过程中,逐个细致学习掌握。加油!!!

表演个小示例

👽 创建 index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <!-- 引入初始化样式 -->
    <link rel="stylesheet" href="./reset.css" />
  </head>
  <body>
    <!-- 使用模块化方式引入入口文件 -->
    <script src="./goboy.js" type="module"></script>
    <div id="stage"></div>
    <canvas id="text" width="700" height="200"></canvas>
    <input id="input" type="text" value="GoBoy IS ME"/>
  </body>
</html>

👽 创建 goboy.js

// 导入 THREE 库
import * as THREE from 'three';

// 定义变量
var height,
  width,
  container,
  scene,
  camera,
  renderer,
  particles = [],
  mouseVector = new THREE.Vector3(0, 0, 0),
  mousePos = new THREE.Vector3(0, 0, 0),
  cameraLookAt = new THREE.Vector3(0, 0, 0),
  cameraTarget = new THREE.Vector3(0, 0, 800), // 摄像机的目标位置
  textCanvas,
  textCtx,
  textPixels = [],
  input;

// 粒子的颜色数组
var colors = ['#F7A541', '#F45D4C', '#FA2E59', '#4783c3', '#9c6cb7'];

// 初始化舞台,设置宽度和高度,并添加事件监听器
function initStage() {
  width = window.innerWidth;
  height = window.innerHeight;
  container = document.getElementById('stage');
  window.addEventListener('resize', resize); // 监听窗口大小变化事件
  container.addEventListener('mousemove', mousemove); // 监听鼠标移动事件
}

// 初始化场景
function initScene() {
  scene = new THREE.Scene();
  renderer = new THREE.WebGLRenderer({
    alpha: true, // 设置透明度
    antialias: true // 开启抗锯齿
  });
  renderer.setPixelRatio(window.devicePixelRatio); // 设置像素比
  renderer.setSize(width, height); // 设置渲染器大小
  container.appendChild(renderer.domElement); // 将渲染器添加到 DOM 中
}

// 随机生成粒子的初始位置
function randomPos(vector) {
  var radius = width * 3;
  var centerX = 0;
  var centerY = 0;
  var r = width + radius * Math.random();
  var angle = Math.random() * Math.PI * 2;
  vector.x = centerX + r * Math.cos(angle);
  vector.y = centerY + r * Math.sin(angle);
}

// 初始化摄像机
function initCamera() {
  var fieldOfView = 75;
  var aspectRatio = width / height;
  var nearPlane = 1;
  var farPlane = 3000;
  camera = new THREE.PerspectiveCamera(
    fieldOfView,
    aspectRatio,
    nearPlane,
    farPlane
  );
  camera.position.z = 800; // 设置摄像机位置
}

// 创建光源
function createLights() {
  // 创建方向光源
  var shadowLight = new THREE.DirectionalLight(0xffffff, 2);
  shadowLight.position.set(20, 0, 10);
  shadowLight.castShadow = true; // 投射阴影
  shadowLight.shadowDarkness = 0.01; // 阴影深度
  scene.add(shadowLight);

  // 创建方向光源
  var light = new THREE.DirectionalLight(0xffffff, 0.5);
  light.position.set(-20, 0, 20);
  scene.add(light);

  // 创建方向光源
  var backLight = new THREE.DirectionalLight(0xffffff, 0.8);
  backLight.position.set(0, 0, -20);
  scene.add(backLight);
}

// 定义粒子构造函数
function Particle() {
  this.vx = Math.random() * 0.05; // x 方向速度
  this.vy = Math.random() * 0.05; // y 方向速度
}

// 粒子的初始化方法
Particle.prototype.init = function(i) {
  var particle = new THREE.Object3D();
  var geometryCore = new THREE.BoxGeometry(20, 20, 20); // 创建几何体
  var materialCore = new THREE.MeshLambertMaterial({
    color: colors[i % colors.length], // 随机选择颜色
    shading: THREE.FlatShading // 设置着色方式
  });
  var box = new THREE.Mesh(geometryCore, materialCore);
  box.geometry.__dirtyVertices = true;
  box.geometry.dynamic = true;
  particle.targetPosition = new THREE.Vector3(
    (textPixels[i].x - width / 2) * 4,
    textPixels[i].y * 5,
    -10 * Math.random() + 20
  );
  particle.position.set(width * 0.5, height * 0.5, -10 * Math.random() + 20);
  randomPos(particle.position);

  // 修改顶点的位置
  var positionAttribute = box.geometry.attributes.position;
  if (positionAttribute) {
    var positions = positionAttribute.array;
    for (var i = 0; i < positions.length; i += 3) {
      positions[i] += -10 + Math.random() * 20;
      positions[i + 1] += -10 + Math.random() * 20;
      positions[i + 2] += -10 + Math.random() * 20;
    }
    positionAttribute.needsUpdate = true;
  } else {
    console.error("Geometry does not have position attribute.");
  }

  particle.add(box);
  this.particle = particle;
};

// 更新粒子的旋转角度
Particle.prototype.updateRotation = function() {
  this.particle.rotation.x += this.vx;
  this.particle.rotation.y += this.vy;
};

// 更新粒子的位置
Particle.prototype.updatePosition = function() {
  this.particle.position.lerp(this.particle.targetPosition, 0.02);
};

// 渲染场景
function render() {
  renderer.render(scene, camera);
}

// 更新粒子状态
function updateParticles() {
  for (var i = 0, l = particles.length; i < l; i++) {
    particles[i].updateRotation();
    particles[i].updatePosition();
  }
}

// 设置粒子的位置
function setParticles() {
  for (var i = 0; i < textPixels.length; i++) {
    if (particles[i]) {
      particles[i].particle.targetPosition.x =
        (textPixels[i].x - width / 2) * 4;
      particles[i].particle.targetPosition.y = textPixels[i].y * 5;
      particles[i].particle.targetPosition.z = -10 * Math.random() + 20;
    } else {
      var p = new Particle();
      p.init(i);
      scene.add(p.particle);
      particles[i] = p;
    }
  }

  for (var i = textPixels.length; i < particles.length; i++) {
    randomPos(particles[i].particle.targetPosition);
  }
}

// 初始化画布
function initCanvas() {
  textCanvas = document.getElementById('text');
  textCanvas.style.width = width + 'px';
  textCanvas.style.height = 200 + 'px';
  textCanvas.width = width;
  textCanvas.height = 200;
  textCtx = textCanvas.getContext('2d');
  textCtx.font = '700 100px Arial';
  textCtx.fillStyle = '#555';
}

// 初始化输入框
function initInput() {
  input = document.getElementById('input');
  input.addEventListener('keyup', updateText);
  input.value = 'GoBoy IS ME';
}

// 更新文本内容
function updateText() {
  var fontSize = width / (input.value.length * 1.3);
  if (fontSize > 120) fontSize = 120;
  textCtx.font = '700 ' + fontSize + 'px Arial';
  textCtx.clearRect(0, 0, width, 200);
  textCtx.textAlign = 'center';
  textCtx.textBaseline = 'middle';
  textCtx.fillText(input.value.toUpperCase(), width / 2, 50);

  var pix = textCtx.getImageData(0, 0, width, 200).data;
  textPixels = [];
  for (var i = pix.length; i >= 0; i -= 4) {
    if (pix[i] != 0) {
      var x = (i / 4) % width;
      var y = Math.floor(Math.floor(i / width) / 4);

      if (x && x % 6 == 0 && y && y % 6 == 0)
        textPixels.push({
          x: x,
          y: 200 - y + -120
        });
    }
  }
  setParticles();
}

// 动画循环
function animate() {
  requestAnimationFrame(animate);
  updateParticles();
  camera.position.lerp(cameraTarget, 0.2);
  camera.lookAt(cameraLookAt);
  render();
}

// 窗口大小变化时的响应函数
function resize() {
  width = window.innerWidth;
  height = window.innerHeight;
  camera.aspect = width / height;
  camera.updateProjectionMatrix();
  renderer.setSize(width, height);

  textCanvas.style.width = width + 'px';
  textCanvas.style.height = 200 + 'px';
  textCanvas.width = width;
  textCanvas.height = 200;
  updateText();
}

// 鼠标移动时的响应函数
function mousemove(e) {
  var x = e.pageX - width / 2;
  var y = e.pageY - height / 2;
  cameraTarget.x = x * -1;
  cameraTarget.y = y;
}

// 初始化舞台
initStage();
// 初始化场景
initScene();
// 初始化画布
initCanvas();
// 初始化摄像机
initCamera();
// 创建光源
createLights();
// 初始化输入框
initInput();
// 开始动画
animate();
// 稍微延迟更新文本内容,以确保初始状态正确显示
setTimeout(function() {
  updateText();
}, 40);

👽 创建 goboy.css

body {
    overflow: hidden;
    background: #A1DBB2;
  }
  
  div, canvas {
    position: absolute;
  }
  
  #text {
    z-index: 200;
    display: none;
  }
  
  input {
    z-index: 400;
    position: absolute;
    text-transform: uppercase;
    width: 90%;
    bottom: 20px;
    background: none;
    outline: none;
    border: none;
    font-size: 30px;
    color: #222;
    font-weight: bold;
    text-align: center;
    border-bottom: 1px solid #333;
    left: 5%;
  }

👽 展示效果

image.png

Three.js相关资料官网

我正在参与2024腾讯技术创作特训营最新征文,快来和我瓜分大奖!

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。