着色器四边形通道
RenderPassShaderQuad 是使用着色器绘制四边形的 RenderPass,因此片元着色器对四边形覆盖的每个像素执行一次。默认覆盖整个渲染目标,也可由视口指定局部。这是后处理效果及在 GPU 上计算纹理的通道的基础。
通道为其 shader 创建四边形、设置渲染状态,并在执行时绘制。可以直接设置着色器使用,也可以将其作为自定义通道的基类,由子类创建着色器和设置参数。
着色器
顶点着色器通过使用 SEMANTIC_POSITION 的属性接收四边形角点。quadVS 代码块实现此功能,并以 uv0 varying 向片元着色器传递 UV 坐标,因此你只需编写片元着色器。同时提供 GLSL 和 WGSL,以兼容 WebGL2 和 WebGPU:
- GLSL
- WGSL
uniform sampler2D sourceTexture;
uniform vec3 tint;
varying vec2 uv0;
void main() {
vec4 color = texture2D(sourceTexture, uv0);
gl_FragColor = vec4(color.rgb * tint, color.a);
}
var sourceTexture: texture_2d<f32>;
var sourceTextureSampler: sampler;
uniform tint: vec3f;
varying uv0: vec2f;
@fragment fn fragmentMain(input: FragmentInput) -> FragmentOutput {
var output: FragmentOutput;
let color = textureSample(sourceTexture, sourceTextureSampler, input.uv0);
output.color = vec4f(color.rgb * uniform.tint, color.a);
return output;
}
通过 ShaderUtils.createShader 创建着色器,传入两个版本的片元着色器:
const shader = ShaderUtils.createShader(device, {
uniqueName: 'TintShader',
attributes: { aPosition: SEMANTIC_POSITION },
vertexChunk: 'quadVS',
fragmentGLSL: tintGLSL,
fragmentWGSL: tintWGSL
});
着色器可以共享,因此通道不会销毁它。
设置着色器参数
着色器纹理和 uniform 设置在图形设备的作用域上。其他通道也可能设置同名参数,因此应在绘制四边形前立即设置:创建通道时解析一次参数,在覆盖的 execute 中设置值,然后调用负责绘制的 super.execute():
// A pass drawing a texture into its render target, multiplied by a color
class TintPass extends RenderPassShaderQuad {
constructor(device, sourceTexture) {
super(device);
this.name = 'TintPass';
this.sourceTexture = sourceTexture;
this.tint = new Float32Array([1, 0.5, 0.5]);
this.shader = ShaderUtils.createShader(device, {
uniqueName: 'TintShader',
attributes: { aPosition: SEMANTIC_POSITION },
vertexChunk: 'quadVS',
fragmentGLSL: tintGLSL,
fragmentWGSL: tintWGSL
});
// resolve the shader parameters once
this.sourceTextureId = device.scope.resolve('sourceTexture');
this.tintId = device.scope.resolve('tint');
}
execute() {
// set the parameters right before the quad is drawn
this.sourceTextureId.setValue(this.sourceTexture);
this.tintId.setValue(this.tint);
super.execute();
}
}
const pass = new TintPass(app.graphicsDevice, sourceTexture);
pass.init(renderTarget);
// the quad covers the whole render target, so clear it instead of loading its content
pass.setClearColor(Color.BLACK);
将通道添加到相机可每帧绘制,参见相机通道;也可调用 pass.render() 仅渲染一次。
渲染状态和绘制区域
默认四边形覆盖整个目标,绘制时不使用混合、深度测试或面剔除,并替换目标现有内容。可通过以下属性更改:
blendState:将四边形与目标内容混合。depthState、stencilFront和stencilBack:针对目标的深度或模板进行测试。viewport和可选的scissor:仅向目标的一部分绘制。
例如,直接使用通道将着色器绘制到纹理图集的一个块中。不清除图集,因此其他块保留原内容:
const pass = new RenderPassShaderQuad(app.graphicsDevice);
pass.name = 'AtlasTile';
pass.shader = tileShader;
pass.init(atlasRenderTarget);
pass.viewport = new Vec4(256, 0, 256, 256);
pass.render();
// blend a second shader over the same tile
pass.shader = overlayShader;
pass.blendState = BlendState.ALPHABLEND;
pass.render();
pass.destroy();
绘制多个四边形
对于精灵等仅根据自身索引读取不同数据的多个四边形,可设置 numInstances,用一次实例化绘制完成。每个实例读取自身索引(GLSL 的 gl_InstanceID,WGSL 的 pcInstanceIndex),获取对应四边形的数据,例如位置。默认 numInstances 为 undefined,不使用实例化,绘制单个四边形。设为零时不绘制,但通道仍执行,因此仍会清除目标。
要绘制参数不同的多个四边形,例如每盏灯一个,可覆盖 execute,为每个四边形设置参数或视口,再调用 super.execute(),使用通道渲染状态绘制:
execute() {
for (const light of this.lights) {
this.colorId.setValue(light.color);
this.viewport = light.screenRect;
super.execute();
}
}
立即绘制四边形
对于单次绘制,例如只生成一次纹理,drawQuadWithShader 可立即用着色器向目标绘制四边形,无需配置通道:
device.scope.resolve('scale').setValue(4);
drawQuadWithShader(device, renderTarget, shader);
每次调用都会创建、渲染并销毁一个通道,因此每次都有分配开销。它使用图形设备当前的混合状态,加载而不清除目标内容。要每帧绘制或使用独立渲染状态,应创建一次 RenderPassShaderQuad 并复用。
示例
两个 RenderPassShaderQuad 通道向纹理绘制光轨:淡出通道用反向减法混合压暗纹理,笔刷通道沿每个笔刷路径绘制点,并逐点设置视口,全部在一个通道内完成。
后处理效果中的 RenderPassShaderQuad,在场景深度或亮度突变处绘制墨线轮廓。