項目中直接使用的是three.js直接加載gltf模型文件员凝。
gtlf文件被譽為3d界的“jpeg”,很形象
gltf
個人接觸的gltf文件不是很多知牌,所以這里舉一個項目中用的列子油狂,圖片奉上
image
scene.bin文件為3d模型導(dǎo)出的二進制文件,最最主要的文件之一(哈哈)
我在寫模型加載的時候企锌,是引用的外部配置文件榆浓,這樣的話,就能使組件能夠復(fù)用霎俩,寫一個組件哀军,更改不同的配置文件沉眶,加載不同的模型
后面調(diào)用組件時傳遞給組件的參數(shù)
{
// 傳入給3d模型渲染組件的服務(wù)器路徑
"model": "/web_app/three3d/models/019/scene.gltf",
"textures": "",
"envMap": "",
"texturesOn": true,
"enableKeys": false,
"autoRotate": false
}
組件編寫
render函數(shù)長這樣,ref的作用是獲取到標(biāo)簽的真實dom(哈哈杉适,個人理解)
render() {
let style = { width: '100%', height: '100%' };
return (
<div
style={style}
ref={(mount) => { this.mount = mount; }}
/>
);
}
定義一些需要用到的全局變量
let container,
mixer,
controls;
let camera,
scene,
renderer,
light;
// 定義three中的動畫時間
let clock = new THREE.Clock();
let threeConf = this.state.threeConf;
container = document.createElement('div');
this.mount.appendChild(container);
container = this.mount;
let width = this.mount.clientWidth;
let height = this.mount.clientHeight;
camera = new THREE.PerspectiveCamera(60, width / height, 1, 1000);
container.addEventListener('resize', () => {
camera.aspect = width / height;
camera.updateProjectionMatrix();
renderer.setSize(width, height);
}, false);
定義textures環(huán)境圖片谎倔,有這個的話模型就會有反射環(huán)境,圖片是從服務(wù)器拿回來的猿推。
// 環(huán)境設(shè)置片习,設(shè)置之后可以反射周圍環(huán)境
let envMap = new THREE.CubeTextureLoader().load([
'/images/sys/three3d/envmappic/posx.jpg',
'/images/sys/three3d/envmappic/negx.jpg',
'/images/sys/three3d/envmappic/posy.jpg',
'/images/sys/three3d/envmappic/negy.jpg',
'/images/sys/three3d/envmappic/posz.jpg',
'/images/sys/three3d/envmappic/negz.jpg'
]);
scene = new THREE.Scene();
// 添加光線
light = new THREE.HemisphereLight(0xbbbbff, 0x444422);
light.position.set(0, 1, 0);
scene.add(light);
關(guān)于gltf的模型加載器不用自己寫loader加載。scene.gltf文件就是我們需要loader的文件
// 新建loader,加載gltf文件
let loader = new THREE.GLTFLoader();
loader.load(threeConf.model, (gltf) => {
const gltfScene = gltf.scene || gltf.scenes[0];
const clips = gltf.animations || [];
gltfScene.updateMatrixWorld();
const box = new THREE.Box3().setFromObject(gltfScene);
const size = box.getSize(new THREE.Vector3()).length();
const center = box.getCenter(new THREE.Vector3());
// controls.reset();
gltfScene.position.x += gltfScene.position.x - center.x;
gltfScene.position.y += gltfScene.position.y - center.y;
gltfScene.position.z += gltfScene.position.z - center.z;
// 重新設(shè)置相機參數(shù)
controls.maxDistance = size * 10;
camera.near = size / 100;
camera.far = size * 100;
camera.updateProjectionMatrix();
camera.position.copy(center);
camera.position.x += size / 2.0;
camera.position.y += size / 2.0;
camera.position.z += size / 2.0;
camera.lookAt(center);
gltf.scene.traverse((child) => {
if (child.isMesh) {
child.material.envMap = envMap;
}
});
scene.add(gltf.scene);
// 判斷當(dāng)前的gltf問價中是否有動畫
if (clips.length !== 0) {
mixer = new THREE.AnimationMixer(gltfScene);
mixer.clipAction(gltf.animations[0]).play();
} else {
}
});
renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
renderer.setPixelRatio(container.devicePixelRatio);
renderer.setSize(width, height);
renderer.gammaOutput = true;
控制器和動畫的加載
controls = new THREE.OrbitControls(camera, container);
//按鍵控制
controls.enableKeys = false;
// 自動旋轉(zhuǎn)(默認(rèn)flase)
controls.autoRotate = false;
container.appendChild(renderer.domElement);
container.addEventListener('resize', this.onWindowResize, false);
requestAnimationFrame(function fn() {
if (mixer) {
let delta = clock.getDelta();
mixer.update(delta);
}
requestAnimationFrame(fn);
renderer.render(scene, camera);
controls.update();
});
控制器中用到的監(jiān)聽方法
onWindowResize = () => {
camera.aspect = width / height;
camera.updateProjectionMatrix();
renderer.setSize(width, height);
}
最后附上完整代碼
import React from 'react';
const THREE = window.THREE = require('three');
require('three/examples/js/loaders/GLTFLoader');
require('three/examples/js/loaders/DRACOLoader');
require('three/examples/js/loaders/DDSLoader');
require('three/examples/js/controls/OrbitControls');
require('three/examples/js/controls/TrackballControls');
require('three/examples/js/loaders/RGBELoader');
require('three/examples/js/loaders/HDRCubeTextureLoader');
require('three/examples/js/pmrem/PMREMGenerator');
require('three/examples/js/pmrem/PMREMCubeUVPacker');
THREE.DRACOLoader.setDecoderPath('lib/draco/');
class Three3D extends React.Component {
state = ({ threeConf: this.props.threeConf });
componentDidMount() {
let container,
mixer,
controls;
let camera,
scene,
renderer,
light;
// 定義three中的動畫時間
let clock = new THREE.Clock();
let threeConf = this.state.threeConf;
container = document.createElement('div');
this.mount.appendChild(container);
container = this.mount;
let width = this.mount.clientWidth;
let height = this.mount.clientHeight;
camera = new THREE.PerspectiveCamera(60, width / height, 1, 1000);
container.addEventListener('resize', () => {
camera.aspect = width / height;
camera.updateProjectionMatrix();
renderer.setSize(width, height);
}, false);
// 環(huán)境設(shè)置蹬叭,設(shè)置之后可以反射周圍環(huán)境
let envMap = new THREE.CubeTextureLoader().load([
'/images/sys/three3d/envmappic/posx.jpg',
'/images/sys/three3d/envmappic/negx.jpg',
'/images/sys/three3d/envmappic/posy.jpg',
'/images/sys/three3d/envmappic/negy.jpg',
'/images/sys/three3d/envmappic/posz.jpg',
'/images/sys/three3d/envmappic/negz.jpg'
]);
scene = new THREE.Scene();
// 添加光線
light = new THREE.HemisphereLight(0xbbbbff, 0x444422);
light.position.set(0, 1, 0);
scene.add(light);
// 新建loader,加載gltf文件
let loader = new THREE.GLTFLoader();
loader.load(threeConf.model, (gltf) => {
const gltfScene = gltf.scene || gltf.scenes[0];
const clips = gltf.animations || [];
gltfScene.updateMatrixWorld();
const box = new THREE.Box3().setFromObject(gltfScene);
const size = box.getSize(new THREE.Vector3()).length();
const center = box.getCenter(new THREE.Vector3());
// controls.reset();
gltfScene.position.x += gltfScene.position.x - center.x;
gltfScene.position.y += gltfScene.position.y - center.y;
gltfScene.position.z += gltfScene.position.z - center.z;
// 重新設(shè)置相機參數(shù)
controls.maxDistance = size * 10;
camera.near = size / 100;
camera.far = size * 100;
camera.updateProjectionMatrix();
camera.position.copy(center);
camera.position.x += size / 2.0;
camera.position.y += size / 2.0;
camera.position.z += size / 2.0;
camera.lookAt(center);
gltf.scene.traverse((child) => {
if (child.isMesh) {
child.material.envMap = envMap;
}
});
scene.add(gltf.scene);
// 判斷當(dāng)前的gltf問價中是否有動畫
if (clips.length !== 0) {
mixer = new THREE.AnimationMixer(gltfScene);
mixer.clipAction(gltf.animations[0]).play();
} else {
}
});
renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
renderer.setPixelRatio(container.devicePixelRatio);
renderer.setSize(width, height);
renderer.gammaOutput = true;
controls = new THREE.OrbitControls(camera, container);
//按鍵控制
controls.enableKeys = false;
// 自動旋轉(zhuǎn)(默認(rèn)flase)
controls.autoRotate = false;
container.appendChild(renderer.domElement);
container.addEventListener('resize', this.onWindowResize, false);
requestAnimationFrame(function fn() {
if (mixer) {
let delta = clock.getDelta();
mixer.update(delta);
}
requestAnimationFrame(fn);
renderer.render(scene, camera);
controls.update();
});
}
onWindowResize = () => {
camera.aspect = width / height;
camera.updateProjectionMatrix();
renderer.setSize(width, height);
}
render() {
let style = { width: '100%', height: '100%' };
return (
<div
style={style}
ref={(mount) => { this.mount = mount; }}
/>
);
}
}
export default Three3D;
調(diào)用
<Three3d threeConf={this.state.threeConf} />