AgentSkillsCN

engine-shader

为这款引擎定制专属着色器。当用户希望添加视觉特效、自定义材质、卡通风格着色、全息效果,或实现任何个性化的渲染需求时,可使用此技能。内容包括 GLSL 着色器编写、阴影集成,以及材质的创建与优化。

SKILL.md
--- frontmatter
name: engine-shader
description: Create custom shaders for this engine. Use when the user asks to add visual effects, custom materials, toon shading, hologram effects, or any custom rendering. Covers GLSL shader writing, shadow integration, and material creation.

Creating Custom Shaders

This skill guides creation of GLSL shaders that integrate with the engine's shadow system.

Architecture Overview

The engine uses a shader injection system:

  1. You write fragment shaders with custom effects
  2. Engine automatically injects shadow uniforms and getShadow() function
  3. Use standard vertex shader or write custom one
  4. Register shader → Create material → Assign to entities

File Locations

Place shaders in your game's assets folder:

code
assets/
└── shaders/
    ├── my_effect.fs     # Fragment shader (required)
    └── my_effect.vs     # Vertex shader (optional, use standard)

Fragment Shader Template

glsl
#version 330

// Varyings from vertex shader (standard vertex shader provides these)
in vec3 fragPosition;  // World-space position
in vec2 fragTexCoord;  // Texture coordinates
in vec4 fragColor;     // Vertex color
in vec3 fragNormal;    // World-space normal

// Material uniforms (auto-set by raylib)
uniform sampler2D texture0;  // Diffuse texture
uniform vec4 colDiffuse;     // Material color

// Engine injects these automatically:
// uniform vec3 lightDir;    // Directional light direction
// uniform vec4 lightColor;  // Light color
// uniform vec4 ambient;     // Ambient color
// uniform vec3 viewPos;     // Camera position
// float getShadow(vec3 fragPos, vec3 normal); // Returns 0.0 (shadow) to 1.0 (lit)

// Time uniform (auto-updated by engine each frame)
uniform float time;

// Output
out vec4 finalColor;

void main()
{
    // Sample texture
    vec4 texColor = texture(texture0, fragTexCoord);
    vec3 baseColor = texColor.rgb * colDiffuse.rgb;

    // Basic lighting
    vec3 normal = normalize(fragNormal);
    float NdotL = max(dot(normal, -lightDir), 0.0);

    // Get shadow factor (1.0 = lit, 0.0 = shadowed)
    float shadow = getShadow(fragPosition, normal);

    // Apply lighting
    vec3 litColor = baseColor * (NdotL * shadow * lightColor.rgb + ambient.rgb);

    finalColor = vec4(litColor, texColor.a * colDiffuse.a);

    // Gamma correction
    finalColor.rgb = pow(finalColor.rgb, vec3(1.0 / 2.2));
}

Loading and Registering Shaders

In your main.go or init function:

go
eng.InitFunc = func() {
    // Load with standard vertex shader (pass "" for vsPath)
    myShader, err := eng.LoadShaderFromFiles("", "shaders/my_effect.fs")
    if err != nil {
        log.Fatal(err)
    }
    eng.ShaderRegistry().Add("my_effect", myShader)

    // Create material with shader and color
    err = eng.CreateMaterial("my_material", myShader, rl.Red)
    if err != nil {
        log.Fatal(err)
    }

    // Or create textured material
    err = eng.CreateMaterialTextured("my_textured_mat", myShader, "textures/wood.png")
    if err != nil {
        log.Fatal(err)
    }
}

Using Shaders on Entities

Via RenderMesh Component

go
// Use material ID (shader embedded in material)
scene.SetRenderMesh(entity, component.RenderMeshData{
    MeshId:     "cube",
    MaterialId: "my_material",
})

// Or override shader on any material
scene.SetRenderMesh(entity, component.RenderMeshData{
    MeshId:     "cube",
    MaterialId: "default",
    ShaderId:   "my_effect",  // Override material's shader
})

Via Entity Builder

go
entityId := scene.Spawn("MyEntity").
    At(math.NewVector3(0, 1, 0)).
    Mesh("cube", "my_material").  // Uses material's embedded shader
    Build()

Available Injected Uniforms

These are automatically available in your fragment shader after injection:

UniformTypeDescription
lightDirvec3Directional light direction (normalized)
lightColorvec4Light color (RGBA)
ambientvec4Ambient light color
viewPosvec3Camera position in world space
lightVPmat4Light's view-projection matrix (for shadows)
shadowMapsampler2DShadow depth texture
shadowMapResolutionintShadow map size (e.g., 2048)

Injected Functions

glsl
// Get shadow factor at world position
// Returns: 1.0 = fully lit, 0.0 = fully in shadow
// Uses PCF for soft shadow edges
float getShadow(vec3 fragPos, vec3 normal);

Time Uniform

The engine automatically updates a time uniform each frame:

glsl
uniform float time;  // Seconds since game start

void main() {
    // Animate based on time
    float wave = sin(time * 2.0) * 0.5 + 0.5;
    // ...
}

Custom Vertex Shader

Only needed for special effects (vertex animation, displacement). Use standard for most cases.

glsl
#version 330

// Required inputs
in vec3 vertexPosition;
in vec2 vertexTexCoord;
in vec3 vertexNormal;
in vec4 vertexColor;

// Required uniforms
uniform mat4 mvp;
uniform mat4 matModel;
uniform mat4 matNormal;

// Required outputs (for fragment shader and shadow system)
out vec3 fragPosition;
out vec2 fragTexCoord;
out vec4 fragColor;
out vec3 fragNormal;

// Custom uniforms
uniform float time;

void main()
{
    // Custom vertex manipulation
    vec3 pos = vertexPosition;
    pos.y += sin(pos.x * 3.0 + time) * 0.2;  // Wave effect

    // Transform to world space
    fragPosition = vec3(matModel * vec4(pos, 1.0));
    fragTexCoord = vertexTexCoord;
    fragColor = vertexColor;
    fragNormal = normalize(vec3(matNormal * vec4(vertexNormal, 1.0)));

    // Final position
    gl_Position = mvp * vec4(pos, 1.0);
}

Load with custom vertex shader:

go
shader, err := eng.LoadShaderFromFiles("shaders/wave.vs", "shaders/wave.fs")

Common Shader Recipes

Toon/Cel Shading

glsl
// Quantize lighting into discrete bands
float toonFactor;
if (NdotL * shadow > 0.8) {
    toonFactor = 1.0;
} else if (NdotL * shadow > 0.4) {
    toonFactor = 0.7;
} else if (NdotL * shadow > 0.2) {
    toonFactor = 0.45;
} else {
    toonFactor = 0.25;
}

vec3 litColor = baseColor * toonFactor + baseColor * ambient.rgb;

Rim/Fresnel Glow

glsl
vec3 viewDir = normalize(viewPos - fragPosition);
float fresnel = 1.0 - max(dot(normal, viewDir), 0.0);
fresnel = pow(fresnel, 2.0);

vec3 rimColor = vec3(0.0, 0.8, 1.0) * fresnel * 2.0;
finalColor.rgb += rimColor;

Hologram Scan Lines

glsl
float scanLineSpeed = 2.0;
float scanLineFreq = 50.0;
float scanLine = sin((fragPosition.y + time * scanLineSpeed) * scanLineFreq) * 0.5 + 0.5;
scanLine = pow(scanLine, 1.5);

finalColor.a *= scanLine;

Pulse/Glow Effect

glsl
float pulse = sin(time * 3.0) * 0.3 + 0.7;
vec3 glowColor = vec3(1.0, 0.5, 0.0);  // Orange
finalColor.rgb += glowColor * pulse * fresnel;

Dissolve Effect

glsl
uniform float dissolveAmount;  // 0.0 to 1.0

// Pseudo-random based on world position
float noise = fract(sin(dot(fragPosition.xz, vec2(12.9898, 78.233))) * 43758.5453);

if (noise < dissolveAmount) {
    discard;  // Remove fragment
}

// Glow at dissolve edge
if (noise < dissolveAmount + 0.05) {
    finalColor.rgb += vec3(1.0, 0.5, 0.0) * 2.0;
}

Setting Custom Uniforms

For dynamic uniforms beyond time:

go
// In a system's Update or init
shader, _ := eng.ShaderRegistry().Get("my_shader")
loc := rl.GetShaderLocation(shader, "customValue")
rl.SetShaderValue(shader, loc, []float32{1.5}, rl.ShaderUniformFloat)

// For vec3:
rl.SetShaderValue(shader, loc, []float32{1.0, 2.0, 3.0}, rl.ShaderUniformVec3)

Transparent Materials

For transparent shaders, mark the RenderMesh:

go
scene.SetRenderMesh(entity, component.RenderMeshData{
    MeshId:        "cube",
    MaterialId:    "hologram_material",
    IsTransparent: true,  // Renders in transparent pass
})

Transparent entities:

  • Sorted back-to-front by view depth
  • Don't cast shadows
  • Rendered after opaque geometry

Debugging Shaders

  1. Compile Errors: Check console output when loading shader
  2. Visual Debug: Output intermediate values as colors:
    glsl
    finalColor = vec4(normal * 0.5 + 0.5, 1.0);  // Visualize normals
    finalColor = vec4(vec3(shadow), 1.0);         // Visualize shadows
    
  3. Debug Color Shader: Use shaders/debug_color.fs as reference

Example Shaders in Project

See existing implementations:

  • Toon shader: game/assets/shaders/example_toon.fs
  • Hologram: game/assets/shaders/hologram.vs, hologram.fs
  • Debug: game/assets/shaders/debug_color.fs
  • Standard vertex: engine/core/shader.go (embedded)

Important Notes

  1. Shadow Injection: Engine auto-injects shadow code after #version line
  2. Standard Vertex Shader: Pass "" for vsPath to use built-in
  3. Gamma Correction: Apply pow(color, 1/2.2) at end for correct display
  4. Required Varyings: Fragment shader MUST declare expected varyings from vertex shader
  5. Time Uniform: Declare uniform float time; to use it (engine updates automatically)