Built-in Surface Shader Guide

Starting from Cocos Creator 3.7.2, the builtin-standard.effect uses the Surface Shader architecture for implementation.

This article uses the builtin-standard.effect as a typical case to explain the details of Surface Shader.

You can learn about the structure, syntax and rendering process of Surface Shaders.

The following content is recommended to be read in combination with internal/effects/builtin-standard.effect.

Basic Structure

The Surface Shader code usually consists of several parts.

  • CCEffect: Describes the techniques, passes, render states, and vertex attributes used in the shader.
  • Shared UBOs: Defines uniforms that are needed by both vs and fs together for easy access.
  • Macro Remapping: Maps some internal macros so that they can be displayed on the material panel.
  • Surface Functions: Used to declare Surface functions related to Surface Shading.
  • Shader Assembly: Used to assemble the code modules for each vs and fs.

For more details, please visit Surface Shader Structure.

CCEffect

To render an object onto the screen, the following information is needed.

  • Model data(vertices, UV, normals, etc.)
  • Lighting data
  • Rotation, translation, scale in world space.
  • Render passes
  • Render states
  • Textures
  • Uniforms
  • Shader code

Among them, model data, lighting information, and world space information are independent of the material, while texture, uniform, rendering state, shader code, and render process are part of the material information.

CCEffect describes the above material-related information, and together with the engine rendering pipeline, completes the rendering process of a model.

technique

The built-in Surface Shader implements two rendering techniques, opaque and transparent. The former is used for rendering non-transparent objects, and the latter is used for rendering semi-transparent objects.

pass

Each technique of the built-in Surface Shader has only one pass, all of which are in PBR.

Ignoring other details, we can see the outline of a Surface Shader as follows.

  1. CCEffect %{
  2. techniques:
  3. - name: opaque
  4. passes:
  5. - vert: standard-vs
  6. frag: standard-fs
  7. properties: &props
  8. ...
  9. - name: transparent
  10. passes:
  11. - vert: standard-vs
  12. frag: standard-fs
  13. ...
  14. properties: *props
  15. }%

Shader Entry(vert and frag)

The opaque and transparent techniques are completely identical in terms of shader code, the only difference is in the render states.

You can see that they use the same vert and frag entries.

  1. - vert: standard-vs
  2. frag: standard-fs

properties

Since opaque and transparent are completely identical in terms of shader code, the properties involved are the same.

All properties used in the render process are placed in the properties section. For syntax about properties, you can check Optional Pass Parameters

Section Reuse

In the properties section, you can see that the properties of opaque is defined as properties: &props, while the properties of transparent is defined as properties: *props

This is a reuse mechanism of sections in CCEffect.

properties: &props means to name the current properties as props.

properties: *props means to use the properties named props as default value.

The result of the above configuration is: the transparent directly uses the properties of opaque.

phase

By default, Surface Shader participates in the scene rendering stage. But there are also some special stages, such as shadows, reflection probe baking, etc.

For such requirements, we can add specific passes and mark the phase to achieve the purpose.

When the Cocos Creator executes rendering, it will get the pass of the corresponding phase in the material for rendering. If there is none, it means that this object does not participate in this phase.

In Surface Shader is shown as follows.

  • forward-add: Used for the additional lighting phase, when the object is affected by lights other than the main light, this will be called.
  • shadow-caster: Used for the shadow map rendering phase.
  • reflect-map: Used for reflection probe baking
  1. - &forward-add
  2. vert: standard-vs
  3. frag: standard-fs
  4. phase: forward-add
  5. ...
  6. - &shadow-caster
  7. vert: shadow-caster-vs
  8. frag: shadow-caster-fs
  9. phase: shadow-caster
  10. ...
  11. - &reflect-map
  12. vert: standard-vs
  13. frag: reflect-map-fs
  14. phase: reflect-map
  15. ...

As shown in the code above, the phase property is used to mark the participating phase of this pass. And &forward-add, &shadow-caster, &reflect-map are names given to this pass, making it easy for subsequent techniques to reuse.

For example, the transparent directly reuses the forward-add and the shadow-caster passes from opaque.

  1. - name: transparent
  2. passes:
  3. - vert: standard-vs
  4. frag: standard-fs
  5. ...
  6. properties: *props
  7. - *forward-add
  8. - *shadow-caster

Render State

As mentioned at the beginning. To complete the rendering of a model, not only define the rendering process and the required properties but also need to be combined the render state.

Render state mainly involves stencil test, depth test, rasterizer state, transparent blending, etc.

The same rendering process, properties, and shader code combined with different render states, can achieve different effects.

  1. depthStencilState:
  2. depthFunc: equal
  3. depthTest: true
  4. depthWrite: false
  5. blendState:
  6. targets:
  7. - blend: true
  8. blendSrc: one
  9. blendDst: one
  10. blendSrcAlpha: zero
  11. blendDstAlpha: one
  12. rasterizerState:
  13. cullMode: front

Render states have a set of default values, and modifications can be made when necessary.

For example, opaque and transparent only differ in render states.

Embedded Macros

  1. embeddedMacros: { CC_FORCE_FORWARD_SHADING: true }

Sometimes, we want to enable or disable some macros for a specific pass. You can use the embeddedMacros section to do this.

includes

Surface Shader provides two mechanisms for code block references: header files and CCProgram. For details, please see include.

Shared UBO

Many constants are used by both vs and fs or are needed by multiple techniques and passes. Defining them together for easy access.

Shared UBOs are essentially part of the Shader code, written in GLSL.

  1. CCProgram shared-ubos %{
  2. uniform Constants {
  3. vec4 tilingOffset;
  4. vec4 albedo;
  5. vec4 albedoScaleAndCutoff;
  6. vec4 pbrParams;
  7. vec4 emissive;
  8. vec4 emissiveScaleParam;
  9. vec4 anisotropyParam;
  10. };
  11. }%

In the subsequent assembly process, you only need to add a single line #include <shared-ubos> to use.

Macro Remapping

For more details about macro remapping, please refer to Macro Definition and Remapping

In the built-in Surface Shader, the CCProgram macro-remapping segment is used to organize all the macro-remapping stuff, which makes it easier to manage.

As can be seen, in the built-in Surface Shader, #pragma define-meta is used to redirect many built-in macros to the panel.

  1. CCProgram macro-remapping %{
  2. // ui displayed macros
  3. #pragma define-meta HAS_SECOND_UV
  4. #pragma define-meta USE_TWOSIDE
  5. #pragma define-meta IS_ANISOTROPY
  6. #pragma define-meta USE_VERTEX_COLOR
  7. #define CC_SURFACES_USE_SECOND_UV HAS_SECOND_UV
  8. #define CC_SURFACES_USE_TWO_SIDED USE_TWOSIDE
  9. #define CC_SURFACES_LIGHTING_ANISOTROPIC IS_ANISOTROPY
  10. #define CC_SURFACES_USE_VERTEX_COLOR USE_VERTEX_COLOR
  11. // depend on UI macros
  12. #if IS_ANISOTROPY || USE_NORMAL_MAP
  13. #define CC_SURFACES_USE_TANGENT_SPACE 1
  14. #endif
  15. // functionality for each effect
  16. #define CC_SURFACES_LIGHTING_ANISOTROPIC_ENVCONVOLUTION_COUNT 31
  17. }%

Surface Functions

In Surface Shader, two CCProgram sections are defined to handle the specific shader code.

  • CCProgram surface-vertex: Used for handling vs-related calculations.
  • CCProgram surface-fragment: Used for handling fs-related calculations.

CCProgram surface-vertex

The built-in vs process can basically meet the requirements of Surface Shader, which makes surface-vertex very simple and clean.

We take the processing of the second UV as an example.

It first defines the CC_SURFACES_VERTEX_MODIFY_UV macro and then implements the SurfacesVertexModifyUV method.

  1. #define CC_SURFACES_VERTEX_MODIFY_UV
  2. void SurfacesVertexModifyUV(inout SurfacesStandardVertexIntermediate In)
  3. {
  4. In.texCoord = In.texCoord * tilingOffset.xy + tilingOffset.zw;
  5. #if CC_SURFACES_USE_SECOND_UV
  6. In.texCoord1 = In.texCoord1 * tilingOffset.xy + tilingOffset.zw;
  7. #endif
  8. }

This is the core mechanism of Surface Shader, which can rewrite internal functions through macro definitions, and meet specific rendering requirements without modifying the internal source code of the shader framework.

For more details, please refer to Function Replacement Using Macros and Surface Shader Built-in Replaceable Functions

CCProgram surface-fragment

surface-fragment mainly implements the filling of surface information needed for PBR calculation.

Macro Switch

  1. #if USE_ALBEDO_MAP
  2. uniform sampler2D albedoMap;
  3. #pragma define-meta ALBEDO_UV options([v_uv, v_uv1])
  4. #endif

We can see, in the built-in Surface Shader, all textures are wrapped by macro definitions. The advantage of this is that you can turn off the corresponding macros as needed to improve performance.

Macros selectable on the Material Panel

#pragma define-meta + name + options([item0,item1,....]) can define a macro for users to choose.

Take the following code as an example.

  1. #pragma define-meta ALBEDO_UV options([v_uv, v_uv1])

On the material panel, ALBEDO_UV will appear as a drop-down selection box. When the Shader compiles, it will be based on the user’s selected value.

For example, if the user selects v_uv1, the generated final code is as below.

  1. #define ALBEDO_UV v_uv1
  1. #if USE_ALPHA_TEST
  2. #pragma define-meta ALPHA_TEST_CHANNEL options([a, r])
  3. #endif

The same applies to ALPHA_TEST_CHANNEL. By default, the ‘a’ channel is used, but the ‘r’ channel can also be an option.

PBR Channels

  1. #pragma define OCCLUSION_CHANNEL r
  2. #pragma define ROUGHNESS_CHANNEL g
  3. #pragma define METALLIC_CHANNEL b
  4. #pragma define SPECULAR_INTENSITY_CHANNEL a

Surface Shader uses a texture as a PBR map, and according to the definition, we can know the meaning of each channel.

  • r: Ambient Occlusion
  • r: Roughness
  • b: Metallic
  • a: Specular Intensity

Implementation

Like the surface-vertex, the surface-fragment also uses the function replacement mechanism to implement PBR parameter filling.

For more details, please refer to the following pages:

Shader Assembly

The several CCPrograms mentioned above are listed as follows:

  • shared-ubos
  • macro-remapping
  • surface-vertex
  • surface-fragment

These are just some necessary components to implement the Surface Shader. To implement a complete Surface Shader, these parts need to be assembled in combination with other modules of the Surface Shader.

For the specific assembly mechanism, please refer to Surface Shader Assembly

The assembled CCProgram is the content referenced by the CCEffect

  • CCProgram standard-vs
  • CCProgram shadow-caster-vs
  • CCProgram standard-fs
  • CCProgram shadow-caster-fs
  • CCProgram reflect-map-fs