Surface Shader Execution Flow
Surface Shader unifies the shading process and provides a wide range of custom functions for both vertex and fragment shader. Shader writers can override these functions according to their needs.
Please refer to Surface Shader Built-in Replaceable Functions and Function Replacement Using Macros。
This main purpose of this article is to help developers familiarize themselves with the execution flow of Surface Shader and understand the timing of function calls.
Entry Function
Let's first take a look at the CCEffect section in the built-in Surface Shader file.
CCEffect %{
techniques:
- name: opaque
passes:
- vert: standard-vs
frag: standard-fs
...
}%
As we can see, each pass doesn't specify specific entry functions for the vertex shader and fragment shader, which means that the default entry function main
will be used.
From the Surface Shader Structure, we can learn that during the Surface Shader Assembly phase, each Surface Shader includes different header files based on different Render Usages. These header files serve as our entry functions.
Main Function for VS
Take the standard-vs
of the built-in Surface Shader as an example.
CCProgram standard-vs %{
#include <shading-entries/main-functions/render-to-scene/vs>
}%
As we can see, it includes the vs.chunk under render-to-scene
folder.
By opening render-to-scene/vs.chunk, we can see that it only contains one main function, here is the code and comments.
void main()
{
SurfacesStandardVertexIntermediate In;
CCSurfacesVertexInput(In);
CCSurfacesVertexAnimation(In);
In.position.xyz = SurfacesVertexModifyLocalPos(In);
In.normal.xyz = SurfacesVertexModifyLocalNormal(In);
#if CC_SURFACES_USE_TANGENT_SPACE
In.tangent = SurfacesVertexModifyLocalTangent(In);
#endif
SurfacesVertexModifyLocalSharedData(In);
CCSurfacesVertexWorldTransform(In);
In.worldPos = SurfacesVertexModifyWorldPos(In);
In.clipPos = cc_matProj * cc_matView * vec4(In.worldPos, 1.0);
In.clipPos = SurfacesVertexModifyClipPos(In);
vec3 viewDirect = normalize(cc_cameraPos.xyz - In.worldPos);
In.worldNormal.w = dot(In.worldNormal.xyz, viewDirect) < 0.0 ? -1.0 : 1.0;
In.worldNormal.xyz = SurfacesVertexModifyWorldNormal(In);
SurfacesVertexModifyUV(In);
SurfacesVertexModifySharedData(In);
CCSurfacesVertexTransformUV(In);
CCSurfacesVertexTransferFog(In);
CCSurfacesVertexTransferShadow(In);
CCSurfacesVertexTransferLightMapUV(In);
CCSurfacesVertexOutput(In);
}
Main Function for FS
Similarly, let's take a look at the main function of the fragment shader. In the case of the standard-fs
of built-in Surface Shader.
CCProgram standard-fs %{
#include <shading-entries/main-functions/render-to-scene/fs>
}%
the render-to-scene/fs.chunk
contains the following content:
#if (CC_PIPELINE_TYPE == CC_PIPELINE_TYPE_FORWARD || CC_FORCE_FORWARD_SHADING)
#include <shading-entries/main-functions/render-to-scene/pipeline/forward-fs>
#elif CC_PIPELINE_TYPE == CC_PIPELINE_TYPE_DEFERRED
#include <shading-entries/main-functions/render-to-scene/pipeline/deferred-fs>
#endif
As we can see, it distinguishes between the forward and deferred pipelines.
forward-fs
//Define the color output target
layout(location = 0) out vec4 fragColorX;
void main() {
#if CC_DISABLE_STRUCTURE_IN_FRAGMENT_SHADER
//Get the base color and transparency, can be replaced by macros
vec4 color = SurfacesFragmentModifyBaseColorAndTransparency();
#else
//Get surface material data
SurfacesMaterialData surfaceData;
CCSurfacesFragmentGetMaterialData(surfaceData);
//Compute shadow parameters
vec2 shadowBias = vec2(0.0);
...
//Compute fog parameters
#if !CC_FORWARD_ADD
float fogFactor = 1.0;
#endif
//Compute lighting
LightingResult lightingResult;
CCSurfacesLighting(lightingResult, surfaceData, shadowBias);
//Rendering debugging related code
...
//Pixel shading calculation
vec4 color = CCSurfacesShading(surfaceData, lightingResult);
...
//Color output
#if CC_USE_RGBE_OUTPUT
fragColorX = packRGBE(color.rgb); // for reflection-map
return;
#endif
//HDR,LinearToSRGB, and other final computations
#if CC_USE_HDR
#if CC_USE_DEBUG_VIEW == CC_SURFACES_DEBUG_VIEW_COMPOSITE_AND_MISC && CC_SURFACES_ENABLE_DEBUG_VIEW
if (IS_DEBUG_VIEW_COMPOSITE_ENABLE_TONE_MAPPING)
#endif
color.rgb = ACESToneMap(color.rgb);
#endif
#if CC_USE_DEBUG_VIEW == CC_SURFACES_DEBUG_VIEW_COMPOSITE_AND_MISC
if (IS_DEBUG_VIEW_COMPOSITE_ENABLE_GAMMA_CORRECTION)
#endif
color.rgb = LinearToSRGB(color.rgb);
#if !CC_FORWARD_ADD && CC_USE_FOG != CC_FOG_NONE
CC_APPLY_FOG_BASE(color, fogFactor);
#endif
fragColorX = CCSurfacesDebugDisplayInvalidNumber(color);
}
deferred-fs
The deferred rendering is divided into two stages: GBuffer and Lighting.
In the GBuffer stage, the main task is to fill the various render targets by collecting the corresponding surface material data.
//GBuffer 0,1,2
layout(location = 0) out vec4 fragColor0;
layout(location = 1) out vec4 fragColor1;
layout(location = 2) out vec4 fragColor2;
void main () {
//Collect surface material data
SurfacesMaterialData surfaceData;
CCSurfacesFragmentGetMaterialData(surfaceData);
//Fill the GBuffer
fragColor0 = CCSurfacesDeferredOutput0(surfaceData);
fragColor1 = CCSurfacesDeferredOutput1(surfaceData);
fragColor2 = CCSurfacesDeferredOutput2(surfaceData);
//Debug rendering related code
#if CC_USE_DEBUG_VIEW == CC_SURFACES_DEBUG_VIEW_SINGLE && CC_SURFACES_ENABLE_DEBUG_VIEW
vec4 debugColor = vec4(0.0, 0.0, 0.0, 1.0);
CCSurfacesDebugViewMeshData(debugColor);
CCSurfacesDebugViewSurfaceData(debugColor, surfaceData);
if (IS_DEBUG_VIEW_ENABLE_WITH_CAMERA) {
fragColor0 = debugColor;
}
#endif
}
In the deferred rendering Lighting stage, it is controlled by the engine's rendering pipeline and performs lighting calculations using the GBuffer. You can refer to internal/effects/deferred-lighting.effect
.
Similarly, the main functions for other render stage can be found in the internal/chunks/shading-entries/
folder.
Note: The code that can be replaced is named using the
Surface###Modify###
format.