This version is currently experimental and can only be used in the editor
. If you want to use it in Pro Code
, you
need to import the @galacean/engine-shader-shaderlab
package. Please note that the API may change in the next
version, and we will notify you in time.
Similar to functions, classes, and properties in Typescript, Shader code also has its own set of APIs. This article can help you write your own Shader based on these APIs and ShaderLab
syntax.
Let's start with the Unlit template
to briefly introduce our Shader API. First, create an Unlit Shader as shown in the figure below:
The default Unlit template already has built-in skinning calculations and Shadow Pass, so you can see that skeletal animation and shadows are rendered correctly:
The key code is as follows. By calling UsePass "pbr/Default/ShadowCaster"
, the object can be rendered to the Shadowmap; by using the getSkinMatrix
API, you can get the bone matrix to animate the object.
UsePass "pbr/Default/ShadowCaster"
Pass "Example" {
#include "Skin.glsl"
Varyings vert(Attributes attr) {
Varyings v;
vec4 position = vec4(attr.POSITION, 1.0);
// Skin
#ifdef RENDERER_HAS_SKIN
mat4 skinMatrix = getSkinMatrix(attr);
position = skinMatrix * position;
#endif
gl_Position = renderer_MVPMat * position;
v.uv = attr.TEXCOORD_0;
return v;
}
}
The Unlit Shader is not affected by lighting by default. We can call the API provided by Light.glsl
to make the Shader's output affected by lighting:
#include "Light.glsl"
// Demo 演示,我们只简单计算第 1 盏方向光。
DirectLight light = getDirectLight(0);
// 衰减系数,光线越垂直照射的地方越亮
float dotNL = saturate( dot( v.normalWS, -light.direction ) );
baseColor.rgb *= dotNL * light.color;
Of course, in addition, you can also perform vertex color calculations, normal map calculations, ambient light calculations, and other operations, but we do not recommend doing these operations based on the Unlit template
. The PBR template
already has these calculations built-in and provides a more comprehensive lighting model, such as anisotropy, Clear Coat, etc., and provides function overload macros for quick expansion.
We recreate a PBR Shader template
and bind it to the material ball just now. You can see that the material panel already has built-in basic properties, metallic roughness, anisotropy, normal, emission, shadow occlusion, Clear Coat, and other configurations, and can be affected by direct light and ambient light:
Next, we refer to the thin film interference
algorithm to see how to overload the implementation of the lighting model:
DemoPass.glsl
and import it into the main Shader file just now:// PBRShader.gs
SubShader "Default" {
Pass "Forward Pass" {
VertexShader = PBRVertex;
FragmentShader = PBRFragment;
// #include "ForwardPassPBR.glsl"
#include "./DemoPass.glsl"
}
DemoPass.glsl
. As a demo, we only demonstrate modifying the direct light part:// DemoPass.glsl
#include "Common.glsl"
#include "Fog.glsl"
#include "AttributesPBR.glsl"
#include "VaryingsPBR.glsl"
// #include "LightDirectPBR.glsl"
#include "DemoLight.glsl"
#include "LightIndirectPBR.glsl"
#include "VertexPBR.glsl"
#include "FragmentPBR.glsl"
FUNCTION_SPECULAR_LOBE
macro to overload the direct light specular reflection lighting model
. The algorithm part here is copied from thin-film interference, so you don't need to worry too much about it. After overloading, LightDirectPBR.glsl
can recognize this function and replace the lighting model implementation. Key functions for both direct and indirect light provide corresponding overload macros, which will be introduced in detail in the API documentation below:// DemoLight.glsl
#define FUNCTION_SPECULAR_LOBE specularLobe_iridescence
#include "BRDF.glsl"
#include "./IridescenceFunction.glsl"
void specularLobe_iridescence(Varyings varyings, SurfaceData surfaceData, BRDFData brdfData, vec3 incidentDirection, vec3 attenuationIrradiance, inout vec3 specularColor){
vec3 thin = DirectBDRFIridescence(surfaceData, incidentDirection, brdfData);
vec3 BRDF_Specular = BRDF_Specular_GGX( incidentDirection, surfaceData, surfaceData.normal, brdfData.specularColor, brdfData.roughness);
vec3 factor =mix(BRDF_Specular,thin,material_Iridescence);
specularColor += attenuationIrradiance * factor;
}
#include "LightDirectPBR.glsl"
The API call method is as follows:
#include "Common.glsl"
float f2 = pow2(0.5);
Provides common macros like PI
, and general methods like gammaToLinear
, pow2
, etc. See source code for details.
Provides depth fogging methods:
vec4 fog(vec4 color, vec3 positionVS);
Provides system variables for model space, view space, world space, camera coordinates, etc. system variables:
mat4 renderer_LocalMat;
mat4 renderer_ModelMat;
mat4 camera_ViewMat;
mat4 camera_ProjMat;
mat4 renderer_MVMat;
mat4 renderer_MVPMat;
mat4 renderer_NormalMat;
vec3 camera_Position;
vec3 camera_Forward;
vec4 camera_ProjectionParams;
Provides methods to get engine lighting, including direct light and indirect light:
// 直接光
DirectLight getDirectLight(int index);
PointLight getPointLight(int index);
SpotLight getSpotLight(int index);
// 间接光
EnvMapLight scene_EnvMapLight;
#ifdef SCENE_USE_SH
vec3 scene_EnvSH[9];
#endif
#ifdef SCENE_USE_SPECULAR_ENV
samplerCube scene_EnvSpecularSampler;
#endif
Provides some general methods for normal calculation:
// 在切线空间进行法线贴图运算后的法线
vec3 getNormalByNormalTexture(mat3 tbn, sampler2D normalTexture, float normalIntensity, vec2 uv, bool isFrontFacing);
// 利用导数计算切线,针对本身没有切线的模型
mat3 getTBNByDerivatives(vec2 uv, vec3 normal, vec3 position, bool isFrontFacing);
Provides shadow-related functions. See source code for details.
// 获取级联阴影所属层级,比如级联数量设为4,则返回 0~3
int computeCascadeIndex(vec3 positionWS);
// 获取 shadowmap 中的坐标
vec3 getShadowCoord(vec3 positionWS);
// 获取阴影强度,包含采样方式、阴影衰减
float sampleShadowMap(vec3 positionWS, vec3 shadowCoord);
Provides bone calculation methods:
mat4 getSkinMatrix(Attributes attributes);
Provides BS calculation methods:
void calculateBlendShape(Attributes attributes, inout vec4 position, inout vec3 normal, inout vec4 tangent);
In addition to the general API, PBR also encapsulates a series of APIs such as the BRDF lighting model. These are some core links of ForwardPassPBR
. When users extend other materials, such as SSS, thin-film interference, etc., they may also need these functions and can try to #include
reuse these APIs.
Encapsulates all the Attribute variables needed for PBR. See source code for details.
Encapsulates all the Varyings variables needed for PBR. See source code for details.
Encapsulates direct light calculations based on the BRDF lighting model. For more details, see [source code](https://github.com/galacean/engine/blob/main/packages/shader-shaderlab/src/shaders/shadingPBR/LightDirectPBR.glsl).
Generally, you can call it directly:
```glsl
// Evaluate direct lighting
evaluateDirectRadiance(varyings, surfaceData, brdfData, shadowAttenuation, color.rgb);
The following function overload macros are provided to override key calculations of the lighting model:
#define FUNCTION_SURFACE_SHADING surfaceShading
#define FUNCTION_DIFFUSE_LOBE diffuseLobe
#define FUNCTION_SPECULAR_LOBE specularLobe
#define FUNCTION_CLEAR_COAT_LOBE clearCoatLobe
#define FUNCTION_SHEEN_LOBE sheenLobe
void surfaceShading(Varyings varyings, SurfaceData surfaceData, BRDFData brdfData, vec3 incidentDirection, vec3 lightColor, inout vec3 totalDiffuseColor, inout vec3 totalSpecularColor);
void diffuseLobe(Varyings varyings, SurfaceData surfaceData, BRDFData brdfData, vec3 attenuationIrradiance, inout vec3 diffuseColor);
void specularLobe(Varyings varyings, SurfaceData surfaceData, BRDFData brdfData, vec3 incidentDirection, vec3 attenuationIrradiance, inout vec3 specularColor);
float clearCoatLobe(Varyings varyings, SurfaceData surfaceData, BRDFData brdfData, vec3 incidentDirection, vec3 color, inout vec3 specularColor);
void sheenLobe(Varyings varyings, SurfaceData surfaceData, BRDFData brdfData, vec3 incidentDirection, vec3 attenuationIrradiance, inout vec3 diffuseColor, inout vec3 specularColor);
Encapsulates ambient light calculations based on the BRDF lighting model. For more details, see source code.
Generally, you can call it directly:
// IBL
evaluateIBL(varyings, surfaceData, brdfData, color.rgb);
The following function overload macros are provided to override key calculations of the lighting model:
#define FUNCTION_DIFFUSE_IBL evaluateDiffuseIBL
#define FUNCTION_SPECULAR_IBL evaluateSpecularIBL
#define FUNCTION_CLEAR_COAT_IBL evaluateClearCoatIBL
#define FUNCTION_SHEEN_IBL evaluateSheenIBL
void evaluateDiffuseIBL(Varyings varyings, SurfaceData surfaceData, BRDFData brdfData, inout vec3 diffuseColor);
void evaluateSpecularIBL(Varyings varyings, SurfaceData surfaceData, BRDFData brdfData, float radianceAttenuation, inout vec3 specularColor);
float evaluateClearCoatIBL(Varyings varyings, SurfaceData surfaceData, BRDFData brdfData, inout vec3 specularColor);
void evaluateSheenIBL(Varyings varyings, SurfaceData surfaceData, BRDFData brdfData, float radianceAttenuation, inout vec3 diffuseColor, inout vec3 specularColor);
Some methods required by the PBR vertex shader, such as obtaining UV coordinates after TilingOffset, obtaining world coordinates, normals, tangents after skeletal and BS operations, etc. For more details, see source code.
Varyings varyings;
varyings.uv = getUV0(attributes);
VertexInputs vertexInputs = getVertexInputs(attributes);
// positionWS
varyings.positionWS = vertexInputs.positionWS;
// normalWS、tangentWS、bitangentWS
#ifdef RENDERER_HAS_NORMAL
varyings.normalWS = vertexInputs.normalWS;
#ifdef RENDERER_HAS_TANGENT
varyings.tangentWS = vertexInputs.tangentWS;
varyings.bitangentWS = vertexInputs.bitangentWS;
#endif
#endif
gl_Position = renderer_MVPMat * vertexInputs.positionOS;
The key file of the PBR lighting model, encapsulating general calculation functions related to BRDF, as well as the SurfaceData
structure and BRDFData
structure used for subsequent lighting model calculations. For more details, see source code.
Provides transmission and refraction related functions, see source code。
Contains a large number of variables passed from the CPU, such as metallic, roughness, maps, etc., and initializes the SurfaceData
structure through getSurfaceData
. For more details, see source code.
BRDFData brdfData;
// 初始化 SurfaceData 结构体
SurfaceData surfaceData = getSurfaceData(varyings, aoUV, gl_FrontFacing);
// 可以在这加工 SurfaceData 里面的数据
initBRDFData(surfaceData, brdfData);
In addition to the functionality and calling methods of key APIs, you can refer to the ForwardPassPBR on the official website for the organization of the entire file.