summaryrefslogtreecommitdiffstats
path: root/fog.cginc
blob: 62be8e0ad6345782d7d9f11c939a1a3374d9ac97 (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
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
#ifndef __FOG_INC
#define __FOG_INC

#include "audiolink.cginc"
#include "cnlohr.cginc"
#include "interpolators.cginc"
#include "globals.cginc"
#include "LightVolumes.cginc"

#if defined(_RAYMARCHED_FOG)

struct FogParams {
    float3 color;
    float direct_light_intensity;
    float indirect_light_intensity;
    float steps;
    float y_cutoff;
    texture2D dithering_noise;
    float4 dithering_noise_texelsize;
    texture3D density_noise;
    float4 density_noise_scale;
    float3 velocity;
    // Physical description of the medium (all in metres or unit-less)
    float mean_free_path;  //  ⟨s⟩  = 1 / σ_t
    float albedo;          //  ω   = σ_s / σ_t  (0 … 1)
    float g;               //  Henyey-Greenstein anisotropy (−1 … 1)
    float height_scale;    //  H   where ρ(y)=ρ₀·exp(−y/H)
    float height_offset;
    float turbulence;      //  Strength of noise modulation (0 … 1)
    float step_size;
    float step_growth;
#if defined(_RAYMARCHED_FOG_EMITTER_TEXTURE)
    texture2D emitter_texture;
    float4 emitter_texture_texelsize;
    float3 emitter_world_pos;
    float3 emitter_normal;
    float3 emitter_tangent;
    float3 emitter_normal_x_tangent;
    float2 emitter_scale;  // [tangent scale in meters, bitangent scale in meters]
    float2 emitter_scale_rcp;
#endif
};

#if defined(_RAYMARCHED_FOG_EMITTER_TEXTURE)
// Returns weighted color
float3 getEmitterData(FogParams p, float3 pp)
{
  // Using identity a_parallel_to_b = (dot(a, b) / dot(b, b)) * b
  //   float3 along_tangent = dot(p - em_loc, em_tangent) * em_tangent;
  //   float3 along_normal_x_tangent = dot(p - em_loc, em_normal_x_tangent) *
  //       em_normal_x_tangent;
  // Given that em_tangent and em_normal_x_tangent are normalized, and the fact
  // that we really want uvs, we can simplify this:
  float2 uv = float2(dot(pp - p.emitter_world_pos, p.emitter_normal_x_tangent), dot(pp - p.emitter_world_pos, p.emitter_tangent));
  uv *= p.emitter_scale_rcp;
  uv *= 0.5;
  uv += 0.5;

  bool in_range = uv.x < 1 && uv.y < 1 && uv.x > 0 && uv.y > 0;
  [branch]
  if (!in_range) {
    return p.color;
  }

  float4 c = p.emitter_texture.SampleLevel(linear_clamp_s, uv, 0);
  return lerp(p.color, c.rgb, c.a);
}
#endif

// ---------------------------------------------------------------------------
// Henyey–Greenstein phase function
static const float INV_FOUR_PI = 0.079577471545947667884f;  // 1/(4π)

inline float PhaseHG(float cosTheta, float g)
{
    float g2 = g * g;
    return INV_FOUR_PI * (1.0 - g2) / pow(1.0 + g2 - 2.0 * g * cosTheta, 1.5);
}

struct FogResult {
    float4 color;
    float depth;
};

FogResult raymarched_fog(v2f i, FogParams p)
{
  float3 ro = _WorldSpaceCameraPos;
  float3 rd = normalize(i.eyeVec.xyz);

  const float ro_epsilon = 1E-3;
  ro += rd * ro_epsilon;

  float4 clipPos = UnityObjectToClipPos(i.objPos);
  float2 screen_uv = ComputeScreenPos(clipPos) / clipPos.w;
  float zDepthFromMap = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, screen_uv);

  float linearZ =
    GetLinearZFromZDepth_WorksWithMirrors(zDepthFromMap, screen_uv);

  // Get intersection with plane at elevation y.
  float plane_y = p.y_cutoff;
  float distance_to_y = 1E5;
  if (abs(rd.y) > 1E-6) {
    float t = (plane_y - ro.y) / rd.y;
    if (t > 0) {
      distance_to_y = min(t, 1E5);
    }
  }
  linearZ = min(linearZ, distance_to_y);
  linearZ -= ro_epsilon;

  float dither = p.dithering_noise.SampleLevel(point_repeat_s,
      screen_uv * _ScreenParams.xy * p.dithering_noise_texelsize.xy, 0).r;

  const float frame = ((float) AudioLinkData(ALPASS_GENERALVU + int2(1, 0)).x);
  dither = frac(dither + PHI * frame);

  // -----------------------------------------------------------------------
  // Loop-invariant values
  float inv_mean_free_path = 1.0 / max(p.mean_free_path, 1e-4);
  float turb_lo = 1.0 - 0.5 * p.turbulence;
  float turb_hi = 1.0 + 0.5 * p.turbulence;
  float3 time_offset = _Time[0] * p.velocity;

  // Golden-ratio LCG seed
  float dither_seq = frac(dither + PHI);

  // Exponential stepping parameters
  float step_size = p.step_size;
  float step_growth = p.step_growth;
  
  float3 pp = ro;
  float max_dist = linearZ;
  
  float T = 1;    // Transmittance
  float3 L = 0;   // Accumulated radiance
  float traveled = 0;
  
  [loop]
  for (uint ii = 0; ii < p.steps && traveled < max_dist; ++ii)
  {
    // Apply dithering to this step
    float cur_dither = dither_seq;
    float dithered_step = step_size * (cur_dither + 0.5);
    float remaining = max_dist - traveled;
    remaining = max(remaining, 0.1);
    dithered_step = min(dithered_step, remaining);

    // Advance position
    pp += dithered_step * rd;
    traveled += dithered_step;

    // --- Density ----------------------------------------------------------
    float3 noise_coord = (pp + time_offset) * p.density_noise_scale.xyz;
    float noise_sample = p.density_noise.SampleLevel(bilinear_repeat_s, noise_coord, 0).r;
    float fbm_f = 2.0f;
    float fbm_a = 0.5f;
    noise_sample += p.density_noise.SampleLevel(bilinear_repeat_s, noise_coord * fbm_f, 0).r * fbm_a;
    fbm_f *= 2.0f;
    fbm_a *= 0.5f;
    noise_sample += p.density_noise.SampleLevel(bilinear_repeat_s, noise_coord * fbm_f, 0).r * fbm_a;
    noise_sample *= 0.55f;

    noise_sample *= noise_sample;

    float noise_factor = lerp(turb_lo, turb_hi, noise_sample);

    float height_factor = exp(-max(pp.y - p.height_offset, 0.0) / p.height_scale);

    float sigma_t = noise_factor * height_factor * inv_mean_free_path;
    float sigma_s = sigma_t * p.albedo;

    // Analytic integration over the segment
    float exp_term = exp(-sigma_t * dithered_step);

    // --- Incoming radiance ------------------------------------------------
    float3 L_in;
#if defined(_RAYMARCHED_FOG_EMITTER_TEXTURE)
    L_in = getEmitterData(p, pp);
#else
#if 1
    float3 l00 = LightVolumeSH_L0(pp);
    float3 l01r = 0;
    float3 l01g = 0;
    float3 l01b = 0;
#else
    float3 l00, l01r, l01g, l01b;
    LightVolumeSH(pp, l00, l01r, l01g, l01b);
#endif
    float3 indirect = LightVolumeEvaluate(float3(0, 1, 0), l00, l01r, l01g, l01b);

    // Direct from the dominant realtime light
    float3 to_light = (_WorldSpaceLightPos0.w == 0.0) ? normalize(_WorldSpaceLightPos0.xyz)
                                                     : normalize(_WorldSpaceLightPos0.xyz - pp);
    float phase = PhaseHG(dot(to_light, rd), p.g);
    float3 direct = _LightColor0.rgb * phase;

    L_in = (direct * p.direct_light_intensity +
        indirect * p.indirect_light_intensity) * p.color;
#endif

    // --- Accumulate radiance ---------------------------------------------
    float scattering_integral = (sigma_s / sigma_t) * (1.0 - exp_term);
    L += T * scattering_integral * L_in;

    // Update transmittance
    T *= exp_term;

    // Early exit if virtually opaque
    if (T < 1e-7)
      break;
      
    // Advance LCG for the next step
    dither_seq += PHI;
    if (dither_seq >= 1.0) dither_seq -= 1.0;
    
    // Grow step size exponentially
    step_size *= step_growth;
  }
  
  float4 color;
  color.rgb = L;
  color.a = 1 - T;  // Alpha for proper compositing

  FogResult r;
  r.color = color;
  //r.color.rgb = saturate(log(linearZ) / 5.0);
  //r.color.rgb = float3(screen_uv, 0);
  //r.color.a = d;
  r.depth = 0.0001;  // Very small depth value to render in front
  return r;
}

#endif  // _RAYMARCHED_FOG
#endif  // __FOG_INC