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.

  1. CCEffect %{
  2. techniques:
  3. - name: opaque
  4. passes:
  5. - vert: standard-vs
  6. frag: standard-fs
  7. ...
  8. }%

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.

  1. CCProgram standard-vs %{
  2. #include <shading-entries/main-functions/render-to-scene/vs>
  3. }%

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.

  1. void main()
  2. {
  3. SurfacesStandardVertexIntermediate In;
  4. CCSurfacesVertexInput(In);
  5. CCSurfacesVertexAnimation(In);
  6. In.position.xyz = SurfacesVertexModifyLocalPos(In);
  7. In.normal.xyz = SurfacesVertexModifyLocalNormal(In);
  8. #if CC_SURFACES_USE_TANGENT_SPACE
  9. In.tangent = SurfacesVertexModifyLocalTangent(In);
  10. #endif
  11. SurfacesVertexModifyLocalSharedData(In);
  12. CCSurfacesVertexWorldTransform(In);
  13. In.worldPos = SurfacesVertexModifyWorldPos(In);
  14. In.clipPos = cc_matProj * cc_matView * vec4(In.worldPos, 1.0);
  15. In.clipPos = SurfacesVertexModifyClipPos(In);
  16. vec3 viewDirect = normalize(cc_cameraPos.xyz - In.worldPos);
  17. In.worldNormal.w = dot(In.worldNormal.xyz, viewDirect) < 0.0 ? -1.0 : 1.0;
  18. In.worldNormal.xyz = SurfacesVertexModifyWorldNormal(In);
  19. SurfacesVertexModifyUV(In);
  20. SurfacesVertexModifySharedData(In);
  21. CCSurfacesVertexTransformUV(In);
  22. CCSurfacesVertexTransferFog(In);
  23. CCSurfacesVertexTransferShadow(In);
  24. CCSurfacesVertexTransferLightMapUV(In);
  25. CCSurfacesVertexOutput(In);
  26. }

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.

  1. CCProgram standard-fs %{
  2. #include <shading-entries/main-functions/render-to-scene/fs>
  3. }%

the render-to-scene/fs.chunk contains the following content:

  1. #if (CC_PIPELINE_TYPE == CC_PIPELINE_TYPE_FORWARD || CC_FORCE_FORWARD_SHADING)
  2. #include <shading-entries/main-functions/render-to-scene/pipeline/forward-fs>
  3. #elif CC_PIPELINE_TYPE == CC_PIPELINE_TYPE_DEFERRED
  4. #include <shading-entries/main-functions/render-to-scene/pipeline/deferred-fs>
  5. #endif

As we can see, it distinguishes between the forward and deferred pipelines.

forward-fs

  1. //Define the color output target
  2. layout(location = 0) out vec4 fragColorX;
  3. void main() {
  4. #if CC_DISABLE_STRUCTURE_IN_FRAGMENT_SHADER
  5. //Get the base color and transparency, can be replaced by macros
  6. vec4 color = SurfacesFragmentModifyBaseColorAndTransparency();
  7. #else
  8. //Get surface material data
  9. SurfacesMaterialData surfaceData;
  10. CCSurfacesFragmentGetMaterialData(surfaceData);
  11. //Compute shadow parameters
  12. vec2 shadowBias = vec2(0.0);
  13. ...
  14. //Compute fog parameters
  15. #if !CC_FORWARD_ADD
  16. float fogFactor = 1.0;
  17. #endif
  18. //Compute lighting
  19. LightingResult lightingResult;
  20. CCSurfacesLighting(lightingResult, surfaceData, shadowBias);
  21. //Rendering debugging related code
  22. ...
  23. //Pixel shading calculation
  24. vec4 color = CCSurfacesShading(surfaceData, lightingResult);
  25. ...
  26. //Color output
  27. #if CC_USE_RGBE_OUTPUT
  28. fragColorX = packRGBE(color.rgb); // for reflection-map
  29. return;
  30. #endif
  31. //HDR,LinearToSRGB, and other final computations
  32. #if CC_USE_HDR
  33. #if CC_USE_DEBUG_VIEW == CC_SURFACES_DEBUG_VIEW_COMPOSITE_AND_MISC && CC_SURFACES_ENABLE_DEBUG_VIEW
  34. if (IS_DEBUG_VIEW_COMPOSITE_ENABLE_TONE_MAPPING)
  35. #endif
  36. color.rgb = ACESToneMap(color.rgb);
  37. #endif
  38. #if CC_USE_DEBUG_VIEW == CC_SURFACES_DEBUG_VIEW_COMPOSITE_AND_MISC
  39. if (IS_DEBUG_VIEW_COMPOSITE_ENABLE_GAMMA_CORRECTION)
  40. #endif
  41. color.rgb = LinearToSRGB(color.rgb);
  42. #if !CC_FORWARD_ADD && CC_USE_FOG != CC_FOG_NONE
  43. CC_APPLY_FOG_BASE(color, fogFactor);
  44. #endif
  45. fragColorX = CCSurfacesDebugDisplayInvalidNumber(color);
  46. }

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.

  1. //GBuffer 0,1,2
  2. layout(location = 0) out vec4 fragColor0;
  3. layout(location = 1) out vec4 fragColor1;
  4. layout(location = 2) out vec4 fragColor2;
  5. void main () {
  6. //Collect surface material data
  7. SurfacesMaterialData surfaceData;
  8. CCSurfacesFragmentGetMaterialData(surfaceData);
  9. //Fill the GBuffer
  10. fragColor0 = CCSurfacesDeferredOutput0(surfaceData);
  11. fragColor1 = CCSurfacesDeferredOutput1(surfaceData);
  12. fragColor2 = CCSurfacesDeferredOutput2(surfaceData);
  13. //Debug rendering related code
  14. #if CC_USE_DEBUG_VIEW == CC_SURFACES_DEBUG_VIEW_SINGLE && CC_SURFACES_ENABLE_DEBUG_VIEW
  15. vec4 debugColor = vec4(0.0, 0.0, 0.0, 1.0);
  16. CCSurfacesDebugViewMeshData(debugColor);
  17. CCSurfacesDebugViewSurfaceData(debugColor, surfaceData);
  18. if (IS_DEBUG_VIEW_ENABLE_WITH_CAMERA) {
  19. fragColor0 = debugColor;
  20. }
  21. #endif
  22. }

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.