雖然利用threejs來在網頁中渲染3d模型不是第一次折騰了,但是還是遇到了各種問題。總結下我所遇到的問題,希望能給正在使用threejs的小伙伴一個幫助。
一、所使用的軟件與開發環境
-
Maya2014、Blender2.77a
-
webpack + gulp
二、 動畫模型的導入導出
1、格式的選擇
threejs支持的動畫模型有Collada(.dae)、mmd(用過MikuMikuDance的應該知道) 、fbx、json。
Collada里面包含了你場景中所有數據(camera、scene、light),因為手生所有這次沒有采用這種格式。
fbx雖然網頁中可以加載fbx,但我常用的是從其它軟件中導個fbx給blender用。不過這里值的注意一下,以maya為栗子。
maya導出fbx的時候會有一個文件格式的選項: 二進制(導出給blender選擇這種編碼)
、ASCII(導出給threejs直接用選擇這種編碼)
如下圖所示:

2、動畫的分類
- 變形動畫(threejs中
MorphAnimMesh
類)
變形動畫簡單點來說就是通過變形物體來改變物體的樣子,復雜的動畫用這種方法處理就不合試了,因為改變一下所有的頂點都會隨之改變。數據量挺大的。
在blender中的導出json選擇如下所示:

- 骨骼動畫(threejs中
SkinnedMesh
類)
骨骼動畫也就是蒙皮動畫,通過給物體綁定骨骼來對物體進行操作。
在blender中的導出json時存在以下兩種情況:
1、keyframe animation 導出選項以及json代碼如下所示:

2、skeletal animation 導出選項以及json代碼如下所示:

3、從maya中導出fbx至blender中
-
在maya中導出fbx給blender的時候文件格式選擇
二進制
。 -
做模型綁定的時候對模型的數據進行
清零
(這點很重要,不然blender導出json的會出現錯誤情況)、綁定方法的話按照做游戲的綁定方法就可以了。 -
軟邊顯示與硬邊顯示(導出時在maya中選擇
軟邊顯示
)照理說應該是硬邊顯示才對,瞎猜是blender與maya設置不同吧。在頁面中渲染的情況如下所示:

三、 模型渲染到網頁中
1、創建場景
var camera,scene,renderer
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(40, 750 / 1200, 0.1, 10000);
renderer = new THREE.WebGLRenderer({antialias: true, alpha: false, preserveDrawingBuffer: true});
renderer.shadowMap.enabled = true;
renderer.autoClear = true;
var render = function () {
renderer.render(scene, camera);
}
var animate = function () {
requestAnimationFrame(_that.animate);
render();
}
//一個簡單的場景就寫好了
2、創建動畫模型
這里我導出的是json格式的蒙皮動畫所以我采用了threejs中提供的BlendCharacter()
方法來創建我的動畫模型。具體代碼如下所示:
var createSkinningAnimate = function (model) {
var blendMesh = new THREE.BlendCharacter();
blendMesh.modelId = model.id;
blendMesh.load( model.modelSrc, function(){
blendMesh.name = model.modelName;
blendMesh.scale.set(model.scaleX, model.scaleY, model.scaleZ);
blendMesh.position.set(model.x, model.y, model.z);
blendMesh.frustumCulled = false;
blendMesh.material.transparent = true;
//
blendMesh.material.opacity = 0;
if(model.shadow) {
blendMesh.castShadow = true; //表示這個物體是可以產生陰影的
}
if(model.receive) {
blendMesh.receiveShadow = true; //表示這個物體是可以接受(顯示)陰影的
}
if(model.side) {
blendMesh.material.side = THREE.DoubleSide; //解決單雙面顯示問題
}
blendMesh.visible = false;
for(var i = 0, len = model.action.length; i < len; i++) {
blendMesh.applyWeight( model.action[i].name, model.action[i].weight);
};
blendModelGroup.add(blendMesh);
loadModelProgress();
});
}
創建這個模型的時候我遇到了兩個問題:
- 模型出現渲染的時候單雙面問題嗎,具體表現如下所示。

解決方法:
//設置material的side
blendMesh.material.side = THREE.DoubleSide; //解決單雙面顯示問題
- 當鏡頭拉近的時候模型從視野中消失,具體表現如下所示

解決方法:
//設置這個屬性為false時這個bug就好了。
blendMesh.frustumCulled = false;
frustumCulled 這個屬性是至今干嘛用的我不太清楚,求告知。
3、燈光渲染
場景中利用了兩個燈光全局燈(AmbientLight
)與聚光燈(SpotLight
)代碼如下:
_that.ambientLight = new THREE.AmbientLight( 0xffffff);
scene.add(_that.ambientLight);
var spotLight = new THREE.SpotLight(0xffffff);
_that.spotLight = spotLight;
spotLight.angle = 0.74;
spotLight.position.set(0, 60, -1);
spotLight.castShadow = true;
spotLight.penumbra = 0.7;
spotLight.intensity = 2;
spotLight.distance = 200;
spotLight.shadow.camera.near = 50;
spotLight.shadow.camera.far = 200;
spotLight.shadow.camera.fov = 35;
spotLight.shadow.mapSize.height = 1024;
spotLight.shadow.mapSize.width = 1024;
spotLight.name = 'spotLight';
scene.add(spotLight);
spotLight.target.position.set(-15,0,0);
//scene.add(new THREE.SpotLightHelper( spotLight ));
scene.add(spotLight.target);
燈光的調試主要靠gui
與LightHelper
,這兩個東西調試起來非常方便省了不少時間。調試前與調試后如圖所示:

5、調試技巧
-
gui
-
helper
-
topView
helper:threejs的相機、燈光都提供了Helper這個方法,在頁面中渲染添加一個參考線。
topView: 意思就是在頁面中渲染一個頂視圖,如果需要做相機的移動這個方法比較靠譜可以清楚的看見相機是這么運動的。然后發現問題的所在。渲染如下圖所示:

實現代碼:
var testW = 750,
testH = 1200;
var views = {
left: 0.5,
bottom: 0,
width: 0.5,
height: 0.5,
eye: [0, 0, 50],
direction: [-1, 0, 0]
};
var orthoCam = new THREE.OrthographicCamera(testW / -2, testW / 2, testH / 2, testH / -2, -500, 10000);
view.orthoCam = orthoCam;
update () {
var left = Math.floor( testW * 0.0 );
var bottom = Math.floor( testH * 0 );
var width = Math.floor( testW * 1.0 );
var height = Math.floor( testH * 1.0 );
renderer.setViewport( left, bottom, width, height );
renderer.setScissor( left, bottom, width, height );
renderer.setScissorTest ( true );
camera.updateProjectionMatrix();
cameraHelper.update();
cameraHelper.visible = false;
renderer.render(scene, camera);
cameraHelper.visible = true;
var view = views;
orthoCam = view.orthoCam;
orthoCam.left = testW / - 2;
orthoCam.right = testW / 2;
orthoCam.top = testH / 2;
orthoCam.bottom = testH / - 2;
orthoCam.position.x = view.eye[ 0 ];
orthoCam.position.y = view.eye[ 1 ];
orthoCam.position.z = view.eye[ 2 ];
orthoCam.rotation.x = view.direction[ 0 ] * Math.PI * 0.5;
orthoCam.rotation.y = view.direction[ 1 ] * Math.PI * 0.5;
orthoCam.rotation.z = view.direction[ 2 ] * Math.PI * 0.5;
orthoCam.updateProjectionMatrix();
var left1 = Math.floor( testW * view.left );
var bottom1 = Math.floor( testH * view.bottom );
var width1 = Math.floor( testW * view.width );
var height1 = Math.floor( testH * view.height );
renderer.setViewport( left1, bottom1, width1, height1 );
renderer.setScissor( left1, bottom1, width1, height1 );
renderer.setScissorTest ( true );
renderer.shadowMap.enabled = true;
renderer.render( scene, orthoCam );
}
核心創建一個正交相機OrthographicCamera
6、兼容判斷
因為不是所有手機都支持webgl所以需要做一下兼容性的判斷。
- 在ios8雖然支持webgl但是時間跟蹤clock不支持,但在不報錯。查看源碼的情況下估計是
var newTime = ( performance || Date ).now()
沒有得到正確的值。因為時間關系沒有過多的深入,所以頁面在ios8直接跳轉至2d版本。
-getExtension('WEBGL_depth_texture')經過測試在一些安卓上返回為null,所以沒有用這個去做區分。
兼容代碼如下所示:
var canvas = document.createElement("canvas");
var gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
if( gl ){
var OES_TextureExtension = gl.getExtension('OES_texture_half_float');
var EXT_TextureExtension = gl.getExtension( 'EXT_texture_filter_anisotropic' );
}
if(gl && OES_TextureExtension && EXT_TextureExtension&& !(isIos && version < 9)) {
//..3d code
}else {
//..2d code
};
TODO:
雖然這個項目上線了,但是還有很多東西需要去細化完善- -!!。
-
更好的完成相機的移動
-
人物的動畫拆分
-
整個場景動畫優化
之前用threejs做的一個小項目點擊這里查看
最后附上體驗地址:
