ThreeJs--基本组件

317 阅读17分钟

创建场景

一个场景需要显示任何东西,需要如下组件

组件说明
摄像机决定屏幕上哪些东西需要渲染
光源决定材质如何显示及用于产生阴影
对象摄像机透视图里主要的渲染对象
渲染器基于摄像机和场景提供的信息,调用底层图形API执行真正的场景绘制工作

THREE.Scene对象是所有不同对象的容器,这个对象相对简单。

场景的基本功能

THREE.Scene对象中包括:

  1. THREE.Mesh平面
  2. THREE.SpotLight聚光灯
  3. THREE.AmbientLight环境光
  4. THREE.Camera摄像机自动增加的

function initStats(type) {

    var panelType = (typeof type !== 'undefined' && type) && (!isNaN(type)) ? parseInt(type) : 0;
    var stats = new Stats();
    // 0: fps, 1: ms, 2: mb, 3+: custom
    stats.showPanel(panelType); 
    document.body.appendChild(stats.dom);

    return stats;
}
function initTrackballControls(camera, renderer) {
    var trackballControls = new THREE.TrackballControls(camera, renderer.domElement);
    trackballControls.rotateSpeed = 1.0;
    trackballControls.zoomSpeed = 1.2;
    trackballControls.panSpeed = 0.8;
    trackballControls.noZoom = false;
    trackballControls.noPan = false;
    trackballControls.staticMoving = true;
    trackballControls.dynamicDampingFactor = 0.3;
    trackballControls.keys = [65, 83, 68];
  
    return trackballControls;
}
function init() {
    // 创建一个stats对象    
    let stats = initStats();
    // 创建一个场景
    let scene = new THREE.Scene();
    // 创建一个相机
    let camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 100);
    // 设置相机的位置
    camera.position.x = -30;
    camera.position.y = 40;
    camera.position.z = 30;
    // 设置相机的观察点
    camera.lookAt(scene.position);
    // 将相机添加到场景中
    scene.add(camera);
    // 创建一个平面和平面的材质
    let planeGeometry = new THREE.PlaneGeometry(60, 40, 1, 1);
    let planeMaterial = new THREE.MeshLambertMaterial({ color: 0xffffff });
    // 根据平面和材质创建一个网格对象
    let plane = new THREE.Mesh(planeGeometry, planeMaterial);
    // 将平面绕x轴旋转90度及设置平面的位置
    plane.rotation.x = -0.5 * Math.PI;
    plane.position.x = 0;
    plane.position.y = 0;
    plane.position.z = 0;
    // 将平面添加到场景中
    scene.add(plane);
    // 创建一个环境光并添加到场景中
    let ambientLight = new THREE.AmbientLight(0x3c3c3c);
    scene.add(ambientLight);
    // 创建一个聚光灯并添加到场景中
    let spotLight = new THREE.SpotLight(0xffffff, 1.2, 150, 120);
    spotLight.position.set(-40, 60, -10);
    spotLight.castShadow = true;
    scene.add(spotLight);
    // 创建一个渲染器并设置渲染器的背景色及大小
    let renderer = new THREE.WebGLRenderer();
    renderer.setClearColor(new THREE.Color(0x000000));
    renderer.setSize(window.innerWidth, window.innerHeight);
    renderer.shadowMap.enabled = true;
    // 将渲染结果输出到div中
    document.getElementById("webgl-output").appendChild(renderer.domElement);
    // 创建一个控制器
    let controls = new function () {
        // 设置旋转速度
        this.rotationSpeed = 0.02;
        // 设置场景中对象的个数
        this.numberOfObjects = scene.children.length;
        // 移除场景中的最后一个对象
        this.removeCube = function () {
            // 获取场景中的所有对象
            let allChildren = scene.children;
            // 获取场景中的最后一个对象
            let lastObject = allChildren[allChildren.length - 1];
            if (lastObject instanceof THREE.Mesh) {
                // 在场景中移除最后一个对象
                scene.remove(lastObject);
                // 设置场景中对象的个数
                this.numberOfObjects = scene.children.length;
            }
        }
        // 添加一个立方体到场景中
        this.addCube = function () {
            // 设置立方体的大小
            let cubeSize = Math.ceil((Math.random() * 3));
            // 创建立方体的几何体和材质
            let cubeGeometry = new THREE.BoxGeometry(cubeSize, cubeSize, cubeSize);
            let cubeMaterial = new THREE.MeshLambertMaterial({ color: Math.random() * 0xffffff });
            let 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);
        }
    }
    // 创建一个dat.GUI对象
    let gui = new dat.GUI();
    // 将控制器添加到gui中 范围 0-0.5
    gui.add(controls, 'rotationSpeed', 0, 0.5);
    gui.add(controls, 'addCube');
    gui.add(controls, 'removeCube');
    gui.add(controls, 'outputObjects');
    // 监听场景中对象的个数
    gui.add(controls, 'numberOfObjects').listen();
    // 鼠标控制器
    let trackballControls = initTrackballControls(camera, renderer);
    let clock = new THREE.Clock();
    // 渲染场景
    render();

    function render() {
        // 更新鼠标的控制器
        trackballControls.update(clock.getDelta());
        stats.update();
        // THREE.Scene.traverse(callback) callback方法会在每个子对象上执行
        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;
            }
        }); 
        // 
        requestAnimationFrame(render);
        renderer.render(scene, camera);
    }

}

添加网格到场景中

// 添加一个立方体到场景中
	this.addCube = function () {
            // 设置立方体的大小
            let cubeSize = Math.ceil((Math.random() * 3));
            // 创建立方体的几何体和材质
            let cubeGeometry = new THREE.BoxGeometry(cubeSize, cubeSize, cubeSize);
            let cubeMaterial = new THREE.MeshLambertMaterial({ color: Math.random() * 0xffffff });
            let 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;
        }

当点击addCube按钮的时候,一个新的网格对象被添加到场景中,它长宽高都是一个从1到3之间的随机数。除了尺寸是随机的,颜色和位置也是随机的。

移除网格

        // 移除场景中的最后一个对象
        this.removeCube = function () {
            // 获取场景中的所有对象
            let allChildren = scene.children;
            // 获取场景中的最后一个对象
            let lastObject = allChildren[allChildren.length - 1];
            if (lastObject instanceof THREE.Mesh) {
                // 在场景中移除最后一个对象
                scene.remove(lastObject);
                // 设置场景中对象的个数
                this.numberOfObjects = scene.children.length;
            }
        }

THREE.Scene对象的children属性来获取最后一个添加到场景中的对象,children属性将场景中的所有对象存储为数组。在移除对象时我们还判断了是不是THREE.Mesh对象,这样做的原因是避免移除摄像机和光源。我们移除对象后,需要再次更新控制器界面中表示场景中对象数量的属性。

渲染

    function render() {
        // 更新鼠标的控制器
        trackballControls.update(clock.getDelta());
        stats.update();
        // THREE.Scene.traverse(callback) callback方法会在每个子对象上执行
        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;
            }
        }); 
        // 
        requestAnimationFrame(render);
        renderer.render(scene, camera);
    }

THREE.Scene.traverse方法,其参数为方法,这个方法会在场景中的每个对象上执行,直到遍历完场景中的所有对象为止。

渲染效果

20240825-211848.jpg

雾化效果

使用fog可以为整个场景添加雾化效果,就是离摄像机越远就会变的越模糊。

......
scene.fog = new THREE.Fog(0xffffff, 0.015, 100);
......

这里创建了白色雾化效果,后面的两个参数调节雾的显示,near和far的值。

显示效果

20240829-221755.jpg

overrideMaterial属性

场景中的所有物体都会使用该属性指向的材质,即使物体本身设置了材质。这样会减少Three管理的材质数量,提高运行的效率,实际中该属性不常用。 在这个示例中我们使用的材质是THREE.MeshLambertMaterial,该材质类型还能创建出不发光但是可以对场景中的光源产生反应的物体

显示效果

20240829-222455.jpg

容器常用的方法及属性

方法 / 属性描述
add(object)向场景中添加对象
children返回场景中所有对象的列表,包括摄像机和光源
getObjectByName(name,recursive)在创建对象时可以指定唯一标识,使用方法可以查找特定名字的对象,当recursive设置为false时,在调用者子元素查找,否则是在后代对象中查找
remove(object)移除对象
traverse(function)children可以返回场景中的所有对象。该方法也可以遍历调用者和调用者的所有后代,function参数是一个函数,被调用者和每一个后代对象调用function方法
fog增加雾化效果隐藏远程物体的雾化效果
overrideMaterial强制场景中所有物体使用相同的材质

几何体和网格

网格是由形状和材质组成的。

几何体的属性和方法

几何体是由三维空间中的点集和将这些点集连接起来的面组成。

  1. 一个立方体有8个角,也就是有8个顶点
  2. 一个立方体有6个面,每个面包含两个三角形面组成

如果使用的是Three库提供的几何体,不需要定义顶点和面。但是Three这个是开放的,可以自己定义顶点和面。

function initStats(type) {
    var panelType = (typeof type !== 'undefined' && type) && (!isNaN(type)) ? parseInt(type) : 0;
    var stats = new Stats();
    // 0: fps, 1: ms, 2: mb, 3+: custom
    stats.showPanel(panelType);
    document.body.appendChild(stats.dom);
    return stats;
}
function initTrackballControls(camera, renderer) {
    var trackballControls = new THREE.TrackballControls(camera, renderer.domElement);
    trackballControls.rotateSpeed = 1.0;
    trackballControls.zoomSpeed = 1.2;
    trackballControls.panSpeed = 0.8;
    trackballControls.noZoom = false;
    trackballControls.noPan = false;
    trackballControls.staticMoving = true;
    trackballControls.dynamicDampingFactor = 0.3;
    trackballControls.keys = [65, 83, 68];

    return trackballControls;
}
function init() {
    var stats = initStats();
    // 创建一个场景
    let scene = new THREE.Scene();
    // 创建一个相机
    let camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);
    // 设置相机的位置
    camera.position.x = -30;
    camera.position.y = 40;
    camera.position.z = 30;
    // 设置相机的观察点
    camera.lookAt(new THREE.Vector3(5, 0, 0));
    // 将相机添加到场景中
    scene.add(camera);
    // 创建一个平面和平面的材质
    var planeGeometry = new THREE.PlaneGeometry(60, 40, 1, 1);
    var planeMaterial = new THREE.MeshLambertMaterial({ color: 0xffffff });
    var plane = new THREE.Mesh(planeGeometry, planeMaterial);
    // 设置平面可以接收阴影
    plane.receiveShadow = true;
    // 将平面绕x轴旋转90度及设置平面的位置
    plane.rotation.x = -0.5 * Math.PI;
    plane.position.x = 0;
    plane.position.y = 0;
    plane.position.z = 0;
    // 将平面添加到场景中
    scene.add(plane);
    // 创建一个环境光并添加到场景中
    var ambientLight = new THREE.AmbientLight(0x494949);
    scene.add(ambientLight);
    // 创建一个渲染器并设置渲染器的背景色及大小
    let renderer = new THREE.WebGLRenderer();
    renderer.setClearColor(new THREE.Color(0x000000));
    renderer.setSize(window.innerWidth, window.innerHeight);
    renderer.shadowMap.enabled = true;


    // 将渲染结果输出到div中
    document.getElementById("webgl-output").appendChild(renderer.domElement);
    // 创建一个立方体的顶点
    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),
        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),
    ];
    // 创建一个几何体
    let geom = new THREE.Geometry();
    // 设置几何体的顶点和面
    geom.vertices = vertices;
    geom.faces = faces;
    // 计算几何体的法向量
    geom.computeFaceNormals();
    // 创建一个多材质的立方体,除了显示绿色透明的立方体外还显示黑色的线框
    var materials = [
        new THREE.MeshBasicMaterial({ color: 0x000000, wireframe: true }),
        new THREE.MeshLambertMaterial({ opacity: 0.6, color: 0x44ff44, transparent: true })
    ];
    // 创建一个多材质的立方体
    let mesh = THREE.SceneUtils.createMultiMaterialObject(geom, materials);
    mesh.castShadow = true;
    // 为多个网格添加阴影
    mesh.children.forEach(function (e) {
        e.castShadow = true
    });

    scene.add(mesh);
    // 创建一个聚光灯并设置聚光灯的位置及方向
    var spotLight = new THREE.SpotLight(0xffffff, 1, 180, Math.PI / 4);
    spotLight.shadow.mapSize.height = 2048;
    spotLight.shadow.mapSize.width = 2048;
    spotLight.position.set(-40, 30, 30);
    spotLight.castShadow = true;
    spotLight.lookAt(mesh);
    scene.add(spotLight);
    // 创建一个控制器
    function addControl(x, y, z) {
        var controls = new function () {
            this.x = x;
            this.y = y;
            this.z = z;
        };

        return controls;
    }

    var controlPoints = [];
    controlPoints.push(addControl(3, 5, 3));
    controlPoints.push(addControl(3, 5, 0));
    controlPoints.push(addControl(3, 0, 3));
    controlPoints.push(addControl(3, 0, 0));
    controlPoints.push(addControl(0, 5, 0));
    controlPoints.push(addControl(0, 5, 3));
    controlPoints.push(addControl(0, 0, 0));
    controlPoints.push(addControl(0, 0, 3));

    var gui = new dat.GUI();
    gui.add(new function () {
        this.clone = function () {
            // 克隆一个立方体
            var clonedGeometry = mesh.children[0].geometry.clone();
            // 创建一个多材质的立方体,除了显示粉色透明的立方体外还显示黑色的线框
            var materials = [
                new THREE.MeshLambertMaterial({ opacity: 0.8, 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);


        }
    }, 'clone');
    for (var i = 0; i < 8; i++) {
        // 创建一个文件夹
        f1 = gui.addFolder('Vertices ' + (i + 1));
        f1.add(controlPoints[i], 'x', -10, 10);
        f1.add(controlPoints[i], 'y', -10, 10);
        f1.add(controlPoints[i], 'z', -10, 10);

    }
    // 创建一个轨迹球控制器
    var trackballControls = initTrackballControls(camera, renderer);
    var clock = new THREE.Clock();

    render();

    function render() {
        trackballControls.update(clock.getDelta());
        stats.update();

        var vertices = [];
        for (var i = 0; i < 8; i++) {
            vertices.push(new THREE.Vector3(controlPoints[i].x, controlPoints[i].y, controlPoints[i].z));
        }

        mesh.children.forEach(function (e) {
            e.geometry.vertices = vertices;
            e.geometry.verticesNeedUpdate = true;
            e.geometry.computeFaceNormals();
            delete e.geometry.__directGeometry
        });
        requestAnimationFrame(render);
        renderer.render(scene, camera);
    }
}

代码解释一

   // 创建一个立方体的顶点
    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),
        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),
    ];
    // 创建一个几何体
    let geom = new THREE.Geometry();
    // 设置几何体的顶点和面
    geom.vertices = vertices;
    geom.faces = faces;
    // 计算几何体的法向量
    geom.computeFaceNormals();

创建顶点和面,需要注意的是创建面的顶点时的顺序,顶点的顺序决定了某个面是面向摄像机还是背向摄像机得,如果是面向摄像机的面,顶点顺序是顺时针的,反之是逆时针的。 有了这些顶点和面,我们就可以创建实例对象。最后需要设置每个面的法向量,因为法向量决定不同光源下的颜色。

代码解释二

        mesh.children.forEach(function (e) {
            e.geometry.vertices = vertices;
            e.geometry.verticesNeedUpdate = true;
            e.geometry.computeFaceNormals();
            delete e.geometry.__directGeometry
        });

无论何时修改了顶点的属性,立方体都会基于修改后的值重新进行渲染。

  1. 将组成网格的几何体的顶点属性指向一个更新后的顶点数组,如果顶点数组更新了,那么几何体就会更新。
  2. 同时需要告诉几何体更新
  3. 重新计算法向量

代码解释三

    // 创建一个多材质的立方体,除了显示绿色透明的立方体外还显示黑色的线框
    var materials = [
        new THREE.MeshBasicMaterial({ color: 0x000000, wireframe: true }),
        new THREE.MeshLambertMaterial({ opacity: 0.6, color: 0x44ff44, transparent: true })
    ];
    // 创建一个多材质的立方体
    let mesh = THREE.SceneUtils.createMultiMaterialObject(geom, materials);

这里使用的不是一个材质,而是由两个材质组成的数组。这样做的原因是,除了显示绿色透明的立方体外,我们还需要显示线框。

当使用多个材质来创建网格。可以使用THREE.SceneUtils.createMultiMaterialObject方法来创建。这个方法创建的并不是一个网格对象,而是为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.8, 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);
        }

复制立方体的第一个子对象,注意,mesh变量包含两个网格对象:基于两个不同的材质创建的。通过这个复制创建了新的网格。使用translate移动这个网格。

网格对象的属性和方法

创建一个网格需要一个几何体和一个或者多个材质。当网格创建完后,我们可以添加到场景中。网格对象提供了几个属性用于改变它再场景中的位置和现实效果。

方法(属性)描述
position决定对象相对于父对象的位置
rotation设置绕每个轴的旋转弧度
scale沿着x、y和z轴缩放
translateX/Y/Z平移距离
vi四lefalse时,不会渲染到场景中

显示效果

20240825-211849.jpg

选择适合的摄像头

  1. 正交摄像机:立方体渲染尺寸一样。
  2. 透视摄像机:距离摄像机越远,渲染得越小。
function initStats(type) {
    var panelType = (typeof type !== 'undefined' && type) && (!isNaN(type)) ? parseInt(type) : 0;
    var stats = new Stats();
    // 0: fps, 1: ms, 2: mb, 3+: custom
    stats.showPanel(panelType);
    document.body.appendChild(stats.dom);
    return stats;
}
function initTrackballControls(camera, renderer) {
    var trackballControls = new THREE.TrackballControls(camera, renderer.domElement);
    trackballControls.rotateSpeed = 1.0;
    trackballControls.zoomSpeed = 1.2;
    trackballControls.panSpeed = 0.8;
    trackballControls.noZoom = false;
    trackballControls.noPan = false;
    trackballControls.staticMoving = true;
    trackballControls.dynamicDampingFactor = 0.3;
    trackballControls.keys = [65, 83, 68];

    return trackballControls;
}
function init() {
    var stats = initStats();
    // 创建一个场景
    let scene = new THREE.Scene();
    // 创建一个相机及摄像机的位置
    var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);
    camera.position.x = 120;
    camera.position.y = 60;
    camera.position.z = 180;
    // 将相机添加到场景中
    scene.add(camera);
    // 创建一个平面,通过形状及材质
    var planeGeometry = new THREE.PlaneGeometry(250, 250);
    var planeMaterial = new THREE.MeshLambertMaterial({ color: 0xffffff });
    var plane = new THREE.Mesh(planeGeometry, planeMaterial);
    // 旋转90度并设置位置
    plane.rotation.x = -0.5 * Math.PI;
    plane.position.x = 0;
    plane.position.y = 0;
    plane.position.z = 0;
    scene.add(plane);
    // 设置方块边长
    var cubeGeometry = new THREE.BoxGeometry(4, 4, 4);
    // 生产一个方块矩阵
    for (var j = 0; j < (planeGeometry.parameters.height / 5); j++) {
        for (var i = 0; i < planeGeometry.parameters.width / 5; i++) {
            var rnd = Math.random() * 0.75 + 0.25;
            var cubeMaterial = new THREE.MeshLambertMaterial();
            // 颜色随机
            cubeMaterial.color = new THREE.Color(rnd, 0, 0);
            var cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
            cube.position.z = -((planeGeometry.parameters.height) / 2) + 2 + (j * 5);
            cube.position.x = -((planeGeometry.parameters.width) / 2) + 2 + (i * 5);
            cube.position.y = 2;
            scene.add(cube);
        }
    }
    // 创建一个渲染器并设置渲染器的背景色及大小
    var renderer = new THREE.WebGLRenderer();
    renderer.setClearColor(new THREE.Color(0x000000));
    renderer.setSize(window.innerWidth, window.innerHeight);
    // 创建一个平行光
    var directionalLight = new THREE.DirectionalLight(0xffffff, 0.7);
    directionalLight.position.set(-20, 40, 60);
    scene.add(directionalLight);

    // 添加环境光
    var ambientLight = new THREE.AmbientLight(0x292929);
    scene.add(ambientLight);
    var step = 0;

    var trackballControls
    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);
                trackballControls = initTrackballControls(camera, renderer);
                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);
                trackballControls = initTrackballControls(camera, renderer);
                this.perspective = "Perspective";
            }
        };
    };

    var gui = new dat.GUI();
    gui.add(controls, 'switchCamera');
    gui.add(controls, 'perspective').listen();
    // 将渲染结果输出到div中
    document.getElementById("webgl-output").appendChild(renderer.domElement);
    trackballControls = initTrackballControls(camera, renderer);
    var clock = new THREE.Clock();
    render();
    // 渲染函数
    function render() {
        trackballControls.update(clock.getDelta());
        stats.update();
        requestAnimationFrame(render);
        renderer.render(scene, camera);
    }
}

代码解析一

        // 切换摄像机类型
        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);
                trackballControls = initTrackballControls(camera, renderer);
                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);
                trackballControls = initTrackballControls(camera, renderer);
                this.perspective = "Perspective";
            }
        };

正交摄像机和透视摄像机创建方法不一样

  1. 透视摄像机
参数描述
fov表示视场,摄像机中能看到的那部分场景。比如,人类有接近180度的视场,有些鸟类有360视场。计算机不能完全显示我们能看到的场景,所以一般会选择一块较小的区域。
aspect(长宽比)渲染结果的横向尺寸和纵向尺寸的比值
near(近面距离)定义了距离摄像机多远的距离开始渲染,通常会设置尽量小
far(远面距离)定义了摄像机从它所处的位置能够看多远,如果设置小有一部分会渲染不出来,如果设置过大,会影响性能
zoom(变焦)可以放大和缩小场景

20181223145454122.png

  1. 正交摄像机 定义一个需要被渲染的方块区域。
参数描述
left(左边界)设置可视范围的左平面
right(右边界)设置可视范围的右平面
top(上边界)设置可视范围的上平面
bottom(下边界)设置可视范围的下平面
near(近面距离)定义了距离摄像机多远的距离开始渲染,通常会设置尽量小
far(远面距离)定义了摄像机从它所处的位置能够看多远,如果设置小有一部分会渲染不出来,如果设置过大,会影响性能
zoom(变焦)可以放大和缩小场景

u=66848331,3877962368&fm=253&fmt=auto&app=138&f=JPG.webp

将摄像机聚焦在指定点上

到目前为止,我们介绍如下内容:

  1. 如何创建摄像机
  2. 各个参数的含义
  3. 摄像机放置在场景中的位置
  4. 摄像机渲染的区域

接下来看如何设置摄像机的指向位置

function initStats(type) {
    var panelType = (typeof type !== 'undefined' && type) && (!isNaN(type)) ? parseInt(type) : 0;
    var stats = new Stats();
    // 0: fps, 1: ms, 2: mb, 3+: custom
    stats.showPanel(panelType);
    document.body.appendChild(stats.dom);
    return stats;
}
function initTrackballControls(camera, renderer) {
    var trackballControls = new THREE.TrackballControls(camera, renderer.domElement);
    trackballControls.rotateSpeed = 1.0;
    trackballControls.zoomSpeed = 1.2;
    trackballControls.panSpeed = 0.8;
    trackballControls.noZoom = false;
    trackballControls.noPan = false;
    trackballControls.staticMoving = true;
    trackballControls.dynamicDampingFactor = 0.3;
    trackballControls.keys = [65, 83, 68];

    return trackballControls;
}
function init() {
    var stats = initStats();
    // 创建一个场景
    let scene = new THREE.Scene();
    // 创建一个相机及摄像机的位置
    var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);
    camera.position.x = 120;
    camera.position.y = 60;
    camera.position.z = 180;
    // 将相机添加到场景中
    scene.add(camera);
    // 创建一个平面,通过形状及材质
    var planeGeometry = new THREE.PlaneGeometry(250, 250);
    var planeMaterial = new THREE.MeshLambertMaterial({ color: 0xffffff });
    var plane = new THREE.Mesh(planeGeometry, planeMaterial);
    // 旋转90度并设置位置
    plane.rotation.x = -0.5 * Math.PI;
    plane.position.x = 0;
    plane.position.y = 0;
    plane.position.z = 0;
    scene.add(plane);
    // 设置方块边长
    var cubeGeometry = new THREE.BoxGeometry(4, 4, 4);
    // 生产一个方块矩阵
    for (var j = 0; j < (planeGeometry.parameters.height / 5); j++) {
        for (var i = 0; i < planeGeometry.parameters.width / 5; i++) {
            var rnd = Math.random() * 0.75 + 0.25;
            var cubeMaterial = new THREE.MeshLambertMaterial();
            // 颜色随机
            cubeMaterial.color = new THREE.Color(rnd, 0, 0);
            var cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
            cube.position.z = -((planeGeometry.parameters.height) / 2) + 2 + (j * 5);
            cube.position.x = -((planeGeometry.parameters.width) / 2) + 2 + (i * 5);
            cube.position.y = 2;
            scene.add(cube);
        }
    }
    // 创建一个看向的球体
    const lookAtGeom = new THREE.SphereGeometry(2);
    // 根据球体和材质创建一个网格
    const lookAtMesh = new THREE.Mesh(lookAtGeom, new THREE.MeshLambertMaterial({ color: 0x00ff00 }));
    // 将网格添加到场景中
    scene.add(lookAtMesh);

    // 创建一个渲染器并设置渲染器的背景色及大小
    var renderer = new THREE.WebGLRenderer();
    renderer.setClearColor(new THREE.Color(0x000000));
    renderer.setSize(window.innerWidth, window.innerHeight);
    // 创建一个平行光
    var directionalLight = new THREE.DirectionalLight(0xffffff, 0.7);
    directionalLight.position.set(-20, 40, 60);
    scene.add(directionalLight);

    // 添加环境光
    var ambientLight = new THREE.AmbientLight(0x292929);
    scene.add(ambientLight);
    var step = 0;

    var trackballControls
    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);
                trackballControls = initTrackballControls(camera, renderer);
                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);
                trackballControls = initTrackballControls(camera, renderer);
                this.perspective = "Perspective";
            }
        };
    };

    var gui = new dat.GUI();
    gui.add(controls, 'switchCamera');
    gui.add(controls, 'perspective').listen();
    // 将渲染结果输出到div中
    document.getElementById("webgl-output").appendChild(renderer.domElement);
    trackballControls = initTrackballControls(camera, renderer);
    var clock = new THREE.Clock();
    render();
    // 渲染函数
    function render() {
        trackballControls.update(clock.getDelta());
        stats.update();
        step += 0.02;
        if (camera instanceof THREE.Camera) {
            var x = 10 + ( 100 * (Math.sin(step)));
            // 摄像机的焦点指向球体网格
            camera.lookAt(lookAtMesh.position);
            // 设置球体网格的位置
            lookAtMesh.position.copy(new THREE.Vector3(x, 10, 0));
            
        }
        requestAnimationFrame(render);
        renderer.render(scene, camera);
    }
}

代码解释一

   // 创建一个看向的球体
    const lookAtGeom = new THREE.SphereGeometry(2);
    // 根据球体和材质创建一个网格
    const lookAtMesh = new THREE.Mesh(lookAtGeom, new THREE.MeshLambertMaterial({ color: 0x00ff00 }));
    // 将网格添加到场景中
    scene.add(lookAtMesh);

设置球体网格

代码解释二

        if (camera instanceof THREE.Camera) {
            var x = 10 + ( 100 * (Math.sin(step)));
            // 摄像机的焦点指向球体网格
            camera.lookAt(lookAtMesh.position);
            // 设置球体网格的位置
            lookAtMesh.position.copy(new THREE.Vector3(x, 10, 0));
            
        }
  1. 移动x轴坐标
  2. 将摄像机焦点指向球体网格
  3. 设置球体网格的位置

显示效果

20240904-233122.jpg

总结

  1. 介绍了THREE.Scene的所有属性和方法
  2. 展示了如何使用THREE.Geometry对象和使用内部几何体
  3. 介绍了两种摄像机,正交和透视