美文网首页
Threejs 入门并导入外部模型(obj+mtl)

Threejs 入门并导入外部模型(obj+mtl)

作者: heavens丶 | 来源:发表于2021-07-15 17:54 被阅读0次

        目前碰到一个需求,需要把隔壁部门提供的外部模型展现到 WEB 上,并且可旋转和移动视角查看。作为一个从未接触过 WEBGL 的菜鸟来说,一顿谷歌搜出来常用的四个:Threejs、Babylonjs、Scenejs、Layaboxjs,最后由于英文不好和使用人群的原因,选择了第一个。

        决定好了之后,第一步先引入CDN(JQ可以不管):
    <script type="text/javascript" src="https://cdn.bootcdn.net/ajax/libs/jquery/3.3.1/jquery.js"></script>
    <script type="text/javascript" src="https://cdn.bootcdn.net/ajax/libs/three.js/r128/three.min.js"></script>
        引入完毕之后,先进行初始化:
    <script type="text/javascript">
        var container = window.document.getElementById("content_box");                     // 需要插入 canvas 的标签
        var width = container.offsetWidth;
        var height = container.offsetHeight;

        var scene = new THREE.Scene();                                              // 创建场景
        var camera = new THREE.PerspectiveCamera(45, width / height, 1, 1700);      // 创建相机
        var controls = null;                                                        // 控制器

         // 设置相机位置
        camera.position.x = 100;
        camera.position.y = 160;
        camera.position.z = 330;

        var renderer = new THREE.WebGLRenderer();                                   // 渲染
        renderer.shadowMap.enabled = true;                                          // 开启阴影效果
        renderer.setClearColor('#F77C0D', 1);                                       // 设置背景颜色
        renderer.setPixelRatio(window.devicePixelRatio);                            // 设置显示比例
        renderer.setSize(width, height);                                            // 设置渲染大小
        container.appendChild(renderer.domElement);
        render();

        // 执行渲染操作,指定场景、相机作为参数
        function render() {
            renderer.render(scene, camera);
        }
    </script>

        如果 content_box 变成橙色,那说明代码执行成功了。

        Threejs是不能直接导入外部模型的,需要先引入响应的扩展插件:
    <script type="text/javascript" src="https://www.wjceo.com/lib/js/loaders/MTLLoader.js"></script>
    <script type="text/javascript" src="https://www.wjceo.com/lib/js/loaders/OBJLoader.js"></script>
        然后就可以加载外部模型了:
    <script type="text/javascript">
        var objLoader = new THREE.OBJLoader();
        var mtlLoader = new THREE.MTLLoader();
        loadMTL();
        loadOBJ();

        // 加载MTL文件(如果不需要材质,可直接执行loadOBJ(),否则必须优先加载材质)
        function loadMTL() {
            mtlLoader.load('./piers.mtl', function (materials) {
                // OBJ模型会和MaterialCreator包含的材质相对应
                materials.preload();
                objLoader.setMaterials(materials);  
                loadOBJ(materials);
            }, function () {
                console.log('import MTL success!');
            }, function (err) {
                console.log('MTL error!', err);
            });
        }
        // 加载OBJ文件
        function loadOBJ() {
            // 如果没有材质文件,系统自动设置Phong网格材质
            objLoader.load('./piers.obj', function (obj) {
                // 初始化模型坐标值(根据需要自行调整)
                obj.position.x = 0;
                obj.position.y = 0;
                obj.position.z = -40;
                // 设置模型缩放比例
                obj.scale.set(0.7, 0.7, 0.7);
                // 把模型添加到场景里面
                scene.add(obj);
                
                setTimeout(render, 400);
            }, function () {
                console.log('import OBJ success!');
            }, function (err) {
                console.log('OBJ error!', err);
            });
        }
    </script>

        执行后如果没有成功加载的,有可能遇到了以下问题:
        ⑴、请求跨域。类似请求接口一样,加载外部模型也必须要有环境的支持,可以通过 webpack、nginx代理、把代码放在服务器端、用 Chrome 插件(Web Server)等等来解决,具体就不详述了,不会的自行上网查资料。

请求跨域报错

        ⑵、MTL材质贴图路径问题。如果MTL文件带有贴图或者其它材质,可能会产生路径错误的问题,查看控制台会看到贴图文件的 404 错误,这就是因为路径匹配不上的原因。生成MTL文件的时候,外部材质的路径一般是根据你的电脑所配置的,就像这样:


MTL材质贴图路径问题

解决方法很简单,其它不动,只用修改引用材质的路径就可以了(这里我是直接放到同级目录),和 HTML 引入 JS 一样,唯一不同的就是左斜杠(/)换成右斜杠(\)罢了。

map_Ka .\concrete.jpg
map_Kd .\concrete.jpg

友情提示,如果是使用了 webpack 或者脚手架工具的话(如vue-cli),得先看设置的 baseURL 在哪,因为在 webpack 环境里,有时候路径指向的并不是针对于当前文件的相对路径。

        ⑶、MTL 材质透明度问题(巨坑)。如果上述两步解决了还是看不到模型,那很有可能是 MTL 对象的透明度为 0 的原因。Theeejs 某些情况下加载完 MTL 文件后,MTL 对象里面的 opacity 属性默认居然是 0(至今没找到原因,知道的大佬可以在评论区解释下,感谢),所以需要我们手动设置一下:

    <script type="text/javascript">
        // 加载MTL文件
        function loadMTL() {
            mtlLoader.load('./piers.mtl', function (materials) {
                materials.preload();
                objLoader.setMaterials(materials);                                  

                // 遍历所有材质对象,把透明度统一改为1
                var materialsDetail = materials.materials;
                for (var item in materialsDetail) {
                    materialsDetail[item].opacity = 1
                }
                loadOBJ(materials);
            }, function () {
                console.log('import MTL success!');
            }, function (err) {
                console.log('MTL error!', err);
            });
        }
    </script>

       ⑷、外部模型坐标偏离原点问题(巨坑)。如果仍然没看到模型,很有可能给你模型的人把模型偏移了原点很远很远,通过代码貌似设置不了(也可能是没找到办法),我第一次拿到模型的时候,模型距离原点有几十万,当时百思不得其解,最后还是叫群里的大佬帮我看了下模型才发现问题........

       到此,应该可以正常的展现模型了。


桥墩模型(无光)

       但为啥是黑色的呢?其实,我们肉眼所能看见的一切,都是光的反射,没有光,我们能看到的也只是一片漆黑。在这里也一样。

       所以需要添加光源,才能看到实际的样子:
    <script type="text/javascript">
        // 加载OBJ文件
        function loadOBJ() {
            objLoader.load('./piers.obj', function (obj) {
                obj.position.x = 0;
                obj.position.y = 0;
                obj.position.z = -40;
                obj.scale.set(0.7, 0.7, 0.7);
                scene.add(obj);

                // 添加环境光源(可根据具体情况添加不同的光源)
                var ambient = new THREE.AmbientLight(0xffffff, 0.8);
                scene.add(ambient);

                setTimeout(render, 400);
            }, function () {
                console.log('import OBJ success!');
            }, function (err) {
                console.log('OBJ error!', err);
            });
        }
    </script>

       添加完成之后,就能看见差不多的效果了,为啥说差不多呢,如果只是这样基本的添加光源是无法百分百还原的,因为我这边需求没这么高,所以就没在光源上面浪费太多时间。


桥墩模型(有光)
       最后的最后,加上轨道控制器,就是所谓的移动旋转视角:
    <script type="text/javascript" src="https://raw.githubusercontent.com/fibo/three-orbitcontrols/master/OrbitControls.js"></script>
    <script type="text/javascript">
        controls = new THREE.OrbitControls(camera,renderer.domElement);         // 创建控件对象
        controls.addEventListener('change', render);                                // 监听鼠标、键盘事件
    </script>
       至此,Threejs 导入外部模型已经全部完成,效果如下。
最终效果

       完整代码如下:

<!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>
        <style type="text/css">
            html, body, #content_box{margin: 0;padding: 0;width: 100%;height: 100%;}
            #content_box{top: 0;bottom: 0;right: 0;left: 0;z-index: 4;}
        </style>
    </head>
    <body>
        <div id="content_box"></div>
    </body>
    <script type="text/javascript" src="https://cdn.bootcdn.net/ajax/libs/jquery/3.3.1/jquery.js"></script>
    <script type="text/javascript" src="https://cdn.bootcdn.net/ajax/libs/three.js/r128/three.min.js"></script>
    <script type="text/javascript" src="https://www.wjceo.com/lib/js/loaders/MTLLoader.js"></script>
    <script type="text/javascript" src="https://www.wjceo.com/lib/js/loaders/OBJLoader.js"></script>
    <script type="text/javascript" src="https://raw.githubusercontent.com/fibo/three-orbitcontrols/master/OrbitControls.js"></script>
    <script type="text/javascript">
        var container = document.getElementById("content_box");                     // 需要插入 canvas 的标签
        var width = container.offsetWidth;
        var height = container.offsetHeight;

        var scene = new THREE.Scene();                                              // 创建场景
        var camera = new THREE.PerspectiveCamera(45, width / height, 1, 1700);      // 创建相机
        // 设置相机位置
        camera.position.x = 100;
        camera.position.y = 160;
        camera.position.z = 330;

        var objLoader = new THREE.OBJLoader();
        var mtlLoader = new THREE.MTLLoader();
        loadMTL();
        loadOBJ();


        var renderer = new THREE.WebGLRenderer();                                   // 渲染
        renderer.shadowMap.enabled = true;                                          // 开启阴影效果
        renderer.setClearColor('#F77C0D', 1);                                       // 设置背景颜色
        renderer.setPixelRatio(window.devicePixelRatio);                            // 设置显示比例
        renderer.setSize(width, height);                                            // 设置渲染大小
        container.appendChild(renderer.domElement);
        var controls = new THREE.OrbitControls(camera,renderer.domElement);         // 创建控件对象
        controls.addEventListener('change', render);                                // 监听鼠标、键盘事件

        // 执行渲染操作,指定场景、相机作为参数
        function render() {
            console.log('render');
            renderer.render(scene, camera);
        }
        // 加载MTL文件
        function loadMTL() {
            mtlLoader.load('./piers.mtl', function (materials) {
                // OBJ模型会和MaterialCreator包含的材质相对应
                materials.preload();
                objLoader.setMaterials(materials);                                  

                var materialsDetail = materials.materials;
                for (var item in materialsDetail) {
                    materialsDetail[item].opacity = 1
                }
                loadOBJ(materials);
            }, function () {
                console.log('import MTL success!');
            }, function (err) {
                console.log('MTL error!', err);
            });
        }
        // 加载OBJ文件
        function loadOBJ() {
            // 如果没有材质文件,系统自动设置Phong网格材质
            objLoader.load('./piers.obj', function (obj) {
                // 初始化模型坐标值
                obj.position.x = 0;
                obj.position.y = 0;
                obj.position.z = -40;
                // 设置模型缩放比例
                obj.scale.set(0.7, 0.7, 0.7);
                // 把模型添加到场景里面
                scene.add(obj);

                var ambient = new THREE.AmbientLight(0xffffff, 0.8);
                scene.add(ambient);
                var directional = new THREE.DirectionalLight(0xffffff, 0.8);
                directional.position.set(5, 10, 7);

                setTimeout(render, 400);
            }, function () {
                console.log('import OBJ success!');
            }, function (err) {
                console.log('OBJ error!', err);
            });
        }
    </script>
</html>

       合并到项目的截图如下:

项目截图

       研究完成之后,特此记录,希望可以帮到需要帮助的人,有什么疑问或者遗漏,也可以评论留言。

相关文章

网友评论

      本文标题:Threejs 入门并导入外部模型(obj+mtl)

      本文链接:https://www.haomeiwen.com/subject/pnvppltx.html