OutlinePass原理
源码地址在three/examples/jsm/postprocessing/OutlinePass.js
OutlinePass 是一个后处理通道,用于在选中的3D对象周围渲染轮廓线。这就可以做对象选择时的高亮
1. 核心算法步骤如下:
步骤1:深度缓冲区渲染
// 1. Draw Non Selected objects in the depth buffer
this.renderScene.overrideMaterial = this.depthMaterial;
renderer.setRenderTarget( this.renderTargetDepthBuffer );
renderer.clear();
renderer.render( this.renderScene, this.renderCamera );先隐藏选中的对象
使用 MeshDepthMaterial 渲染非选中对象到深度缓冲区
深度缓冲区存储了场景中所有非选中对象的深度信息
步骤2:遮罩生成
// Make non selected objects invisible, and draw only the selected objects, by comparing the depth buffer of non selected objects
this._changeVisibilityOfNonSelectedObjects( false );
this.renderScene.overrideMaterial = this.prepareMaskMaterial;
this.prepareMaskMaterial.uniforms[ 'depthTexture' ].value = this.renderTargetDepthBuffer.texture;隐藏非选中对象,显示选中对象
使用 prepareMaskMaterial 比较选中对象与深度缓冲区
生成遮罩纹理,标识轮廓区域
步骤3:边缘检测
// 3. Apply Edge Detection Pass
this._fsQuad.material = this.edgeDetectionMaterial;
this.edgeDetectionMaterial.uniforms[ 'maskTexture' ].value = this.renderTargetMaskDownSampleBuffer.texture;
this.edgeDetectionMaterial.uniforms[ 'texSize' ].value.set( this.renderTargetMaskDownSampleBuffer.width, this.renderTargetMaskDownSampleBuffer.height );边缘检测着色器通过比较相邻像素的差异来检测边缘:
void main() {
vec2 invSize = 1.0 / texSize;
vec4 uvOffset = vec4(1.0, 0.0, 0.0, 1.0) * vec4(invSize, invSize);
vec4 c1 = texture2D( maskTexture, vUv + uvOffset.xy);
vec4 c2 = texture2D( maskTexture, vUv - uvOffset.xy);
vec4 c3 = texture2D( maskTexture, vUv + uvOffset.yw);
vec4 c4 = texture2D( maskTexture, vUv - uvOffset.yw);
float diff1 = (c1.r - c2.r)*0.5;
float diff2 = (c3.r - c4.r)*0.5;
float d = length( vec2(diff1, diff2) );
// ... 边缘颜色选择逻辑
}步骤4:模糊处理
// 4. Apply Blur on Half res
this._fsQuad.material = this.separableBlurMaterial1;
this.separableBlurMaterial1.uniforms[ 'colorTexture' ].value = this.renderTargetEdgeBuffer1.texture;
this.separableBlurMaterial1.uniforms[ 'direction' ].value = OutlinePass.BlurDirectionX;
this.separableBlurMaterial1.uniforms[ 'kernelRadius' ].value = this.edgeThickness;使用可分离高斯模糊来平滑轮廓线,支持不同分辨率的多级模糊。
2. 准备着色器材质
深度准备材质 (_getPrepareMaskMaterial)
fragmentShader:
`#include <packing>
varying vec4 vPosition;
varying vec4 projTexCoord;
uniform sampler2D depthTexture;
uniform vec2 cameraNearFar;
void main() {
float depth = unpackRGBAToDepth(texture2DProj( depthTexture, projTexCoord ));
float viewZ = - DEPTH_TO_VIEW_Z( depth, cameraNearFar.x, cameraNearFar.y );
float depthTest = (-vPosition.z > viewZ) ? 1.0 : 0.0;
gl_FragColor = vec4(0.0, depthTest, 1.0, 1.0);
}`可分离模糊材质 (_getSeparableBlurMaterial)
fragmentShader:
`#include <common>
// ... 高斯PDF函数
void main() {
vec2 invSize = 1.0 / texSize;
float sigma = kernelRadius/2.0;
float weightSum = gaussianPdf(0.0, sigma);
vec4 diffuseSum = texture2D( colorTexture, vUv) * weightSum;
// ... 高斯模糊计算
}`从中看到的一些表象问题的原因
1. 性能开销大
需要多次渲染场景(深度缓冲区、遮罩生成、边缘检测、模糊处理)
多个渲染目标的内存占用
复杂的后处理管线,每帧都要执行
2. 内存消耗高
this.renderTargetMaskBuffer = new WebGLRenderTarget( this.resolution.x, this.resolution.y );
this.renderTargetDepthBuffer = new WebGLRenderTarget( this.resolution.x, this.resolution.y, { type: HalfFloatType } );
this.renderTargetMaskDownSampleBuffer = new WebGLRenderTarget( resx, resy, { type: HalfFloatType } );
this.renderTargetBlurBuffer1 = new WebGLRenderTarget( resx, resy, { type: HalfFloatType } );
this.renderTargetBlurBuffer2 = new WebGLRenderTarget( Math.round( resx / 2 ), Math.round( resy / 2 ), { type: HalfFloatType } );创建了6个渲染目标
使用 HalfFloatType 增加内存占用
分辨率越高,内存消耗越大