事先说明
优化方法是根据chatGPT的回答下,我这里记录一下,有的方法进行了尝试,有的还没有。
1、模型面数过多导致渲染卡顿
可以通过减少面数来优化,也可以使用LOD技术(Level of Detail)在不同距离下使用不同的模型细节来优化。
使用LOD技术可以在不同距离下使用不同的模型细节来优化three.js渲染性能,下面是具体步骤:
创建多个模型 ,每个模型的面数和细节不同,这些模型应该是同一个对象的不同版本。
将这些模型按照从低到高的细节顺序添加到同一个LOD(Level of Detail)对象中,如下所示:
const lod
= new THREE. LOD ( ) ;
const lowDetailModel
= ... // 低细节模型
const midDetailModel
= ... // 中细节模型
const highDetailModel
= ... // 高细节模型
lod
. addLevel ( lowDetailModel
, 0 ) ; // 添加低细节模型,距离为0
lod
. addLevel ( midDetailModel
, 100 ) ; // 添加中细节模型,距离为100
lod
. addLevel ( highDetailModel
, 200 ) ; // 添加高细节模型,距离为200
将LOD对象添加到场景中。 scene
. add ( lod
) ;
在渲染循环中,根据相机与LOD对象的距离 ,自动选择当前需要显示的模型细节等级。可以使用THREE.LOD对象的update方法来实现。 function render ( ) {
requestAnimationFrame ( render
) ;
lod
. update ( camera
) ;
renderer
. render ( scene
, camera
) ;
}
2、材质贴图过大导致渲染卡顿
可以通过减小贴图尺寸 ,压缩贴图格式 ,使用纹理集(Texture Atlas)等方式来优化。
使用纹理集(Texture Atlas)可以将多张小纹理图合并成一张大纹理图 ,从而减少渲染时的纹理切换次数,优化three.js渲染性能,下面是具体步骤:
创建一张大纹理图,并将多张小纹理图拼接在一起 ,这些小纹理图应该是同一对象的不同部分 ,如下所示:const texture
= new THREE. TextureLoader ( ) . load ( 'atlas.png' ) ;
const material
= new THREE. MeshBasicMaterial ( { map : texture
} ) ;
将每个物体的UV坐标映射到对应的小纹理图区域 ,需要根据小纹理图在大纹理图中的位置和大小计算出UV坐标,如下所示:const geometry
= new THREE. BoxGeometry ( 1 , 1 , 1 ) ;
const uvAttribute
= geometry
. attributes
. uv
;
for ( let i
= 0 ; i
< uvAttribute
. count
; i
++ ) {
const u
= uvAttribute
. getX ( i
) ;
const v
= uvAttribute
. getY ( i
) ;
// 根据小纹理图在大纹理图中的位置和大小计算出UV坐标
uvAttribute
. setXY ( i
, u
* smallTextureWidth
/ bigTextureWidth
+ smallTextureX
/ bigTextureWidth
, v
* smallTextureHeight
/ bigTextureHeight
+ smallTextureY
/ bigTextureHeight
) ;
}
在渲染循环中,更新大纹理图的偏移和缩放值。 function render ( ) {
requestAnimationFrame ( render
) ;
const time
= Date
. now ( ) * 0.001 ;
texture
. offset
. x
= time
* 0.1 ; // x方向偏移量
texture
. offset
. y
= time
* 0.2 ; // y方向偏移量
texture
. repeat
. set ( 2 , 2 ) ; // 横向和纵向缩放值
renderer
. render ( scene
, camera
) ;
}
3、着色器复杂度过高导致渲染卡顿
可以通过简化着色器 ,使用预编译的着色器,使用Instancing等方式来优化。
使用Instancing(实例化)可以将多个相同的物体复用同一个几何体和材质,并在渲染时进行一次性绘制 ,从而减少渲染调用次数,优化three.js渲染性能,下面是具体步骤:
创建一个几何体和材质,将它们分别作为多个物体的原型。 const geometry
= new THREE. BoxGeometry ( 1 , 1 , 1 ) ;
const material
= new THREE. MeshBasicMaterial ( { color : 0xff0000 } ) ;
创建一个InstancedBufferGeometry对象,并将原型几何体的属性复制到它的属性中。 const instances
= 10000 ; // 实例数量
const instancedGeometry
= new THREE. InstancedBufferGeometry ( ) ;
instancedGeometry
. copy ( geometry
) ; // 复制几何体属性
const translations
= new Float32Array ( instances
* 3 ) ; // 实例位置数组
for ( let i
= 0 ; i
< instances
; i
++ ) {
translations
[ i
* 3 ] = Math
. random ( ) * 100 - 50 ;
translations
[ i
* 3 + 1 ] = Math
. random ( ) * 100 - 50 ;
translations
[ i
* 3 + 2 ] = Math
. random ( ) * 100 - 50 ;
}
instancedGeometry
. setAttribute ( 'translation' , new THREE. InstancedBufferAttribute ( translations
, 3 ) ) ;
创建一个InstancedMesh对象,并将原型材质和实例化几何体作为它的参数。 const instancedMesh
= new THREE. InstancedMesh ( instancedGeometry
, material
, instances
) ;
scene
. add ( instancedMesh
) ;
在渲染循环中,更新实例化几何体的属性,即实例的位置、旋转和缩放等信息。 function render ( ) {
requestAnimationFrame ( render
) ;
const time
= Date
. now ( ) * 0.001 ;
for ( let i
= 0 ; i
< instances
; i
++ ) {
const translation
= instancedMesh
. geometry
. attributes
. translation
;
translation
. setXYZ ( i
, Math
. sin ( time
+ i
* 0.5 ) * 5 , Math
. cos ( time
+ i
* 0.3 ) * 5 , i
* 0.1 ) ;
}
instancedMesh
. geometry
. attributes
. translation
. needsUpdate
= true ; // 更新实例位置属性
renderer
. render ( scene
, camera
) ;
}
4、不合理的渲染方式导致渲染卡顿
可以通过使用合适的渲染方式,如WebGL2渲染 ,使用Web Worker等方式来优化。
Ⅰ、使用WebGL2可以在现代浏览器中利用新的图形处理能力,优化three.js渲染性能,下面是具体步骤: ① 在渲染器中启用WebGL2。
const renderer
= new THREE. WebGLRenderer ( { canvas : canvas
, context : canvas
. getContext ( 'webgl2' ) } ) ;
② 使用WebGL2支持的新特性,如transform feedback、instanced arrays等。 例如,以下代码演示了如何使用transform feedback来记录顶点位置的变化:
const transformFeedback
= new THREE. WebGL2TransformFeedback ( ) ;
const bufferGeometry
= new THREE. BufferGeometry ( ) ;
const positions
= new Float32Array ( [ 0 , 0 , 0 ] ) ;
bufferGeometry
. setAttribute ( 'position' , new THREE. BufferAttribute ( positions
, 3 ) ) ;
const shader
= new THREE. ShaderMaterial ( {
vertexShader : `
out vec3 transformedPosition;
void main() {
transformedPosition = position;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
` ,
fragmentShader : `
void main() {
gl_FragColor = vec4(1.0);
}
` ,
transformFeedback : {
// 将顶点位置记录到transformedPosition变量中
varyings : [ 'transformedPosition' ] ,
// 开启transform feedback
enabled : true ,
// 设置bufferGeometry的位置属性为transform feedback的输出属性
bufferGeometry : bufferGeometry
}
} ) ;
const mesh
= new THREE. Mesh ( bufferGeometry
, shader
) ;
scene
. add ( mesh
) ;
function render ( ) {
requestAnimationFrame ( render
) ;
renderer
. setRenderTarget ( null ) ;
// 开始transform feedback
transformFeedback
. begin ( ) ;
renderer
. render ( scene
, camera
) ;
// 结束transform feedback,并将变化后的顶点位置存储到bufferGeometry中
transformFeedback
. end ( ) ;
// 更新顶点位置
positions
. set ( bufferGeometry
. getAttribute ( 'position' ) . array
) ;
bufferGeometry
. setAttribute ( 'position' , new THREE. BufferAttribute ( positions
, 3 ) ) ;
renderer
. render ( scene
, camera
) ;
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 ---------------------------------------------------------------分隔线-----------------------------------------------------------------
Ⅱ、使用Web Worker可以将计算密集型的任务分离到另一个线程中,从而避免主线程被阻塞,优化three.js渲染性能,下面是具体步骤:
① 创建一个Web Worker,用于处理计算密集型的任务。
const worker
= new Worker ( 'worker.js' ) ;
② 在Web Worker中定义处理函数。
// worker.js
function process ( data ) {
// 计算密集型的任务
return result
;
}
onmessage = function ( event ) {
const result
= process ( event
. data
) ;
postMessage ( result
) ;
} ;
③ 在主线程中将任务发送到Web Worker,并设置回调函数处理返回结果。
function render ( ) {
requestAnimationFrame ( render
) ;
// 发送任务到Web Worker
worker
. postMessage ( data
) ;
worker
. onmessage = function ( event ) {
const result
= event
. data
;
// 处理返回结果
} ;
renderer
. render ( scene
, camera
) ;
}
通过以上步骤,就可以使用Web Worker来将计算密集型的任务分离到另一个线程中,从而避免主线程被阻塞,优化three.js渲染性能。需要注意的是,Web Worker中无法直接访问主线程的DOM和three.js对象,需要通过消息传递来实现通信。
5、CPU和GPU资源不平衡导致渲染卡顿
可以通过分析性能监控,优化代码逻辑,使用requestAnimationFrame等方式来平衡CPU和GPU资源占用。
使用requestAnimationFrame可以让浏览器根据自身的渲染节奏调整动画的帧率 ,从而避免过度渲染,优化three.js渲染性能,下面是具体步骤:
将渲染函数作为requestAnimationFrame的回调函数。 function render ( ) {
// 渲染代码
renderer
. render ( scene
, camera
) ;
// 请求下一帧动画
requestAnimationFrame ( render
) ;
}
在初始化时调用一次requestAnimationFrame,启动动画。 var animationId
= requestAnimationFrame ( render
) ;
在动画结束时,记得停止requestAnimationFrame,以避免不必要的资源消耗。 function stop ( ) {
cancelAnimationFrame ( animationId
) ;
}
需要注意的是,使用requestAnimationFrame时需要避免在渲染循环中进行过多的计算,以免影响渲染性能。