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.
This skill guides creation of GLSL shaders that integrate with the engine's shadow system.
The engine uses a shader injection system:
getShadow() functionPlace shaders in your game's assets folder:
assets/
└── shaders/
├── my_effect.fs # Fragment shader (required)
└── my_effect.vs # Vertex shader (optional, use standard)
#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));
}
In your main.go or init function:
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)
}
}
// 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
})
entityId := scene.Spawn("MyEntity").
At(math.NewVector3(0, 1, 0)).
Mesh("cube", "my_material"). // Uses material's embedded shader
Build()
These are automatically available in your fragment shader after injection:
| Uniform | Type | Description |
|---|---|---|
lightDir | vec3 | Directional light direction (normalized) |
lightColor | vec4 | Light color (RGBA) |
ambient | vec4 | Ambient light color |
viewPos | vec3 | Camera position in world space |
lightVP | mat4 | Light's view-projection matrix (for shadows) |
shadowMap | sampler2D | Shadow depth texture |
shadowMapResolution | int | Shadow map size (e.g., 2048) |
// 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);
The engine automatically updates a time uniform each frame:
uniform float time; // Seconds since game start
void main() {
// Animate based on time
float wave = sin(time * 2.0) * 0.5 + 0.5;
// ...
}
Only needed for special effects (vertex animation, displacement). Use standard for most cases.
#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:
shader, err := eng.LoadShaderFromFiles("shaders/wave.vs", "shaders/wave.fs")
// 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;
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;
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;
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;
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;
}
For dynamic uniforms beyond time:
// 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)
For transparent shaders, mark the RenderMesh:
scene.SetRenderMesh(entity, component.RenderMeshData{
MeshId: "cube",
MaterialId: "hologram_material",
IsTransparent: true, // Renders in transparent pass
})
Transparent entities:
finalColor = vec4(normal * 0.5 + 0.5, 1.0); // Visualize normals
finalColor = vec4(vec3(shadow), 1.0); // Visualize shadows
shaders/debug_color.fs as referenceSee existing implementations:
game/assets/shaders/example_toon.fsgame/assets/shaders/hologram.vs, hologram.fsgame/assets/shaders/debug_color.fsengine/core/shader.go (embedded)#version line"" for vsPath to use built-inpow(color, 1/2.2) at end for correct displayuniform float time; to use it (engine updates automatically)PyTorch深度学习模式与最佳实践,用于构建稳健、高效且可复现的训练流程、模型架构和数据加载。