github的地址 歡迎star!
之前專案中用到了3D模型演示的問題,整理了一下之前學習總結以及遇到的坑。3D 框架有老牌引擎 Three.js 和微軟的 Babylon.js
對比一下還是使用更為普遍的Three.jsThree.js基礎概念
主要來自於《Three.js開飯指南》也可以參考線上網站threejs教程
3個基礎概念:場景(scene)、相機(camera)和渲染器(renderer)。
-
Sence 場景:場景是一個載體,容器,所有的一切都執行在這個容器裡面(存放著所有渲染的物體和使用的光源)
-
相機camera的作用是定義可視域,相當於我們的雙眼,生產一個個快照,最為常用的是 PerspectiveCamera 透視攝像機,其他還有ArrayCamera 陣列攝像機(包含多個子攝像機,通過這一組子攝像機渲染出實際效果,適用於 VR 場景),CubeCamera 立方攝像機(建立六個 PerspectiveCamera(透視攝像機),適用於鏡面場景),StereoCamera 立體相機(雙透視攝像機適用於 3D 影片、視差效果)。相機主要分為兩類正投影相機和透視相機,正投影相機的話, 所有方塊渲染出來的尺寸都一樣; 物件和相機之間的距離不會影響渲染結果,而透視相機接近真實世界,看物體會產生遠近高低各不同
-
PerspectiveCamera 透視攝像機--模擬人眼的視覺,根據物體距離攝像機的距離,近大遠小
- 渲染器renderer則負責用如何渲染出影象,是使用WegGL還是Canvas,類似於react中render,產生實際的頁面效果
其他一些概念
- Mesh 網格:有了場景和攝像頭就可以看到 3D 場景中的物體,場景中的我們最為常用的物體稱為網格。網格由兩部分組成:幾何體和材質
- 材料(Materials),紋理( Textures):物體的表面屬性可以是單純的顏色,也可以是很複雜的情況,比如反射/透射/折射的情況,還可以有紋理圖案。比如包裝盒外面的貼圖。
- Geometry 幾何形狀:threejs使用Geometry定義物體的幾何形狀,其實Geometry的核心就是點集,之所以有這麼多的Geometry,是為了更方便的建立各種形狀的點集
- 光照(Lights):組成部分。 如果 沒有 光源, 我們 就不 可能 看到 任何 渲染 結果,具體介紹可以檢視光照效果和Phong光照模型。一些常用的光源:
- AmbientLight 環境光源,屬於基礎光源,為場景中的所有物體提供一個基礎亮度。
- DirectionalLight 平行光源:類似太陽光,發出的光源都是平行的
- HemisphereLight 半球光源:只有圓球的半邊會發出光源。
- PointLight 點光源:一個點向四周發出光源,一般用於燈泡。
- SpotLight 聚光燈光源:一個圓錐體的燈光
- 注意:並不是每一種光源都能產生陰影(Shadow):DirectionalLight, PointLight, SpotLight三種能產生陰影,另外如要開啟模型的陰影的話,模型是由多個 Mesh 組成的,只開啟父的 Mesh 的陰影是不行的,還需要遍歷父 Mesh 下所有的子 Mesh 為其開啟投射陰影 castShadow 和接收投射陰影 receiveShadow。
- 載入器(Loaders):用來解析的匯入的模型檔案,常見的有OBJLoader(載入.obj檔案)、JSONLoader,MTLLoader
// 場景是所有物體的容器
var scene = new THREE.Scene();
// 相機,相機決定了場景中那個角度的景色會顯示出來。相機就像人的眼睛一樣,人站在不同位置,抬頭或者低頭都能夠看到不同的景色。
var camera = new THREE.PerspectiveCamera(75, window.innerWidth/window.innerHeight, 0.1, 1000);
// 渲染器renderer的domElement元素,表示渲染器中的畫布,所有的渲染都是畫在domElement上的
var renderer = new THREE.WebGLRenderer(); // 渲染器
renderer.setSize(window.innerWidth, window.innerHeight);
// 設定渲染器的大小為視窗的內寬度,也就是內容區的寬度
document.body.appendChild(renderer.domElement);
// 渲染迴圈
function animate() {
render();
// 呼叫requestAnimationFrame函式,傳遞一個callback引數,則在下一個動畫幀時,會呼叫callback這個函式。
requestAnimationFrame( animate );
}
動畫方案:
一:改變camera
function animation()
{
//renderer.clear();
camera.position.x =camera.position.x +1;
renderer.render(scene, camera);
requestAnimationFrame(animation);
}
// camera.position.x =camera.position.x +1;
// 將相機不斷的沿著x軸移動1個單位,也就是相機向右移動,那麼相機中物體是向左移動的。
// 呼叫requestAnimationFrame(animation)函式,這個函式又會在下一個動畫幀出發animation()函式,這樣就不斷改變了相機的位置,從而物體看上去在移動了。
// 另外,必須要重視render函式,這個函式是重新繪製渲染結果,如果不呼叫這個函式,那麼即使相機的位置變化了,但是沒有重新繪製,仍然顯示的是上一幀的動畫 renderer.render(scene, camera);
二:改變物體自身位置--mesh
mesh就是指的物體,它有一個位置屬性position,這個position是一個THREE.Vector3型別變數,所以你要把它向左移動,只需要將x的值不斷的減少就可以了。這裡我們減去的是1個單位。
// [渲染真實性---光源運用](http://www.hewebgl.com/article/getarticle/60)
THREE.Light ( hex )
它有一個引數hex,接受一個16進位制的顏色值。例如要定義一種紅色的光源,我們可以這樣來定義:
Var redLight = new THREE.Light(0xFF0000);
// [文理--3D物體的皮膚:](http://www.hewebgl.com/article/getarticle/68)
紋理類由THREE.Texture表示,其建構函式如下所示:
THREE.Texture( image, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy )
複製程式碼
一下就是Three.js的基本概念
然後給出一個簡單的例子
// 引入 Three.js 庫
<script src="https://unpkg.com/three"></script>
function init () {
// 獲取瀏覽器視窗的寬高,後續會用
var width = window.innerWidth
var height = window.innerHeight
// 建立一個場景
var scene = new THREE.Scene()
// 建立一個具有透視效果的攝像機
var camera = new THREE.PerspectiveCamera(45, width / height, 0.1, 800)
// 設定攝像機位置,並將其朝向場景中心
camera.position.x = 10
camera.position.y = 10
camera.position.z = 30
camera.lookAt(scene.position)
// 建立一個 WebGL 渲染器,Three.js 還提供 <canvas>, <svg>, CSS3D 渲染器。
var renderer = new THREE.WebGLRenderer()
// 設定渲染器的清除顏色(即背景色)和尺寸。
// 若想用 body 作為背景,則可以不設定 clearColor,然後在建立渲染器時設定 alpha: true,即 new THREE.WebGLRenderer({ alpha: true })
renderer.setClearColor(0xffffff)
renderer.setSize(width, height)
// 建立一個長寬高均為 4 個單位長度的立方體(幾何體)
var cubeGeometry = new THREE.BoxGeometry(4, 4, 4)
// 建立材質(該材質不受光源影響)
var cubeMaterial = new THREE.MeshBasicMaterial({
color: 0xff0000
})
// 建立一個立方體網格(mesh):將材質包裹在幾何體上
var cube = new THREE.Mesh(cubeGeometry, cubeMaterial)
// 設定網格的位置
cube.position.x = 0
cube.position.y = -2
cube.position.z = 0
// 將立方體網格加入到場景中
scene.add(cube)
// 將渲染器的輸出(此處是 canvas 元素)插入到 body 中
document.body.appendChild(renderer.domElement)
// 渲染,即攝像機拍下此刻的場景
renderer.render(scene, camera)
}
init()
複製程式碼
線上的例子點選
實際運用
three效能監視器stats
主要是用來顯示效能幀數的
-
FPS:最後一秒的幀數,越大越流暢
-
MS:渲染一幀需要的時間(毫秒),越低越好
-
MB:佔用的記憶體資訊
-
CUSTOM:自定義皮膚
var stats = new Stats()
stats.showPanel(1)
document.body.appendChild(stats.dom)
function animate() {
requestAnimationFrame(animate)
}
requestAnimationFrame(animate)
複製程式碼
具體一些不用匯入的例子
可以在 github.com/mrdoob/thre… 下載檔案,檢視\three.js-master\examples中例子熟悉相應的程式碼
匯入3D模型例子
匯入模型型別介紹
匯入模型檔案需要用到相應的loader,常用3d軟體匯出的格式,專案中主要是用了OBJ和MTL型別,OBJ定義了幾何體,MTL定義了材質
//當mtl中引用了dds型別的圖片時,還需匯入DDSLoader檔案。
//這裡的src路徑視實際開發而定
<script src="js/loaders/DDSLoader.js"></script>
<script src="js/loaders/MTLLoader.js"></script>
<script src="js/loaders/OBJLoader.js"></script>
THREE.Loader.Handlers.add( /\.dds$/i, new THREE.DDSLoader() );
var mtlLoader = new THREE.MTLLoader();
//設定路徑,也可不是設定,在load中載入完整路徑也可
mtlLoader.setPath( 'obj/male02/' );
mtlLoader.load( 'male02_dds.mtl',
// 資源載入成功後執行的函式
//@params materials THREE.MTLLoader.MaterialCreator
function( materials ) {
materials.preload();
var objLoader = new THREE.OBJLoader();
objLoader.setMaterials( materials );
objLoader.setPath( 'obj/male02/' );
objLoader.load( 'male02.obj', function ( object ) {
object.position.y = - 95;
scene.add( object );
});
});
複製程式碼
具體例子可以檢視
匯入OBJ存在問題,模型匯出為obj格式後,檔案太大(放在伺服器就會產生嚴重的效能問題),而且還需要額外對MTL匯入,不然只會顯示幾何模型
glTF 模型格式
.obj 是靜態模型,不支援動畫資料儲存,無法使用模型的動畫,而且體積大, glTF 是由 Khronos Group 開發的 3D 模型檔案格式,該格式的特點是最大程度的減少了 3D 模型檔案的大小,提高了傳輸、載入以及解析 3D 模型檔案的效率,並且它可擴充套件,可互操作。
.gltf包含場景中節點層次結構、攝像機、網格、材質以及動畫等描述資訊
Three.js 中使用 glTF 格式需額外引入 GLTFLoader.js 載入器。
var gltfLoader = new THREE.gltfLoader()
gltfLoader.load('./assets/box.gltf', function(sence) {
var object = scene.gltf // 模型物件
scene.add(object) // 將模型新增到場景中
})
複製程式碼
glTF 模型中可以使用 Blender 建模軟體製作動畫,匯出後使用 GLTFLoader 載入到 Three.js 中,可以拿到一個 animations 陣列,animations 裡包含了模型的每個動畫 Action 動作。
為了獲取更好的網路效能,還可以使用Draco工具進行壓縮,只有在模型檔案很多時,才推薦壓縮(因為壓縮後格式改變,需要引入其他的解析工具)
動畫
上面說到了動畫,關於動畫,可以直接三方庫Tween 動畫,在許睿提供的研究裡面有相關的運用。一般在Three.js動畫是使用requestAnimationFrame(),當你需要更新螢幕畫面時就可以呼叫此方法。在瀏覽器下次重繪前執行回撥函式。回撥的次數通常是每秒60次。
對模型實現淡入淡出、縮放、位移、旋轉等動畫推薦使用 GSAP 來實現更為簡便。
let tween = new TimelineMax()
tween
.to(box.scale, 1, { // 從 1 縮放至 2,花費 1 秒
x: 2,
y: 2,
z: 2,
ease: Power0.easeInOut, // 速度曲線
onStart: function() {
// 監聽動畫開始
},
onUpdate: function() {
// 監聽動畫過程
},
onComplete: function() {
// 監聽動畫結束
}
})
.to(box.position, 1, { // 縮放結束後,位移 x 至 10,花費 1 秒
x: 10,
y: 0,
z: 0
})
複製程式碼
控制orbitcontrols
場景控制器,OrbitControls 是用於除錯 Camera 的方法,例項化後可以通過滑鼠拖拽來旋轉 Camera 鏡頭的角度,滑鼠滾輪可以控制 Camera 鏡頭的遠近距離,旋轉和遠近都會基於場景的中心點,在除錯預覽則會輕鬆許多。
// 引入檔案
<script src="js/OrbitControls.js"></script>
//場景控制器初始化
function initControls() {
controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.enabled = true; // 滑鼠控制是否可用
// 是否自動旋轉
controls.autoRotate = true;
controls.autoRotateSpeed = 0.05;
//是否可旋轉,旋轉速度(滑鼠左鍵)
controls.enableRotate = true;
controls.rotateSpeed = 0.3;
//controls.target = new THREE.Vector();//攝像機聚焦到某一個點
//最大最小相機移動距離(景深相機)
controls.minDistance = 10;
controls.maxDistance = 40;
//最大仰視角和俯視角
controls.minPolarAngle = Math.PI / 4; // 45度視角
controls.maxPolarAngle = Math.PI / 2.4; // 75度視角
//慣性滑動,滑動大小預設0.25
controls.enableDamping = true;
controls.dampingFactor = 0.25;
//是否可平移,預設移動速度為7px
controls.enablePan = true;
controls.panSpeed = 0.5;
//controls.screenSpacePanning = true;
//滾輪縮放控制
controls.enableZoom = true;
controls.zoomSpeed = 1.5;
//水平方向視角限制
//controls.minAzimuthAngle = -Math.PI/4;
//controls.maxAzimuthAngle = Math.PI/4;
}
複製程式碼
點選互動
在3D模型中,滑鼠點選是重要的互動。對於 Three.js,它沒有類似 DOM 的層級關係,並且處於三維環境中,那麼我們則需要通過以下方式來判斷某物件是否被選中。
function onDocumentMouseDown(event) {
// 點選位置建立一個 THREE.Vector3 向量
var vector = new THREE.Vector3(( event.clientX / window.innerWidth ) * 2 - 1, -( event.clientY / window.innerHeight ) * 2 + 1, 0.5);
// vector.unproject 方法將螢幕上的點選位置轉換成 Three.js 場景中的座標
vector = vector.unproject(camera);
// 使用 THREE.Raycaster 可以向場景中發射光線
var raycaster = new THREE.Raycaster(camera.position, vector.sub(camera.position).normalize());
// 使用 raycaster.intersectObjects 方法來判斷指定的物件中哪些被該光線照射到的,
// 從而顯示不同的顏色
var intersects = raycaster.intersectObjects([sphere, cylinder, cube]);
if (intersects.length > 0) {
console.log(intersects[0]);
// 點選後改變透明度
intersects[0].object.material.transparent = true;
intersects[0].object.material.opacity = 0.1;
<!--...... 在這裡可以實現你所需要的互動-->
}
}
複製程式碼
react中實踐運用
// 引入相關的依賴
npm i -S three
<!--GisThree.js-->
<!--當然 這個程式碼還有很大的優化空間啊!-->
import React, { Component, Fragment } from 'react';
import './GisThree.less';
import OBJLoader from './threejsLibs/OBJLoader';
import Orbitcontrols from './threejsLibs/OrbitControls';
import MTLLoader from './threejsLibs/MTLLoader_module';
import { Icon } from 'antd';
import exhibitObj from './modal/exhibit2.obj';
import exhibitMtl from './modal/exhibit2.mtl';
let THREE = require('three');
Orbitcontrols(THREE);
OBJLoader(THREE);
MTLLoader(THREE);
// 排除這些名字的3D模型
const objectArrName = [ "房屋1101", "房屋1150", "房屋600", "房屋70", "房屋45", "房屋362", "房屋363", "房屋364", "房屋500" ];
class GisThree extends Component {
constructor( props ) {
super(props);
this.state = {
isModel: false,
currentName: '暫無名字',
clientX: 0,
clientY: 0
};
this.threeRef = React.createRef();
}
componentDidMount() {
const width = window.innerWidth;
const height = window.innerHeight;
// todo 初始化場景
const scene = new THREE.Scene();
// todo 載入相機
const camera = new THREE.PerspectiveCamera(60, width / height, 0.1, 80);
camera.position.set(0, 25, 25);
camera.lookAt(new THREE.Vector3(0, 0, 0));
//todo 載入光線
const ambLight = new THREE.AmbientLight(0x404040, 0.5);
const pointLight = new THREE.PointLight(0x404040, 0.8);
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.5);
pointLight.position.set(100, 10, 0);
pointLight.receiveShadow = true;
scene.add(ambLight);
scene.add(pointLight);
scene.add(directionalLight);
//todo renderer
const renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setSize(width, height - 10);
//renderer.setClearColor(0xb9d3ff,1);
renderer.setClearColor(0x000000, 1.0);
//todo 載入模型model
let mtlLoader = new THREE.MTLLoader();
mtlLoader.load(exhibitMtl,
function ( materials ) {
console.log('sdj exhibit.obj', materials)
materials.preload();
let objLoader = new THREE.OBJLoader();
objLoader.setMaterials(materials);
objLoader.load(exhibitObj, function ( object ) {
console.log('sdj exhibit.obj')
console.log('sdj exhibit.obj object', object);
for ( let i = 0; i < object.children.length; i++ ) {
let material = object.children[ i ].material;
let meshObj = new THREE.Mesh(object.children[ i ].geometry, material);
meshObj.receiveShadow = true;
meshObj.castShadow = true;
meshObj.scale.set(0.02, 0.02, 0.02);
meshObj.name = "房屋" + i;
meshObj.position.x = 0;
meshObj.position.y = 0;
meshObj.position.z = -20;
scene.add(meshObj);
}
});
}
);
// todo 場景控制器初始化
const controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.enabled = true; // 滑鼠控制是否可用
// 是否自動旋轉
controls.autoRotate = true;
controls.autoRotateSpeed = 0.05;
//是否可旋轉,旋轉速度(滑鼠左鍵)
controls.enableRotate = true;
controls.rotateSpeed = 0.3;
//controls.target = new THREE.Vector();//攝像機聚焦到某一個點
//最大最小相機移動距離(景深相機)
controls.minDistance = 10;
controls.maxDistance = 40;
//最大仰視角和俯視角
controls.minPolarAngle = Math.PI / 4; // 45度視角
controls.maxPolarAngle = Math.PI / 2.4; // 75度視角
//慣性滑動,滑動大小預設0.25
controls.enableDamping = true;
controls.dampingFactor = 0.25;
//是否可平移,預設移動速度為7px
controls.enablePan = true;
controls.panSpeed = 0.5;
//controls.screenSpacePanning = true;
//滾輪縮放控制
controls.enableZoom = true;
controls.zoomSpeed = 1.5;
//水平方向視角限制
//controls.minAzimuthAngle = -Math.PI/4;
//controls.maxAzimuthAngle = Math.PI/4;
//todo 繫結到類上
this.scene = scene;
this.camera = camera;
this.renderer = renderer;
this.controls = controls;
//滑鼠移入和移出事件高亮顯示選中的模型
this.currentObjectColor = null; //移入模型的顏色
this.currentObject = null; //滑鼠移入的模型
// 初始化場景
// 載入到dom元素上
this.threeRef.current.appendChild(this.renderer.domElement)
this.start();
window.addEventListener('resize',this.resizeFunc1 ,false);
window.addEventListener('resize',this.resizeFunc2 ,false);
}
componentWillUnmount() {
this.stop();
this.threeRef.current.removeChild(this.renderer.domElement);
window.removeEventListener('resize',this.resizeFunc1 ,false);
window.removeEventListener('resize',this.resizeFunc2 ,false);
}
// 初始化
start = () => {
if(!this.frameId){
this.frameId = requestAnimationFrame(this.animate)
}
}
// 解除安裝元件的時候去除
stop = () => {
cancelAnimationFrame(this.frameId);
}
// 更新狀態
animate = () => {
this.controls.update();
this.renderScene();
this.frameId = requestAnimationFrame(this.animate);
}
renderScene = () => {
this.renderer.render(this.scene, this.camera);
}
// 是否展示彈窗
changeModel = ( e ) => {
e.stopPropagation();
this.setState({
isModel: !this.state.isModel
})
}
closeModel = ( e ) => {
e.stopPropagation();
if (this.controls && !this.controls.autoRotate){
this.controls.autoRotate = true;
}
this.setState({
isModel: false
})
}
// 點選3D模型匹配
mouseClick = (e) => {
// 滑鼠座標對映到三維座標
e.preventDefault();
const that = this;
const mouse = new THREE.Vector2();
mouse.x = (e.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(e.clientY / window.innerHeight) * 2 + 1;
if(!this.camera || !this.scene) return;
let vector = new THREE.Vector3(mouse.x, mouse.y, 0.5).unproject(this.camera);
let raycaster = new THREE.Raycaster(this.camera.position, vector.sub(this.camera.position).normalize());
let intersects = raycaster.intersectObjects(this.scene.children, true); //選中的三維模型
console.log('sdj position',intersects)
if (intersects.length > 0) {
let SELECTED = intersects[0];
let currentName = SELECTED.object.name;
console.log('sdj position', e.clientX, e.clientY, e.screenX, e.screenY);
if (objectArrName.indexOf(currentName) == -1) {
if (this.controls.autoRotate){
this.controls.autoRotate = false;
}
that.changeModel(e);
that.setState({
currentName,
clientX: e.clientX,
clientY: (e.clientY - 60)
})
console.log("你選中的物體的名字是:" + currentName);
}
}
}
// 滑鼠聚焦
mouseenterObject = (e) => {
// 滑鼠座標對映到三維座標
e.preventDefault();
let mouse = new THREE.Vector2();
mouse.x = (e.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(e.clientY / window.innerHeight) * 2 + 1;
let vector = new THREE.Vector3(mouse.x, mouse.y, 0.5).unproject(this.camera);
let raycaster = new THREE.Raycaster(this.camera.position, vector.sub(this.camera.position).normalize());
let intersects = raycaster.intersectObjects(this.scene.children, true); //選中的三維模型
if (!intersects.length && this.currentObjectColor && this.currentObject) { //從模型處移到外面
this.currentObject.object.material.color.setHex(this.currentObjectColor);
this.currentObjectColor = null;
this.currentObject = null;
}
if (intersects.length > 0) {
let SELECTED = intersects[0];
let currentName = SELECTED.object.name;
if (objectArrName.indexOf(currentName) == -1) {
if (this.currentObject && currentName === this.currentObject.object.name) {
return;
}
if (this.currentObjectColor && this.currentObject && currentName !== this.currentObject.object.name) { //color值是一個物件
this.currentObject.object.material.color.setHex(this.currentObjectColor);
}
this.currentObject = SELECTED;
this.currentObjectColor = SELECTED.object.material.color.getHex();
SELECTED.object.material.color.set(0x74bec1);
} else {
if (this.currentObjectColor && this.currentObject && currentName !== this.currentObject.object.name) { //color值是一個物件
this.currentObject.object.material.color.setHex(this.currentObjectColor);
}
this.currentObjectColor = null;
this.currentObject = null;
}
}
}
resizeFunc1 = () => {
this.controls.update();
}
resizeFunc2 = (e) => {
this.camera.aspect = window.innerWidth / window.innerHeight;
this.camera.updateProjectionMatrix();
this.renderer.setSize(window.innerWidth, window.innerHeight);
}
render() {
return (
<Fragment>
<div
className={ this.props.className || 'three-component' }
id="d3"
ref={ this.threeRef }
onClick={this.mouseClick}
onMouseMove={this.mouseenterObject}
/>
{
this.state.isModel && (
<div
className="three-modal"
style={ {
top: this.state.clientY,
left: this.state.clientX
} }
>
<Icon
className="three-modal-close"
type="close" theme="outlined"
onClick={ this.closeModel }
/>
<ul>
<li>
<span className="modal-title">出租屋編碼</span>
<span className="modal-data">{ this.state.currentName }</span>
</li>
<li>
<span className="modal-title">地址</span>
<span className="modal-data">社群一號</span>
</li>
<li>
<span className="modal-title">每層樓棟數</span>
<span className="modal-data">6</span>
</li>
<li>
<span className="modal-title">層數</span>
<span className="modal-data">16</span>
</li>
</ul>
</div>
)
}
</Fragment>
)
}
}
export default GisThree;
複製程式碼
在伺服器出現的錯誤,而本地伺服器沒有問題 參考 stackoverflow.com/questions/4…
objLoader.js:624 Uncaught Error: THREE.OBJLoader: Unexpected line: "<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no"><meta name="theme-color" content="#000000"><link rel="manifest" href="/manifest.json"><link rel="shortcut icon" href="/favicon.ico"><title>智慧社群_管理後臺</title><link href="/static/css/main.bdb0e864.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div><script src="/config.js"></script><script type="text/javascript" src="/static/js/charts.24f90613.js"></script><script type="text/javascript" src="/static/js/vendor.0b9068d0.js"></script><script type="text/javascript" src="/static/js/main.cfa93993.js"></script></body></html>"
at OBJLoader.parse (objLoader.js:624)
at objLoader.js:385
at XMLHttpRequest.<anonymous> (three1.js:630)
objLoader.js:624 Uncaught Error: THREE.OBJLoader: Unexpected line: "<!doctype html>"
at OBJLoader.parse (objLoader.js:624)
at objLoader.js:385
at XMLHttpRequest.<anonymous> (three1.js:630)
複製程式碼
最後發現棄用mtl-loader之後(且升級到webpack4)正確顯示了材質,以及出現了git忽略了.obj問題,看部落格,全域性的gitignore_global.txt中忽略了.obj問題,好坑!!!
如果有錯誤或者不嚴謹的地方,請務必給予指正,十分感謝!