第2话 Mesh对象的属性和threejs中的两种相机

本文详细介绍了使用Three.js库创建3D场景,包括添加方块、控制物体属性、使用场景雾化效果、材质覆盖、自定义几何对象、使用GUI控制mesh对象属性以及相机的使用。通过实例代码展示了如何在dat.GUI中添加按钮以控制场景中的物体,以及如何切换透视相机和正交相机。同时,还展示了如何动态改变相机的聚焦点,使场景可视区域发生变化。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

构建一个"向场景中添加方块"的功能

这主要要在dat.GUI中添加按钮以控制场景中各物体对象的属性。

<!DOCTYPE html>
<html>
<head>
<title>第2话</title>
<script type="text/javascript" src="../libs/three.js"></script>
<script type="text/javascript" src="../libs/stats.js"></script>
<script type="text/javascript" src="../libs/dat.gui.js"></script>
</head>
<body>

<div id="threejs-example"></div>
<div id="Stats-output"></div>

<script type="text/javascript">
    function init() {
        var scene = new THREE.Scene();
        var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);
        scene.add(camera);

        var renderer = new THREE.WebGLRenderer();
        renderer.setClearColor(new THREE.Color(0xEEEEEE, 1.0));
        renderer.setSize(window.innerWidth, window.innerHeight);
        renderer.shadowMapEnabled = true;

        // 新建一个平面plane
        var planeGeometry = new THREE.PlaneGeometry(60, 20, 1, 1);
        var planeMaterial = new THREE.MeshLambertMaterial({color: 0xcccccc});
        var plane = new THREE.Mesh(planeGeometry, planeMaterial);
        plane.receiveShadow = true;

        plane.rotation.x = -0.5 * Math.PI;
        plane.position.x = 15;
        plane.position.y = 0;
        plane.position.z = 0;
        scene.add(plane);

        camera.position.x = -30;
        camera.position.y = 40;
        camera.position.z = 30;
        camera.lookAt(scene.position);

        // 添加自然光(平行光)
        var ambientLight = new THREE.AmbientLight(0x0c0c0c);
        scene.add(ambientLight);

        // 添加聚光灯光源
        var spotLight = new THREE.SpotLight( 0xffffff );
        spotLight.position.set( -40, 60, -10 );
        spotLight.castShadow = true;
        scene.add(spotLight);

        document.getElementById("threejs-example").appendChild(renderer.domElement);

        var controls = new function () {
            this.rotationSpeed = 0.02;
            this.numberOfObjects = scene.children.length;       // scene.children[]数组中存放的是所有在场景中的cube对象,scene.children.length可以得到场景中的物体数量

            this.addCube = function() {
                var cbeSize = Math.ceil(Math.random() * 3);
                var cubeGeometry = new THREE.cubeGeometry(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.width);
                cube.position.y = Math.round(Math.random() * 5);
                cube.position.z = -20 + Math.round(Math.random() * planeGeometry.height);
                scene.add(cube);
                this.numberOfObjects = scene.children.length;
            }

            this.removeCube = function() {
                var allChildren = scene.children;
                var lastObject = allChildren[allChildren.length-1];
                if(lastObject instanceof THREE.Mesh) {  // 场景中最后一个cube对象
                    scene.remove(lastObject);           // scene.remove()可以从场景中移除物体
                    this.numberOfObjects = scene.children.length;
                }
            }
        };

        var gui = new dat.GUI();
        gui.add(controls, 'rotationSpeed', 0, 0.5); // GUI中的滑动条
        gui.add(controls, 'addCube');               // GUI中的"addCube"按钮
        gui.add(controls, 'removeCube');
        gui.add(controls, 'outputObjects');
        gui.add(controls, 'numberOfObjects').listen();  // GUI中的监听器(监测场景中的物体数量变化)

        var stats = initStats();
        var step = 0;

        renderScene();
        function renderScene() {
            stats.update();

            scene.traverse(function (ele) {       // scene.traverse(funcA())会对所有scene内的子对象调用funcA()
                if (ele instanceof THREE.Mesh && ele != plane) {    // 忽略plane平面
                    ele.rotation.x += controls.rotationSpeed;     // 通过traverse()来更新cube对象的旋转速度
                    ele.rotation.y += controls.rotationSpeed;
                    ele.rotation.z += controls.rotationSpeed;
                }
            });

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

        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;
        }
    }
    window.onload = init;
</script>
</body>
</html>

请添加图片描述

点击右边的outputObjects按钮,控制台输出:
请添加图片描述

这是场景中各元素的属性构成。

使用场景雾化效果

在init()函数的开头加上:

var scene = new THREE.Scene();
/* 开启场景雾化效果 */
scene.fog = new THREE.Fog(0xffffff, 0.015, 100);        // 0xffffff白色雾化效果,near近处的值0.015,far远处的值100
// scene.fog = new THREE.FogExp2( 0xffffff, 0.015 );    // 0xffffff白色雾化效果,雾的浓度0.015

请添加图片描述

使用材质覆盖效果

在新建场景之后,往init()代码中添加一句:

/* 开启材质覆盖效果 */
 scene.overrideMaterial = new THREE.MeshLambertMaterial({color: 0xffff00});
// 场景内所有物体的材质都是MeshLambertMaterial({color: 0xffffff})

请添加图片描述

threejs中内置的几何对象

Three.js库中封装了很多现成的几何体,可以直接调用相应的几何对象构造函数在三维场景中使用它们。确定好几何对象后,只要再加上材质,就创建出了一个完整的mesh对象。

<!DOCTYPE html>
<html>
<head>
    <title>第2话</title>
    <script type="text/javascript" src="../libs/three.js"></script>
    <script type="text/javascript" src="../libs/ParametricGeometries.js"></script>
    <script type="text/javascript" src="../libs/ConvexGeometry.js"></script>

    <script type="text/javascript" src="../libs/stats.js"></script>
    <script type="text/javascript" src="../libs/dat.gui.js"></script>
</head>
<body>

<div id="Stats-output"></div>
<div id="threejs-example"></div>

<script type="text/javascript">
    function init() {
        var stats = initStats();
        var scene = new THREE.Scene();
        var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);

        var renderer = new THREE.WebGLRenderer();

        renderer.setClearColor(new THREE.Color(0xEEEEEE, 1.0));
        renderer.setSize(window.innerWidth, window.innerHeight);
        renderer.shadowMapEnabled = true;

        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;

        plane.rotation.x = -0.5 * Math.PI;
        plane.position.x = 0;
        plane.position.y = 0;
        plane.position.z = 0;
        scene.add(plane);

        camera.position.x = -50;
        camera.position.y = 30;
        camera.position.z = 20;
        camera.lookAt(new THREE.Vector3(-10, 0, 0));

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

        var spotLight = new THREE.SpotLight(0xffffff);
        spotLight.position.set(-40, 40, 50);
        spotLight.castShadow = true;
        scene.add(spotLight);

        addGeometries(scene);
        document.getElementById("threejs-example").appendChild(renderer.domElement);

        var step = 0;
        renderScene();
        function addGeometries(scene) {
            var geoms = [];
            geoms.push(new THREE.CylinderGeometry(1, 4, 4));
            geoms.push(new THREE.BoxGeometry(2, 2, 2));
            geoms.push(new THREE.SphereGeometry(2));
            geoms.push(new THREE.IcosahedronGeometry(4));

            var points = [
                new THREE.Vector3(2, 2, 2),
                new THREE.Vector3(2, 2, -2),
                new THREE.Vector3(-2, 2, -2),
                new THREE.Vector3(-2, 2, 2),
                new THREE.Vector3(2, -2, 2),
                new THREE.Vector3(2, -2, -2),
                new THREE.Vector3(-2, -2, -2),
                new THREE.Vector3(-2, -2, 2)
            ];
            geoms.push(new THREE.ConvexGeometry(points));

            var pts = [];
            var detail = .1, radius = 3;
            for (var angle = 0.0; angle < Math.PI; angle += detail)
                pts.push(new THREE.Vector3(Math.cos(angle) * radius, 0, Math.sin(angle) * radius));

            geoms.push(new THREE.LatheGeometry(pts, 12));
            geoms.push(new THREE.OctahedronGeometry(3));
            geoms.push(new THREE.ParametricGeometry(THREE.ParametricGeometries.mobius3d, 20, 10));
            geoms.push(new THREE.TetrahedronGeometry(3));
            geoms.push(new THREE.TorusGeometry(3, 1, 10, 10));
            geoms.push(new THREE.TorusKnotGeometry(3, 0.5, 50, 20));

            var j = 0;
            for (var i = 0; i < geoms.length; i++) {
                var cubeMaterial = new THREE.MeshLambertMaterial({wireframe: true, color: Math.random() * 0xffffff});

                var materials = [

                    new THREE.MeshLambertMaterial({color: Math.random() * 0xffffff, shading: THREE.FlatShading}),
                    new THREE.MeshBasicMaterial({color: 0x000000, wireframe: true})

                ];

                var mesh = THREE.SceneUtils.createMultiMaterialObject(geoms[i], materials);
                mesh.traverse(function (e) {
                    e.castShadow = true
                });

                mesh.position.x = -24 + ((i % 4) * 12);
                mesh.position.y = 4;
                mesh.position.z = -8 + (j * 12);

                if ((i + 1) % 4 == 0) j++;
                scene.add(mesh);
            }
        }

        function  renderScene() {
            stats.update();
            requestAnimationFrame(renderScene);
            renderer.render(scene, camera);
        }

        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;
        }
    }
    window.onload = init
</script>
</body>
</html>

请添加图片描述

自定义几何对象

threejs库中的geometry和其他大多数三维库中的一样,基本上是三维空间中的点集,以及一些将这些点连接起来的面。

举个栗子,一个方块有8个角,每个角都可以定义为x,y和z坐标的一个组合,所以每个方块都是三维空间中的8个点。在threejs库中,这些点称为顶点(vertice)。

另外,一个方块有6个侧面,每个角有一个顶点。在threejs库里,每个侧面称为面(face)。

当使用Three.js库提供的这些几何体时,你不必亲自定义所有的这些顶点和面。对于一个方块而言,你只需给出它的长宽高即可,Threejs会利用这些信息在正确的位置自动创建一个拥有8个顶点的几何体,并用正确的面连接起来。

虽然threejs库提供了很多内置的几何体对象,但是你仍然可以通过定义顶点和面,手工创建几何体。例如:

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)
];

var geom = new THREE.Geometry;
geom.vertices = vertices;
geom.faces = faces;
geom.computeCentroids();
geom.mergeVertices();

可以写一个程序通过GUI上的滑动条来动态控制自定义几何对象的形状。

<!DOCTYPE html>
<html>
<head>
    <title>第2话</title>
    <script type="text/javascript" src="../libs/three.js"></script>
    <script type="text/javascript" src="../libs/stats.js"></script>
    <script type="text/javascript" src="../libs/dat.gui.js"></script>
</head>
<body>

<div id="Stats-output"></div>
<div id="threejs-example"></div>

<script type="text/javascript">
    function init() {
        var stats = initStats();
        var scene = new THREE.Scene();

        var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);
        var renderer = new THREE.WebGLRenderer();

        renderer.setClearColor(new THREE.Color(0xEEEEEE, 1.0));
        renderer.setSize(window.innerWidth, window.innerHeight);
        renderer.shadowMapEnabled = true;

        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;

        plane.rotation.x = -0.5 * Math.PI;
        plane.position.x = 0;
        plane.position.y = 0;
        plane.position.z = 0;
        scene.add(plane);

        camera.position.x = -20;
        camera.position.y = 25;
        camera.position.z = 20;
        camera.lookAt(new THREE.Vector3(5, 0, 0));

        var spotLight = new THREE.SpotLight(0xffffff);
        spotLight.position.set(-40, 60, 10);
        spotLight.castShadow = true;
        scene.add(spotLight);

        document.getElementById("threejs-example").appendChild(renderer.domElement);

        var step = 0;
        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)
        ];

        // 根据vertices提供的顶点坐标和faces提供的面坐标新生成场景中第一个mesh物体
        var geom = new THREE.Geometry();
        geom.vertices = vertices;
        geom.faces = faces;
        geom.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 (ele) {  // 为mesh实例组(由多重材质生成的多个mesh实例被打包到了一个组上)下的所有子mesh对象开启"发射阴影"效果
            ele.castShadow = true
        });
        scene.add(mesh);

        function addControl(x, y, z) {
            var controls = new function () {
                this.x = x;     // controls.x = x
                this.y = y;
                this.z = z;
            };
            return controls;    // controls是一个对象,controls{x: x, y: y, z: z}
        }

        // 初始化controlPoints数组中的坐标内容,这是原物体的初始形状,后面可通过GUI上的滑动条进行更改
        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 () {  // 向GUI中添加一个clone按钮
                var clonedGeometry = mesh.children[0].geometry.clone(); // 调用clone()方法复制几何对象
                var materials = [
                    new THREE.MeshLambertMaterial({opacity: 0.6, color: 0xff44ff, transparent: true}),
                    new THREE.MeshBasicMaterial({color: 0x000000, wireframe: true})
                ];

                // 通过复制来的几何对象再加上前面定义好的材质重新生成一个原mesh对象的克隆体
                // 这里是使用多重材质创建mesh物体对象(使用多个材质创建mesh对象时,其实根据材质的数量产生了多个mesh对象,只不过threejs将它们拼合成了一个组,再作为整体把它们显示出来)
                var mesh2 = THREE.SceneUtils.createMultiMaterialObject(clonedGeometry, materials);
                mesh2.children.forEach(function (ele) {
                    ele.castShadow = true
                });

                mesh2.translateX(5);    // 让克隆出来的物体相比原物体偏移一定距离
                mesh2.translateZ(5);
                mesh2.name = "clone";
                scene.remove(scene.getChildByName("clone"));    // 删除原来画布上有的"clone"几何对象
                scene.add(mesh2);
            }
        }, 'clone');

        for (var i = 0; i < 8; i++) {
            f1 = gui.addFolder('Vertices ' + (i + 1));  // 新建8个文件夹,每个文件夹各存放x,y,z三个滑动条
            f1.add(controlPoints[i], 'x', -10, 10);     // 每个滑动条都控制着controlPoints[i]中分量x|y|z的值,范围都是-10 -- 10
            f1.add(controlPoints[i], 'y', -10, 10);
            f1.add(controlPoints[i], 'z', -10, 10);
        }

        renderScene();
        function renderScene() {
            stats.update();

            var vertices = [];
            for (var i = 0; i < 8; i++) {               // 更新节点数组,更新节点在x,y,z方向上的坐标值
                vertices.push(new THREE.Vector3(controlPoints[i].x, controlPoints[i].y, controlPoints[i].z));
            }

            mesh.children.forEach(function (ele) {
                ele.geometry.vertices = vertices;
                ele.geometry.verticesNeedUpdate = true; // 允许几何对象的节点数组更新
                ele.geometry.computeFaceNormals();      // 重新根据更新后的节点数组计算几何对象的侧面
            });

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

        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;
        }
    }
    window.onload = init
</script>
</body>
</html>

请添加图片描述

使用gui控制mesh对象的属性

mesh对象的属性大致有position,rotation,scale,translateX,translateY和translateZ等。

<!DOCTYPE html>
<html>
<head>
    <title>第2话</title>
    <script type="text/javascript" src="../libs/three.js"></script>
    <script type="text/javascript" src="../libs/stats.js"></script>
    <script type="text/javascript" src="../libs/dat.gui.js"></script>
</head>
<body>

<div id="Stats-output"></div>
<div id="threejs-example"></div>

<script type="text/javascript">
    function init() {
        var stats = initStats();
        var scene = new THREE.Scene();

        var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);

        var renderer = new THREE.WebGLRenderer();
        renderer.setClearColor(new THREE.Color(0xEEEEEE, 1.0));
        renderer.setSize(window.innerWidth, window.innerHeight);
        renderer.shadowMapEnabled = true;

        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;

        plane.rotation.x = -0.5 * Math.PI;
        plane.position.x = 0;
        plane.position.y = 0;
        plane.position.z = 0;
        scene.add(plane);

        camera.position.x = -30;
        camera.position.y = 40;
        camera.position.z = 30;
        camera.lookAt(scene.position);

        // 添加微弱的自然光
        var ambientLight = new THREE.AmbientLight(0x0c0c0c);
        scene.add(ambientLight);

        // 添加聚光灯光源来生成阴影
        var spotLight = new THREE.SpotLight(0xffffff);
        spotLight.position.set(-40, 60, 20);
        spotLight.castShadow = true;
        scene.add(spotLight);

        document.getElementById("threejs-example").appendChild(renderer.domElement);

        var step = 0;

        // 构建controls对象的初始状态,为之后要实现的gui功能做准备
        var controls = new function () {
            this.scaleX = 1;
            this.scaleY = 1;
            this.scaleZ = 1;

            this.positionX = 0;
            this.positionY = 4;
            this.positionZ = 0;

            this.rotationX = 0;
            this.rotationY = 0;
            this.rotationZ = 0;
            this.scale = 1;

            this.translateX = 0;
            this.translateY = 0;
            this.translateZ = 0;

            this.visible = true;

            this.translate = function () {
                cube.translateX(controls.translateX);
                cube.translateY(controls.translateY);
                cube.translateZ(controls.translateZ);

                controls.positionX = cube.position.x;
                controls.positionY = cube.position.y;
                controls.positionZ = cube.position.z;
            }
        };

        // 向场景中添加一个初始mesh对象cube
        var material = new THREE.MeshLambertMaterial({color: 0x44ff44});
        var geom = new THREE.BoxGeometry(5, 8, 3);
        var cube = new THREE.Mesh(geom, material);
        cube.position.y = 4;
        cube.castShadow = true;
        scene.add(cube);

        // 构建gui,以通过滑动条来变换物体的形状,大小,位置
        var gui = new dat.GUI();

        guiScale = gui.addFolder('scale');
        guiScale.add(controls, 'scaleX', 0, 5);     // 控制controls对象中的分量scaleX
        guiScale.add(controls, 'scaleY', 0, 5);
        guiScale.add(controls, 'scaleZ', 0, 5);

        guiPosition = gui.addFolder('position');
        var contorl_X = guiPosition.add(controls, 'positionX', -10, 10);
        var contorl_Y = guiPosition.add(controls, 'positionY', -4, 20);
        var contorl_Z = guiPosition.add(controls, 'positionZ', -10, 10);

        // 当contorl_X, contorl_Y和contorl_Z发生变化时,触发onChange()函数,将controls对象中的坐标更新为场景中物体cube的坐标位置
        contorl_X.listen();
        contorl_X.onChange(function (value) {
            cube.position.x = controls.positionX;
        });

        contorl_Y.listen();
        contorl_Y.onChange(function (value) {
            cube.position.y = controls.positionY;
        });

        contorl_Z.listen();
        contorl_Z.onChange(function (value) {
            cube.position.z = controls.positionZ;
        });

        guiRotation = gui.addFolder('rotation');
        guiRotation.add(controls, 'rotationX', -4, 4);
        guiRotation.add(controls, 'rotationY', -4, 4);
        guiRotation.add(controls, 'rotationZ', -4, 4);

        guiTranslate = gui.addFolder('translate');
        guiTranslate.add(controls, 'translateX', -10, 10);
        guiTranslate.add(controls, 'translateY', -10, 10);
        guiTranslate.add(controls, 'translateZ', -10, 10);
        guiTranslate.add(controls, 'translate');    
        // 点击'translate'按钮之后,调用translate()函数(这个函数在controls对象的内部被定义)来更新translateX, translateY, translateZ,即物体的位移值

        gui.add(controls, 'visible');   // gui中添加visible可选框
        renderScene();

        function renderScene() {
            stats.update();

            // 更新物体的可见状态,旋转状态和大小尺寸
            cube.visible = controls.visible;

            cube.rotation.x = controls.rotationX;
            cube.rotation.y = controls.rotationY;
            cube.rotation.z = controls.rotationZ;
            cube.scale.set(controls.scaleX, controls.scaleY, controls.scaleZ);

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

        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;
        }
    }
    window.onload = init
</script>
</body>
</html>

请添加图片描述

position属性

首先是position属性。

事实上,一个对象的位置是相对于其父对象而言的,而父对象通常是盛放mesh对象的那个外层场景scene。通过position属性,你可以设置对象的x, y, z轴坐标。
为一个对象设置位置,其实有三种方法。
1.
首先是最直观的三个分量分开设置的方法:

cube.position.x = 10;
cube.position.y = 3;
cube.position.z = 1;

也可以使用set()函数一次性设置这三个分量:

cube.position.set(10, 3, 1);

由于position属性实质上是一个THREE.Vector3对象,所以也可以使用三维向量的方法设置对象坐标:

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

前面说过,使用THREE.SceneUtilscreateMultiMaterialObject()函数可以利用多个材质创建一个mesh实例对象组,这个组中的所有子mesh实例的几何结构都是一样的,但材质不同。

此时,如果我们改变其中一个网格的位置,可以清晰的看到两个独立的对象,而如果我们移动这个对象组,那么他们的偏移量就是一样的。

rotation属性

通过rotation属性,可以设置对象绕坐标轴旋转的角度。

rotation属性设置的三种方法与position属性设置的三种方法相似。

cube.rotation.x = 0.5 * Math.PI;    // 设置对象绕x轴旋转的角度
cube.rotation.set(0.5 * Math.PI, 0, 0); // 使用set()函数设置对象分别绕三个坐标轴旋转的角度
cube.rotation = new THREE.Vector3(0.5 * Math.PI, 0, 0); // 使用Vector3(x, y, z)同时设置对象分别绕三个坐标轴旋转的角度

translate属性

使用translate函数也可以改变对象的位置,但和position不同,translate属性并不定义对象将要放在哪里的绝对位置,而是定义相对于当前位置对象移动的量。

假设你在场景中添加了一个球体,位置是(1,2,3),现在我们想让这个对象沿着x轴平移,使用translateX(4),那么球体现在的位置就是(5,2,3)。
threejs中的两种相机

threejs中有两种需要特别注意的相机——透视相机(PerspectiveCamera)和正交相机(OrthographicCamer),这两种相机也是3D渲染技术如OpenGL等中使用最多的相机。

<!DOCTYPE html>
<html>
<head>
    <title>第2话</title>
    <script type="text/javascript" src="../libs/three.js"></script>
    <script type="text/javascript" src="../libs/stats.js"></script>
    <script type="text/javascript" src="../libs/dat.gui.js"></script>
</head>
<body>

<div id="Stats-output"></div>
<div id="threejs-example"></div>

<script type="text/javascript">
    function init() {
        var stats = initStats();
        var scene = new THREE.Scene();

        // 定义场景相机的初始状态(之后camera将会通过gui控制面板被更改)
        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;

        var renderer = new THREE.WebGLRenderer();
        renderer.setClearColor(new THREE.Color(0xEEEEEE, 1.0));
        renderer.setSize(window.innerWidth, window.innerHeight);

        // 创建一个白色的平面
        var planeGeometry = new THREE.PlaneGeometry(180, 180);
        var planeMaterial = new THREE.MeshLambertMaterial({color: 0xffffff});
        var plane = new THREE.Mesh(planeGeometry, planeMaterial);

        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);    // cube物体的材质颜色随机(深红或浅红)
                var cube = new THREE.Mesh(cubeGeometry, cubeMaterial);

                cube.position.z = -((planeGeometry.parameters.height) / 2) + 2 + (j * 5);   // 将平面plane用cube方块填充满
                cube.position.x = -((planeGeometry.parameters.width) / 2) + 2 + (i * 5);
                cube.position.y = 2;

                scene.add(cube);
            }
        }

        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);

        document.getElementById("threejs-example").appendChild(renderer.domElement);

        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";      // controls.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";
                }
            };
        };

        var gui = new dat.GUI();
        gui.add(controls, 'switchCamera');
        gui.add(controls, 'perspective').listen();  // 监听controls.perspective值的变化
        camera.lookAt(scene.position);
        renderScene();

        function renderScene() {
            stats.update();
            requestAnimationFrame(renderScene);
            renderer.render(scene, camera);
        }

        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;
        }
    }
    window.onload = init
</script>
</body>
</html>

请添加图片描述

可见,使用OrthographicCamera相机,渲染出来的所有方块的尺寸都一样大,没有“近大远小”的特点,物体对象与相机之间的距离不会影响渲染结果。我们在平时的使用过程中,还是要尽量使用PerspectiveCamera相机,因为它的渲染效果更接近真实世界的感受。

再来分析一下创建PerspectiveCamera和OrthographicCamera相机的构造方法,这两个相机的构造方法有些不一样:

透视相机

先来看一看THREE.PerspectiveCamera()所需要的参数:

请添加图片描述

透视相机的视椎体(场景中将被计算机渲染出来的部分):
请添加图片描述

正投影相机
再来看一看THREE.OrthographicCamera()所需要的参数:

请添加图片描述

正交相机的视方体:

请添加图片描述

改变相机的聚焦点

一般来讲,相机默认会指向场景的中心,即坐标position(0, 0, 0),但我们也可以很方便的改变相机所看的位置。如:

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

如果将上一小节中的代码改动一下,可以使相机的聚焦点动态发生改变,从而使场景可视区域也发生变化。

<!DOCTYPE html>
<html>
<head>
    <title>第2话</title>
    <script type="text/javascript" src="../libs/three.js"></script>
    <script type="text/javascript" src="../libs/stats.js"></script>
    <script type="text/javascript" src="../libs/dat.gui.js"></script>
</head>
<body>

<div id="Stats-output"></div>
<div id="threejs-example">
</div>

<script type="text/javascript">
    function init() {
        var stats = initStats();
        var 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;

        var renderer = new THREE.WebGLRenderer();
        renderer.setClearColor(new THREE.Color(0xEEEEEE, 1.0));
        renderer.setSize(window.innerWidth, window.innerHeight);

        var planeGeometry = new THREE.PlaneGeometry(180, 180);
        var planeMaterial = new THREE.MeshLambertMaterial({color: 0xffffff});
        var plane = new THREE.Mesh(planeGeometry, planeMaterial);

        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);
        var cubeMaterial = new THREE.MeshLambertMaterial({color: 0x00ee22});
        for (var j = 0; j < (planeGeometry.parameters.height / 5); j++) {
            for (var i = 0; i < planeGeometry.parameters.width / 5; i++) {
                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);
            }
        }

        // 添加一个loookAtMesh物体以指示相机当前的聚焦位置
        var lookAtGeom = new THREE.SphereGeometry(2);
        var lookAtMesh = new THREE.Mesh(lookAtGeom, new THREE.MeshLambertMaterial({color: 0xff0000}));
        scene.add(lookAtMesh);

        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);

        document.getElementById("threejs-example").appendChild(renderer.domElement);

        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";
                }
            };
        };

        var gui = new dat.GUI();
        gui.add(controls, 'switchCamera');
        gui.add(controls, 'perspective').listen();

        renderScene();

        var step = 0;
        function renderScene() {
            stats.update();

            step += 0.02;   // 相机移动的速度
            if (camera instanceof THREE.Camera) {
                var x = 10 + ( 100 * (Math.sin(step)));
                camera.lookAt(new THREE.Vector3(x, 10, 0)); // 动态改变相机的聚焦点
                lookAtMesh.position.copy(new THREE.Vector3(x, 10, 0));  // 改变lookAtMesh物体的坐标为此时相机的聚焦点
            }
            requestAnimationFrame(renderScene);
            renderer.render(scene, camera);
        }

        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;
        }
    }
    window.onload = init
</script>
</body>
</html>

请添加图片描述

### 解决PyCharm无法加载Conda虚拟环境的方法 #### 配置设置 为了使 PyCharm 能够成功识别并使用 Conda 创建的虚拟环境,需确保 Anaconda 的路径已正确添加至系统的环境变量中[^1]。这一步骤至关重要,因为只有当 Python 解释器及其关联工具被加入 PATH 后,IDE 才能顺利找到它们。 对于 Windows 用户而言,在安装 Anaconda 时,默认情况下会询问是否将它添加到系统路径里;如果当时选择了否,则现在应该手动完成此操作。具体做法是在“高级系统设置”的“环境变量”选项内编辑 `Path` 变量,追加 Anaconda 安装目录下的 Scripts 文件夹位置。 另外,建议每次新建项目前都通过命令行先激活目标 conda env: ```bash conda activate myenvname ``` 接着再启动 IDE 进入工作区,这样有助于减少兼容性方面的问题发生概率。 #### 常见错误及修复方法 ##### 错误一:未发现任何解释器 症状表现为打开 PyCharm 新建工程向导页面找不到由 Conda 构建出来的 interpreter 列表项。此时应前往 Preferences/Settings -> Project:...->Python Interpreter 下方点击齿轮图标选择 Add...按钮来指定自定义的位置。按照提示浏览定位到对应版本 python.exe 的绝对地址即可解决问题。 ##### 错误二:权限不足导致 DLL 加载失败 有时即使指定了正确的解释器路径,仍可能遇到由于缺乏适当的操作系统级许可而引发的功能缺失现象。特别是涉及到调用某些特定类型的动态链接库 (Dynamic Link Library, .dll) 时尤为明显。因此拥有管理员身份执行相关动作显得尤为重要——无论是从终端还是图形界面触发创建新 venv 流程均如此处理能够有效规避此类隐患。 ##### 错误三:网络连接异常引起依赖下载超时 部分开发者反馈过因网速慢或者其他因素造成 pip install 操作中途断开进而影响整个项目的初始化进度条卡住的情况。对此可尝试调整镜像源加速获取速度或是离线模式预先准备好所需资源包后再继续后续步骤。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值