通过漫天花雨来入门 Three.js

开发 前端
Three.js 用于渲染一个 3D 的场景,里面会有很多物体,比如立方体、圆柱、圆环、圆锥等各种几何体(以 Geometry 为后缀),比如点(Points)线(Line)面(Sprite)等基础物体。

[[436062]]

随着元宇宙概念的火爆,3D 渲染相关的技术频繁被提及,而 Three.js 是基于 WebGL 的 api 封装的用于简化 3D 场景的开发的框架, 是入门 3D 的不错的抓手,今天我们就来入门下 Three.js。

我们基于 Three.js 来实现一个花瓣雨的效果。

Three.js 的基础

Three.js 用于渲染一个 3D 的场景,里面会有很多物体,比如立方体、圆柱、圆环、圆锥等各种几何体(以 Geometry 为后缀),比如点(Points)线(Line)面(Sprite)等基础物体。这些所有的物体怎么管理呢?

用一个场景 Scene 来承载,所有的物体都会被添加到 Scene 里。

所以有这样的 api:

  1. const scene = new THREE.Scene(); 
  2.  
  3. scene.add(xxx); 
  4. scene.add(yyy); 

当然,物体之间可以做分组 Group,组内的物体可以统一管理,之后再添加到 Scene 里。

  1. const scene = new THREE.Scene(); 
  2.  
  3. const group = new THREE.Group(); 
  4.  
  5. group.add(xxx); 
  6. group.add(yyy); 
  7.  
  8. scene.add(group); 

这种场景、物体、分组的概念,在很多游戏引擎中也有类似的 api,大家都是这么来管理的。

可以添加到 Scene 中的物体,除了几何体(Geometry)、点线面等,还有辅助工具,比如坐标系工具(AxisHelper)。其实这些工具也是用集合体、点线面封装出来的,只不过是作为工具来临时添加到 Scene 中。

  1. const axisHelper = new THREE.AxisHelper(max); 
  2. scene.add(axisHelper) 

有了场景和场景中的各种物体,怎么渲染出来呢?

调用 Renderer,这个类是专门负责渲染 Scene 中各种物体的。

但是还有个问题,三维的世界(scene)怎么渲染到二维的屏幕呢?

如图,从一个点找个角度来看三维世界,或者从一个平面来平行的看三维世界,看到的就是二维的。

这两种方式,第一种叫做透视、第二种叫做正交。

生成二维图像,就像照相机的功能一样,所以这种概念叫做 Camera。

在 Three.js 里面有 PerspectiveCamera (透视相机)和 OrthographicCamera(正交相机),分别对应上面两种三维转二维的方式。

这两个 Camera 的参数还是挺多的,但是理解了也挺简单:

  1. new Three.PerspectiveCamera( fov, aspect, near, far ) 
  2. new Three.OrthographicCamera( leftrighttop, bottom, near, far ) 

先看透视相机的,它要看三维世界,那就要有一个最近和最远两个位置,然后从一个点看过去会有一个视野的角度,看到的画面还有个宽高比。

这就是为什么 PerspectCamera 有 near、far、fov、aspect 这四个参数。

正交相机的参数也是差不多的意思,不过因为不是从一个点,看的,而是从一个面做的投影,那么就没有角度的参数,而是有上下左右的四个面位置的参数。

正交相机的上下左右位置也不是随便的,比例要和画面的宽高比一样,所以一般都是这么算:

  1. const width = window.innerWidth; 
  2. const height = window.innerHeight; 
  3. //窗口宽高比 
  4. const k = width / height; 
  5. //三维场景的显示的上下范围 
  6. const s = 200; 
  7.  
  8. // 上下范围 s 再乘以宽高比 k 就是左右的范围,而远近随便设置一个数就行 
  9. const camera = new THREE.OrthographicCamera(-s * k, s * k, s, -s, 1, 1000); 

上面的正交相机的参数里面,远近可以设置为 1 和 1000,上下设置为 200,左右就可以根据宽高比算出来。这就是相机所看到的二维画面的范围。

有了场景 Scene 中的各种物体,有了照相机 Camera,就可以用渲染器 Renderer 渲染出画面来了。

  1. const renderer = new THREE.WebGLRenderer(); 
  2. //设置渲染区域尺寸 
  3. renderer.setSize(width, height) 
  4.  
  5. renderer.render(scene, camera) 

不过,一般不会只渲染一帧,有动画效果的话,会使用 requestAnimationFrame 的 api 一帧帧的不停渲染。

  1. function render() { 
  2.     renderer.render(scene, camera) 
  3.  
  4.     requestAnimationFrame(render) 
  5. render(); 

这就是 Three.js 的大概流程:Scene 中有几何体Geometry、点线面、辅助工具等各种物体,物体还可以做分组,然后通过正交或者透视相机来设置看到的二维画面,之后用 Renderer 渲染出来。有动画效果的话,要用 requestAnimationFrame 来一帧帧的渲染。

下面我们来实现一下花瓣雨的效果。

花瓣雨实现

首先我们要创建场景 Scene 中的物体,也就是各种花瓣,这个需要显示的是一个平面,可以用 Sprite。

Sprite 是精灵的意思,在 Three.js 中,它就是一个永远面向相机的二维平面。

我们给 Sprite 贴上花瓣的纹理就可以了。

我们先准备一些花瓣的贴图,类似这种:

花瓣的数量有很多,我们生成 400 个,加到花瓣分组里,然后添加到场景中:

  1. const scene = new THREE.Scene(); 
  2. /** 
  3.  * 花瓣分组 
  4.  */ 
  5. const petal = new THREE.Group(); 
  6.  
  7. function create() { 
  8.     var texture1 = new THREE.TextureLoader().load("img/h1.png"); 
  9.     var texture2 = new THREE.TextureLoader().load("img/h2.png"); 
  10.     var texture3 = new THREE.TextureLoader().load("img/h3.png"); 
  11.     var texture4 = new THREE.TextureLoader().load("img/h4.png"); 
  12.     var texture5 = new THREE.TextureLoader().load("img/h5.png"); 
  13.     var imageList = [texture1, texture2, texture3, texture4, texture5]; 
  14.  
  15.     for (let i = 0; i < 400; i++) { 
  16.         var spriteMaterial = new THREE.SpriteMaterial({ 
  17.             map: imageList[Math.floor(Math.random() * imageList.length)],//设置精灵纹理贴图 
  18.         }); 
  19.         var sprite = new THREE.Sprite(spriteMaterial); 
  20.         petal.add(sprite); 
  21.  
  22.         sprite.scale.set(40, 50, 1);  
  23.         sprite.position.set(2000 * (Math.random() - 0.5), 2000 * Math.random(), 0) 
  24.     } 
  25.     scene.add(petal) 
  26.  
  27. create(); 

400 个 Sprite 随机贴上了不同的花瓣的纹理贴图,然后设置了下放缩,之后随机设置了一个在场景中的位置。

我们在 Scene 中加入坐标系辅助工具来看下坐标:

  1. const axisHelper = new THREE.AxisHelper(1000); 
  2. scene.add(axisHelper) 

红色是 x 轴,向右是递增的,绿色是 y 轴,向上是递增的。z 轴我们暂时用不到。

所以,根据代码,花瓣的 x 的范围就是随机的 -1000 到 1000,y 的范围就是 0 到 2000。

然后,我们创建正交相机:

  1. const width = window.innerWidth; 
  2. const height = window.innerHeight; 
  3. //窗口宽高比 
  4. const k = width / height; 
  5. //三维场景的显示的上下范围 
  6. const s = 200; 
  7. const camera = new THREE.OrthographicCamera(-s * k, s * k, s, -s, 1, 1000); 

设置下相机的位置和方向:

  1. camera.position.set(0, 200, 500) 
  2. camera.lookAt(scene.position) 

我们创建相机的时候指定了二维能显示的范围,相机在这个范围内的哪个位置都行。

然后创建渲染器,设置下大小和背景颜色,把渲染到的 canvas 元素插入到 dom 中。

  1. const renderer = new THREE.WebGLRenderer(); 
  2. //设置渲染区域尺寸 
  3. renderer.setSize(width, height) 
  4. //设置背景颜色 
  5. renderer.setClearColor(0xFFFFFF, 1) 
  6. //body元素中插入canvas对象 
  7. document.body.appendChild(renderer.domElement) 

之后就用 requestAnimation 不断地一帧帧渲染就行了。

  1. function render() { 
  2.     petal.children.forEach(sprite => { 
  3.         sprite.position.y -= 1; 
  4.         sprite.position.x += 0.5; 
  5.         if (sprite.position.y < -400) { 
  6.             sprite.position.y = 800; 
  7.         } 
  8.         if (sprite.position.x > 1000) { 
  9.             sprite.position.x = -1000 
  10.         } 
  11.     }); 
  12.  
  13.     renderer.render(scene, camera) 
  14.  
  15.     requestAnimationFrame(render) 

每次重新渲染之前,我们修改下花瓣的位置,产生下落效果,如果超出了范围,就移到上面去重新开始落,这样就是不间断的花瓣雨效果。

完整代码如下:

  1. <!DOCTYPE html> 
  2. <html lang="en"
  3. <head> 
  4.     <meta charset="UTF-8"
  5.     <title>花瓣雨</title> 
  6.     <style> 
  7.         body { 
  8.             margin: 0; 
  9.             overflow: hidden; 
  10.         } 
  11.     </style> 
  12.     <script src="js/three.min.js"></script> 
  13. </head> 
  14. <body> 
  15. <script> 
  16.  
  17.     const scene = new THREE.Scene(); 
  18.     /** 
  19.      * 花瓣分组 
  20.      */ 
  21.     const petal = new THREE.Group(); 
  22.  
  23.     const width = window.innerWidth; 
  24.     const height = window.innerHeight; 
  25.     //窗口宽高比 
  26.     const k = width / height; 
  27.     //三维场景的显示的上下范围 
  28.     const s = 200; 
  29.     const camera = new THREE.OrthographicCamera(-s * k, s * k, s, -s, 1, 1000); 
  30.  
  31.     const renderer = new THREE.WebGLRenderer(); 
  32.  
  33.     function create() { 
  34.         //设置相机位置 
  35.         camera.position.set(0, 200, 500) 
  36.         camera.lookAt(scene.position) 
  37.      
  38.         //设置渲染区域尺寸 
  39.         renderer.setSize(width, height) 
  40.         //设置背景颜色 
  41.         renderer.setClearColor(0xFFFFFF, 1) 
  42.         //body元素中插入canvas对象 
  43.         document.body.appendChild(renderer.domElement) 
  44.  
  45.         // const axisHelper = new THREE.AxisHelper(1000); 
  46.         // scene.add(axisHelper) 
  47.  
  48.         var textureTree1 = new THREE.TextureLoader().load("img/h1.png"); 
  49.         var textureTree2 = new THREE.TextureLoader().load("img/h2.png"); 
  50.         var textureTree3 = new THREE.TextureLoader().load("img/h3.png"); 
  51.         var textureTree4 = new THREE.TextureLoader().load("img/h4.png"); 
  52.         var textureTree5 = new THREE.TextureLoader().load("img/h5.png"); 
  53.         var imageList = [textureTree1, textureTree2, textureTree3, textureTree4, textureTree5]; 
  54.  
  55.         for (let i = 0; i < 400; i++) { 
  56.             var spriteMaterial = new THREE.SpriteMaterial({ 
  57.                 map: imageList[Math.floor(Math.random() * imageList.length)],//设置精灵纹理贴图 
  58.             }); 
  59.             var sprite = new THREE.Sprite(spriteMaterial); 
  60.             petal.add(sprite); 
  61.  
  62.             sprite.scale.set(40, 50, 1);  
  63.             sprite.position.set(2000 * (Math.random() - 0.5), 2000 * Math.random(), 0) 
  64.         } 
  65.         scene.add(petal) 
  66.     } 
  67.  
  68.  
  69.     function render() { 
  70.         petal.children.forEach(sprite => { 
  71.             sprite.position.y -= 1; 
  72.             sprite.position.x += 0.5; 
  73.             if (sprite.position.y < -400) { 
  74.                 sprite.position.y = 800; 
  75.             } 
  76.             if (sprite.position.x > 1000) { 
  77.                 sprite.position.x = -1000 
  78.             } 
  79.         }); 
  80.  
  81.         renderer.render(scene, camera) 
  82.  
  83.         requestAnimationFrame(render) 
  84.     } 
  85.  
  86.     create() 
  87.     render() 
  88. </script> 
  89. </body> 
  90. </html> 

总结

Three.js 是为了简化 3D 渲染的框架,它提供了场景 Scene 的 api,里面可以包含各种可渲染的物体:立方体、圆锥等各种几何体 Geometry、点线面、坐标系等辅助工具。这些物体还可以通过 Group 分组来统一管理。

Sence 要渲染出来需要指定一个相机,分为从点去看的透视相机 PerspectiveCamera,从平面去投影的正交相机 OrthographicCamera。理解了它们的原理才能理解 Camera 的参数。

之后通过 Renderer 渲染出来,如果有动画需要用 requestAnimationFrame 来一帧帧的渲染。

这是 Three.js 的大概渲染流程。

之后我们实现了一个花瓣雨的案例。用到了 Sprite 这种物体,它是一个永远面向相机的平面,用来做这种效果很合适。

当然,Three.js 的东西还是比较多的,这篇文章只是入下门,后面我们会继续深入,做更多的有意思的 3D 场景和效果。

 

责任编辑:姜华 来源: 神光的编程秘籍
相关推荐

2017-05-08 11:41:37

WebGLThree.js

2022-07-15 13:09:33

Three.js前端

2019-11-29 09:30:37

Three.js3D前端

2021-04-23 16:40:49

Three.js前端代码

2021-04-21 09:20:15

three.js3d前端

2022-07-08 10:39:09

Three.js元宇宙VR

2022-01-16 19:23:25

Three.js粒子动画群星送福

2021-12-14 11:44:37

可视化Three.js 信息

2022-03-07 09:20:00

JavaScripThree.jsNFT

2021-12-03 07:27:30

全景浏览Three.js

2020-11-23 07:43:18

JS

2021-11-23 22:50:14

.js 3D几何体

2021-11-27 10:42:01

Three.js3D可视化AudioContex

2023-07-13 10:48:22

web 3DThree.jsBlender

2021-12-07 13:44:43

Three.js3D可视化3D 游戏

2017-12-26 17:42:12

前端WebGLThree.js

2024-02-26 00:00:00

前端工具Space.js

2023-08-04 09:56:15

2022-11-21 18:01:24

CSSthree.js

2022-07-22 14:56:17

Canvas鸿蒙
点赞
收藏

51CTO技术栈公众号