一、理解three:
1.一个可以在某个3D建模软件打开的东西,通过某种方案在浏览器中打开;
2.不要试图手动去创建3D图形,当然比较闲的话可以这样操作,
3.把three当作一个3D模型播放器,在播放器里可以对模型做一些操作:调色调光,调整坐标,切换视角,播放模型中的动画…;
4.某个建模软件(3dMax…)把已经做好的模型导出了模型文件(ojb,glb,gltf,fbx等格式),通过某个可支持此格式的Loader,最终渲染到场景里 ,就像webpack打包css需要css-loader一样的道理;
5.three 把WebGL的图形引擎封装成了3d api
二、基本思路:
(1)构建场景Scene->
(2)通过Renderer渲染场景到画布->
(3)需要借助**Camera帮忙观察物体->
(4)通过loader(GLTFLoader,FBXLoader…)加载模型 ->
(5)通过requestAnimationFrame 把最新场景数据渲染出来
三、源代码
import *as THREE from './lib.js';
import BG from './model/environment/bg.jpeg'
//模型
import xsr_fbx from './model/xsr/xsr.fbx'
//纹理贴图
import xsr_fbx_texture from './model/xsr/Stormland Robo 03H.png'
import xsr_fbx_logo_texture from './model/xsr/stormland_logo.png'
const models = [
{name:'机器头',path:require('./model/DamagedHelmet.glb').default,position:[0, 0, 5],type:'glb'},
{name:'像素人',path:xsr_fbx,position:[0, 0, 50],type:'fbx',
texture:[
{name:'gardener,hologram_2,hologram',path:xsr_fbx_texture},
{name:'Plane',path:xsr_fbx_logo_texture}],
},
]
const modelScene={
State:{
showGrid:false,
showLightOrigin:false,
wireframe:false,
},
Scene:null,
Renderer:null,
Camera:null,
Model:null,
Lights:null,
AnimationMixer:null,
Tclock:new THREE.Clock(),
TestGui:null,
TestStats:null,
Controls:null,
GridHelper:new THREE.GridHelper( 300, 50, 0x00FF12, 0xFFFFFF ),
init:{
//添加场景
Scene:function(){
this.Scene = new THREE.Scene()
this.Scene.background = new THREE.Color(0x282923);
this.Scene.background = new THREE.TextureLoader().load(BG)
// THREE.Cache.enabled = true;
},
//添加渲染器
Renderer:function(){
this.Renderer = new THREE.WebGLRenderer({antialias: true,alpha: true,premultipliedAlpha:true,precision: 'highp'})
this.Renderer.setPixelRatio(window.devicePixelRatio);
this.Renderer.setSize(window.innerWidth, window.innerHeight);
this.Renderer.setClearColor(0xeeeeee);
this.Renderer.shadowMap.enabled = true;
this.Renderer.physicallyCorrectLights = true;
this.Renderer.outputEncoding = THREE.sRGBEncoding;
ThreeApp.appendChild(this.Renderer.domElement);
},
//添加相机
Camera:function(){
this.Camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight,1, 10000)
this.Camera.position.set(0, 0, 50);
this.Camera.lookAt(this.Scene.position)
},
TestGui:function(){
let _this = this;
this.TestGui = new THREE.Dat.GUI()
this.TestGui.add({
changeBg: function () {
_this.Scene.background = new THREE.Color(0x1A1A1A);
}
}, "changeBg");
},
//帧率状态
Stats() {
this.TestStats = new THREE.Stats();
document.body.appendChild(this.TestStats.dom);
}
},
//加载模型GLTF FBX
modelLoader:function(MODEL){
const loadTip = this.addLoadTip();
this.Controls.autoRotate = false;
//添加环境hdr
MODEL.hdr && this.HdrLoader(MODEL.hdr);
let Loader = '',MTYPE = MODEL.type || 'glb';
if('glb,gltf'.indexOf(MTYPE)!=-1){
Loader = new THREE.GLTFLoader()
}
else if('fbx'.indexOf(MTYPE)!=-1){
Loader = new THREE.FBXLoader()
}else{
loadTip.textContent='请使用glb,gltf,fbx格式模型';
return;
}
Loader.load(MODEL.path, (geometry)=> {
loadTip.textContent='加载完成!';
//移除模型
this.Model && this.Scene.remove(this.Model);
//设置相机位置
this.Camera.position.set(...MODEL.position);
this.Model = 'fbx'.indexOf(MTYPE)!=-1?geometry:geometry.scene;
//遍历模型字节点,获取相关参数设置
this.Model.traverse(function(child) {
if(MODEL.texture){
MODEL.texture.map(item=>{
if(item.name.indexOf(child.name)!=-1){
child.material = new THREE.MeshPhongMaterial({
map: THREE.ImageUtils.loadTexture(item.path)//颜色贴图
});
}
})
}
if (child.isMesh) {
// child.material.emissiveMap = child.material.map;
//child.material.side = THREE.DoubleSide;
child.material.shininess=1;
child.castShadow = true
child.receiveShadow = true
child.material.transparent=true;//材质允许透明 如果有玻璃材质时开启
child.material.opacity=1;//材质默认透明度
}
}
);
//模型自动居中
THREE.ModelAutoCenter(this.Model)
//查找模型动画,
if(this.Model.animations.length>0){
this.AnimationMixer = new THREE.AnimationMixer(this.Model);
this.AnimationMixer.clipAction(this.Model.animations[0]).play();
/* 其他模型动画方案:
const animationClip = this.Model.animations.find(animationClip => animationClip.name === "Walk");
this.AnimationMixer.clipAction(animationClip).play();
*/
}
//把模型放入场景中
this.Scene.add(this.Model);
//加载完成后开始自动播放
setTimeout(()=>{
loadTip.style.display='none';
this.Controls.autoRotate = true;
},1000);
},
(xhr)=>{
//加载进度
loadTip.textContent=(parseInt(xhr.loaded/xhr.total*100))+'%加载中...';
},
(err)=>{
loadTip.textContent='模型加载失败!'
console.log('模型加载失败!')
}
);
},
//加载光源
addLight:function(){
this.Lights = [
{name:'AmbientLight',obj:new THREE.AmbientLight(0xFFFFFF,1)},
{name:'DirectionalLight_top',obj:new THREE.DirectionalLight(0xFFFFFF,3),position:[0, 15, 0]},
{name:'DirectionalLight_bottom',obj:new THREE.DirectionalLight(0x1B1B1B,3),position:[0, -200, 0]},
{name:'DirectionalLight_right1',obj:new THREE.DirectionalLight(0xFFFFFF,1.5),position:[0, -5, 20]},
{name:'DirectionalLight_right2',obj:new THREE.DirectionalLight(0xFFFFFF,1.5),position:[0, -5, -20]},
];
this.Lights.map(item=>{
item.obj.name=item.name;
item.position && item.obj.position.set(...item.position);
item.Helper = new THREE.PointLightHelper( item.obj );
this.Scene.add(item.obj);
})
},
//加载HDR贴图环境光
HdrLoader:function(HDR){
const pmremGenerator = new THREE.PMREMGenerator(this.Renderer); // 使用hdr作为背景色
pmremGenerator.compileEquirectangularShader();
const textureLoader = new THREE.RGBELoader()
textureLoader.load(HDR,(texture, textureData)=> {
const envMap = pmremGenerator.fromEquirectangular(texture).texture;
envMap.isPmremTexture = true;
pmremGenerator.dispose();
this.Scene.environment = envMap; // 给场景添加环境光效果
this.Scene.background = envMap; // 给场景添加背景图
});
},
//添加事件
addControls:function() {
this.Controls = new THREE.OrbitControls(this.Camera, this.Renderer.domElement);
// 如果使用animate方法时,将此函数删除
//controls.addEventListener( 'change', render );
// 使动画循环使用时阻尼或自转 意思是否有惯性
this.Controls.enableDamping = true;
//是否可以缩放
this.Controls.enableZoom = true;
//设置相机距离原点的最远距离-可以控制缩放程度
this.Controls.minDistance = 0;
//设置相机距离原点的最远距离
this.Controls.maxDistance = 3000;//800
//是否开启右键拖拽
this.Controls.enablePan = false;
//动态阻尼系数 就是鼠标拖拽旋转灵敏度
this.Controls.dampingFactor = 0.5;
//是否自动旋转
this.Controls.autoRotate = false;
this.Controls.autoRotateSpeed = 1;
},
//模型切换
switchModel(){
const _scope = this;
var switchModelStyle = document.createElement('style');
switchModelStyle.type = "text/css";
switchModelStyle.innerText +='.modelList{position:fixed;width:100%; display:flex;justify-content:space-around; bottom:0;left:0;color:#0EF4F4;background:rgba(14,14,44,0.9);cursor:pointer;}\
.modelList li{width:50%;line-height:30px;padding:5px;text-align:center;font-size:14px;}.modelList li:last-child{border:0;}.modelList li:hover,.modelList li.on{background:#0E2440;}'
const modelUL = document.createElement('ul');
modelUL.className='modelList'
models.map((item,index)=>{
modelUL.innerHTML+=''+item.name+' ';
})
document.head.insertBefore(switchModelStyle, document.head.lastChild);
ThreeApp.insertBefore(modelUL,ThreeApp.firstChild);
let LIS = modelUL.children;
for(let i=0;i{_scope.Scene.remove(item.Helper)})
_scope.State.showLightOrigin = false
}else{
_scope.Lights.map(item=>{_scope.Scene.add(item.Helper)})
_scope.State.showLightOrigin = true
}
}
},
{
name:'骨架模式',
todo:function(){
if(_scope.State.wireframe){
_scope.Model.traverse( child=> {
if ( child.isMesh ) {
child.material.wireframe=false
}
})
_scope.State.wireframe = false
}else{
_scope.Model.traverse( child=> {
if ( child.isMesh ) {
child.material.wireframe=true
}
})
_scope.State.wireframe = true
}
}
},
]
//辅助面板DomTree
var helpPanelStyle = document.createElement('style');
helpPanelStyle.type = "text/css";
helpPanelStyle.innerText +='#helpPanel{position:fixed;width:80px;top:50px;left:0;color:#0EF4F4;background:#0E0E2C;cursor:pointer;}\
#helpPanel li{border-bottom:1px solid #fff;line-height:30px;text-align:center;font-size:14px;}#helpPanel li:last-child{border:0;}#helpPanel li.on{color:green;}';
var helpPanel = document.createElement('ul');
helpPanel.id='helpPanel';
Panels.forEach(item=>{
let LI = document.createElement('li');
LI.innerText=item.name;
LI.οnclick=function(){
this.className = this.className=='on'?'':'on'
item.todo(this)
}
helpPanel.appendChild(LI);
})
document.head.insertBefore(helpPanelStyle, document.head.lastChild);
ThreeApp.insertBefore(helpPanel,ThreeApp.firstChild)
},
animation:function(){
//更新控制器
this.Renderer.render(this.Scene, this.Camera);
this.TestStats.update();
this.Controls.update();
this.AnimationMixer && this.AnimationMixer.update(this.Tclock.getDelta());
requestAnimationFrame(()=>this.animation());
},
onWindowResize:function() {
this.Camera.aspect = window.innerWidth / window.innerHeight;
this.Camera.updateProjectionMatrix();
this.Renderer.setSize(window.innerWidth, window.innerHeight);
this.Renderer.render(this.Scene, this.Camera);
},
run:function(){
this.init.Renderer.call(this)
this.init.Scene.call(this)
this.init.Camera.call(this)
this.addControls();
//添加环境光
this.addLight()
this.modelLoader(models[0]);
//添加辅助面板
this.addPanel();
this.animation();
this.switchModel()
window.onresize = ()=>this.onWindowResize();
}
}
modelScene.run();
四、演示效果
演示效果:source.nullno.com/three-demo/