在 Web 中,实现 3D 的场景有很多方法,css 是最简单的,另外还有 canvas,webGL,webVR 等。但是,如果想让浏览器实现一个复杂的 3D 场景,如果你不好好利用 GPU 的话,那么你的网页的 jank 真的会让人 GG。而能使用 GPU 的技术也有很多,比如 transfrom 3d 的相关属性。不过考虑到灵活性,webGL 应该是第一选择,不过,webGL 没有一定的基础,确实很难掌握。而,three.js 就是抽象 webGL,让前端能根据简单的图形原理来作出有一定基础的 3D 场景。
在 Three.js 中,遵循的是一般 3D 世界的原理,它具有如下的内容:
那如何创建一个简单的 three.js 呢? 直接放 demo:
// 设置场景大小(实际就是canvas 的大小) const WIDTH = 400; const HEIGHT = 300; // 设置相机的相关属性 const VIEW_ANGLE = 45; const ASPECT = WIDTH / HEIGHT; // 屏幕比例 const NEAR = 0.1; const FAR = 10000; const container = document.querySelector('#container'); // 创建渲染器 const renderer = new THREE.WebGLRenderer(); const camera = new THREE.PerspectiveCamera( VIEW_ANGLE, ASPECT, NEAR, FAR ); // 创建场景 const scene = new THREE.Scene(); // 将相机添加到场景中 scene.add(camera); // 设置渲染器 renderer.setSize(WIDTH, HEIGHT); // 将渲染器放在指定容器中 container.appendChild(renderer.domElement);
看到 网格
感觉又是什么新奇的概念,实际上就是我们通常所说的几何体,比如:球体,平面,管道和圆柱体等。当然,这些只是一些原始模型,你还可以自定一些其他模型,比如使用一些其他的软件进行 3D 建模等。
// 设置球体的相关属性 const RADIUS = 50; const SEGMENTS = 16; const RINGS = 16; // 创建一个球体的网格,然后使用一个材质覆盖 const sphere = new THREE.Mesh( new THREE.SphereGeometry( RADIUS, SEGMENTS, RINGS), sphereMaterial); // 向后移动 z sphere.position.z = -300; // 将该球体添加到场景中 scene.add(sphere);
材质实际上就是网格上面覆盖的内容,THREE.js 提供了一些简单实用的材质去应用到你的网格中:
实际上,材质在 WebGL 中,理解为着色器(Shaders),也就是定义怎么将样式覆盖到网格上,这就会牵扯到 GLSL,一种直接和 GPU 交流的语言。不过,THREE.js 已经将这层给抽象出来,它提供了一些常用的材质,如果你想自定义一些,可以直接使用 MeshShaderMaterial
来写相关的着色器。 这里我们使用 Lambert 材质:
const sphereMaterial = new THREE.MeshLambertMaterial( { color: 0xCC0000 });
如果执行到上面的代码,你会得到全黑的 canvas。因为如果你没有提供合适的光照,THREE.js 默认使用全景光,即,没有光照,就是全黑。所以,这里,我们需要添加光照:
// 添加点光源 const pointLight = new THREE.PointLight(0xFFFFFF); // 设置点光源位置 pointLight.position.x = 10; pointLight.position.y = 50; pointLight.position.z = 130; // 添加到场景中 scene.add(pointLight);
当然,你添加了上面的代码也不一定你能够渲染出来,因为,你根本没执行渲染操作。这里会涉及到相机的概念,我们可以将渲染理解为快照,你虽然将物体放在空间里面,但你并没有按下快门(渲染),那么该物体是不会被投影到你的屏幕上的,所以,你需要执行一次快照,让物体成功的渲染:
renderer.render(scene, camera);
当然,如果你还设计到动画,则可以利用 RAF 来进行重复执行快照:
function update () { // Draw! renderer.render(scene, camera); // Schedule the next frame. requestAnimationFrame(update); } // Schedule the first frame. requestAnimationFrame(update);
上面,我们通过 new Mesh 创建了一个几何体实例,而且是每个几何体实例都是继承了 Object3D。它上面挂在几个通用的属性:
另外,当我们在不断进行渲染时,有可能会遇到渲染不成功的情况,那么这个时候,有可能是 THREE.js 把你的 Object3D 给缓存了。那应该怎么强制关闭缓存呢? 直接使用:
// 顶点发生改变 sphere.geometry.verticesNeedUpdate = true; // 正常更新 sphere.geometry.normalsNeedUpdate = true;
总的代码如下:
// Set the scene size. const WIDTH = window.innerWidth; const HEIGHT = window.innerHeight; // Set some camera attributes. const VIEW_ANGLE = 45; const ASPECT = WIDTH / HEIGHT; const NEAR = 0.1; const FAR = 10000; // Get the DOM element to attach to const container = document.querySelector('#container'); // Create a WebGL renderer, camera // and a scene const renderer = new THREE.WebGLRenderer(); const camera = new THREE.PerspectiveCamera( VIEW_ANGLE, ASPECT, NEAR, FAR ); const scene = new THREE.Scene(); // Add the camera to the scene. scene.add(camera); // Start the renderer. renderer.setSize(WIDTH, HEIGHT); // Attach the renderer-supplied // DOM element. container.appendChild(renderer.domElement); // create a point light const pointLight = new THREE.PointLight(0xFFFFFF); // set its position pointLight.position.x = 10; pointLight.position.y = 50; pointLight.position.z = 130; // add to the scene scene.add(pointLight); // create the sphere's material const sphereMaterial = new THREE.MeshLambertMaterial( { color: 0xCC0000 }); // Set up the sphere vars const RADIUS = 50; const SEGMENTS = 16; const RINGS = 16; // Create a new mesh with // sphere geometry - we will cover // the sphereMaterial next! const sphere = new THREE.Mesh( new THREE.SphereGeometry( RADIUS, SEGMENTS, RINGS), sphereMaterial); // Move the Sphere back in Z so we // can see it. sphere.position.z = -300; // Finally, add the sphere to the scene. scene.add(sphere); function update () { // Draw! renderer.render(scene, camera); // Schedule the next frame. requestAnimationFrame(update); } // Schedule the first frame. requestAnimationFrame(update);