Similar to functions, classes, and properties in TypeScript, shader code also has its own set of APIs and配套的 UIScript. This article will guide you through customizing your shaders using these APIs and ShaderLab.
We'll start with the Unlit template
to introduce our shader API. Follow the steps below to create a Unlit shader:
The engine will automatically generate the shader file and UIScript file for you:
By default, the Unlit template includes skinning calculations and a Shadow Pass. As shown below, skeletal animations and shadows render correctly:
Key code includes using UsePass "pbr/Default/ShadowCaster"
to enable shadow mapping and getSkinMatrix
to animate the mesh:
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;
}
}
Unlit shaders are unaffected by lighting by default. To make the output respond to lighting, use APIs from Light.glsl
:
#include "Light.glsl"
// Demo: Calculate only the first directional light
DirectLight light = getDirectLight(0);
float dotNL = saturate(dot(v.normalWS, -light.direction));
baseColor.rgb *= dotNL * light.color;
While vertex color calculations, normal maps, and ambient lighting can also be implemented, we recommend using the PBR template
instead of the Unlit template
. The PBR template already includes these features and provides a more comprehensive lighting model (e.g., anisotropy, Clear Coat) with macro-based extensions.
Create a PBR Shader template
and bind it to your material. The material panel will now include settings for base properties, metallic/roughness, anisotropy, normals, emissive, occlusion, and Clear Coat, all responsive to direct and ambient lighting:
DemoPass.glsl
file and include it in your main shader:// PBRShader.gs
SubShader "Default" {
Pass "Forward Pass" {
VertexShader = PBRVertex;
FragmentShader = PBRFragment;
// #include "ForwardPassPBR.glsl"
#include "./DemoPass.glsl"
}
}
DemoPass.glsl
(demo shows direct light changes):// 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
(example uses thin-film interference):// 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"
#include "Common.glsl"
float f2 = pow2(0.5);
Provides macros like PI
and utility functions (gammaToLinear
, pow2
). See source.
Depth fog calculation:
vec4 fog(vec4 color, vec3 positionVS);
System variables for model/view/world space:
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;
Access engine lighting data:
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
Normal calculation utilities:
vec3 getNormalByNormalTexture(mat3 tbn, sampler2D normalTexture, float normalIntensity, vec2 uv, bool isFrontFacing);
mat3 getTBNByDerivatives(vec2 uv, vec3 normal, vec3 position, bool isFrontFacing);
Shadow-related functions (source):
int computeCascadeIndex(vec3 positionWS);
vec3 getShadowCoord(vec3 positionWS);
float sampleShadowMap(vec3 positionWS, vec3 shadowCoord);
Skinning calculation:
mat4 getSkinMatrix(Attributes attributes);
Blend shape calculation:
void calculateBlendShape(Attributes attributes, inout vec4 position, inout vec3 normal, inout vec4 tangent);
All PBR attribute variables (source).
All PBR varying variables (source).
Direct lighting calculations based on BRDF (source).
Usage:
evaluateDirectRadiance(varyings, surfaceData, brdfData, shadowAttenuation, color.rgb);
Override Macros:
#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
// Function signatures...
Indirect lighting (IBL) calculations (source).
Usage:
evaluateIBL(varyings, surfaceData, brdfData, color.rgb);
Override Macros:
#define FUNCTION_DIFFUSE_IBL evaluateDiffuseIBL
#define FUNCTION_SPECULAR_IBL evaluateSpecularIBL
#define FUNCTION_CLEAR_COAT_IBL evaluateClearCoatIBL
#define FUNCTION_SHEEN_IBL evaluateSheenIBL
// Function signatures...
Vertex shader utilities for UV/TBN/skinning (source):
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;
Core PBR lighting calculations and data structures (source).
Transmission/refraction functions (source).
Handles material properties and surface data initialization (source):
BRDFData brdfData;
SurfaceData surfaceData = getSurfaceData(varyings, aoUV, gl_FrontFacing);
initBRDFData(surfaceData, brdfData);
For complete file organization examples, refer to the official ForwardPassPBR implementation.