Threejs 入門并導(dǎo)入外部模型(obj+mtl)

? ? ? ? 目前碰到一個(gè)需求,需要把隔壁部門提供的外部模型展現(xiàn)到 WEB 上,并且可旋轉(zhuǎn)和移動(dòng)視角查看。作為一個(gè)從未接觸過 WEBGL 的菜鳥來說,一頓谷歌搜出來常用的四個(gè):Threejs、Babylonjs、Scenejs、Layaboxjs,最后由于英文不好和使用人群的原因,選擇了第一個(gè)。

? ? ? ? 決定好了之后,第一步先引入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>
? ? ? ? 引入完畢之后,先進(jìn)行初始化:
    <script type="text/javascript">
        var container = window.document.getElementById("content_box");                     // 需要插入 canvas 的標(biāo)簽
        var width = container.offsetWidth;
        var height = container.offsetHeight;

        var scene = new THREE.Scene();                                              // 創(chuàng)建場(chǎng)景
        var camera = new THREE.PerspectiveCamera(45, width / height, 1, 1700);      // 創(chuàng)建相機(jī)
        var controls = null;                                                        // 控制器

         // 設(shè)置相機(jī)位置
        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);                                       // 設(shè)置背景顏色
        renderer.setPixelRatio(window.devicePixelRatio);                            // 設(shè)置顯示比例
        renderer.setSize(width, height);                                            // 設(shè)置渲染大小
        container.appendChild(renderer.domElement);
        render();

        // 執(zhí)行渲染操作,指定場(chǎng)景、相機(jī)作為參數(shù)
        function render() {
            renderer.render(scene, camera);
        }
    </script>

? ? ? ? 如果 content_box 變成橙色,那說明代碼執(zhí)行成功了。

? ? ? ? Threejs是不能直接導(dǎo)入外部模型的,需要先引入響應(yīng)的擴(kuò)展插件:
    <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文件(如果不需要材質(zhì),可直接執(zhí)行l(wèi)oadOBJ(),否則必須優(yōu)先加載材質(zhì))
        function loadMTL() {
            mtlLoader.load('./piers.mtl', function (materials) {
                // OBJ模型會(huì)和MaterialCreator包含的材質(zhì)相對(duì)應(yīng)
                materials.preload();
                objLoader.setMaterials(materials);  
                loadOBJ(materials);
            }, function () {
                console.log('import MTL success!');
            }, function (err) {
                console.log('MTL error!', err);
            });
        }
        // 加載OBJ文件
        function loadOBJ() {
            // 如果沒有材質(zhì)文件,系統(tǒng)自動(dòng)設(shè)置Phong網(wǎng)格材質(zhì)
            objLoader.load('./piers.obj', function (obj) {
                // 初始化模型坐標(biāo)值(根據(jù)需要自行調(diào)整)
                obj.position.x = 0;
                obj.position.y = 0;
                obj.position.z = -40;
                // 設(shè)置模型縮放比例
                obj.scale.set(0.7, 0.7, 0.7);
                // 把模型添加到場(chǎng)景里面
                scene.add(obj);
                
                setTimeout(render, 400);
            }, function () {
                console.log('import OBJ success!');
            }, function (err) {
                console.log('OBJ error!', err);
            });
        }
    </script>

? ? ? ? 執(zhí)行后如果沒有成功加載的,有可能遇到了以下問題:
? ? ? ? ⑴、請(qǐng)求跨域。類似請(qǐng)求接口一樣,加載外部模型也必須要有環(huán)境的支持,可以通過 webpack、nginx代理、把代碼放在服務(wù)器端、用 Chrome 插件(Web Server)等等來解決,具體就不詳述了,不會(huì)的自行上網(wǎng)查資料。

請(qǐng)求跨域報(bào)錯(cuò)

? ? ? ? ⑵、MTL材質(zhì)貼圖路徑問題。如果MTL文件帶有貼圖或者其它材質(zhì),可能會(huì)產(chǎn)生路徑錯(cuò)誤的問題,查看控制臺(tái)會(huì)看到貼圖文件的 404 錯(cuò)誤,這就是因?yàn)槁窂狡ヅ洳簧系脑?。生成MTL文件的時(shí)候,外部材質(zhì)的路徑一般是根據(jù)你的電腦所配置的,就像這樣:


MTL材質(zhì)貼圖路徑問題

解決方法很簡(jiǎn)單,其它不動(dòng),只用修改引用材質(zhì)的路徑就可以了(這里我是直接放到同級(jí)目錄),和 HTML 引入 JS 一樣,唯一不同的就是左斜杠(/)換成右斜杠(\)罷了。

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

友情提示,如果是使用了 webpack 或者腳手架工具的話(如vue-cli),得先看設(shè)置的 baseURL 在哪,因?yàn)樵?webpack 環(huán)境里,有時(shí)候路徑指向的并不是針對(duì)于當(dāng)前文件的相對(duì)路徑。

? ? ? ? ⑶、MTL 材質(zhì)透明度問題(巨坑)。如果上述兩步解決了還是看不到模型,那很有可能是 MTL 對(duì)象的透明度為 0 的原因。Theeejs 某些情況下加載完 MTL 文件后,MTL 對(duì)象里面的 opacity 屬性默認(rèn)居然是 0(至今沒找到原因,知道的大佬可以在評(píng)論區(qū)解釋下,感謝),所以需要我們手動(dòng)設(shè)置一下:

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

                // 遍歷所有材質(zhì)對(duì)象,把透明度統(tǒng)一改為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>

? ? ? ?⑷、外部模型坐標(biāo)偏離原點(diǎn)問題(巨坑)。如果仍然沒看到模型,很有可能給你模型的人把模型偏移了原點(diǎn)很遠(yuǎn)很遠(yuǎn),通過代碼貌似設(shè)置不了(也可能是沒找到辦法),我第一次拿到模型的時(shí)候,模型距離原點(diǎn)有幾十萬,當(dāng)時(shí)百思不得其解,最后還是叫群里的大佬幫我看了下模型才發(fā)現(xiàn)問題........

? ? ? ?到此,應(yīng)該可以正常的展現(xiàn)模型了。


橋墩模型(無光)

? ? ? ?但為啥是黑色的呢?其實(shí),我們?nèi)庋鬯芸匆姷囊磺校际枪獾姆瓷?,沒有光,我們能看到的也只是一片漆黑。在這里也一樣。

? ? ? ?所以需要添加光源,才能看到實(shí)際的樣子:
    <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);

                // 添加環(huán)境光源(可根據(jù)具體情況添加不同的光源)
                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>

? ? ? ?添加完成之后,就能看見差不多的效果了,為啥說差不多呢,如果只是這樣基本的添加光源是無法百分百還原的,因?yàn)槲疫@邊需求沒這么高,所以就沒在光源上面浪費(fèi)太多時(shí)間。


橋墩模型(有光)
? ? ? ?最后的最后,加上軌道控制器,就是所謂的移動(dòng)旋轉(zhuǎn)視角:
    <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);         // 創(chuàng)建控件對(duì)象
        controls.addEventListener('change', render);                                // 監(jiān)聽鼠標(biāo)、鍵盤事件
    </script>
? ? ? ?至此,Threejs 導(dǎo)入外部模型已經(jīng)全部完成,效果如下。
最終效果

? ? ? ?完整代碼如下:

<!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 的標(biāo)簽
        var width = container.offsetWidth;
        var height = container.offsetHeight;

        var scene = new THREE.Scene();                                              // 創(chuàng)建場(chǎng)景
        var camera = new THREE.PerspectiveCamera(45, width / height, 1, 1700);      // 創(chuàng)建相機(jī)
        // 設(shè)置相機(jī)位置
        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);                                       // 設(shè)置背景顏色
        renderer.setPixelRatio(window.devicePixelRatio);                            // 設(shè)置顯示比例
        renderer.setSize(width, height);                                            // 設(shè)置渲染大小
        container.appendChild(renderer.domElement);
        var controls = new THREE.OrbitControls(camera,renderer.domElement);         // 創(chuàng)建控件對(duì)象
        controls.addEventListener('change', render);                                // 監(jiān)聽鼠標(biāo)、鍵盤事件

        // 執(zhí)行渲染操作,指定場(chǎng)景、相機(jī)作為參數(shù)
        function render() {
            console.log('render');
            renderer.render(scene, camera);
        }
        // 加載MTL文件
        function loadMTL() {
            mtlLoader.load('./piers.mtl', function (materials) {
                // OBJ模型會(huì)和MaterialCreator包含的材質(zhì)相對(duì)應(yīng)
                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() {
            // 如果沒有材質(zhì)文件,系統(tǒng)自動(dòng)設(shè)置Phong網(wǎng)格材質(zhì)
            objLoader.load('./piers.obj', function (obj) {
                // 初始化模型坐標(biāo)值
                obj.position.x = 0;
                obj.position.y = 0;
                obj.position.z = -40;
                // 設(shè)置模型縮放比例
                obj.scale.set(0.7, 0.7, 0.7);
                // 把模型添加到場(chǎng)景里面
                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>

? ? ? ?合并到項(xiàng)目的截圖如下:

項(xiàng)目截圖

? ? ? ?研究完成之后,特此記錄,希望可以幫到需要幫助的人,有什么疑問或者遺漏,也可以評(píng)論留言。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
禁止轉(zhuǎn)載,如需轉(zhuǎn)載請(qǐng)通過簡(jiǎn)信或評(píng)論聯(lián)系作者。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容