? ? ? ? 目前碰到一個(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)查資料。

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

解決方法很簡(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)目的截圖如下:
