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
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
|
#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;
float emitter_luminance;
float emitter_intensity;
#endif
#if defined(_RAYMARCHED_FOG_EMITTER_TEXTURE_WARPING)
float emitter_texture_warping_octaves;
float emitter_texture_warping_strength;
float emitter_texture_warping_scale;
float emitter_texture_warping_speed;
#endif
#if defined(_RAYMARCHED_FOG_DENSITY_EXPONENT)
float density_exponent;
#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;
#if defined(_RAYMARCHED_FOG_EMITTER_TEXTURE_WARPING)
for (uint ii = 0; ii < p.emitter_texture_warping_octaves; ++ii) {
uv += p.dithering_noise.SampleLevel(bilinear_repeat_s,
uv * p.emitter_texture_warping_scale + _Time[0] * p.emitter_texture_warping_speed, 0).rgb
* p.emitter_texture_warping_strength;
}
#endif
bool in_range = uv.x < 1 && uv.y < 1 && uv.x > 0 && uv.y > 0;
[branch]
if (!in_range) {
return 0;
}
float4 c = p.emitter_texture.SampleLevel(linear_clamp_s, uv, 0);
return lerp(0, 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;
};
float3 aces_filmic(float3 x) {
float a = 2.51f;
float b = 0.03f;
float c = 2.43f;
float d = 0.59f;
float e = 0.14f;
return saturate((x*(a*x+b))/(x*(c*x+d)+e));
}
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;
noise_sample *= 0.66666666f;
#if defined(_RAYMARCHED_FOG_DENSITY_EXPONENT)
// The expected value (EV) of `noise_sample` is 0.5. If we set it to 1.0f
// then exponentiate, the EV will remain closer to 0.5f.
noise_sample += 0.5f;
noise_sample = pow(noise_sample, p.density_exponent);
noise_sample -= 0.5f;
#endif
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;
// No need for directional SH coefficients. Skipping them saves 2 3D texture reads.
float3 l00 = LightVolumeSH_L0(pp);
float3 l01r = 0;
float3 l01g = 0;
float3 l01b = 0;
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;
#if defined(_RAYMARCHED_FOG_EMITTER_TEXTURE)
// 1. emitted radiance of the pixel ------------------------------------
float3 Le = getEmitterData(p, pp) * p.emitter_luminance; // [W·sr⁻¹·m⁻²]
// 2. direction and phase term -----------------------------------------
float3 w_e = normalize(p.emitter_world_pos - pp); // to pixel centre
float phase_e = PhaseHG(dot(w_e, rd), p.g); // same HG phase
// 3. pixel's apparent solid angle (flat-quadrilateral approx) ---------
float dist2 = dot(p.emitter_world_pos - pp,
p.emitter_world_pos - pp);
float pixel_area =
4.0f * p.emitter_scale.x * p.emitter_scale.y *
p.emitter_texture_texelsize.x * p.emitter_texture_texelsize.y;
float solid_ang = pixel_area / dist2; // Δω ≈ A / r²
// 4. additive in-scattered radiance from the display ------------------
float3 L_em = Le * solid_ang * phase_e * p.emitter_intensity;
// Use baked luminance as a cheap proxy for shadowing from terrain.
float indirect_brightness = luminance(indirect);
L_in += L_em * indirect_brightness * indirect_brightness;
#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
|