渲染器
渲染器(Renderer)负责将 3D 场景绘制到 2D 画布上。Three.js 提供了多种渲染器,其中 WebGLRenderer 是最常用的。
系统架构概述
plaintext
┌─────────────────────────────────────────────────────────────────────────┐
│ 渲染系统架构 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ 渲染管线流程 │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ Scene ──────▶ Scene Graph ──────▶ Frustum Culling ──────▶ │
│ │ │
│ ▼ │
│ Camera ────────────────────────▶ View Matrix ──────▶ │
│ │ │
│ ▼ │
│ Lights ────────────────────────────────▶ Shading ──────▶ │
│ │ │
│ ▼ │
│ Materials ───────────────────────────▶ GPU Shader ──────▶ │
│ │ │
│ ▼ │
│ Geometry ─────────────────────────────▶ Vertex Processing ──────▶ │
│ │ │
│ ▼ │
│ Textures ────────────────────────────▶ Fragment Processing ──────▶ │
│ │ │
│ ▼ │
│ Render Target ◀──────────┘ │
│ │ │
│ ▼ │
│ Canvas/Screen │
│ │
└─────────────────────────────────────────────────────────────────────────┘渲染器类型对比
| 渲染器 | 底层技术 | 主要用途 | 性能 | 特点 |
|---|---|---|---|---|
| WebGLRenderer | WebGL | 通用 3D 渲染 | 高 | 功能最全 |
| WebGLRenderer (WebGL2) | WebGL2 | 现代 3D 渲染 | 更高 | 支持 3D 纹理等 |
| WebGPURenderer | WebGPU | 下一代渲染 | 最高 | 实验性 |
| SVGRenderer | SVG | 矢量图输出 | 低 | 适用于打印 |
| CSS2DRenderer | CSS/DOM | 2D 标签 | - | UI 元素 |
| CSS3DRenderer | CSS 3D | 3D UI | - | 3D DOM 元素 |
WebGLRenderer 基础
创建渲染器
javascript
const renderer = new THREE.WebGLRenderer({
canvas: undefined, // 指定 canvas 元素
antialias: true, // 抗锯齿
alpha: false, // 透明背景
premultipliedAlpha: false, // 预乘 alpha
preserveDrawingBuffer: true, // 保留绘制缓冲(截图用)
powerPreference: 'high-performance', // 性能模式
stencil: true, // 模板缓冲
depth: true, // 深度缓冲
logarithmicDepthBuffer: false // 对数深度缓冲
})构造参数详解
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
canvas | HTMLCanvasElement | 自动创建 | 指定渲染目标 |
antialias | Boolean | false | 开启抗锯齿 |
alpha | Boolean | false | 允许透明背景 |
premultipliedAlpha | Boolean | true | 预乘 alpha 值 |
preserveDrawingBuffer | Boolean | false | 保留缓冲区(截图必需) |
powerPreference | String | 'default' | GPU 电源模式 |
stencil | Boolean | true | 模板缓冲区 |
depth | Boolean | true | 深度缓冲区 |
logarithmicDepthBuffer | Boolean | false | 对数深度(解决 Z-fighting) |
javascript
// powerPreference 可选值
// 'default' - 浏览器默认
// 'high-performance' - 高性能 GPU
// 'low-power' - 低功耗 GPU(移动设备)基本配置
javascript
// 设置渲染尺寸
renderer.setSize(window.innerWidth, window.innerHeight)
// 设置像素比(高清屏适配)
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
// 设置背景色
renderer.setClearColor(0x333333)
renderer.setClearColor(0x333333, 1) // 第二个参数是透明度
// 设置透明背景
renderer.setClearAlpha(0)
// 添加到 DOM
document.body.appendChild(renderer.domElement)
// 获取 canvas 元素
const canvas = renderer.domElement渲染器属性
颜色空间配置(重要更新)
⚠️ 版本变更说明:Three.js r152+ 版本中,
outputEncoding已废弃,改用outputColorSpace。
javascript
// ===== Three.js r152+ 版本 =====
// 输出颜色空间
renderer.outputColorSpace = THREE.SRGBColorSpace // sRGB 颜色空间(推荐)
renderer.outputColorSpace = THREE.LinearSRGBColorSpace // 线性 sRGB(默认)
renderer.outputColorSpace = THREE.DisplayP3ColorSpace // Display P3 颜色空间
// ===== Three.js r151 及更早版本(已废弃)=====
// renderer.outputEncoding = THREE.sRGBEncoding // 已废弃
// renderer.outputEncoding = THREE.LinearEncoding // 已废弃
// 颜色空间说明
// SRGBColorSpace: 适合直接显示到屏幕,自动进行 gamma 校正
// LinearSRGBColorSpace: 线性空间,适合后处理计算
// DisplayP3ColorSpace: 宽色域,适合 HDR 显示器色调映射
色调映射用于将 HDR(高动态范围)颜色映射到显示器可显示的范围。
javascript
// 色调映射类型
renderer.toneMapping = THREE.NoToneMapping // 无色调映射
renderer.toneMapping = THREE.LinearToneMapping // 线性映射
renderer.toneMapping = THREE.ReinhardToneMapping // Reinhard 映射
renderer.toneMapping = THREE.CineonToneMapping // Cineon 映射
renderer.toneMapping = THREE.ACESFilmicToneMapping // ACES 电影级映射(推荐)
renderer.toneMapping = THREE.CustomToneMapping // 自定义映射
// 曝光度(影响整体亮度)
renderer.toneMappingExposure = 1.0 // 默认值
renderer.toneMappingExposure = 0.5 // 降低曝光
renderer.toneMappingExposure = 2.0 // 提高曝光| 色调映射 | 特点 | 适用场景 |
|---|---|---|
| NoToneMapping | 无处理 | HDR 显示器 |
| LinearToneMapping | 简单线性 | 基础场景 |
| ReinhardToneMapping | 自然过渡 | 一般场景 |
| CineonToneMapping | 电影感 | 电影效果 |
| ACESFilmicToneMapping | 专业电影级 | 推荐、高质量渲染 |
阴影配置
javascript
// 开启阴影
renderer.shadowMap.enabled = true
// 阴影类型
renderer.shadowMap.type = THREE.PCFShadowMap // PCF 软阴影(默认)
renderer.shadowMap.type = THREE.PCFSoftShadowMap // PCF 软阴影(更好)
renderer.shadowMap.type = THREE.BasicShadowMap // 基础阴影(最快)
renderer.shadowMap.type = THREE.VSMShadowMap // VSM 阴影(最软)| 阴影类型 | 质量 | 性能 | 特点 |
|---|---|---|---|
| BasicShadowMap | ★☆☆ | ★★★ | 无过滤,最快 |
| PCFShadowMap | ★★☆ | ★★☆ | 基础软阴影 |
| PCFSoftShadowMap | ★★★ | ★☆☆ | 更软的阴影,推荐 |
| VSMShadowMap | ★★★ | ★★☆ | 方差阴影,边缘最软 |
其他重要属性
javascript
// ===== 自动清除 =====
renderer.autoClear = true // 自动清除(颜色+深度+模板)
renderer.autoClearColor = true // 自动清除颜色
renderer.autoClearDepth = true // 自动清除深度
renderer.autoClearStencil = true // 自动清除模板
// ===== 对象排序 =====
renderer.sortObjects = true // 自动排序对象(对透明对象重要)
// 透明对象需要排序才能正确渲染
// 不透明对象可以关闭排序提升性能
// ===== 裁剪平面 =====
renderer.clippingPlanes = [] // 裁剪平面数组
renderer.localClippingEnabled = false // 启用局部裁剪
// ===== 信息统计 =====
console.log(renderer.info)
// {
// memory: {
// geometries: 1, // 几何体数量
// textures: 1 // 纹理数量
// },
// render: {
// calls: 2, // 绘制调用次数
// triangles: 36, // 三角形数量
// points: 0, // 点数量
// lines: 0, // 线数量
// frame: 0 // 帧数
// },
// programs: 1 // 着色器程序数量
// }渲染方法
基础渲染
javascript
// 渲染场景
renderer.render(scene, camera)
// 渲染循环
function animate() {
requestAnimationFrame(animate)
// 更新场景状态
cube.rotation.x += 0.01
// 渲染
renderer.render(scene, camera)
}
animate()清除画布
javascript
// 清除所有缓冲
renderer.clear()
// 清除特定缓冲
renderer.clearColor() // 清除颜色缓冲
renderer.clearDepth() // 清除深度缓冲
renderer.clearStencil() // 清除模板缓冲
// 同时清除多个
renderer.clear(true, true, true) // color, depth, stencil
// 清除渲染目标
renderer.clearTarget(renderTarget, color, depth, stencil)视口与裁剪
javascript
// 设置视口
renderer.setViewport(x, y, width, height)
// 设置裁剪区域
renderer.setScissor(x, y, width, height)
renderer.setScissorTest(true) // 开启裁剪测试
// 分屏渲染示例
function renderSplitScreen() {
const width = window.innerWidth
const height = window.innerHeight
renderer.setScissorTest(true)
// 左侧视角
renderer.setViewport(0, 0, width / 2, height)
renderer.setScissor(0, 0, width / 2, height)
renderer.render(scene, camera1)
// 右侧视角
renderer.setViewport(width / 2, 0, width / 2, height)
renderer.setScissor(width / 2, 0, width / 2, height)
renderer.render(scene, camera2)
// 小地图
const mapSize = 150
renderer.setViewport(width - mapSize, height - mapSize, mapSize, mapSize)
renderer.setScissor(width - mapSize, height - mapSize, mapSize, mapSize)
renderer.render(scene, minimapCamera)
renderer.setScissorTest(false)
}渲染目标(RenderTarget)
渲染到纹理而非屏幕,用于后处理、反射、延迟渲染等高级技术。
基础用法
javascript
// 创建渲染目标
const renderTarget = new THREE.WebGLRenderTarget(width, height, {
minFilter: THREE.LinearFilter,
magFilter: THREE.LinearFilter,
format: THREE.RGBAFormat,
type: THREE.UnsignedByteType
})
// 渲染到目标
renderer.setRenderTarget(renderTarget)
renderer.render(scene, camera)
renderer.setRenderTarget(null) // 恢复默认(屏幕)
// 使用渲染结果作为纹理
const material = new THREE.MeshBasicMaterial({
map: renderTarget.texture
})RenderTarget 参数
| 参数 | 说明 | 推荐值 |
|---|---|---|
width | 宽度 | 根据需求 |
height | 高度 | 根据需求 |
minFilter | 缩小过滤 | LinearFilter |
magFilter | 放大过滤 | LinearFilter |
format | 颜色格式 | RGBAFormat |
type | 数据类型 | UnsignedByteType / HalfFloatType |
depthBuffer | 深度缓冲 | true |
stencilBuffer | 模板缓冲 | false |
samples | MSAA 采样数 | 0 或 4 |
javascript
// 高质量渲染目标
const hqRenderTarget = new THREE.WebGLRenderTarget(2048, 2048, {
minFilter: THREE.LinearMipmapLinearFilter,
magFilter: THREE.LinearFilter,
format: THREE.RGBAFormat,
type: THREE.HalfFloatType,
anisotropy: renderer.capabilities.getMaxAnisotropy()
})
// 带深度纹理的渲染目标
const depthRenderTarget = new THREE.WebGLRenderTarget(width, height, {
minFilter: THREE.NearestFilter,
magFilter: THREE.NearestFilter,
format: THREE.DepthFormat,
type: THREE.UnsignedShortType
})
// MSAA 渲染目标(WebGL2)
const msaaRenderTarget = new THREE.WebGLRenderTarget(width, height, {
samples: 4 // 4x MSAA
})后处理效果
javascript
import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js'
import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass.js'
import { UnrealBloomPass } from 'three/examples/jsm/postprocessing/UnrealBloomPass.js'
import { ShaderPass } from 'three/examples/jsm/postprocessing/ShaderPass.js'
import { FXAAShader } from 'three/examples/jsm/shaders/FXAAShader.js'
// 创建后处理组合器
const composer = new EffectComposer(renderer)
// 添加渲染通道
const renderPass = new RenderPass(scene, camera)
composer.addPass(renderPass)
// 添加泛光效果
const bloomPass = new UnrealBloomPass(
new THREE.Vector2(window.innerWidth, window.innerHeight),
1.5, // 强度
0.4, // 半径
0.85 // 阈值
)
composer.addPass(bloomPass)
// 添加抗锯齿
const fxaaPass = new ShaderPass(FXAAShader)
fxaaPass.uniforms['resolution'].value.set(
1 / window.innerWidth,
1 / window.innerHeight
)
composer.addPass(fxaaPass)
// 渲染循环使用 composer
function animate() {
requestAnimationFrame(animate)
composer.render()
}自定义后处理着色器
javascript
// 自定义后处理着色器
const CustomShader = {
uniforms: {
tDiffuse: { value: null },
time: { value: 0 },
resolution: { value: new THREE.Vector2() }
},
vertexShader: `
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`,
fragmentShader: `
uniform sampler2D tDiffuse;
uniform float time;
uniform vec2 resolution;
varying vec2 vUv;
void main() {
vec4 color = texture2D(tDiffuse, vUv);
// 添加扫描线效果
float scanline = sin(vUv.y * resolution.y * 2.0) * 0.04;
color.rgb -= scanline;
// 添加色差效果
float offset = 0.002 * sin(time * 2.0);
color.r = texture2D(tDiffuse, vUv + vec2(offset, 0.0)).r;
color.b = texture2D(tDiffuse, vUv - vec2(offset, 0.0)).b;
// 添加暗角
float vignette = 1.0 - length(vUv - 0.5) * 0.8;
color.rgb *= vignette;
gl_FragColor = color;
}
`
}
const customPass = new ShaderPass(CustomShader)
customPass.uniforms['resolution'].value.set(window.innerWidth, window.innerHeight)
composer.addPass(customPass)多渲染器与多场景
多场景渲染
javascript
// 创建多个场景
const scene3D = new THREE.Scene()
const sceneUI = new THREE.Scene()
// 渲染多个场景
function render() {
// 渲染 3D 场景
renderer.render(scene3D, camera3D)
// 清除深度缓冲,保留颜色
renderer.clearDepth()
// 渲染 UI 场景(叠加在 3D 场景上)
renderer.render(sceneUI, cameraUI)
}多画布渲染
javascript
// 创建多个渲染器
const renderer1 = new THREE.WebGLRenderer({ canvas: canvas1 })
const renderer2 = new THREE.WebGLRenderer({ canvas: canvas2 })
renderer1.setSize(400, 300)
renderer2.setSize(400, 300)
// 渲染到不同画布
function render() {
renderer1.render(scene, camera1)
renderer2.render(scene, camera2)
}性能优化
关闭不必要的功能
javascript
// 如果不需要阴影
renderer.shadowMap.enabled = false
// 如果不需要排序(只有不透明对象)
renderer.sortObjects = false
// 限制像素比(移动设备)
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
// 或直接设为 1(最低质量,最高性能)
renderer.setPixelRatio(1)按需渲染
javascript
// 只在需要时渲染
let needsRender = true
function animate() {
requestAnimationFrame(animate)
if (needsRender) {
renderer.render(scene, camera)
needsRender = false
}
}
// 触发渲染的事件
controls.addEventListener('change', () => { needsRender = true })
window.addEventListener('resize', () => { needsRender = true })
scene.addEventListener('objectAdded', () => { needsRender = true })WebGL 上下文丢失处理
javascript
// 监听上下文丢失
renderer.domElement.addEventListener('webglcontextlost', (event) => {
event.preventDefault()
console.log('WebGL 上下文丢失')
// 暂停应用或显示提示
})
// 监听上下文恢复
renderer.domElement.addEventListener('webglcontextrestored', () => {
console.log('WebGL 上下文恢复')
// 重新初始化场景
initScene()
})
// 预防措施
// 1. 避免过多 WebGL 上下文(标签页过多)
// 2. 合理管理资源
// 3. 移动设备上更注意性能监控
javascript
class RendererMonitor {
constructor(renderer) {
this.renderer = renderer
this.lastTime = performance.now()
this.frameCount = 0
this.fps = 0
}
update() {
this.frameCount++
const currentTime = performance.now()
if (currentTime - this.lastTime >= 1000) {
this.fps = this.frameCount
this.frameCount = 0
this.lastTime = currentTime
const info = this.renderer.info
console.log({
fps: this.fps,
drawCalls: info.render.calls,
triangles: info.render.triangles,
geometries: info.memory.geometries,
textures: info.memory.textures
})
}
}
}
const monitor = new RendererMonitor(renderer)
function animate() {
monitor.update()
renderer.render(scene, camera)
}高级特性
VR 渲染
javascript
import { VRButton } from 'three/examples/jsm/webxr/VRButton.js'
// 开启 VR
renderer.xr.enabled = true
document.body.appendChild(VRButton.createButton(renderer))
// VR 渲染循环
renderer.setAnimationLoop(() => {
// 更新场景
renderer.render(scene, camera)
})立体渲染
javascript
import { StereoEffect } from 'three/examples/jsm/effects/StereoEffect.js'
const effect = new StereoEffect(renderer)
effect.setSize(window.innerWidth, window.innerHeight)
function animate() {
requestAnimationFrame(animate)
effect.render(scene, camera)
}访问 WebGL 上下文
javascript
// 获取 WebGL 上下文
const gl = renderer.getContext()
// 查询 WebGL 信息
console.log('WebGL Version:', gl.getParameter(gl.VERSION))
console.log('Vendor:', gl.getParameter(gl.VENDOR))
console.log('Renderer:', gl.getParameter(gl.RENDERER))
console.log('Max Texture Size:', gl.getParameter(gl.MAX_TEXTURE_SIZE))
console.log('Max Vertex Attribs:', gl.getParameter(gl.MAX_VERTEX_ATTRIBS))
// 查询扩展
const extensions = {
anisotropy: gl.getExtension('EXT_texture_filter_anisotropic'),
floatTextures: gl.getExtension('OES_texture_float'),
floatLinear: gl.getExtension('OES_texture_float_linear'),
depthTexture: gl.getExtension('WEBGL_depth_texture')
}
// 使用各向异性过滤
if (extensions.anisotropy) {
const maxAnisotropy = gl.getParameter(
extensions.anisotropy.MAX_TEXTURE_MAX_ANISOTROPY_EXT
)
texture.anisotropy = maxAnisotropy
}其他渲染器
SVGRenderer
将场景渲染为 SVG,适用于需要矢量输出的场景。
javascript
import { SVGRenderer } from 'three/examples/jsm/renderers/SVGRenderer.js'
const renderer = new SVGRenderer()
renderer.setSize(window.innerWidth, window.innerHeight)
document.body.appendChild(renderer.domElement)
renderer.render(scene, camera)
// 导出 SVG
const svgData = renderer.domElement.outerHTML
const blob = new Blob([svgData], { type: 'image/svg+xml' })CSS2DRenderer
渲染 CSS 2D 元素,用于标签、标注等。
javascript
import { CSS2DRenderer, CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer.js'
const labelRenderer = new CSS2DRenderer()
labelRenderer.setSize(window.innerWidth, window.innerHeight)
labelRenderer.domElement.style.position = 'absolute'
labelRenderer.domElement.style.top = '0'
labelRenderer.domElement.style.pointerEvents = 'none'
document.body.appendChild(labelRenderer.domElement)
// 创建标签
const labelDiv = document.createElement('div')
labelDiv.className = 'label'
labelDiv.textContent = 'Hello World'
labelDiv.style.color = 'white'
labelDiv.style.backgroundColor = 'rgba(0,0,0,0.5)'
labelDiv.style.padding = '5px'
const label = new CSS2DObject(labelDiv)
label.position.set(0, 1, 0)
mesh.add(label)
// 渲染循环
function animate() {
renderer.render(scene, camera)
labelRenderer.render(scene, camera)
}CSS3DRenderer
渲染 CSS 3D 元素,用于 3D UI。
javascript
import { CSS3DRenderer, CSS3DObject } from 'three/examples/jsm/renderers/CSS3DRenderer.js'
const css3DRenderer = new CSS3DRenderer()
css3DRenderer.setSize(window.innerWidth, window.innerHeight)
css3DRenderer.domElement.style.position = 'absolute'
css3DRenderer.domElement.style.top = '0'
css3DRenderer.domElement.style.pointerEvents = 'none'
document.body.appendChild(css3DRenderer.domElement)
// 创建 3D UI 元素
const element = document.createElement('div')
element.style.width = '100px'
element.style.height = '100px'
element.style.background = 'red'
element.style.opacity = '0.8'
const object = new CSS3DObject(element)
object.position.set(0, 0, 0)
object.rotation.y = Math.PI / 4
scene.add(object)WebGPU 渲染器(实验性)
⚠️ 注意:WebGPU 是实验性功能,需要浏览器支持。
javascript
import WebGPU from 'three/examples/jsm/capabilities/WebGPU.js'
import WebGPURenderer from 'three/examples/jsm/renderers/webgpu/WebGPURenderer.js'
// 检查 WebGPU 支持
if (WebGPU.isAvailable()) {
const renderer = new WebGPURenderer({
antialias: true
})
renderer.setSize(window.innerWidth, window.innerHeight)
renderer.setPixelRatio(window.devicePixelRatio)
document.body.appendChild(renderer.domElement)
// 渲染
renderer.render(scene, camera)
} else {
console.warn('WebGPU 不可用,回退到 WebGL')
// 使用 WebGLRenderer
}截图功能
javascript
// 基础截图
function takeScreenshot() {
// 需要设置 preserveDrawingBuffer: true
const dataURL = renderer.domElement.toDataURL('image/png')
const link = document.createElement('a')
link.download = 'screenshot.png'
link.href = dataURL
link.click()
}
// 高分辨率截图
function takeHighResScreenshot(scale = 2) {
const width = window.innerWidth * scale
const height = window.innerHeight * scale
// 临时修改渲染器尺寸
renderer.setSize(width, height)
camera.aspect = width / height
camera.updateProjectionMatrix()
// 渲染
renderer.render(scene, camera)
// 截图
const dataURL = renderer.domElement.toDataURL('image/png')
// 恢复原始尺寸
renderer.setSize(window.innerWidth, window.innerHeight)
camera.aspect = window.innerWidth / window.innerHeight
camera.updateProjectionMatrix()
// 下载
const link = document.createElement('a')
link.download = 'screenshot_hires.png'
link.href = dataURL
link.click()
}
// 截取特定区域
function takeRegionScreenshot(x, y, width, height) {
const canvas = document.createElement('canvas')
canvas.width = width
canvas.height = height
const ctx = canvas.getContext('2d')
// 从 WebGL canvas 复制像素
const imageData = renderer.domElement.getContext('webgl')
.readPixels(x, y, width, height, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array(width * height * 4))
ctx.putImageData(new ImageData(new Uint8ClampedArray(imageData), width, height), 0, 0)
return canvas.toDataURL('image/png')
}常见问题
Q1: 渲染器大小不正确?
javascript
// 确保 setSize 使用正确的尺寸
renderer.setSize(container.clientWidth, container.clientHeight)
// 响应窗口变化
window.addEventListener('resize', () => {
const width = window.innerWidth
const height = window.innerHeight
camera.aspect = width / height
camera.updateProjectionMatrix()
renderer.setSize(width, height)
})
// 注意:renderer.domElement 尺寸可能受 CSS 影响
// 确保 canvas 的 CSS 不会改变其显示尺寸Q2: 画面模糊?
javascript
// 1. 设置正确的像素比
renderer.setPixelRatio(window.devicePixelRatio)
// 2. 开启抗锯齿
const renderer = new THREE.WebGLRenderer({ antialias: true })
// 3. 使用后处理抗锯齿
import { SMAAPass } from 'three/examples/jsm/postprocessing/SMAAPass.js'
const smaaPass = new SMAAPass(width, height)
composer.addPass(smaaPass)
// 4. 确保纹理尺寸足够
texture.minFilter = THREE.LinearMipmapLinearFilter
texture.anisotropy = renderer.capabilities.getMaxAnisotropy()Q3: 性能问题?
javascript
// 1. 降低像素比
renderer.setPixelRatio(1) // 移动设备推荐
// 2. 减少绘制调用
// - 合并几何体
// - 使用 InstancedMesh
// - 减少材质切换
// 3. 按需渲染
let needsRender = true
// 4. 检查性能瓶颈
console.log(renderer.info)
// 5. 使用简单材质测试
material = new THREE.MeshBasicMaterial() // 最快
// 6. 关闭不必要的功能
renderer.shadowMap.enabled = falseQ4: 截图是空白或黑色?
javascript
// 原因:preserveDrawingBuffer 未开启
// 解决方案 1:创建渲染器时设置
const renderer = new THREE.WebGLRenderer({
preserveDrawingBuffer: true
})
// 解决方案 2:在渲染后立即截图
function animate() {
renderer.render(scene, camera)
// 渲染后立即截图
if (needScreenshot) {
const dataURL = renderer.domElement.toDataURL('image/png')
// ...
needScreenshot = false
}
}最佳实践
1. 标准初始化模板
javascript
// 创建渲染器
const renderer = new THREE.WebGLRenderer({
antialias: true,
alpha: false,
powerPreference: 'high-performance',
preserveDrawingBuffer: false
})
// 基础配置
renderer.setSize(window.innerWidth, window.innerHeight)
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
// 颜色空间(r152+)
renderer.outputColorSpace = THREE.SRGBColorSpace
// 色调映射
renderer.toneMapping = THREE.ACESFilmicToneMapping
renderer.toneMappingExposure = 1.0
// 阴影
renderer.shadowMap.enabled = true
renderer.shadowMap.type = THREE.PCFSoftShadowMap
// 添加到 DOM
document.body.appendChild(renderer.domElement)2. 资源清理
javascript
function disposeRenderer() {
// 清理场景资源
scene.traverse((child) => {
if (child.geometry) {
child.geometry.dispose()
}
if (child.material) {
const materials = Array.isArray(child.material)
? child.material
: [child.material]
materials.forEach((mat) => {
// 清理纹理
for (const key in mat) {
if (mat[key] && typeof mat[key].dispose === 'function') {
mat[key].dispose()
}
}
mat.dispose()
})
}
})
// 清理渲染器
renderer.dispose()
// 移除 DOM
renderer.domElement.remove()
}3. 封装渲染管理器
javascript
class RendererManager {
constructor(options = {}) {
this.renderer = this.createRenderer(options)
this.setupResize()
}
createRenderer(options) {
const renderer = new THREE.WebGLRenderer({
antialias: options.antialias ?? true,
alpha: options.alpha ?? false,
powerPreference: 'high-performance'
})
renderer.setSize(window.innerWidth, window.innerHeight)
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
renderer.outputColorSpace = THREE.SRGBColorSpace
renderer.toneMapping = THREE.ACESFilmicToneMapping
if (options.shadows) {
renderer.shadowMap.enabled = true
renderer.shadowMap.type = THREE.PCFSoftShadowMap
}
return renderer
}
setupResize() {
window.addEventListener('resize', () => {
this.renderer.setSize(window.innerWidth, window.innerHeight)
})
}
render(scene, camera) {
this.renderer.render(scene, camera)
}
dispose() {
this.renderer.dispose()
}
get domElement() {
return this.renderer.domElement
}
}