关于three.js:Threejs实现穿越云层动效

51次阅读

共计 5461 个字符,预计需要花费 14 分钟才能阅读完成。

上文说到,我对《你的性情主导色》流动中最感兴趣的局部就是通过 Three.js 实现穿梭云层动效了,据作者说每朵云呈现的地位都是随机的,成果很好,下图是我实现的版本。

在线 Demo

首先说下实现穿梭云层动效的基本思路:

  1. 沿着 Z 轴平均的放一堆 64*64 的立体图形,这些立体的 X 坐标和 Y 坐标是随机的(很像下图的桶装薯片)
  2. 把下面的所有图形合并成一个大的图形
  3. 把大的图形和贴片材质(云)生成网格,网格放进场景中
  4. 动效就是将相机从远处沿着 Z 轴迟缓挪动,就会有了穿梭云层的成果

首先官网文档提供了一个创立一个场景的疾速开始,浏览后能够对上面的内容更好的了解。

上面介绍下 Three.js 中的基本概念。仅限我这老手的了解。有讲的好的文档或者分享,欢送帮忙指个路。

场景

场景就是一块空间,用来装下咱们想要渲染的内容。最简略的用途就是,场景能够增加一个网格,而后渲染进去。

// 初始化场景
var scene = new THREE.Scene();

// 其余代码...
// 把物体增加进场景
scene.add(mesh);
// 渲染场景
renderer.render(scene, camera);

这里说下场景中的坐标规定:原点是 canvas 的立体核心,Z 轴垂直于 X、Y 轴,正向是冲着咱们的,我这里把 Z 轴的线做了些旋转,不然咱们看不到,如下图:

代码:

const scene = new THREE.Scene();

var camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 1, 1000);
camera.position.set(0, 0, 100);

const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

// 线段 1,红色的,从原点到 X 轴 40
const points = [];
points.push(new THREE.Vector3(0, 0, 0));
points.push(new THREE.Vector3(40, 0, 0));
const geometry1 = new THREE.BufferGeometry().setFromPoints(points);
var material1 = new THREE.LineBasicMaterial({color: 'red'});
var line1 = new THREE.Line(geometry1, material1);

// 线段 2,蓝色的,从原点到 Y 轴 40
points.length = 0;
points.push(new THREE.Vector3(0, 0, 0));
points.push(new THREE.Vector3(0, 40, 0));
const geometry2 = new THREE.BufferGeometry().setFromPoints(points);
var material2 = new THREE.LineBasicMaterial({color: 'blue'});
var line2 = new THREE.Line(geometry2, material2);

// 线段 3,绿色的,从原点到 Z 轴 40
points.length = 0;
points.push(new THREE.Vector3(0, 0, 0));
points.push(new THREE.Vector3(0, 0, 40));
const geometry3 = new THREE.BufferGeometry().setFromPoints(points);
var material3 = new THREE.LineBasicMaterial({color: 'green'});
var line3 = new THREE.Line(geometry3, material3);
// 做了个旋转,不然看不到 Z 轴上的线
line3.rotateX(Math.PI / 8);
line3.rotateY(-Math.PI / 8);

scene.add(line1, line2, line3);

renderer.render(scene, camera);

相机

场景内的物体要想被咱们看见,也就是渲染进去,须要相机去“看”,通过下面的坐标系图,咱们晓得同一个物体,相机察看的角度不同,必定也会呈现出不一样的画面。最罕用的就是这里用的透视相机,能够穿透物体,用在这里正好穿透云层,成果拔群。

// 初始化相机
camera = new THREE.PerspectiveCamera(70, pageWidth / pageHeight, 1, 1000);

// 最初,场景和相机一起渲染进去,咱们就可能看到场景中的物体了
renderer.render(scene, camera);

材质

材质很好了解,在最后的例子中,应用 MeshBasicMaterial 给立方体增加了色彩。材质的应用形式是,将材质和图形独特生成一个网格,咱们这里应用的是比较复杂的贴图材质。

// 贴图材质
const material = new THREE.ShaderMaterial({
  // 这里的值是给着色器传递的
  uniforms: {
    map: {
      type: 't',
      value: texture
    },
    fogColor: {
      type: 'c',
      value: fog.color
    },
    fogNear: {
      type: 'f',
      value: fog.near
    },
    fogFar: {
      type: 'f',
      value: fog.far
    }
  },
  vertexShader: vShader,
  fragmentShader: fShader,
  transparent: true
});

图形和网格

Three.js默认提供了很多的几何体图形,也就是各种Geometry,他们的基类是BufferGeometry

图形能够进行合并,像这里就是 clone 了很多个一样的立体图形,通过批改各自的地位,生成合并后造成一大片云的成果。

最后我认为图形和网格是一个概念,起初晓得了,材质和图形能够生成网格,网格能够放进场景中。

// 把下面合并进去的形态和材质,生成一个网格
mesh = new THREE.Mesh(mergedGeometry, material);

渲染

将场景和相机渲染到指标元素上,会生成一个canvas,如果是一个动态的场景,那么渲染结束就能够了。然而如果是一个会动的场景,这里须要用到一个原生函数requestAnimationFrame

function animate() {requestAnimationFrame(animate);
  renderer.render(scene, camera);
}

下面的代码是一个渲染循环,在个别屏幕上的频率是 60HZ,在高刷屏幕上会增长刷新频率,也就是会给用户良好的刷新体验,不须要咱们本人应用 setInterval 去管制。并且当用户切换到其它的标签页时,它会暂停刷新,不会节约用户贵重的处理器资源,也不会损耗电池的使用寿命。

揭秘过程

过程其实很有意思,也很波折。

扒下来了《你的性情主导色》流动的前端代码,然而云层动效相干有很多代码压缩过了,看不懂。

怎么办?而后我就去 three.js 找官网的例子去,找了半天只找到一个下图这样的:

起初通过各种搜寻,终于在 three.js 的讨论区发现了这种穿梭云层的特效,是 three.js 的作者很久之前写的例子。

把云层动效源码拿到手当前,我比照后感觉 imyzf 同学应该也是从这个例子中借鉴了一下。

我发现源码中的 three.js 的版本有一些落后,源码中的版本是 55,最新的是 131 版本,版本差距有点大,曾经没有了下面的一些类和 API,上面介绍下不同的局部:

THREE.Geometry

首先就是这个类在最新版没有了,这个类是用来将很多个立体图形,合并为一个图形。察看上面的代码,55 的版本是学生成一个 Geometry,而后生成一个立体网格,调整网格的坐标后,把网格和Geometry 合并(这里有点不懂了,图形怎么和网格合并,而且是同一个网格,我猜是在合并的时候新生成了一个网格)。

// 初始化一个根底的图形
geometry = new THREE.Geometry();
// 初始化一个 64*64 的立体
var plane = new THREE.Mesh(new THREE.PlaneGeometry(64, 64));

for (var i = 0; i < 8000; i++) {
  // 调整立体图案的地位和旋转角度等
  plane.position.x = Math.random() * 1000 - 500;
  plane.position.y = -Math.random() * Math.random() * 200 - 15;
  plane.position.z = i;
  plane.rotation.z = Math.random() * Math.PI;
  plane.scale.x = plane.scale.y = Math.random() * Math.random() * 1.5 + 0.5;
  // 立体合并到根底图形
  THREE.GeometryUtils.merge(geometry, plane);
}

查问最新文档后,发现所有图形的基类 BufferGeometry 提供 clone 办法,立体图形天然也能够被 clone 进去。

// 一个立体形态
const geometry = new THREE.PlaneGeometry(64, 64);
const geometries = [];

for (var i = 0; i < CloudCount; i++) {const instanceGeometry = geometry.clone();

  // 把这个克隆进去的云,通过随机参数,做一些位移,达到一堆云彩的成果,每次渲染进去的云堆都不一样
  // X 轴偏移后,通过调整相机地位达到均衡
  // Y 轴想把云彩放在场景的偏下地位,所以都是负值
  // Z 轴位移就是:以后第几个云 * 每个云所占的 Z 轴长度
  instanceGeometry.translate(Math.random() * RandomPositionX, -Math.random() * RandomPositionY, i * perCloudZ);

  geometries.push(instanceGeometry);
}

// 把这些形态合并
const mergedGeometry = BufferGeometryUtils.mergeBufferGeometries(geometries);

GeometryUtils.merge

旧代码码中有一个这样的 API,这是一个很重要的 API,目标就是合并图形和网格,生成一片云,最新版的 three.js 曾经没有了。

// 合并所有的立体图形到一个根底图形
THREE.GeometryUtils.merge(geometry, plane);

通过查问最新版的文档,发现了能够将一组图形进行合并,集体感觉比下面的好一些,语义上好很多。下面的代码是反复的把同一个立体合并到一个根底图形下面,上面是把这一组立体合成为一个新的立体。

// 把这些形态合并
const mergedGeometry = BufferGeometryUtils.mergeBufferGeometries(geometries);

着色器

着色器代码逻辑我是齐全的没有批改,GLSL(OpenGL 着色语言 OpenGL Shading Language),原来的着色器代码是写在 <script> 元素标签里的,这和咱们的工程化我的项目不合乎。

// 原来的
<script id="vs" type="x-shader/x-vertex">
  varying vec2 vUv;
  void main()
  {
      vUv = uv;
      gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
  }
</script>

<script id="fs" type="x-shader/x-fragment">
   uniform sampler2D map;
   uniform vec3 fogColor;
   uniform float fogNear;
   uniform float fogFar;
   varying vec2 vUv;
   void main()
   {
       float depth = gl_FragCoord.z / gl_FragCoord.w;
       float fogFactor = smoothstep(fogNear, fogFar, depth);
       gl_FragColor = texture2D(map, vUv);
       gl_FragColor.w *= pow(gl_FragCoord.z, 20.0);
       gl_FragColor = mix(gl_FragColor, vec4( fogColor, gl_FragColor.w), fogFactor );
  }
</script>

起初找了几个中央才晓得能够工夫应用字符串代替:

const vShader = `
    varying vec2 vUv;
    void main()
    {
      vUv = uv;
      gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
    }
  `;

顶点着色器和片元着色器代码,我目前是真的不懂,先抄为敬。

源码

最初放上源码,感兴趣的同学能够看一下,欢送 Star 和提出倡议。

正文完
 0