summaryrefslogtreecommitdiff
path: root/common/shaders/render/raymarching.wgsl
blob: 3adec8d06a425b72213c2deed3727090b5073eef (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
// Common functions for Signed Distance Field (SDF) raymarching.
//
// Required user-defined functions:
//   - df(vec3<f32>) -> f32
//       Distance field for single-pass rendering (rayMarch, normal, shadow)
//   - dfWithID(vec3<f32>) -> RayMarchResult
//       Distance field with object ID for two-pass rendering (rayMarchWithID, normalWithID, shadowWithStoredDistance)
//
// Provided constants:
//   TOLERANCE, MAX_RAY_LENGTH, MAX_RAY_MARCHES, NORM_OFF

const TOLERANCE: f32 = 0.0005;
const MAX_RAY_LENGTH: f32 = 20.0;
const MAX_RAY_MARCHES: i32 = 80;
const NORM_OFF: f32 = 0.005;

// Computes the surface normal of the distance field at a point `pos`.
fn normal(pos: vec3<f32>) -> vec3<f32> {
  let eps = vec2<f32>(NORM_OFF, 0.0);
  var nor: vec3<f32>;
  nor.x = df(pos + eps.xyy) - df(pos - eps.xyy);
  nor.y = df(pos + eps.yxy) - df(pos - eps.yxy);
  nor.z = df(pos + eps.yyx) - df(pos - eps.yyx);
  return normalize(nor);
}

// Performs the raymarching operation.
// Returns the distance along the ray to the surface, or MAX_RAY_LENGTH if no surface is hit.
fn rayMarch(ro: vec3<f32>, rd: vec3<f32>, initt: f32) -> f32 {
  var t = initt;
  for (var i = 0; i < MAX_RAY_MARCHES; i++) {
    if (t > MAX_RAY_LENGTH) {
      t = MAX_RAY_LENGTH;
      break;
    }
    let d = df(ro + rd * t);
    if (d < TOLERANCE) {
      break;
    }
    t += d;
  }
  return t;
}

// Computes a soft shadow for a given point.
fn shadow(lp: vec3<f32>, ld: vec3<f32>, mint: f32, maxt: f32) -> f32 {
  let ds = 1.0 - 0.4;
  var t = mint;
  var nd = 1e6;
  let soff = 0.05;
  let smul = 1.5;
  let MAX_SHD_MARCHES = 20;

  for (var i = 0; i < MAX_SHD_MARCHES; i++) {
    let p = lp + ld * t;
    let d = df(p);
    if (d < TOLERANCE || t >= maxt) {
      let sd = 1.0 - exp(-smul * max(t / maxt - soff, 0.0));
      return select(mix(sd, 1.0, smoothstep(0.0, 0.025, nd)), sd, t >= maxt);
    }
    nd = min(nd, d);
    t += ds * d;
  }
  let sd = 1.0 - exp(-smul * max(t / maxt - soff, 0.0));
  return sd;
}

// ============================================================================
// Two-Pass Raymarching Support
// ============================================================================
// Design note: RayMarchResult is passed/returned by value (not pointer).
// At 12 bytes (3×f32), return value optimization makes this efficient.
// See doc/CODING_STYLE.md for rationale.

struct RayMarchResult {
    distance: f32,      // Distance to surface (MAX_RAY_LENGTH if miss)
    distance_max: f32,  // Total distance marched (for fog/AO)
    object_id: f32,     // Object identifier (0.0 = background)
}

// Raymarch with object ID tracking.
fn rayMarchWithID(ro: vec3<f32>, rd: vec3<f32>, init: RayMarchResult) -> RayMarchResult {
  var t = init.distance;
  var result = init;

  for (var i = 0; i < MAX_RAY_MARCHES; i++) {
    if (t > MAX_RAY_LENGTH) {
      result.distance = MAX_RAY_LENGTH;
      result.distance_max = MAX_RAY_LENGTH;
      break;
    }
    let sample = dfWithID(ro + rd * t);
    if (sample.distance < TOLERANCE) {
      result.distance = t;
      result.distance_max = t;
      result.object_id = sample.object_id;
      break;
    }
    t += sample.distance;
  }

  return result;
}

// Reconstruct world position from stored result.
fn reconstructPosition(ray_origin: vec3<f32>, ray_dir: vec3<f32>, result: RayMarchResult) -> vec3<f32> {
  return ray_origin + ray_dir * result.distance;
}

// Normal calculation using dfWithID.
fn normalWithID(pos: vec3<f32>) -> vec3<f32> {
  let eps = vec2<f32>(NORM_OFF, 0.0);
  var nor: vec3<f32>;
  nor.x = dfWithID(pos + eps.xyy).distance - dfWithID(pos - eps.xyy).distance;
  nor.y = dfWithID(pos + eps.yxy).distance - dfWithID(pos - eps.yxy).distance;
  nor.z = dfWithID(pos + eps.yyx).distance - dfWithID(pos - eps.yyx).distance;
  return normalize(nor);
}

// Shadow using stored intersection distance.
fn shadowWithStoredDistance(lp: vec3<f32>, ld: vec3<f32>, stored_dist: f32) -> f32 {
  let ds = 1.0 - 0.4;
  var t = 0.01;
  var nd = 1e6;
  let soff = 0.05;
  let smul = 1.5;
  let MAX_SHD_MARCHES = 20;

  for (var i = 0; i < MAX_SHD_MARCHES; i++) {
    let p = lp + ld * t;
    let d = dfWithID(p).distance;
    if (d < TOLERANCE || t >= stored_dist) {
      let sd = 1.0 - exp(-smul * max(t / stored_dist - soff, 0.0));
      return select(mix(sd, 1.0, smoothstep(0.0, 0.025, nd)), sd, t >= stored_dist);
    }
    nd = min(nd, d);
    t += ds * d;
  }
  let sd = 1.0 - exp(-smul * max(t / stored_dist - soff, 0.0));
  return sd;
}