【汽车之家】使用Threejs绘制一个三维坐标系

3,726 阅读5分钟
three是一个功能非常强大的3D类库,只要理解了它内部的规则,可以很轻松的创建出各种炫酷的场景。

通过本文你可以了解到:

  1. three的几个核心(场景、相机、渲染器、物体)
  2. 三维坐标系在three中的应用
  3. 绘制一个带网格面的三维坐标系

一、Three几大核心简述

1.1、场景:

  • 场景是Three中的载体,基本上所有元素都必须添加到场景中才能显示出来;
  • 场景是一个理论上无限的三维空间;
  • 在场景中的中心位置的坐标值(0,0,0)

1.2、相机

  • 相机是让整个three动起来的方式之一
  • 相机必须加入到场景中,才能操作它的位置,或旋转角度,从而改变相机所拍摄到的画面,实现动画的效果
  • 相机种类有多重,可以根据需求选择对应的相机,正常情况下我们都用的是透视相机

// 创建一个新的相机
var camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 1, 1000);
  • three官网封装了非常好用的轨道控制器,我们只需要实例化就可以实现平移、旋转、缩放等操作

 // 定义控制器
 ar controls = new THREE.OrbitControls(camera, renderer.domElement);
 // camera : 实例化的相机
 // renderer : 实例化的渲染器

1.3 渲染器

  • 渲染器的唯一作用就是将相机拍摄到的场景渲染到画布中
  • 可以通过设置定时器的方式来制作动画,在定时器中调整相机的位置,或者更改物体的位置都能实现动画的效果

// 通过定时器,来让three动起来
function animate() {
    // 定时执行动画
    requestAnimationFrame(animate);

    // 旋转正方体
    mesh.rotation.x += 0.005;
    mesh.rotation.y += 0.01;

    // 更新渲染器,播放正方体旋转后的画面
    renderer.render(scene, camera);
} 

1.4、物体

  • 一个物体由几何体、材质、纹理组成

// 加载纹理。 在three中有多种加载器,用于加载各种元素,可以直接加载模型,纹理,字体等;
// 纹理的主要作用是,在几何体表面附着一层皮肤,让物体具有真实世界中的样子
// 纹理只能应用在材质上, 
var texture = new THREE.TextureLoader().load('./img/crate.gif');

// 创建一个正方形的几何体, 注:可以通过不同的方法创建不通过的几何体  
 var geometry = new THREE.BoxBufferGeometry(200, 200, 200);

// 创建基础材质,并将上面创建好的纹理应用在材质上
var material = new THREE.MeshBasicMaterial({ map: texture });

// 创建网格, 将几何体和材质传入网格函数,生成能够显示的three模型, 在three中的所有可见的模型都是通过Mesh以及其的衍生类创建出来的,
// 一个可见的模型,需要有几何体以及材质才能被看见,
mesh = new THREE.Mesh(geometry, material);


// 将模型添加到场景中, 如果没有指定位置,那么就从three坐标系中的0,0,0 开始
scene.add(mesh);

  • 物体实例化以后,可以更改其位置相关属性和材质相关属性达到动画的效果。

二、三维坐标系在three中的应用

  • 在three中的坐标系是三维的,也就是每个点的位置由(x, y, z)组成
  • 我们需要自己定义一个基准数值,这样方便所有物体的定位,例如,我我们将坐标轴每一段定义为1,那么X轴可以这样设定,从中心点开始(0,0,0)向右为正(1,0,0),向左为负(-1,0,0)
  • 了解了坐标系的概念,我们就设置相机和物体的位置,从而构建一个丰富的3D场景

三、绘制一个带网格的三维坐标系

直接上代码

<html>

<head>
    <title>My first Three.js app</title>
    <style>
        body {
            margin: 0;
            background: #fff;
        }

        canvas {
            width: 100%;
            height: 100%
        }
    </style>
</head>

<body>
    <script src="js/three.min.js"></script>
    <script src="js/OrbitControls.js"></script>
    <script>
        var lines = [];
        var xAxis = ['X轴测试1', 'X轴测试2', 'X轴测试3', 'X轴测试4',]
        var yAxis = ['Y轴测试1', 'Y轴测试2', 'Y轴测试3', 'Y轴测试4', 'Y轴测试5',]
        var zAxis = ['Z轴测试1', 'Z轴测试2', 'Z轴测试3', 'Z轴测试4', 'Z轴测试5', 'Z轴测试6',]


        // 创建场景
        var scene = new THREE.Scene();

        // 创建透视相机
        var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);
        // 设置相机的位置
        camera.position.set(6, 4, 11)

        var renderer = new THREE.WebGLRenderer({
            precision: 'highp',
            alpha: true,
            antialias: true,
            powerPreference: 'high-performance'
        });
        // 设置渲染器的清除颜色
        renderer.setClearColor(0xffffff, 0);
        // 是指渲染器的大小
        renderer.setSize(window.innerWidth, window.innerHeight);

        document.body.appendChild(renderer.domElement);


        // 定义控制器
        var controls = new THREE.OrbitControls(camera, renderer.domElement);
        controls.maxDistance = 40; // 最大缩放值
        controls.minDistance = 2; // 最小缩放值

        // 给控制器添加事件,不要通过定时器来实现交互,可以极大的优化性能
        controls.addEventListener('change', () => {
            renderer.render(scene, camera);
        });


        // 定义环境光
        var ambientLight = new THREE.AmbientLight(0xffffff);
        // 将光源添加到场景中 -- 所有事物都需要添加到场景中才能发生作用
        scene.add(ambientLight);

        // 创建大立方体
        function creatRect(){

            // 创建一个面的网格
            const creatFace = (width, height) => {
                // 创建面的模型组
                let face = new THREE.Group();

                // 定义网格材质
                let material = new THREE.LineBasicMaterial({
                    color: 0x000000,
                    transparent: true,
                    opacity: 0.35
                });


                // 绘制纵线
                for (let i = 0; i <= width; i++) {
                    // 定义几何形状
                    let geometry = new THREE.Geometry();
                    geometry.vertices.push(
                        new THREE.Vector3(i, 0, 0),
                        new THREE.Vector3(i, height, 0),
                    )
                    // 定义线段
                    let _line = new THREE.LineSegments(geometry, material);

                    // 将线段添加至线框组中
                    lines.push(_line)

                    // 将线段加入到面模型组
                    face.add(_line);
                }

                // 绘制横线
                for (let i = 0; i <= height; i++) {
                    // 定义几何形状
                    let geometry = new THREE.Geometry();
                    geometry.vertices.push(
                        new THREE.Vector3(0, i, 0),
                        new THREE.Vector3(width, i, 0),
                    )
                    // 定义线段
                    let _line = new THREE.LineSegments(geometry, material);

                    // 将线段添加至线框组中
                    lines.push(_line)

                    // 将线段加入到面模型组
                    face.add(_line);
                }

                return face
            }

            // 立方体的容器盒子
            let box = new THREE.Group();

            let _nNum = xAxis.length; // x轴 或 n轴
            let _vNum = yAxis.length; // y轴 或 v轴
            let _uNum = zAxis.length;// z轴 或 u轴

            // 获取相对中心的偏移量
            let offsetX = xAxis.length / 2; // X轴的偏移值
            let offsetY = yAxis.length / 2;  // Y轴的偏移值
            let offsetZ = zAxis.length / 2; // Z轴的偏移值

            /**
             * 创建左面和右面
             */
            let left = creatFace(_uNum, _vNum); // Z决定宽度, Y决定高度
            left.position.set(-offsetX, -offsetY, offsetZ)
            left.rotation.set(0, Math.PI / 2, 0)

            let right = creatFace(_uNum, _vNum);
            right.position.set(_nNum - offsetX, -offsetY, offsetZ)
            right.rotation.set(0, Math.PI / 2, 0)


            /**
             * 创建上面和下面
             */
            let top = creatFace(_nNum, _vNum);
            top.position.set(-offsetX, -offsetY, offsetZ)
            top.rotation.set(Math.PI * 2, 0, 0)

            let bottom = creatFace(_nNum, _vNum);
            bottom.position.set(-offsetX, -offsetY, -_uNum + offsetZ);
            bottom.rotation.set(Math.PI * 2, 0, 0);

            /**
             * 创建前面和后面
             */
            let before = creatFace(_nNum, _uNum);
            before.position.set(-offsetX, -offsetY, offsetZ)
            before.rotation.set(-Math.PI / 2, 0, 0);

            let after = creatFace(_nNum, _uNum);
            after.position.set(-offsetX, _vNum - offsetY, offsetZ)
            after.rotation.set(-Math.PI / 2, 0, 0);


            box.add(left);
            box.add(right);
            box.add(top);
            box.add(bottom);
            box.add(before);
            box.add(after);

            box.position.set(0, 0, 0);

            scene.add(box)
        }

        creatRect()
        renderer.render(scene, camera);
    </script>
</body>

</html>


作者:汽车之家-前端体验部-罗龙