|
|
@@ -0,0 +1,462 @@
|
|
|
+# Surface Shaders
|
|
|
+
|
|
|
+Surface shaders are custom shaders that can be applied to materials and decals in R3D. They provide a simplified way to write GLSL code by abstracting away the complexity of multiple render passes.
|
|
|
+
|
|
|
+## Table of Contents
|
|
|
+
|
|
|
+- [Overview](#overview)
|
|
|
+- [Entry Points](#entry-points)
|
|
|
+- [Built-in Variables](#built-in-variables)
|
|
|
+- [Varyings](#varyings)
|
|
|
+- [Uniforms](#uniforms)
|
|
|
+- [Material Sampling](#material-sampling)
|
|
|
+- [Usage Hints](#usage-hints)
|
|
|
+- [Best Practices](#best-practices)
|
|
|
+- [Quick Reference](#quick-reference)
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## Overview
|
|
|
+
|
|
|
+A surface shader is a simplified shader interface that allows you to modify vertex and fragment behavior without worrying about the underlying render pipeline. R3D automatically handles multiple render passes (opaque, transparent, shadows, etc.) from a single shader definition.
|
|
|
+
|
|
|
+### Basic Example
|
|
|
+
|
|
|
+```glsl
|
|
|
+uniform float u_time;
|
|
|
+
|
|
|
+void fragment() {
|
|
|
+ ALBEDO *= 0.5 + 0.5 * sin(u_time);
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+### Loading a Shader
|
|
|
+
|
|
|
+```c
|
|
|
+// From file
|
|
|
+R3D_SurfaceShader* shader = R3D_LoadSurfaceShader("my_shader.glsl");
|
|
|
+
|
|
|
+// From memory
|
|
|
+const char* code = "void fragment() { ALBEDO = vec3(1.0, 0.0, 0.0); }";
|
|
|
+R3D_SurfaceShader* shader = R3D_LoadSurfaceShaderFromMemory(code);
|
|
|
+
|
|
|
+// Don't forget to unload when done
|
|
|
+R3D_UnloadSurfaceShader(shader);
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## Entry Points
|
|
|
+
|
|
|
+Surface shaders have two optional entry points: `vertex()` and `fragment()`. At least one must be defined.
|
|
|
+
|
|
|
+### Vertex Stage
|
|
|
+
|
|
|
+Runs once per vertex, before rasterization. Use it to modify vertex positions, colors, or pass data to the fragment stage.
|
|
|
+
|
|
|
+```glsl
|
|
|
+void vertex() {
|
|
|
+ POSITION.y += sin(POSITION.x * 10.0) * 0.1;
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+### Fragment Stage
|
|
|
+
|
|
|
+Runs once per pixel. Use it to modify final surface properties like albedo, roughness, or emission.
|
|
|
+
|
|
|
+```glsl
|
|
|
+void fragment() {
|
|
|
+ ALBEDO = vec3(1.0, 0.0, 0.0); // Red surface
|
|
|
+ ROUGHNESS = 0.5;
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+### Both Stages
|
|
|
+
|
|
|
+You can define both stages to create complex effects:
|
|
|
+
|
|
|
+```glsl
|
|
|
+varying float v_height;
|
|
|
+
|
|
|
+void vertex() {
|
|
|
+ v_height = POSITION.y;
|
|
|
+}
|
|
|
+
|
|
|
+void fragment() {
|
|
|
+ ALBEDO = mix(vec3(0.0, 0.5, 0.0), vec3(1.0, 1.0, 1.0), v_height);
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## Built-in Variables
|
|
|
+
|
|
|
+Built-in variables are pre-defined values you can read and modify in your shader. They can only be accessed within their corresponding entry point.
|
|
|
+
|
|
|
+### Vertex Stage
|
|
|
+
|
|
|
+All vertex-stage variables are initialized with local (pre-transformation) attribute values:
|
|
|
+
|
|
|
+| Variable | Type | Description |
|
|
|
+|----------|------|-------------|
|
|
|
+| `POSITION` | `vec3` | Vertex position |
|
|
|
+| `TEXCOORD` | `vec2` | Texture coordinates |
|
|
|
+| `NORMAL` | `vec3` | Vertex normal |
|
|
|
+| `TANGENT` | `vec4` | Vertex tangent (w = handedness) |
|
|
|
+| `COLOR` | `vec4` | Vertex color |
|
|
|
+| `EMISSION` | `vec3` | Vertex emission |
|
|
|
+
|
|
|
+**Example:**
|
|
|
+```glsl
|
|
|
+void vertex() {
|
|
|
+ POSITION *= 1.5; // Scale vertex position
|
|
|
+ COLOR.rgb *= 0.5; // Darken vertex color
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+### Fragment Stage
|
|
|
+
|
|
|
+Fragment-stage variables are pre-initialized with material values (unless `R3D_NO_AUTO_FETCH` is defined):
|
|
|
+
|
|
|
+| Variable | Type | Description |
|
|
|
+|----------|------|-------------|
|
|
|
+| `TEXCOORD` | `vec2` | Interpolated texture coordinates |
|
|
|
+| `NORMAL` | `vec3` | Surface normal (world space) |
|
|
|
+| `TANGENT` | `vec3` | Surface tangent |
|
|
|
+| `BITANGENT` | `vec3` | Surface bitangent |
|
|
|
+| `ALBEDO` | `vec3` | Base color |
|
|
|
+| `ALPHA` | `float` | Transparency |
|
|
|
+| `EMISSION` | `vec3` | Emissive color |
|
|
|
+| `NORMAL_MAP` | `vec3` | Normal map value |
|
|
|
+| `OCCLUSION` | `float` | Ambient occlusion |
|
|
|
+| `ROUGHNESS` | `float` | Surface roughness (0 = smooth, 1 = rough) |
|
|
|
+| `METALNESS` | `float` | Metallic property (0 = dielectric, 1 = metal) |
|
|
|
+
|
|
|
+**Example:**
|
|
|
+```glsl
|
|
|
+void fragment() {
|
|
|
+ ALBEDO = vec3(1.0, 0.0, 0.0); // Red surface
|
|
|
+ ROUGHNESS = 0.2; // Shiny
|
|
|
+ METALNESS = 1.0; // Metallic
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+### Important Notes
|
|
|
+
|
|
|
+- Built-in variables are **scoped to their entry point**. You cannot access `ALBEDO` in `vertex()` or `POSITION` in `fragment()`.
|
|
|
+- If you need to pass data between stages, use [varyings](#varyings).
|
|
|
+- To use built-in variables in helper functions, pass them as parameters:
|
|
|
+
|
|
|
+```glsl
|
|
|
+vec3 darken(vec3 color, float amount) {
|
|
|
+ return color * amount;
|
|
|
+}
|
|
|
+
|
|
|
+void fragment() {
|
|
|
+ ALBEDO = darken(ALBEDO, 0.5);
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## Varyings
|
|
|
+
|
|
|
+Varyings allow you to pass data from the vertex stage to the fragment stage. They are automatically interpolated across the triangle.
|
|
|
+
|
|
|
+### Basic Usage
|
|
|
+
|
|
|
+```glsl
|
|
|
+varying float v_height;
|
|
|
+
|
|
|
+void vertex() {
|
|
|
+ v_height = POSITION.y;
|
|
|
+}
|
|
|
+
|
|
|
+void fragment() {
|
|
|
+ ALBEDO = mix(vec3(0.2, 0.8, 0.2), vec3(1.0), v_height);
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+### Interpolation Qualifiers
|
|
|
+
|
|
|
+You can control how varyings are interpolated using qualifiers:
|
|
|
+
|
|
|
+| Qualifier | Description |
|
|
|
+|-----------|-------------|
|
|
|
+| `flat` | No interpolation (use value from provoking vertex) |
|
|
|
+| `smooth` | Perspective-correct interpolation (default) |
|
|
|
+| `noperspective` | Linear interpolation in screen space |
|
|
|
+
|
|
|
+**Example:**
|
|
|
+```glsl
|
|
|
+flat varying int v_material_id;
|
|
|
+noperspective varying vec2 v_screen_uv;
|
|
|
+
|
|
|
+void vertex() {
|
|
|
+ v_material_id = 1;
|
|
|
+ v_screen_uv = TEXCOORD;
|
|
|
+}
|
|
|
+
|
|
|
+void fragment() {
|
|
|
+ if (v_material_id == 1) {
|
|
|
+ ALBEDO = texture(u_texture, v_screen_uv).rgb;
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+### Limits
|
|
|
+
|
|
|
+- **Maximum varyings:** 32 (hardware permitting)
|
|
|
+- In practice, you'll rarely need more than a handful
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## Uniforms
|
|
|
+
|
|
|
+Uniforms are constant values that can be set from your C code. They remain constant across all vertices/fragments in a draw call.
|
|
|
+
|
|
|
+### Supported Types
|
|
|
+
|
|
|
+**Values:**
|
|
|
+- Scalars: `bool`, `int`, `float`
|
|
|
+- Vectors: `vec2`, `vec3`, `vec4`
|
|
|
+- Matrices: `mat2`, `mat3`, `mat4`
|
|
|
+
|
|
|
+**Samplers:**
|
|
|
+- `sampler1D`, `sampler2D`, `sampler3D`, `samplerCube`
|
|
|
+
|
|
|
+### Declaring Uniforms
|
|
|
+
|
|
|
+```glsl
|
|
|
+uniform float u_time;
|
|
|
+uniform vec3 u_color;
|
|
|
+uniform mat4 u_transform;
|
|
|
+uniform sampler2D u_texture;
|
|
|
+```
|
|
|
+
|
|
|
+### Setting Uniforms from C
|
|
|
+
|
|
|
+**Values:**
|
|
|
+```c
|
|
|
+float time = GetTime();
|
|
|
+R3D_SetSurfaceShaderUniform(shader, "u_time", &time);
|
|
|
+
|
|
|
+Vector3 color = {1.0f, 0.0f, 0.0f};
|
|
|
+R3D_SetSurfaceShaderUniform(shader, "u_color", &color);
|
|
|
+```
|
|
|
+
|
|
|
+**Samplers:**
|
|
|
+```c
|
|
|
+Texture2D texture = LoadTexture("texture.png");
|
|
|
+R3D_SetSurfaceShaderSampler(shader, "u_texture", texture);
|
|
|
+```
|
|
|
+
|
|
|
+### Important Notes
|
|
|
+
|
|
|
+- **Default values:** All uniforms default to zero (samplers have no default texture)
|
|
|
+- **Persistence:** Uniform values persist across frames until changed
|
|
|
+- **Per-shader state:** Each shader maintains its own uniform state
|
|
|
+- **Update timing:** Uniforms are uploaded to GPU only when needed (marked dirty)
|
|
|
+- **No per-draw updates:** You cannot change uniforms between draw calls within a single frame. Since rendering happens at `R3D_End()`, only the last uniform value set before `R3D_End()` will be used.
|
|
|
+
|
|
|
+### Example
|
|
|
+
|
|
|
+```glsl
|
|
|
+uniform float u_time;
|
|
|
+uniform sampler2D u_noise;
|
|
|
+
|
|
|
+void fragment() {
|
|
|
+ vec2 uv = TEXCOORD + texture(u_noise, TEXCOORD * 2.0).xy * 0.1;
|
|
|
+ ALBEDO *= 0.5 + 0.5 * sin(u_time + uv.x * 10.0);
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+```c
|
|
|
+float time = 0.0f;
|
|
|
+Texture2D noise = LoadTexture("noise.png");
|
|
|
+
|
|
|
+R3D_SetSurfaceShaderSampler(shader, "u_noise", noise);
|
|
|
+
|
|
|
+while (!WindowShouldClose()) {
|
|
|
+ time += GetFrameTime();
|
|
|
+ R3D_SetSurfaceShaderUniform(shader, "u_time", &time);
|
|
|
+
|
|
|
+ R3D_Begin();
|
|
|
+ // ... draw with shader ...
|
|
|
+ R3D_End();
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## Material Sampling
|
|
|
+
|
|
|
+By default, material textures (albedo, normal, ORM) are automatically sampled and available as built-in variables in the fragment stage.
|
|
|
+
|
|
|
+### Disabling Auto-Fetch
|
|
|
+
|
|
|
+If you want to start with zero values and sample materials manually, define:
|
|
|
+
|
|
|
+```glsl
|
|
|
+#define R3D_NO_AUTO_FETCH
|
|
|
+```
|
|
|
+
|
|
|
+This sets `ALBEDO`, `NORMAL_MAP`, and `OCCLUSION`/`ROUGHNESS`/`METALNESS` to zero.
|
|
|
+
|
|
|
+### Manual Sampling Functions
|
|
|
+
|
|
|
+```glsl
|
|
|
+vec4 SampleAlbedo(vec2 texCoord);
|
|
|
+vec3 SampleEmission(vec2 texCoord);
|
|
|
+vec3 SampleNormal(vec2 texCoord);
|
|
|
+vec3 SampleOrm(vec2 texCoord); // (Occlusion, Roughness, Metalness)
|
|
|
+```
|
|
|
+
|
|
|
+### Quick Auto-Fill
|
|
|
+
|
|
|
+To automatically fill all built-in variables with material values:
|
|
|
+
|
|
|
+```glsl
|
|
|
+void FetchMaterial(vec2 texCoord);
|
|
|
+```
|
|
|
+
|
|
|
+### Example
|
|
|
+
|
|
|
+```glsl
|
|
|
+#define R3D_NO_AUTO_FETCH
|
|
|
+
|
|
|
+void fragment() {
|
|
|
+ // Sample material at distorted UV
|
|
|
+ vec2 distorted_uv = TEXCOORD + vec2(sin(TEXCOORD.y * 10.0) * 0.1, 0.0);
|
|
|
+ FetchMaterial(distorted_uv);
|
|
|
+
|
|
|
+ // Or sample individual textures
|
|
|
+ // ALBEDO = SampleAlbedo(distorted_uv).rgb;
|
|
|
+ // vec3 orm = SampleOrm(distorted_uv);
|
|
|
+ // ROUGHNESS = orm.g;
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## Usage Hints
|
|
|
+
|
|
|
+R3D compiles multiple shader variants for different render passes (opaque, transparent, shadows, etc.). By default, only the opaque variant is pre-compiled; others compile on-demand when needed.
|
|
|
+
|
|
|
+### The Problem
|
|
|
+
|
|
|
+On-demand compilation can cause stuttering when a new variant is first used. For example, if your shader is used on a transparent object, the transparent variant compiles when the object first becomes visible.
|
|
|
+
|
|
|
+### The Solution
|
|
|
+
|
|
|
+Use `#pragma usage` to specify which variants should be pre-compiled:
|
|
|
+
|
|
|
+```glsl
|
|
|
+#pragma usage transparent shadow
|
|
|
+```
|
|
|
+
|
|
|
+### Available Usage Hints
|
|
|
+
|
|
|
+| Hint | Description |
|
|
|
+|------|-------------|
|
|
|
+| `opaque` | Opaque rendering (default if no pragma specified) |
|
|
|
+| `prepass` | Transparent pre-pass rendering |
|
|
|
+| `transparent` | Transparent rendering (alpha blending) |
|
|
|
+| `shadow` | Shadow map rendering |
|
|
|
+| `decal` | Decal rendering |
|
|
|
+| `probe` | Reflection probe rendering |
|
|
|
+
|
|
|
+### Examples
|
|
|
+
|
|
|
+**Opaque object with shadows:**
|
|
|
+```glsl
|
|
|
+#pragma usage opaque shadow
|
|
|
+
|
|
|
+void fragment() {
|
|
|
+ ALBEDO = vec3(1.0, 0.0, 0.0);
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+**Transparent object:**
|
|
|
+```glsl
|
|
|
+#pragma usage transparent
|
|
|
+
|
|
|
+void fragment() {
|
|
|
+ ALBEDO = vec3(0.0, 0.5, 1.0);
|
|
|
+ ALPHA = 0.5;
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+**Decal shader:**
|
|
|
+```glsl
|
|
|
+#pragma usage decal
|
|
|
+
|
|
|
+void fragment() {
|
|
|
+ ALBEDO = vec3(1.0, 1.0, 0.0);
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+### Important Notes
|
|
|
+
|
|
|
+- Usage hints are **optional**; missing variants will still compile on-demand
|
|
|
+- Multiple hints can be specified: `#pragma usage opaque transparent shadow`
|
|
|
+- If no pragma is specified, only `opaque` is pre-compiled
|
|
|
+- Variants not in the pragma can still be used; they just compile lazily
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## Best Practices
|
|
|
+
|
|
|
+### Performance
|
|
|
+
|
|
|
+1. **Minimize varyings:** Only pass what's needed
|
|
|
+2. **Leverage vertex math:** Compute values per-vertex when they can be interpolated
|
|
|
+3. **Avoid dynamic loops & heavy branches:** Keep loops bounded and branches predictable/simple
|
|
|
+4. **Reuse calculations:** Don't repeat work in shader
|
|
|
+
|
|
|
+### Debugging
|
|
|
+
|
|
|
+1. **Test incrementally:** Start simple, add complexity gradually
|
|
|
+2. **Use constants first:** Replace textures or calculations with constants to confirm logic
|
|
|
+3. **Isolate effects:** Disable parts of the shader to identify issues
|
|
|
+4. **Use emissive for debugging:** `EMISSION = vec3(some_value)` to visualize values
|
|
|
+
|
|
|
+### Organization
|
|
|
+
|
|
|
+1. **One shader per effect:** Don't create mega-shaders with branches for different effects
|
|
|
+2. **Use meaningful names:** You can prefix uniforms with `u_`, varyings with `v_`
|
|
|
+3. **Use usage hints:** Pre-compile variants you know you'll need
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## Quick Reference
|
|
|
+
|
|
|
+### Loading/Unloading
|
|
|
+```c
|
|
|
+R3D_SurfaceShader* R3D_LoadSurfaceShader(const char* filePath);
|
|
|
+R3D_SurfaceShader* R3D_LoadSurfaceShaderFromMemory(const char* code);
|
|
|
+void R3D_UnloadSurfaceShader(R3D_SurfaceShader* shader);
|
|
|
+```
|
|
|
+
|
|
|
+### Setting Uniforms
|
|
|
+```c
|
|
|
+void R3D_SetSurfaceShaderUniform(R3D_SurfaceShader* shader, const char* name, const void* value);
|
|
|
+void R3D_SetSurfaceShaderSampler(R3D_SurfaceShader* shader, const char* name, Texture texture);
|
|
|
+```
|
|
|
+
|
|
|
+### Shader Structure
|
|
|
+```glsl
|
|
|
+#pragma usage <hints> // Optional: opaque, transparent, shadow, etc.
|
|
|
+#define R3D_NO_AUTO_FETCH // Optional: disable automatic material sampling
|
|
|
+
|
|
|
+uniform <type> <name>; // Uniforms
|
|
|
+varying <type> <name>; // Varyings (communication between stages)
|
|
|
+
|
|
|
+void vertex() { // Optional: vertex stage
|
|
|
+ // Modify POSITION, NORMAL, etc.
|
|
|
+}
|
|
|
+
|
|
|
+void fragment() { // Optional: fragment stage
|
|
|
+ // Modify ALBEDO, ROUGHNESS, etc.
|
|
|
+}
|
|
|
+```
|