Featured image of post 从零开始的Three.js渲染3D模型

从零开始的Three.js渲染3D模型

在 HTML 中渲染 glb 模型

三个月前的项目,一直想写一篇文章整理下,拖延了这么久,都忘得差不多了。其实最好是在知识还热乎的时候记下来,这篇文章最好的发表时间是三个月前╮(╯▽╰)╭

先放效果图:

model gif

引入 Library

首先,引入需要的js库,可以从 three.js 官方github仓库 中找到并下载,你可能会用到的:

前两个是必须用到的,最后一个是根据你使用的文件格式,选择不同的 loader 处理器。我还尝试过 STLLoader FBXLoader

1
2
3
4
5
6
    <!-- 包下载路径 -->
    <!-- https://github.com/mrdoob/three.js/blob/dev/examples/js/loaders/STLLoader.js -->
    <script src="assets/js/3d/three.js"></script>
    <script src="assets/js/3d/OrbitControls.js"></script>
    <script src="assets/js/3d/GLTFLoader.js"></script>
    <script src="assets/js/3d/RGBELoader.js"></script>

引入 RGBELoader 是因为在后面用到了 HDR 贴图,简而言之,就是调整环境光,给模型上色。

在 html 文件中创建一个 div ,让我们的模型能够显示出来。

1
<div id="mymodel" style="width:500px; height: 600px;margin:100px auto;"> </div>

加载模型

初始处理

首先,创建一个模型渲染方法,需要传入要绑定的页面元素的 ID,在方法里对模型进行处理。

1
2
3
4
5
6
7
8
function glbViewer(id){

  var elem = document.getElementById(elementID);
  
  var camera = new THREE.PerspectiveCamera(70,
                elem.clientWidth / elem.clientHeight, 1, 1000);

}

添加相机 camera,它决定了元素在页面中看起来的效果,就像一个投影相机,它接收4个参数,表示垂直视野角度,渲染窗口的长宽比,最近端的截面,最远端的截面。

perspective camera

添加渲染器 renderer,设置 aplha 为 true,这样背景颜色透明,模型就可以浸染到页面背景中。设置渲染器画布的宽高和元素宽高一致,然后将它 append 到页面中。

1
2
3
4
5
6
7
8
9
var renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
renderer.setSize(elem.clientWidth, elem.clientHeight);

renderer.setPixelRatio(window.devicePixelRatio);
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = 1;
renderer.outputEncoding = THREE.sRGBEncoding;

elem.appendChild(renderer.domElement);

outputEncoding 控制输出渲染编码,toneMapping 即使贴图不是 HDR,也可以塑造更真实的效果,toneMappingExposure 调整曝光度。

添加页面自适应,窗口调整时重置画布的大小

1
2
3
4
5
window.addEventListener('resize', function () {
    renderer.setSize(elem.clientWidth, elem.clientHeight);
    camera.aspect = elem.clientWidth/elem.clientHeight;
    camera.updateProjectionMatrix();
}, false);

然后,我们来定义 controls –控制模型的变换缩放和旋转。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
var controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.enableDamping = true; // 启用 damping 给控制器带来重量感
controls.dampingFactor = 0.1;  //动态系数因子 越小越灵敏
controls.rotateSpeed = 0.05;
controls.enableZoom = true; // 是否可以缩放
controls.autoRotate = true; // 是否自动旋转
controls.autoRotateSpeed = 5; // 自动旋转速度

controls.minDistance = 2;
controls.maxDistance = 10;
controls.target.set(0, 0.5, - 0.2);
// 决定了初始化时 模型在页面视野中的角度

controls.update();

设置场景

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
var scene = new THREE.Scene();
new THREE.RGBELoader()
    .load('shanghai_riverside_4k.hdr', function (texture) { // hdr 文件和 index.html 在同级
        texture.mapping = THREE.EquirectangularReflectionMapping;
        scene.background = new THREE.Color(0xffffff);
        scene.environment = texture;

        const loader = new THREE.GLTFLoader()
        .setPath('assets/models/'); //根据你放的模型或者图片的位置决定是否要使用setPath
        loader.load('white.glb', function (gltf) {
            camera.position.set(4, 0.3, -1.2);
            gltf.scene.position.y = -1.8;
            scene.add(gltf.scene);

            // 创建闭包 不断调用 animate 动画函数
            // animate 函数通过调用 controls 的 update 函数来更新整个场景,然后让 three.js 去渲染
            var animate = function () {
                requestAnimationFrame(animate);
                controls.update();
                renderer.render(scene, camera);
            };
            animate();

        });
    });

你可以在 HDR 资源网站 下载你需要的 hdr 文件–可以理解为全景图。

完工

调用上面的方法,将模型和我们前面定义的页面元素绑定起来。

1
2
3
4
5
    <script type="text/javascript">
        window.onload = function () {
            glbViewer("mymodel")
        }
    </script>

总结

three.js 官网不仅仅有文档,也提供了各种文件格式各种效果渲染的实例,可以结合例子和 Github 中实例源码进行学习,手把手带你上手 three.js

有一次建模的同事提供的是零件版 3D 模型,UI 同事是在专业的 3D 渲染和处理的软件( keyshot )中打开,所以导致我在浏览器中渲染出来的效果和他们看到的效果不一样,有时候问题可能是模型并不完整而不是代码问题。

gltf 在线查看:https://gltf-viewer.donmccurdy.com/

后续还有一些细节或者问题待优化,需要自己学习和研究一下 Blender ,自己建模和贴图。

完整代码:(附赠一个 stl 渲染 demo)

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
<body>
    <div id="model" style="width:500px; height: 600px;margin:100px auto;"> </div>
    <!-- 包下载路径 -->
    <!-- https://github.com/mrdoob/three.js/blob/dev/examples/js/loaders/STLLoader.js -->
    <script src="assets/js/3d/three.js"></script>
    <script src="assets/js/3d/OrbitControls.js"></script>
    <script src="assets/js/3d/GLTFLoader.js"></script>
    <script src="assets/js/3d/RGBELoader.js"></script>
    <script src="assets/js/3d/STLLoader.js"></script> 
    <script>
        function glbViewer(elementID) {
            var elem = document.getElementById(elementID)
            var camera = new THREE.PerspectiveCamera(70,
                elem.clientWidth / elem.clientHeight, 1, 1000);
            var renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
            renderer.setSize(elem.clientWidth, elem.clientHeight);
            renderer.setPixelRatio(window.devicePixelRatio);
            renderer.toneMapping = THREE.ACESFilmicToneMapping;
            renderer.toneMappingExposure = 1;
            renderer.outputEncoding = THREE.sRGBEncoding;
            elem.appendChild(renderer.domElement);

            window.addEventListener('resize', function () {
                renderer.setSize(elem.clientWidth, elem.clientHeight);
                camera.aspect = elem.clientWidth / elem.clientHeight;
                camera.updateProjectionMatrix();
            }, false);

            var controls = new THREE.OrbitControls(camera, renderer.domElement);
            controls.minDistance = 2;
            controls.maxDistance = 10;
            controls.target.set(0, 0.5, - 0.2);
            controls.autoRotate = false; //自动旋转
            controls.autoRotateSpeed = 5; //旋转速度
            controls.update();

            var scene = new THREE.Scene();
            new THREE.RGBELoader()
                // .setPath('assets/3d/')
                .load('shanghai_riverside_4k.hdr', function (texture) {
                    texture.mapping = THREE.EquirectangularReflectionMapping;
                    scene.background = new THREE.Color(0xffffff);
                    scene.environment = texture;

                    const loader = new THREE.GLTFLoader()
                    .setPath('assets/models/');
                    loader.load('white.glb', function (gltf) {
                        camera.position.set(4, 0.3, -1.2);
                        gltf.scene.position.y = -1.8;

                        scene.add(gltf.scene);

                        var animate = function () {
                            requestAnimationFrame(animate);
                            controls.update();
                            renderer.render(scene, camera);
                        };
                        animate();

                    });
                });

        }
    </script>
    <script type="text/javascript">
        window.onload = function () {
            glbViewer("model")
        }
    </script>

    <!-- <script>
        function STLViewer(model, elementID) {
            var elem = document.getElementById(elementID)
            var camera = new THREE.PerspectiveCamera(70,
                elem.clientWidth / elem.clientHeight, 1, 1000);
            var renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
            renderer.setSize(elem.clientWidth, elem.clientHeight);
            elem.appendChild(renderer.domElement);
            window.addEventListener('resize', function () {
                renderer.setSize(elem.clientWidth, elem.clientHeight);
                camera.aspect = elem.clientWidth / elem.clientHeight;
                camera.updateProjectionMatrix();
            }, false);
            var controls = new THREE.OrbitControls(camera, renderer.domElement);
            controls.enableDamping = true;
            controls.rotateSpeed = 0.05;
            controls.dampingFactor = 0.1;
            controls.enableZoom = true;
            controls.autoRotate = true; //自动旋转
            controls.autoRotateSpeed = .75;
            var scene = new THREE.Scene();
            scene.add(new THREE.HemisphereLight(0xe74c3c, 1));
            (new THREE.STLLoader()).load(model, function (geometry) {
                const material = new THREE.MeshPhysicalMaterial({
                    metalness: 0.25,
                    roughness: 0.1,
                    opacity: 1.0,
                    transparent: true,
                    transmission: 0.99,
                    clearcoat: 1.0,
                    clearcoatRoughness: 0.25
                })
                var mesh = new THREE.Mesh(geometry, material);
                scene.add(mesh);

                var middle = new THREE.Vector3();
                geometry.computeBoundingBox();
                geometry.boundingBox.getCenter(middle);
                mesh.geometry.applyMatrix4(new THREE.Matrix4().makeTranslation(
                    -middle.x, -middle.y, -middle.z));
                var largestDimension = Math.max(geometry.boundingBox.max.x,
                    geometry.boundingBox.max.y,
                    geometry.boundingBox.max.z)
                camera.position.y = -largestDimension * 1.6;
                var animate = function () {
                    requestAnimationFrame(animate);
                    controls.update();
                    renderer.render(scene, camera);
                };
                animate();
            });
        }
    </script>
    <script type="text/javascript">
        window.onload = function () {
            STLViewer("exa.stl", "model")
        }
    </script> -->
</body>

参考资料

Three.js 官方文档

如何在页面极速渲染3D模型-腾讯 ISUX 团队

Three.js 真实渲染

Licensed under CC BY-NC-SA 4.0
Built with Hugo
Theme Stack designed by Jimmy