Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 14 additions & 15 deletions docs/shaders/surface_shader.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Surface shaders are custom shaders that can be applied to materials and decals i

## 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.
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, blend, shadows, etc.) from a single shader definition.

### Basic Example

Expand Down Expand Up @@ -420,31 +420,30 @@ void fragment() {

## 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.
R3D compiles multiple shader variants for different render passes (opaque, blend, 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.
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 `blend` 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
#pragma usage blend shadow
```

### Available Usage Hints

| Hint | Description |
|------|-------------|
| `opaque` | Opaque rendering for **lit objects** (default if no pragma specified) |
| `prepass` | Transparent pre-pass rendering for **lit objects** |
| `transparent` | Transparent rendering (color/alpha blending) for **lit objects** |
| `unlit` | Unlit rendering (handles both opaque and transparent **unlit objects**) |
| `shadow` | Shadow map rendering |
| `decal` | Decal rendering |
| `probe` | Reflection probe rendering |
| `opaque` | Opaque rendering for **lit objects** (default if no pragma specified). Supports alpha cutoff. |
| `blend` | Forward blended rendering (color/alpha blending) for **lit objects**. |
| `unlit` | Unlit rendering (handles opaque, cutoff, and blended **unlit objects**). |
| `shadow` | Shadow map depth rendering. |
| `decal` | Decal projection rendering into G-Buffer targets. |
| `probe` | Reflection probe capture rendering. |

### Examples

Expand All @@ -460,7 +459,7 @@ void fragment() {

**Transparent object:**
```glsl
#pragma usage transparent
#pragma usage blend

void fragment() {
ALBEDO = vec3(0.0, 0.5, 1.0);
Expand Down Expand Up @@ -491,8 +490,8 @@ void fragment() {
### Important Notes

- Usage hints are **optional**; missing variants will still compile on-demand
- Multiple hints can be specified: `#pragma usage opaque transparent shadow`
- Rendering mode separation: `opaque`, `prepass`, and `transparent` apply to **lit objects** only, while `unlit` applies to **unlit objects** regardless of opacity
- Multiple hints can be specified: `#pragma usage opaque blend shadow`
- Rendering mode separation: `opaque` and `blend` apply to **lit objects** only, while `unlit` applies to **unlit objects** regardless of opacity
- If no pragma is specified, only `opaque` is pre-compiled
- Variants not in the pragma can still be used; they just compile lazily

Expand Down Expand Up @@ -539,7 +538,7 @@ void R3D_SetSurfaceShaderSampler(R3D_SurfaceShader* shader, const char* name, Te

### Shader Structure
```glsl
#pragma usage <hints> // Optional: opaque, transparent, shadow, etc.
#pragma usage <hints> // Optional: opaque, blend, shadow, etc.
#define R3D_NO_AUTO_FETCH // Optional: disable automatic material sampling

uniform <type> <name>; // Uniforms
Expand Down
2 changes: 1 addition & 1 deletion examples/dof.c
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ int main(void)
for (int y = 0; y < Y_INSTANCES; y++)
{
positions[idx] = (Vector3) {x * spacing - offsetX, 0, y * spacing - offsetZ};
colors[idx] = (Color){rand()%256, rand()%256, rand()%256, 255};
colors[idx] = ColorFromHSV((float)(rand()%3600) * .1f, 0.6f, 1.0f);
idx++;
}
}
Expand Down
1 change: 1 addition & 0 deletions examples/particles.c
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ int main(void)

// Setup particle material
R3D_Material material = R3D_GetDefaultMaterial();
material.transparencyMode = R3D_TRANSPARENCY_BLEND;
material.billboardMode = R3D_BILLBOARD_FRONT;
material.blendMode = R3D_BLEND_ADDITIVE;
material.albedo.texture = R3D_GetBlackTexture();
Expand Down
2 changes: 1 addition & 1 deletion examples/stencil.c
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ int main(void)
matXrayGhost.depth.mode = R3D_COMPARE_ALWAYS;
matXrayGhost.stencil.mode = R3D_COMPARE_NOTEQUAL;
matXrayGhost.stencil.ref = 0x01;
matXrayGhost.transparencyMode = R3D_TRANSPARENCY_ALPHA;
matXrayGhost.transparencyMode = R3D_TRANSPARENCY_BLEND;
matXrayGhost.unlit = true;

// Main outline sphere material
Expand Down
2 changes: 1 addition & 1 deletion examples/transparency.c
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ int main(void)
// Create cube model
R3D_Mesh cube = R3D_GenMeshCube(1, 1, 1);
R3D_Material matCube = R3D_MATERIAL_BASE;
matCube.transparencyMode = R3D_TRANSPARENCY_ALPHA;
matCube.transparencyMode = R3D_TRANSPARENCY_BLEND;
matCube.albedo.color = (Color){150, 150, 255, 100};
matCube.orm.occlusion = 1.0f;
matCube.orm.roughness = 0.2f;
Expand Down
31 changes: 15 additions & 16 deletions include/r3d/r3d_material.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
}, \
.uvOffset = {0.0f, 0.0f}, \
.uvScale = {1.0f, 1.0f}, \
.alphaCutoff = 0.01f, \
.alphaCutoff = 0.5f, \
.depth = { \
.mode = R3D_COMPARE_LESS, \
.offsetFactor = 0.0f, \
Expand All @@ -70,9 +70,9 @@
.opZFail = R3D_STENCIL_KEEP, \
.opPass = R3D_STENCIL_REPLACE, \
}, \
.transparencyMode = R3D_TRANSPARENCY_DISABLED, \
.transparencyMode = R3D_TRANSPARENCY_OPAQUE, \
.billboardMode = R3D_BILLBOARD_DISABLED, \
.blendMode = R3D_BLEND_MIX, \
.blendMode = R3D_BLEND_ALPHA, \
.cullMode = R3D_CULL_BACK, \
.unlit = false, \
.priority = 0, \
Expand All @@ -84,16 +84,14 @@
// ========================================

/**
* @brief Transparency modes.
* @brief Material transparency handling modes.
*
* This enumeration defines how a material handles transparency during rendering.
* It controls whether transparency is disabled, rendered using a depth pre-pass,
* or rendered with standard alpha blending.
* Defines how material opacity and blending are processed during rendering.
*/
typedef enum R3D_TransparencyMode {
R3D_TRANSPARENCY_DISABLED, ///< No transparency, supports alpha cutoff.
R3D_TRANSPARENCY_PREPASS, ///< Supports transparency with shadows. Writes shadows for alpha > 0.1 and depth for alpha > 0.99.
R3D_TRANSPARENCY_ALPHA, ///< Standard transparency without shadows or depth writes.
R3D_TRANSPARENCY_OPAQUE, ///< Fully opaque in G-Buffer (supports hard alpha cutoff/masking). Ignores blend mode.
R3D_TRANSPARENCY_HYBRID, ///< Two-pass rendering: opaque cutoff in G-Buffer + forward blending.
R3D_TRANSPARENCY_BLEND, ///< Blended only (forward pass, no depth write / shadow casting).
} R3D_TransparencyMode;

/**
Expand All @@ -112,14 +110,15 @@ typedef enum R3D_BillboardMode {
/**
* @brief Blend modes.
*
* Defines common blending modes used in 3D rendering to combine source and destination colors.
* @note The blend mode is applied only if you are in forward rendering mode or auto-detect mode.
* Defines common blending modes to combine source and destination colors.
* @note Ignored when R3D_TRANSPARENCY_OPAQUE is selected.
*/
typedef enum R3D_BlendMode {
R3D_BLEND_MIX, ///< Default mode: the result will be opaque or alpha blended depending on the transparency mode.
R3D_BLEND_ADDITIVE, ///< Additive blending: source color is added to the destination, making bright effects.
R3D_BLEND_MULTIPLY, ///< Multiply blending: source color is multiplied with the destination, darkening the image.
R3D_BLEND_PREMULTIPLIED_ALPHA ///< Premultiplied alpha blending: source color is blended with the destination assuming the source color is already multiplied by its alpha.
R3D_BLEND_ALPHA, ///< Standard alpha blending: source color is blended using its alpha channel.
R3D_BLEND_ADDITIVE, ///< Pure additive blending: source color is added directly to destination (ignores alpha).
R3D_BLEND_ADD_ALPHA, ///< Alpha-modulated additive blending: source color scaled by alpha before adding to destination.
R3D_BLEND_MULTIPLY, ///< Multiply blending: source color is multiplied with destination, darkening the image.
R3D_BLEND_PREMULTIPLIED_ALPHA ///< Premultiplied alpha blending: assumes source color is already multiplied by its alpha.
} R3D_BlendMode;

/**
Expand Down
15 changes: 12 additions & 3 deletions shaders/include/user/scene.frag
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,15 @@ void FetchMaterial(vec2 texCoord)

#define fragment()

void SceneFragment(vec2 texCoord, mat3 tbn, float alphaCutoff)
void SceneFragment(vec2 texCoord, mat3 tbn, float alphaCutoff, float cutoffSign)
{
// CutoffSign:
// -> +1.0 : Opaque cutoff
// -> -1.0 : Inverted test for hybrid blended
// -> 0.0 : Disables alpha test

#define ALPHA_TEST(alpha) if (cutoffSign * (alpha - alphaCutoff) < 0.0) discard;

/* --- Fill input variables --- */

TEXCOORD = texCoord;
Expand All @@ -117,7 +124,7 @@ void SceneFragment(vec2 texCoord, mat3 tbn, float alphaCutoff)

#if !defined(R3D_NO_AUTO_FETCH)
vec4 color = vColor * texture(uAlbedoMap, texCoord);
if (color.a < alphaCutoff) discard;
ALPHA_TEST(color.a);
ALBEDO = color.rgb;
ALPHA = color.a;

Expand All @@ -143,5 +150,7 @@ void SceneFragment(vec2 texCoord, mat3 tbn, float alphaCutoff)
fragment();

// Alpha cutoff again after user code
if (ALPHA < alphaCutoff) discard;
ALPHA_TEST(ALPHA);

#undef ALPHA_TEST
}
2 changes: 1 addition & 1 deletion shaders/scene/decal.frag
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ void main()
if (difference < 0.0) discard;

/* Sample material maps with alpha cutoff */
SceneFragment(decalTexCoord, TBN, uAlphaCutoff);
SceneFragment(decalTexCoord, TBN, uAlphaCutoff, 1.0);

/* Compute fade factor */
float fadeAlpha = clamp(difference / uFadeWidth, 0.0, 1.0) * ALPHA;
Expand Down
2 changes: 1 addition & 1 deletion shaders/scene/depth.frag
Original file line number Diff line number Diff line change
Expand Up @@ -42,5 +42,5 @@ uniform float uAlphaCutoff;
void main()
{
// NOTE: The depth is automatically written
SceneFragment(vTexCoord, mat3(1.0), uAlphaCutoff);
SceneFragment(vTexCoord, mat3(1.0), uAlphaCutoff, 1.0);
}
2 changes: 1 addition & 1 deletion shaders/scene/depth_cube.frag
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,6 @@ uniform float uFar;

void main()
{
SceneFragment(vTexCoord, mat3(1.0), uAlphaCutoff);
SceneFragment(vTexCoord, mat3(1.0), uAlphaCutoff, 1.0);
gl_FragDepth = length(vPosition - uViewPosition) / uFar;
}
4 changes: 3 additions & 1 deletion shaders/scene/forward.frag
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ uniform samplerCubeArray uIrradianceTex;
uniform samplerCubeArray uPrefilterTex;
uniform sampler2D uBrdfLutTex;

uniform float uAlphaCutoff;
uniform float uCutoffSign;
uniform float uNormalScale;
uniform float uOcclusion;
uniform float uRoughness;
Expand Down Expand Up @@ -91,7 +93,7 @@ void main()
{
/* Sample material maps */

SceneFragment(vTexCoord, vTBN, 0.0);
SceneFragment(vTexCoord, vTBN, uAlphaCutoff, uCutoffSign);

vec3 ORM = vec3(OCCLUSION, ROUGHNESS, METALNESS);
mat3 TBN = mat3(TANGENT, BITANGENT, NORMAL);
Expand Down
2 changes: 1 addition & 1 deletion shaders/scene/geometry.frag
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ uniform float uSpecular;

void main()
{
SceneFragment(vTexCoord, vTBN, uAlphaCutoff);
SceneFragment(vTexCoord, vTBN, uAlphaCutoff, 1.0);

mat3 TBN = mat3(TANGENT, BITANGENT, NORMAL);
vec3 N = normalize(TBN * M_NormalScale(NORMAL_MAP * 2.0 - 1.0, uNormalScale));
Expand Down
3 changes: 2 additions & 1 deletion shaders/scene/unlit.frag
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ layout(location = 0) out vec4 FragColor;

uniform sampler2D uAlbedoMap;
uniform float uAlphaCutoff;
uniform float uCutoffSign;

// ================================
// User Override
Expand All @@ -50,7 +51,7 @@ uniform float uAlphaCutoff;

void main()
{
SceneFragment(vTexCoord, mat3(1.0), uAlphaCutoff);
SceneFragment(vTexCoord, mat3(1.0), uAlphaCutoff, uCutoffSign);

FragColor = vec4(ALBEDO, ALPHA);
FragColor = FogColorMix(FragColor, vLinearDepth);
Expand Down
14 changes: 7 additions & 7 deletions src/importer/r3d_importer_material.c
Original file line number Diff line number Diff line change
Expand Up @@ -178,8 +178,8 @@ static void load_param_blend_mode(R3D_Material* material, const struct aiMateria
}
if (strcmp(alphaMode.data, "BLEND") == 0)
{
material->transparencyMode = R3D_TRANSPARENCY_PREPASS;
material->blendMode = R3D_BLEND_MIX;
material->transparencyMode = R3D_TRANSPARENCY_HYBRID;
material->blendMode = R3D_BLEND_ALPHA;
return;
}
}
Expand All @@ -192,12 +192,12 @@ static void load_param_blend_mode(R3D_Material* material, const struct aiMateria
{
case aiBlendMode_Default:
// sColor*sAlpha + dColor*(1-sAlpha)
material->transparencyMode = R3D_TRANSPARENCY_PREPASS;
material->blendMode = R3D_BLEND_MIX;
material->transparencyMode = R3D_TRANSPARENCY_HYBRID;
material->blendMode = R3D_BLEND_ALPHA;
return;
case aiBlendMode_Additive:
// sColor*1 + dColor*1
material->transparencyMode = R3D_TRANSPARENCY_DISABLED;
material->transparencyMode = R3D_TRANSPARENCY_BLEND;
material->blendMode = R3D_BLEND_ADDITIVE;
return;
default:
Expand All @@ -209,8 +209,8 @@ static void load_param_blend_mode(R3D_Material* material, const struct aiMateria
// alpha == 0 is likely a degenerate material, ignore it
if (material->albedo.color.a > 0 && material->albedo.color.a < 255)
{
material->transparencyMode = R3D_TRANSPARENCY_ALPHA;
material->blendMode = R3D_BLEND_MIX;
material->transparencyMode = R3D_TRANSPARENCY_BLEND;
material->blendMode = R3D_BLEND_ALPHA;
}
}

Expand Down
25 changes: 7 additions & 18 deletions src/modules/r3d_driver.c
Original file line number Diff line number Diff line change
Expand Up @@ -483,29 +483,18 @@ void r3d_driver_set_stencil_state(R3D_StencilState state)
r3d_driver_set_stencil_op(glOpFail, glOpZFail, glOpPass);
}

void r3d_driver_set_blend_mode(R3D_BlendMode blend, R3D_TransparencyMode transparency)
void r3d_driver_set_blend_mode(R3D_BlendMode blend)
{
switch (blend)
{
case R3D_BLEND_MIX:
if (transparency == R3D_TRANSPARENCY_DISABLED)
{
r3d_driver_set_blend_func(GL_FUNC_ADD, GL_ONE, GL_ZERO);
}
else
{
r3d_driver_set_blend_func(GL_FUNC_ADD, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
}
case R3D_BLEND_ALPHA:
r3d_driver_set_blend_func(GL_FUNC_ADD, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
break;
case R3D_BLEND_ADDITIVE:
if (transparency == R3D_TRANSPARENCY_DISABLED)
{
r3d_driver_set_blend_func(GL_FUNC_ADD, GL_ONE, GL_ONE);
}
else
{
r3d_driver_set_blend_func(GL_FUNC_ADD, GL_SRC_ALPHA, GL_ONE);
}
r3d_driver_set_blend_func(GL_FUNC_ADD, GL_ONE, GL_ONE);
break;
case R3D_BLEND_ADD_ALPHA:
r3d_driver_set_blend_func(GL_FUNC_ADD, GL_SRC_ALPHA, GL_ONE);
break;
case R3D_BLEND_MULTIPLY:
r3d_driver_set_blend_func(GL_FUNC_ADD, GL_DST_COLOR, GL_ZERO);
Expand Down
3 changes: 1 addition & 2 deletions src/modules/r3d_driver.h
Original file line number Diff line number Diff line change
Expand Up @@ -127,9 +127,8 @@ void r3d_driver_set_stencil_state(R3D_StencilState state);
/*
* Applies the given blend mode.
* Assumes that GL_BLEND is already enabled.
* Some modes like MIX or ADD behave differently depending on the transparency mode.
*/
void r3d_driver_set_blend_mode(R3D_BlendMode blend, R3D_TransparencyMode transparency);
void r3d_driver_set_blend_mode(R3D_BlendMode blend);

/*
* Applies the given cull mode.
Expand Down
Loading
Loading