LOD
LOD(Level of Detail)是一种3D图形优化技术,其核心思想是:
根据观察者(相机)与物体的距离,动态调整物体的细节级别。距离越远,使用的几何体越简单;距离越近,使用越复杂的几何体。
分类
1. 基于距离的LOD(Distance-based LOD)
最经典的实现方式,通过物体与相机的绝对距离或相对距离决定使用的模型精度。
原理
预先为物体创建多个细节层级的模型(如LOD0:高模,LOD1:中模,LOD2:低模),并设定每个层级的距离阈值。当物体与相机的距离进入某个阈值区间时,切换到对应的模型。
示例:
相机距离物体 < 10米:使用高模(LOD0);
10米 ≤ 距离 < 30米:使用中模(LOD1);
距离 ≥ 30米:使用低模(LOD2)。
优缺点:
优点:实现简单,计算成本低(仅需距离判断);
缺点:未考虑物体在屏幕上的实际大小(例如,远处的大物体可能因屏幕占比大而需要更高细节)。
2. 基于屏幕空间的LOD(Screen-space LOD)
针对“基于距离”的缺陷优化,通过物体在屏幕投影面积(或像素占比)决定LOD层级。
原理:
将物体投影到屏幕空间,计算其覆盖的像素数量(或包围盒尺寸)。当投影面积小于某个阈值时,切换到更低精度的模型。
关键步骤:
计算物体在相机视口中的投影包围盒(Bounding Box);
统计包围盒覆盖的像素总数(可通过渲染目标分辨率或相机视锥体计算);
根据像素占比选择LOD层级(如占比 > 1000像素用高模,500-1000用中模,否则用低模)。
优缺点:
优点:更符合人眼感知(屏幕占比大的物体需要更高细节);
缺点:计算成本较高(需每帧计算投影面积),对动态物体需频繁更新。
3. 基于视角的LOD(Angle-based LOD)
结合观察方向与物体表面的夹角(即视角方向与物体法线的夹角),优先对正面(法线朝向相机)的物体使用高模,侧面或背面使用低模。
原理:
当物体表面与相机视线的夹角较小时(正面),说明该区域对视觉贡献大,使用高模;反之(侧面/背面)使用低模。
应用场景:
常用于角色模型(如角色的面部正面用高模,后脑勺或身体侧面用低模),或机械结构(如零件的可见面用高模,隐藏面用低模)。
一、CPU简单实现
基于距离的LOD
const geometry: [THREE.BufferGeometry, number][] = [
[new THREE.IcosahedronGeometry(100, 30), 50], // 最高细节,50单位内
[new THREE.IcosahedronGeometry(100, 8), 300], // 高细节,300单位内
[new THREE.IcosahedronGeometry(100, 4), 1000], // 中等细节,1000单位内
[new THREE.IcosahedronGeometry(100, 2), 2000], // 低细节,2000单位内
[new THREE.IcosahedronGeometry(100, 1), 8000] // 最低细节,8000单位内
];这里定义了5个不同细节级别的二十面体:
细分参数:从30到1,数值越大几何体越复杂
切换距离:从50到8000单位,定义了每个级别的有效范围二、GPU实现
完整代码
import { useEffect, useRef } from "react";
import * as THREE from 'three';
import { BufferGeometryUtils, FlyControls, OrbitControls } from "three/examples/jsm/Addons.js";
const LodCpu: React.FC = () => {
const mountRef = useRef<HTMLDivElement>(null);
useEffect(() => {
const PARTICLE_SIZE = 20;
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 15000);
camera.position.z = 1000;
// 添加雾效增强深度感
scene.fog = new THREE.Fog(0x000000, 1, 15000);
// 设置光照
const pointLight = new THREE.PointLight(0xff2200, 3, 0, 0);
pointLight.position.set(0, 0, 0);
scene.add(pointLight);
const dirLight = new THREE.DirectionalLight(0xffffff, 3);
dirLight.position.set(0, 0, 1).normalize();
scene.add(dirLight);
// 定义LOD几何体层级
const geometry: [THREE.BufferGeometry, number][] = [
[new THREE.IcosahedronGeometry(100, 30), 50],
[new THREE.IcosahedronGeometry(100, 8), 300],
[new THREE.IcosahedronGeometry(100, 4), 1000],
[new THREE.IcosahedronGeometry(100, 2), 2000],
[new THREE.IcosahedronGeometry(100, 1), 8000]
];
const clock = new THREE.Clock();
const material = new THREE.MeshLambertMaterial({
color: 0xffffff,
wireframe: true
});
// 创建多个LOD对象
for (let j = 0; j < 10; j++) {
const lod = new THREE.LOD();
for (let i = 0; i < geometry.length; i++) {
const mesh = new THREE.Mesh(geometry[i][0] as THREE.BufferGeometry, material);
mesh.scale.set(1.5, 1.5, 1.5);
mesh.updateMatrix();
mesh.matrixAutoUpdate = false;
lod.addLevel(mesh, geometry[i][1] as number);
}
// 随机位置分布
lod.position.x = 1000 * (0.5 - Math.random());
lod.position.y = 750 * (0.5 - Math.random());
lod.position.z = 1000 * (0.5 - Math.random());
lod.updateMatrix();
lod.matrixAutoUpdate = false;
scene.add(lod);
}
// 渲染器设置
let renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
mountRef.current?.appendChild(renderer.domElement);
// 飞行控制器
let controls = new FlyControls(camera, renderer.domElement);
controls.movementSpeed = 1000;
controls.rollSpeed = Math.PI / 10;
// 窗口大小调整处理
const onWindowResize = () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
window.addEventListener('resize', onWindowResize);
// 渲染循环
const animate = () => {
requestAnimationFrame(animate);
renderer.render(scene, camera);
controls.update(clock.getDelta());
};
animate();
return () => {
renderer.dispose();
};
}, []);
return (
<div ref={mountRef} style={{ width: '100vw', height: '100vh' }} />
);
};
export default LodCpu;