three.js使用注意事项
1. 贴图反向
texture.flipY = false;
2. 贴图没有填充满模型
textureMap.wrapS = textureMap.wrapT = THREE.RepeatWrapping;
3. 贴图透明度
transparent: false;
//树叶
blending: THREE.MultiplyBlending;
4. 深度冲突
无需深度检测的Material设置 depthTest:false
new THREE.WebGLRenderer( {
logarithmicDepthBuffer: true } );
5. 渲染顺序问题
WebGLRenderer设置sortObjects: false;
每个Mesh手动设置renderOrder的顺序;
6. 多层次细节
const lod = new THREE.LOD();
//Create spheres with 3 levels of detail and create new LOD levels for them
for (let i = 0; i < 3; i++) {
const geometry = new THREE.IcosahedronBufferGeometry(10, 3 - i);
const mesh = new THREE.Mesh(geometry, material);
lod.addLevel(mesh, i * 75);
//addLevel ( object : Object3D, distance : Float ) : this
//object —— 在这个层次中将要显示的Object3D。
//distance —— 将显示这一细节层次的距离。
}
scene.add(lod);
7. 抗锯齿
//antialias - 是否执行抗锯齿。默认为false.
new THREE.WebGLRenderer({
antialias: true });
8. 阴影
//渲染器开启渲染阴影效果
renderer.shadowMapEnabled = true;
this.renderer.shadowMap.enable = true;
this.renderer.shadowMap.type = THREE.PCFSoftShadowMap;
//平面接收投影
plane.receiveShadow = true;
//点光源产生投影
spotLight.castShadow = true;
//物体对象产生投影
cube.castShadow = true;
阴影使用可能遇到的问题
● 阴影模糊,增加 shadowMapWidth 和 shadowMapHeight,或保证用于计算阴影区域紧密包围在对象周围(shadowCameraNear, shadowCameraFar, shadowCameraFov)
● 产生阴影与接收阴影设置,光源生成阴影,几何体是否接收或投射阴影 castShadow 和 receiveShadow
● 薄对象渲染阴影时可能出现奇怪的渲染失真,可通过 shadowBias 轻微偏移阴影来修复
● 调整 shadowDarkness 来改变阴影的暗度
● 阴影更柔和,可在 THREE. WebGLRenderer 设置不同 shadowMapType。默认 THREE. PCFShadowMap, 柔和:PCFSoftShadowMap
9. html 标签,CSS2DRenderer
const moonDiv = document.createElement('div');
moonDiv.innerHTML = 'Moon';
//保证能点击
moonDiv.style.pointerEvents = 'auto';
const moonLabel = new CSS2DObject(moonDiv);
moonLabel.position.set(0, 10, 0);
moon.add(moonLabel);
labelRenderer = new CSS2DRenderer();
labelRenderer.setSize(container.offsetWidth, container.offsetHeight);
labelRenderer.domElement.style.position = 'absolute';
labelRenderer.domElement.style.top = '0px';
//不妨碍界面上的东东
labelRenderer.domElement.style.pointerEvents = 'none';
container.appendChild(labelRenderer.domElement);
function onWindowResize() {
labelRenderer.setSize(container.offsetWidth, container.offsetHeight);
}
function animate() {
labelRenderer.render(scene, camera);
}
10. 颜色问题
//底色透明
this.renderer.setClearColor(0x000000, 0);
//模型渲染,默认THREE.LinearEncoding
this.renderer.outputEncoding = THREE.sRGBEncoding;
THREE.LinearEncoding;
THREE.sRGBEncoding;
THREE.GammaEncoding;
THREE.RGBEEncoding;
THREE.LogLuvEncoding;
THREE.RGBM7Encoding;
THREE.RGBM16Encoding;
THREE.RGBDEncoding;
THREE.BasicDepthPacking;
THREE.RGBADepthPacking;
11. 分辨率问题
this.renderer.setPixelRatio(window.devicePixelRatio);
//分辨率越高渲染压力就越大
12. 物体居中
function setModelCenter(object, viewControl) {
if (!object) {
return;
}
if (object.updateMatrixWorld) {
object.updateMatrixWorld();
}
// 获得包围盒得min和max
const box = new THREE.Box3().setFromObject(object);
let objSize = box.getSize(new THREE.Vector3());
// 返回包围盒的中心点
const center = box.getCenter(new THREE.Vector3());
object.position.x += object.position.x - center.x;
object.position.y += object.position.y - center.y;
object.position.z += object.position.z - center.z;
let width = objSize.x;
let height = objSize.y;
let depth = objSize.z;
let centroid = new THREE.Vector3().copy(objSize);
centroid.multiplyScalar(0.5);
if (viewControl.autoCamera) {
this.camera.position.x =
centroid.x * (viewControl.centerX || 0) + width * (viewControl.width || 0);
this.camera.position.y =
centroid.y * (viewControl.centerY || 0) + height * (viewControl.height || 0);
this.camera.position.z =
centroid.z * (viewControl.centerZ || 0) + depth * (viewControl.depth || 0);
} else {
this.camera.position.set(
viewControl.cameraPosX || 0,
viewControl.cameraPosY || 0,
viewControl.cameraPosZ || 0
);
}
this.camera.lookAt(0, 0, 0);
}
13. 清空资源
function cleanNext(obj, idx) {
if (idx < obj.children.length) {
this.cleanElmt(obj.children[idx]);
}
if (idx + 1 < obj.children.length) {
this.cleanNext(obj, idx + 1);
}
}
function cleanElmt(obj) {
if (obj) {
if (obj.children && obj.children.length > 0) {
this.cleanNext(obj, 0);
obj.remove(...obj.children);
}
if (obj.geometry) {
obj.geometry.dispose && obj.geometry.dispose();
}
if (obj.material) {
for (const v of Object.values(obj.material)) {
if (v instanceof THREE.Texture) {
v.dispose && v.dispose();
}
}
obj.material.dispose && obj.material.dispose();
}
obj.dispose && obj.dispose();
obj.clear && obj.clear();
}
}
function cleanObj(obj) {
this.cleanElmt(obj);
obj?.parent?.remove && obj.parent.remove(obj);
}
function cleanAll() {
window.removeEventListener('resize');
cancelAnimationFrame(this.threeAnim);
if (this.stats) {
this.container.removeChild(this.stats.domElement);
this.stats = null;
}
this.cleanObj(this.scene);
this.controls && this.controls.dispose();
this.renderer.renderLists && this.renderer.renderLists.dispose();
this.renderer.dispose && this.renderer.dispose();
this.renderer.forceContextLoss();
let gl = this.renderer.domElement.getContext('webgl');
gl && gl.getExtension('WEBGL_lose_context').loseContext();
this.renderer.setAnimationLoop(null);
this.renderer.domElement = null;
this.renderer.content = null;
console.log('清空资源', this.renderer.info);
this.renderer = null;
THREE.Cache.clear();
if (this.map) {
this.map.destroy();
}
}
14. 模型显示面的问题
.side:Integer
定义将要渲染哪一面 - 正面,背面或两者。 默认为 THREE.FrontSide。其他选项有 THREE.BackSide 和 THREE.DoubleSide。
material.side = THREE.DoubleSide;
15. Raycaster 鼠标拾取
不要检测全局,用 actionObjs 收集需要动作的物体
this.raycaster = new THREE.Raycaster();
this.mouse = new THREE.Vector2();
this.container.style.cursor = 'pointer';
this.container.addEventListener(
'pointerdown',
(event) => {
event.preventDefault();
this.mouse.x =
((event.offsetX - this.container.offsetLeft) / this.container.offsetWidth) * 2 - 1;
this.mouse.y =
-((event.offsetY - this.container.offsetTop) / this.container.offsetHeight) * 2 + 1;
let vector = new THREE.Vector3(this.mouse.x, this.mouse.y, 1).unproject(this.camera);
this.raycaster.set(this.camera.position, vector.sub(this.camera.position).normalize());
this.raycaster.setFromCamera(this.mouse, this.camera);
const intersects = raycaster.intersectObjects(this.actionObjs, true);
if (intersects?.length) {
console.log('action', intersects[0]);
this.raycasterAction(intersects[0]);
}
},
false
);
16. Canvas 贴图
生成的 canvas 大小最好是正常模型贴图大小的五倍以上,有可能因为缩放问题,导致贴图模糊
17.打包后线上效果与开发时效果存在差异
将three的相关js提到html上,从外部引入,这样能保证three不会因为打包而乱了,导致效果有问题
FBXLoader外部引入,记得把libs里面的inflate也加上
18.THREE.js截图
new THREE.WebGLRenderer({
preserveDrawingBuffer: true //保留缓冲区
});
import {
saveAs } from 'file-saver-fixed';
function convertBase64UrlToBlob(base64) {
let parts = base64.split(';base64,');
let contentType = parts[0].split(':')[1];
let raw = window.atob(parts[1]);
let rawLength = raw.length;
let uInt8Array = new Uint8Array(rawLength);
for (let i = 0; i < rawLength; i++) {
uInt8Array[i] = raw.charCodeAt(i);
}
return new Blob([uInt8Array], {
type: contentType });
}
saveImage: () => {
let image = threeModel.renderer.domElement.toDataURL('image/jpeg');
let blob = convertBase64UrlToBlob(image);
saveAs(blob, new Date().getTime() + '.jpg');
}
18.glb压缩过的模型加载,记得加DRACOLoader
记得将three.js/examples/js/libs/draco/gltf
目录下的draco解码器全部放在public/draco文件夹下,否则会导致模型加载失败!
let dracoLoader = new THREE.DRACOLoader();
dracoLoader.setDecoderPath('draco/');
dracoLoader.setDecoderConfig({
type: 'js' });//或者{type: "wasm"}
dracoLoader.preload();
const loader = new THREE.GLTFLoader();
loader.setDRACOLoader(dracoLoader);
return loader;