Multi-pass rendering allows an object to be rendered multiple times, each time using different rendering logic. This tutorial uses the implementation of an outline effect to teach the principles and applications of multi-pass rendering.
Multi-pass rendering refers to executing multiple rendering passes on the same object within a single draw call:
SubShader "Default" {
Pass "Outline" {
// First pass: Render the outline
}
Pass "Main" {
// Second pass: Render the object itself
}
}Execution Order: The engine will execute the passes sequentially, according to their declaration order. ## Outline Effect Implementation
The outline effect is achieved using two passes:
Shader "Tutorial/04-Outline" {
SubShader "Default" {
// Pass 1: Outline Pass - Render the enlarged back face first
Pass "Outline" {
// Rendering state: Only render the front face for outlining
RasterState customRasterState {
CullMode = CullMode.Front; // Cull front face, only render back face
}
DepthState customDepthState {
WriteEnabled = true;
CompareFunction = CompareFunction.LessEqual;
}
RasterState = customRasterState;
DepthState = customDepthState;
mat4 renderer_MVPMat;
vec4 material_OutlineColor;
float material_OutlineWidth;
struct a2v {
vec4 POSITION;
vec3 NORMAL;
};
VertexShader = outlineVert;
FragmentShader = outlineFrag;
// Outline vertex shader: Expand vertices along the normal direction
void outlineVert(a2v input) {
// Expand the vertex along the normal direction
vec4 pos = input.POSITION;
pos.xyz += input.NORMAL * material_OutlineWidth;
gl_Position = renderer_MVPMat * pos;
}
// Outline fragment shader: Output the outline color
void outlineFrag() {
gl_FragColor = material_OutlineColor;
}
}
// Pass 2: Main Pass - Render the object itself
Pass "Main" {
// Rendering state: Render the back face normally
RasterState customRasterState {
CullMode = CullMode.Back; // Cull back face, render front face
}
DepthState customDepthState {
WriteEnabled = true;
CompareFunction = CompareFunction.LessEqual;
}
RasterState = customRasterState;
DepthState = customDepthState;
mat4 renderer_MVPMat;
vec4 material_BaseColor;
vec3 camera_Position;
struct a2v
``` {
vec4 POSITION;
vec3 NORMAL;
};
struct v2f {
vec3 worldNormal;
vec3 worldPos;
};
VertexShader = mainVert;
FragmentShader = mainFrag;
// Main vertex shader
v2f mainVert(a2v input) {
v2f output;
gl_Position = renderer_MVPMat * input.POSITION;
// Pass world space normal and position (simplified processing)
output.worldNormal = input.NORMAL;
output.worldPos = input.POSITION.xyz;
return output;
}
// Main fragment shader: Simple Lambert shading
void mainFrag(v2f input) {
vec3 normal = normalize(input.worldNormal);
vec3 lightDir = normalize(vec3(1.0, 1.0, 1.0)); // Fixed light direction
// Lambertian diffuse shading
float NdotL = max(dot(normal, lightDir), 0.0);
vec3 diffuse = material_BaseColor.rgb * NdotL;
// Add ambient light
vec3 ambient = material_BaseColor.rgb * 0.3;
gl_FragColor = vec4(diffuse + ambient, material_BaseColor.a);
}
}Multi-pass rendering increases the number of draw calls:
This shader will:
Go to Playground