《Three.js开发指南第2版》读书笔记1

书本源码地址:https://github.com/josdirksen/learning-threejs

第1章 使用Three.js创建你的第一个三位场景

<!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>
    <script src="./libs/three.js"></script>
  </head>
  <body>
    <div id="WebGL-output"> </div>
      <script></script>
       
          
 </body>
</html>
 //定义场景
      var scene = new THREE.Scene();

坐标轴

      //创建轴并添加到场景中
      var axies = new THREE.AxisHelper(20);
      scene.add(axies);      

添加平面

      //定义平面 宽60,高20
      var planeGeomentry = new THREE.PlaneGeometry(60, 20);
      //材质
      var planeMaterial = new THREE.MeshBasicMaterial({ color: 0xcccccc });
      //合并网格对象
      var plane = new THREE.Mesh(planeGeomentry, planeMaterial);
      //绕x轴旋转90度
      plane.rotation.x = -0.5 * Math.PI;
      //场景中位置设置
      plane.position.x = 15;
      plane.position.y = 0;
      plane.position.z = 0;
      //将平面对象添加到场景中
      scene.add(plane);

摄像机


      //定义摄像机
      var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);
      //设置摄像机的位置
      camera.position.x = -30;
      camera.position.y = 40;
      camera.position.z = 30;
      //摄像机指向场景中心默认(0,0,0)
      camera.lookAt(scene.position);

渲染器

      //定义渲染器
      var renderer = new THREE.WebGLRenderer();
      renderer.setClearColorHex();
      //设置场景的背景颜色
      renderer.setClearColor(new THREE.Color(0xeeeeee));
      //设置场景的大小
      renderer.setSize(window.innerWidth, window.innerHeight);
      //输出元素到对应HTML元素中
      document.getElementById('WebGL-output').appendChild(renderer.domElement);
      //渲染器使用指定的摄像机渲染场景
      renderer.render(scene, camera);

添加物体对象

 //长方体
        var cubeGeometry = new THREE.BoxGeometry(4, 4, 4);
        var cubeMaterial = new THREE.MeshBasicMaterial({color: 0xff0000, wireframe: true});//材质框架wireframe
        var cube = new THREE.Mesh(cubeGeometry, cubeMaterial); 
        cube.position.x = -4;
        cube.position.y = 3;
        cube.position.z = 0;
        scene.add(cube);

        //球体
        var sphereGeometry = new THREE.SphereGeometry(4, 20, 20);
        var sphereMaterial = new THREE.MeshBasicMaterial({color: 0x7777ff, wireframe: true});
        var sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
        sphere.position.x = 20;
        sphere.position.y = 4;
        scene.add(sphere);

点光源

  //定义点光源
      var spotLight = new THREE.SpotLight(0xffffff);
      //点光源位置
      spotLight.position.set(-40, 60, -10);
      //点光源产生投影
      spotLight.castShadow = true;
      scene.add(spotLight);

材质

材质MeshBasicMaterial不会对光源有任何反应,指挥使用指定的眼色渲染物体

材质MeshLambertMaterial MeshPhongMaterial在渲染时会对光源产生反应

//没有设置光源会对象会全黑 
var cubeMaterial = new THREE.MeshLambertMaterial({ color: 0xff0000 });

投影

//渲染器开启渲染阴影效果
 renderer.shadowMapEnabled = true;
//平面接收投影
 plane.receiveShadow = true;

//点光源产生投影
spotLight.castShadow = true;

//物体对象产生投影
cube.castShadow = true;

动画

  function renderScene() {
        //保持动画能够持续运行
        requestAnimationFrame(renderScene);
        renderer.render(scene, camera);
      }

帧数统计图形

  <script src="./libs/stats.js"></script>
  <div id="Stats-output"></div>
function initStats() {
        var stats = new Stats();
        stats.setMode(0);
        stats.domElement.style.position = 'absolute';
        stats.domElement.style.left = '0px';
        stats.domElement.style.top = '0px';
        document.getElementById('Stats-output').appendChild(stats.domElement);
        return stats;
      }

var stats = initStats();
      
      function renderScene() {
        //保持动画能够持续运行
          //帧数统计图更新
        stats.update();
        requestAnimationFrame(renderScene);
        renderer.render(scene, camera);
      }
      renderScene();

立方体旋转

  function renderScene() {
        //保持动画能够持续运行
        stats.update();
        //旋转立方体
        cube.rotation.x += 0.02;
        cube.rotation.y += 0.02;
        cube.rotation.z += 0.02;

        requestAnimationFrame(renderScene);
        renderer.render(scene, camera);
      }

球体弹跳

  var step = 0;
      function renderScene() {
        //保持动画能够持续运行
        stats.update();
    
//球体弹跳
        step += 0.04;
        sphere.position.x = 20 + 10 * Math.cos(step);
        sphere.position.y = 2 + 10 * Math.abs(Math.sin(step));

        requestAnimationFrame(renderScene);
        renderer.render(scene, camera);
      }

datGUI

控制小球弹跳和立方体的旋转的速度

<script src="./libs/dat.gui.js"></script>
//js对象
      var controls = new (function () {
        this.rotationSpeed = 0.02;
        this.bouncingSpeed = 0.03;
      })();

      var gui = new dat.GUI();
      //速度范围在0~0.5
      gui.add(controls, 'rotationSpeed', 0, 0.5);
      gui.add(controls, 'bouncingSpeed', 0, 0.5);


function renderScene() {
        //保持动画能够持续运行
        stats.update();
        //旋转立方体
        let speed = controls.rotationSpeed;
        cube.rotation.x += speed;
        cube.rotation.y += speed;
        cube.rotation.z += speed;

        step += controls.bouncingSpeed;
        sphere.position.x = 20 + 10 * Math.cos(step);
        sphere.position.y = 2 + 10 * Math.abs(Math.sin(step));

        requestAnimationFrame(renderScene);
        renderer.render(scene, camera);
      }

场景自适应浏览器

 function onResize() {
     //屏幕长宽比调整
        camera.aspect = window.innerWidth / window.innerHeight;
        camera.updateProjectionMatrix();
     //渲染器尺寸调整
        renderer.setSize(window.innerWidth, window.innerHeight);
      }
      window.addEventListener('resize', onResize, false);

第2章 构建Three.js场景的基本组件

环境光

 var ambientLight = new THREE.AmbientLight(0x0c0c0c);
   scene.add(ambientLight);

datGUI控制对象添加

var controls = new function () {
            this.rotationSpeed = 0.02;
    //场景中的对象个数
            this.numberOfObjects = scene.children.length;
//移除最新添加的立方体
            this.removeCube = function () {
                var allChildren = scene.children;
                var lastObject = allChildren[allChildren.length - 1];
                if (lastObject instanceof THREE.Mesh) {
                    scene.remove(lastObject);
                    this.numberOfObjects = scene.children.length;
                }
            };
//添加随机立方体
            this.addCube = function () {

                var cubeSize = Math.ceil((Math.random() * 3));
                var cubeGeometry = new THREE.BoxGeometry(cubeSize, cubeSize, cubeSize);
                var cubeMaterial = new THREE.MeshLambertMaterial({color: Math.random() * 0xffffff});
                var cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
                cube.castShadow = true;
                cube.name = "cube-" + scene.children.length;
//立方体随机位置
                cube.position.x = -30 + Math.round((Math.random() * planeGeometry.parameters.width));
                cube.position.y = Math.round((Math.random() * 5));
                cube.position.z = -20 + Math.round((Math.random() * planeGeometry.parameters.height));

               
                scene.add(cube);
                this.numberOfObjects = scene.children.length;
            };
//输出对象信息
            this.outputObjects = function () {
                console.log(scene.children);
            }
        };
     var gui = new dat.GUI();
        gui.add(controls, 'rotationSpeed', 0, 0.5);
        gui.add(controls, 'addCube');
        gui.add(controls, 'removeCube');
        gui.add(controls, 'outputObjects');
        gui.add(controls, 'numberOfObjects').listen();



function render() {
            stats.update();

            //立方体旋转
            scene.traverse(function (e) {
                if (e instanceof THREE.Mesh && e != plane) {

                    e.rotation.x += controls.rotationSpeed;
                    e.rotation.y += controls.rotationSpeed;
                    e.rotation.z += controls.rotationSpeed;
                }
            });

            // render using requestAnimationFrame
            requestAnimationFrame(render);
            renderer.render(scene, camera);
        }

THREE.Scene.Add:向场景中添加对象

THREE.Scene.Remove:移除场景中的对象

THREE.Scene.children:获取场景中的所有的子对象列表

THREE.Scene.getObjectByName:利用name属性,获取场景中特定的对象

给场景添加雾化效果

//0xffffff白色雾化效果 0.015近处的属性值 100远处的属性值 雾的浓度线性增长
        scene.fog = new THREE.Fog(0xffffff, 0.015, 100);
//另一种雾化效果,浓度0.01,浓度随距离指数增长
scene.fog=new THREE.FogExp2( 0xffffff, 0.01 );

使用overrideMaterial属性

场景中的所有物体都会使用该属性的材质,即便自身设置了材质

scene.overrideMaterial = new THREE.MeshLambertMaterial({color: 0xffffff});

MeshLambertMaterial创建出不发光但可以对场景中的光源产生光源的物体

THREE.Scene常用的方法和属性

方法、属性描述
add(object)向场景中添加对象,可创建对象组
children返回场景中所有对象的数组,包括摄像机和光源
getObjectByName(name,recursive)创建对象时可指定唯一标识的name,使用该刚发可以查找特定名字的对象.recursive=false在调用者子元素上查找,recursive=true在调用者的所有后台对象上查找
remove(object)object场景中对象的引用,将对象从场景中移除
traverse(function)children属性可返回场景中的所有物体,用于遍历调用者和调用者所有的后代,被调用者和每一个后代对象调用function方法
fog为场景添加雾化效果,可产生隐藏远处物体的雾化效果
overrideMaterial强制场景中的所有物体使用相同的材质

几何体

//构成几何体的顶点
var vertices = [
            new THREE.Vector3(1, 3, 1),
            new THREE.Vector3(1, 3, -1),
            new THREE.Vector3(1, -1, 1),
            new THREE.Vector3(1, -1, -1),
            new THREE.Vector3(-1, 3, -1),
            new THREE.Vector3(-1, 3, 1),
            new THREE.Vector3(-1, -1, -1),
            new THREE.Vector3(-1, -1, 1)
        ];
//保存由顶点链接起来创建的三角形面
        var faces = [
 //new THREE.Face3(0, 2, 1)使用vertices数组中的点0,2,1创建而成的三角面         
            new THREE.Face3(0, 2, 1),
            new THREE.Face3(2, 3, 1),
            new THREE.Face3(4, 6, 5),
            new THREE.Face3(6, 7, 5),
            new THREE.Face3(4, 5, 1),
            new THREE.Face3(5, 0, 1),
            new THREE.Face3(7, 6, 2),
            new THREE.Face3(6, 3, 2),
            new THREE.Face3(5, 7, 0),
            new THREE.Face3(7, 2, 0),
            new THREE.Face3(1, 3, 4),
            new THREE.Face3(3, 6, 4),
        ];
//实例化几何对象
        var geom = new THREE.Geometry();
        geom.vertices = vertices;
        geom.faces = faces;
//three.js会决定每个面的法向量,法向量用于决定不同光源下的颜色
        geom.computeFaceNormals();

注意创建面的顶点时创建顺序,顶点顺序决定了某个面是面向摄像机还是背向摄像机的

创建面向摄像机的面,顶点的顺序是顺时针的,反之逆时针

对于渲染器和游戏引擎来说,使用三角形更加容易,三角形渲染起来效率更高

//render动画循环中
function render(){
    //....
mesh.children.forEach(function (e) {
    //几何体网格指向一个更新后的顶点数组
                e.geometry.vertices = vertices;
    //告诉几何对象顶点更新
                e.geometry.verticesNeedUpdate = true;
    //重新计算每个面
                e.geometry.computeFaceNormals();
            });
    //...
}

多种材质创建网格

var materials = [
            new THREE.MeshLambertMaterial({opacity: 0.6, color: 0x44ff44, transparent: true}),
            new THREE.MeshBasicMaterial({color: 0x000000, wireframe: true})

        ];

        var mesh = THREE.SceneUtils.createMultiMaterialObject(geom, materials);

//子对象都添加阴影
 mesh.children.forEach(function (e) {
            e.castShadow = true            
        });

复制一个对象

this.clone = function () {

                var clonedGeometry = mesh.children[0].geometry.clone();
                var materials = [
                    new THREE.MeshLambertMaterial({opacity: 0.6, color: 0xff44ff, transparent: true}),
                    new THREE.MeshBasicMaterial({color: 0x000000, wireframe: true})

                ];
//创建要复制的对象的新网格
                var mesh2 = THREE.SceneUtils.createMultiMaterialObject(clonedGeometry, materials);
                mesh2.children.forEach(function (e) {
                    e.castShadow = true
                });
//移动新创建的网格,删除之前的副本(若存在)并把这个副本添加到场景中
                mesh2.translateX(5);
                mesh2.translateZ(5);
                mesh2.name = "clone";
                scene.remove(scene.getChildByName("clone"));
                scene.add(mesh2);


            }

THREE.SceneUtils.createMultiMaterialObject为几何体添加线框

THREE.WireframeHelper()也可以添加线框

//为网格mesh添加线框,线框颜色0x000000
var helper=new THREE.WireframeHelper(mesh,0x000000);
scene.add(helper)

helper实际上THREE.Line对象,可设置线框如何显示,如helper.material.linewidth=2指定线框的宽度

网格对象的属性和方法

方法、属性描述
position对象相对于父对象的位置,通常父对象为THREE.Scene对象或THREE.Object3D对象
rotation设置绕每个轴旋转的旋转弧度,ThreeJs还提供了设置相对特定轴的旋转弧度的方法:rotateX(),rotateY(),rotateZ()
scale沿x,y,z轴缩放对象
translateX(amount)沿x轴将对象平移amount距离
translateY(amount)沿y轴将对象平移amount距离
translateZ(amount)沿z轴将对象平移amount距离
visible该属性为false时,Mesh将不会被渲染到场景中

设置position位置

cube.position.x=10
cube.position.y=3
cube.position.z=1
//一次性设置xyz坐标值
cube.position.set(10,3,1)

cube.positon=new THREE.Vector3(10,3,1)

THREE.SceneUtils.createMultiMaterialObject创建一个多材质对象,返回的是网格组

改变网格组中某个对象的位置,可看到两个不同的THREE.Mesh对象

移动网格组,它们的偏移量是一样的

设置rotation旋转

cube.rotation.x=0.5*Math.PI
cube.rotation.set(0.5*Math.PI,0,0)
cube.rotation=new THREE.Vector3(0.5*Math.PI,0,0)

使用度数(0~360)

var degree=45
var inRadians=degree*(Math.PI/180)

scale缩放

值大于1放大,反之缩小

cube.scale.x=1.5
cube.scale.set(1.5,1,1)
cube.scale=new THREE.Vector3(1.5,1,1)

移动

相对于当前位置的平移距离

cube.translateX(10);
                cube.translateY(-10);
                cube.translateZ(5);

摄像机

透视投影摄像机,对象距离摄像机越远会被渲染得越小

正交投影摄像机,所有立方体被渲染出来的尺寸都一样,对象相对于摄像机的距离不影响渲染结果

    var controls = new function () {
            this.perspective = "Perspective";
        //切换摄像机
            this.switchCamera = function () {
                if (camera instanceof THREE.PerspectiveCamera) {
                    //正交投影摄像机
                    camera = new THREE.OrthographicCamera(window.innerWidth / -16, window.innerWidth / 16, window.innerHeight / 16, window.innerHeight / -16, -200, 500);
                    camera.position.x = 120;
                    camera.position.y = 60;
                    camera.position.z = 180;
                    camera.lookAt(scene.position);
                    this.perspective = "Orthographic";
                } else {
                    //透视摄像机
                    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);
                    camera.position.x = 120;
                    camera.position.y = 60;
                    camera.position.z = 180;

                    camera.lookAt(scene.position);
                    this.perspective = "Perspective";
                }
            };
        };

透视摄像机

new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000)
参数描述
fovfov表示视场,摄像机中能看到的那部分场景,推荐默认值50
aspect(长宽比)渲染结果的横向尺寸和纵向尺寸的比值,推荐默认值window.innerWidth / window.innerHeight
near(近面距离)从距离摄像机多近的距离开始渲染,通常设置尽量小,从而能够渲染从摄像机位置可看到的所有物体,推荐默认值0.1
far(远面距离)摄像机从它所在的位置能够看到多远,推荐默认值:1000
zoom(变焦)可放缩场景,负数时场景上下颠倒,推荐默认值:1

正交投影摄像机

 new THREE.OrthographicCamera(window.innerWidth / -16, window.innerWidth / 16, window.innerHeight / 16, window.innerHeight / -16, -200, 500);
参数描述
left(左边界)可视范围的做屏幕,渲染不放的左边界,负值将不会看到,超过边界不会被看得
right(右边界)可渲染区域的另一个侧面
top(上边界)可渲染区域的最上面
bottom(下边界)可渲染区域的最下面
near(近面距离)基于摄像机所处位置,从这一点开始渲染场景
far(远面距离)基于摄像机所处位置,渲染场景到这一点位置
zoom(变焦)放缩场景

将摄像机聚焦在指定点上

camera.lookAt(new THREE.Vector3(x,y,z))

lookAt可以追随物体,看向特定的网格

camera.lookAt(mesh.position)
Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐