从导演👈角度手搓3D旋转地球😱 | three.js入门实战🤔

1,534 阅读8分钟

倔友请看下图

recording.gif

你是否知道 ,上面旋转的 3d 地球 , 其实是由下面的平面图形实现的

image.png

沙雕表情.gif

你一定很好奇 , 到底是怎么实现 3d 效果的 ?

其实这背后 , 有一位大名鼎鼎的 大导演 —— three.js

ok , 说到这里 , 我们来想象一下, 假如你是一位大导演 , 该如何打造你的大片 ?

首先是拍摄场景搭建 —— 类比代码中的场景、相机与渲染器设置

  • 拍摄场地(场景 scene
  • 摄像机(相机 camera
  • 后期处理设备(渲染器 renderer

然后确定拍摄的主体 —— 类比纹理加载与构建 3D 物体

  • 我们拍摄的主体是地球

在然后捕捉主体动作—— 类比动画逻辑与交互

  • 跟随拍摄(鼠标移动与相机视角变化)
  • 主体旋转(3D 物体旋转)

现在我们从导演的角度来指导一场地球自旋转大片 !!! 👈👈👈

先给出我的代码 , 倔友有需要可以放心食用 , 记得改loader.load("earth.jpg", function (texture) 中的 earth.jpg 为自己图片的路径 , 图片可以截取我上面的平面图 , 也可以到我的仓库食用

Ganzhibin/aigc-full-stack-learing

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>3D 地球</title>
    <!-- 画地球 选择框架 加速 -->
    <script src="https://unpkg.com/three@0.128.0/build/three.min.js"></script>
  </head>
  <body>
    <canvas id="webglcanvas"></canvas>
    <script>
      // 3d 地球
      // 3D 时间就是镜头拍出的世界,导演
      let canvas, // 3d 容器
        camera, // 镜头
        scene, // 场景
        renderer, // 渲染器
        group; // 组


      // 物品
      let mouseX = 0,  mouseY = 0; // mousemove 坐标
      // 球心
      let windowHalfX = window.innerWidth / 2; 
      let windowHalfY = window.innerHeight / 2;

      init();
      animate();

      // 准备
      function init() {
        canvas = document.getElementById("webglcanvas"); // DOM
        camera = new THREE.PerspectiveCamera(
          60,
          window.innerWidth / window.innerHeight,
          1,
          2000
        ); // 实例化 相机
        // 相机离scene场景
        camera.position.z = 500;

        scene = new THREE.Scene(); // 实例化 场景
        scene.background = new THREE.Color(0xffffff); // 背景色

        group = new THREE.Group(); // 组
        scene.add(group);
        // 纹理加载器
        let loader = new THREE.TextureLoader(); // 简单的加载器
        loader.load("earth.jpg", function (texture) {
          let geometry = new THREE.SphereGeometry(200, 20, 20); // 球体
          let material = new THREE.MeshBasicMaterial({
            // 材质
            map: texture,
          });
          // 球体 网格 用于绘制
          // 网格 网格材质 网格几何体
          let mesh = new THREE.Mesh(geometry, material); // 网格
          group.add(mesh);
          // 渲染器 目标是canvas
          renderer = new THREE.WebGLRenderer({
            canvas: canvas,
            antialias: true,
          });
          renderer.setSize(window.innerWidth, window.innerHeight);
          // renderer.render(scene, camera);
          document.addEventListener("mousemove", onDocumentMouseMove, false);
        });
      }

      function onDocumentMouseMove(event) {
        mouseX = event.clientX - windowHalfX;
        mouseY = event.clientY - windowHalfY;
      }

      function animate() {
        // 递归 屏幕的刷帧率  60帧/s
        requestAnimationFrame(animate);
        render();
      }

      function render() {
        camera.position.x += (mouseX - camera.position.x) * 0.05;
        camera.position.y += (mouseY - camera.position.y) * 0.05;
        camera.lookAt(scene.position);
        group.rotation.y -= 0.005;
        renderer.render(scene, camera);
      }
    
    </script>
  </body>
</html>

整体拍摄场景搭建

(类比代码中的场景、相机与渲染器设置)

拍摄场地(场景 scene

想象我们要拍摄一部关于地球的影片.

首先得有一个合适的拍摄场地,这就好比代码里创建的 Scene 对象。

scene = new THREE.Scene();

这里的 THREE 就是我们的导演😁 ,这里导演为我们提供了场地 。

这行代码就是在虚拟世界中开辟出了这么一块空间,用来放置所有要拍摄的元素。

scene.background = new THREE.Color(0xffffff);

相当于给这个拍摄场地刷上了白色的背景墙,为整个拍摄环境确定了一个底色,后续所有的“拍摄对象”(3D物体)都会呈现在这个有白色背景的空间里,就像现实中在摄影棚里搭建背景一样。

摄像机(相机 camera

有了场地,还需要一台摄像机来捕捉画面。

camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 2000);

这里导演为我们提供了相机😀

其中 60 度是摄像机镜头的垂直视角角度,决定了能拍摄到上下多宽范围的场景,

就像现实中不同焦距的镜头视角宽窄不一样;

window.innerWidth / window.innerHeight

是根据拍摄场地(浏览器窗口)的宽高比来设置摄像机画面的比例,确保拍摄出来的画面不会变形,类似于要根据实际拍摄环境调整摄像机的画面比例设置;

12000 分别是近裁剪面和远裁剪面的距离,也就是规定了在多近和多远的物体能进入到拍摄画面中,太近或者太远的就不在镜头里了,就好比现实中我们设置摄像机的聚焦范围一样。

camera.position.z = 500; 则是把摄像机放在了距离拍摄场地中心 500 的位置上,确定了拍摄的站位,站得远一些可以拍摄到更大范围的场景内容呢。

后期处理设备(渲染器 renderer

拍摄完素材后,需要有设备来把拍摄到的内容进行处理并展示出来,渲染器 renderer 就起到这个作用。

renderer = new THREE.WebGLRenderer({ canvas: canvas, antialias: true });

相当于我们选择了一台专门的后期处理设备,并告诉它要把处理好的画面输出到特定的“大屏幕”(也就是代码里的 canvas 元素这个画布)上,开启 antialias(抗锯齿)功能就好比给处理后的画面做了一个美化,让画面边缘更加平滑、细腻,看起来更舒服。

renderer.setSize(window.innerWidth, window.innerHeight); 就是根据实际展示的“大屏幕”(浏览器窗口)大小来调整输出画面的尺寸,确保画面能完整、适配地展示出来,就像在不同尺寸的荧幕上播放影片要调整好画面比例和大小一样。

拍摄主体准备

(类比纹理加载与构建3D物体)

我们想要拍摄的主体是地球 ,可一开始只有一张地球的平面照片(earth.jpg 这个纹理图片),这就像是现实中只有一张平面的海报一样,是二维的。

但我们要把它变成能在这个3D拍摄场景里呈现的物体呀,代码里是这么做的:

let loader = new THREE.TextureLoader();
loader.load("earth.jpg", function (texture) {
  let geometry = new THREE.SphereGeometry(200, 20, 20);
  let material = new THREE.MeshBasicMaterial({
    map: texture,
  });
  let mesh = new THREE.Mesh(geometry, material);
  group.add(mesh);
  //...
});

首先TextureLoader 加载这张平面的地球海报(纹理图片),然后创建一个 SphereGeometry(球体几何形状),这就好比我们找了一个球形的道具模型,它规定了这个拍摄主体的外形是一个球体,半径为 200,还有水平和垂直方向的分段数(就好像这个球形道具是由多少个小块拼接而成的,影响球体的精细程度)。

接着用加载好的地球海报纹理给这个球形道具裹上一层外皮,也就是通过 MeshBasicMaterial 材质并把纹理映射到材质上,这样这个球形道具就有了地球的外观了。

最后把这个有地球外观的球形道具(Mesh 网格对象)添加到 group(可以想象成一个放置道具的展示架)里,再把这个展示架放到我们之前搭建好的拍摄场地(场景 scene)中,这下我们就把原本二维的地球照片变成了一个可以在3D场景里拍摄的、具有地球外观的球形物体啦,就像把平面海报变成了实实在在的、能从各个角度拍摄的立体道具一样。

拍摄过程中的动态操作

(类比动画逻辑与交互)

跟随拍摄(鼠标移动与相机视角变化)

在拍摄的时候,我们希望摄像机可以跟着一些动作来改变拍摄角度,让画面更生动、更有交互性。效果如下:(仔细看鼠标位置)

recording.gif

代码里通过监听鼠标移动事件来实现这个效果,就好比现实中摄影师根据拍摄对象的移动或者导演的指示来灵活移动摄像机一样。

function onDocumentMouseMove(event) {
  mouseX = event.clientX - windowHalfX;
  mouseY = event.clientY - windowHalfY;
}

当鼠标在浏览器窗口(拍摄场地对应的可视范围)中移动时,会记录下鼠标相对窗口中心的位置变化,就像摄影师时刻留意拍摄对象在场地中的位置变化一样。然后在 render 函数里:

function render() {
  camera.position.x += (mouseX - camera.position.x) * 0.05;
  camera.position.y += (mouseY - camera.position.y) * 0.05;
  camera.lookAt(scene.position);
  //...
}

根据鼠标位置的变化,相应地去微调摄像机的 xy 坐标位置,让摄像机跟着移动,并且通过 camera.lookAt(scene.position); 让摄像机始终对准拍摄场地的中心位置(也就是我们的拍摄主体所在的大致区域),实现了类似现实中摄影师跟着拍摄对象移动、转动摄像机来捕捉不同角度画面的效果,让观众(网页用户)可以通过鼠标操作看到不同视角下的3D地球模型。

主体旋转(3D物体旋转)
为了让拍摄的主体(地球模型)更有动态感,我们还让它在拍摄过程中旋转起来呀,代码里通过以下语句实现:

group.rotation.y -= 0.005;

就好像我们给那个放置地球模型的展示架安装了一个可以绕 y 轴缓慢旋转的装置,

每次拍摄一帧画面(每次 render 函数执行一次,相当于拍摄一帧画面,因为渲染器会根据当前状态绘制一帧画面到 canvas 上),

这个展示架就带着地球模型绕 y 轴转动一点点角度,这样连续起来看,就呈现出地球模型在不断旋转的动态效果,

让整个拍摄画面更加生动、吸引人,就像在拍摄一个自转的地球一样。

最后,通过渲染器的 renderer.render(scene, camera); 不断地把每一帧根据摄像机视角拍摄到的、带有旋转地球模型的画面处理好,并展示在 canvas 这个“大屏幕”上,就形成了我们最终看到的、可以交互且动态展示的3D地球模型效果,就像经过一系列拍摄、处理后在荧幕上播放出一部精彩的关于地球的影片 !😎

总结

所以说,这段代码就像是一场精心策划的摄影过程,从

  • 搭建拍摄场景
  • 准备拍摄主体
  • 到在拍摄中进行动态操作

代码逻辑就是这样写的 , 可是还是要多亏我们的大导演three.js , 为我们提供了很多好用的api , 它作为WebGL(图形渲染技术)的上层框架 ,为我们提供了很多便捷 , 今后将会深入学习 ~

点个赞再走吧 , 写作需要激励 , 如果倔友觉得不错就点赞收藏评论吧 ~

小狗淋雨表情包.gif