Advanced Features and Code Reuse

This tutorial, using a simple skeletal animation demo, will teach you:

  • Using the #include directive to reuse code snippets
  • Registering engine-built-in and custom code snippets
  • Using engine-built-in variables
  • Calling engine-built-in functions
  • Debugging and development techniques

The #include Directive

The #include directive allows you to reference external code snippets, enabling code reuse:

#include "Common.glsl"      // Common functions
#include "Skin.glsl"        // Skeletal animation

Benefits:

  • Avoid code duplication
  • Modular development
  • Reuse engine-optimized code

Engine-Built-in Code Snippets

The Galacean engine provides a rich set of built-in code snippets:

These snippets are registered by default in the editor. Learn more about the API in the Shader API documentation.

Commonly Used Built-in Snippets

Common.glsl - Common Functions

#include "Common.glsl"
 
// Provides common mathematical functions and constants
float value = pow2(0.5);    // Square function
vec4 linearColor = sRGBToLinear(color); // sRGB to linear color conversion

Skin.glsl - Skeletal Animation

#include "Skin.glsl"
 
// Provides skeletal animation-related functions
mat4 skinMatrix = getSkinMatrix(attr);

Skeletal Animation Shader

Skeletal animation requires processing vertex transformations:

Vertex Data Structure

struct Attributes {
vec4 POSITION;
vec2 TEXCOORD_0;
#ifdef RENDERER_HAS_SKIN
vec4 JOINTS_0;    // Bone index
vec4 WEIGHTS_0;   // Bone weight
#endif
};
``` Skeleton Transformation Calculation
```glsl
#include "Skin.glsl"
 
Varyings vert(Attributes Attribute) {
Varyings output;
 
// Perform skeleton transformation using engine-provided functions
// The getSkinMatrix function is from Skin.glsl
mat4 skinMatrix = getSkinMatrix(attr);
 
// Apply skeleton transformation
vec4 skinnedPosition = skinMatrix * attr.POSITION;
 
// MVP transformation
gl_Position = renderer_MVPMat * skinnedPosition;
 
output.uv = attr.TEXCOORD_0;
return output;
}

Complete Skinned Animation Shader

Shader "Tutorial/05-SkinnedUnlit" {
SubShader "Default" {
Pass "Forward" {
// Engine-provided variables and matrices
#include "Transform.glsl"
 
// Material properties
vec4 material_BaseColor;
sampler2D material_BaseTexture;
 
struct Attributes {
vec4 POSITION;
vec2 TEXCOORD_0;
#ifdef RENDERER_HAS_SKIN
vec4 JOINTS_0;    // Bone indices
vec4 WEIGHTS_0;   // Bone weights
#endif
};
 
struct Varyings {
vec2 uv;
};
 
// Include engine-provided skinned animation code snippet
#include "Skin.glsl"
 
VertexShader = vert;
FragmentShader = frag;
 
Varyings vert(Attributes attr) {
Varyings output;
 
// Perform skeleton transformation using engine-provided functions
// The getSkinMatrix function is from Skin.glsl
mat4 skinMatrix = getSkinMatrix(attr);
 
// Apply skeleton transformation
vec4 skinnedPosition = skinMatrix * attr.POSITION;
 
// MVP transformation
gl_Position = renderer_MVPMat * skinnedPosition;
 
// Pass UV coordinates
output.uv = attr.TEXCOORD_0;
 
return output;
}
 
void frag(Varyings varying) {
// Simple Unlit shading
vec4 texColor = texture2D(material_BaseTexture, varying.uv);
vec4 finalColor = texColor * material_BaseColor;
 
gl_FragColor = finalColor;
}
}
}

Custom Shader Fragments

If the built-in shader fragments don't meet your needs, you can create your own:

Registering Custom Fragments

import { ShaderFactory } from '@galacean/engine';
 
const customShaderCode = `
vec3 customLighting(vec3 normal, vec3 lightDir) {
float NdotL = max(dot(normal, lightDir), 0.0);
return vec3(NdotL);
}
`;
 
ShaderFactory.registerInclude('CustomLighting', customShaderCode);

Using Custom Fragments

#include "CustomLighting"
 
void frag() {
vec3 lighting = customLighting(normal, lightDir);
gl_FragColor = vec4(lighting, 1.0);
}

Engine Built-in Variable System

The engine provides a rich set of built-in variables that can be used directly, such as transformation matrix variables:

mat4 renderer_LocalMat;    // Local transformation matrix
mat4 renderer_ModelMat;    // Model matrix
mat4 renderer_MVMat;       // Model-view matrix
mat4 renderer_MVPMat;      // MVP matrix
mat4 renderer_NormalMat;   // Normal transformation matrix

Go to Shader Built-in Variables to learn more about available variables.

Debugging and Development Tips

1. Incremental Development

// First, implement basic functionality
void frag() {
gl_FragColor = material_BaseColor;
}
 
// Then, gradually add complex features
void frag() {
vec4 color = material_BaseColor;
 
#ifdef USE_TEXTURE
color *= texture2D(material_BaseTexture, uv);
#endif
 
#ifdef USE_LIGHTING
color.rgb *= lighting;
#endif
 
gl_FragColor = color;
}

2. Using Debug Colors

// Debug UV coordinates
gl_FragColor = vec4(uv, 0.0, 1.0);
 
// Debugging normals
gl_FragColor = vec4(normal * 0.5 + 0.5, 1.0);

3. Leveraging the Advantages of Macros

During compilation, ShaderLab will remove unused code blocks. Therefore, using macros appropriately can significantly improve performance.

// Without macros: Every pixel needs to be checked
void frag() {
vec4 color = material_BaseColor;
 
if (useTexture > 0.5) {  // Runtime check, poor performance
color *= texture2D(material_BaseTexture, uv);
}
 
gl_FragColor = color;
}
 
// With macros: If the macro condition is not met, the code block is removed during compilation, resulting in better performance
void frag() {
vec4 color = material_BaseColor;
 
#ifdef USE_TEXTURE  // Determined at compile time, better performance
color *= texture2D(material_BaseTexture, uv);
#endif
 
gl_FragColor = color;
}

Running Results

This shader will:

  1. Demonstrate the declaration of skeleton-related structures
  2. Use the engine's built-in Skin.glsl fragment shader to implement skeletal animation
  3. Achieve efficient skeletal animation rendering

Go to Playground

Was this page helpful?