跳到主要内容

着色器四边形通道

RenderPassShaderQuad 是使用着色器绘制四边形的 RenderPass,因此片元着色器对四边形覆盖的每个像素执行一次。默认覆盖整个渲染目标,也可由视口指定局部。这是后处理效果及在 GPU 上计算纹理的通道的基础。

通道为其 shader 创建四边形、设置渲染状态,并在执行时绘制。可以直接设置着色器使用,也可以将其作为自定义通道的基类,由子类创建着色器和设置参数。

着色器​

顶点着色器通过使用 SEMANTIC_POSITION 的属性接收四边形角点。quadVS 代码块实现此功能,并以 uv0 varying 向片元着色器传递 UV 坐标,因此你只需编写片元着色器。同时提供 GLSL 和 WGSL,以兼容 WebGL2 和 WebGPU:

uniform sampler2D sourceTexture;
uniform vec3 tint;
varying vec2 uv0;

void main() {
vec4 color = texture2D(sourceTexture, uv0);
gl_FragColor = vec4(color.rgb * tint, color.a);
}

通过 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() 仅渲染一次。

渲染状态和绘制区域​

默认四边形覆盖整个目标,绘制时不使用混合、深度测试或面剔除,并替换目标现有内容。可通过以下属性更改:

例如,直接使用通道将着色器绘制到纹理图集的一个块中。不清除图集,因此其他块保留原内容:

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,在场景深度或亮度突变处绘制墨线轮廓。

墨线轮廓