Search across packages, versions, files, declarations, and comments. Use multiple terms to narrow results (AND), and add @vertex, @fragment, or @compute to filter by shader stage.
1// Wave-surface sampling for the composed cascade material: cascade-space
2// displacement and FFT normal-cross reads, far-tier fallbacks, the Jacobian
3// SSS pinch, and the scrolled detail-normal slopes. Owned by bevy-aqua-waves.
5#define_import_path aqua::waves::displace
7#import bevy_pbr::mesh_view_bindings::globals
9#import aqua::cascade::{CREST_SSS_MAXIMUM, CREST_SSS_RANGE, MIN_NORMAL_Y, MIN_SAMPLE_WEIGHT, advected_world, cascade_layout, detail_normal, detail_sampler, fft_surface, flow_frame, lod_alpha, lod_count, lod_data, lod_sampler, screen_texture_lod, surface, world_to_uv}
11const NORMAL_DIRECTION_0: vec2<f32> = vec2(0.94, 0.34);
12const NORMAL_DIRECTION_1: vec2<f32> = vec2(-0.85, -0.53);
13const CAPILLARY_DIRECTION: vec2<f32> = vec2(0.4226, 0.9063);
14const CAPILLARY_ROTATION: mat2x2<f32> = mat2x2(
15 vec2(0.819152, 0.573576),
16 vec2(-0.573576, 0.819152),
17);
19// Mean square of the decoded WaveNormals.png XY slope. Resolved fade energy
20// moves into unresolved roughness instead of disappearing with distance.
21const WAVE_NORMALS_SLOPE_VARIANCE: f32 = 0.05602466;
22const NORMAL_SCROLL_MULTIPLIER: f32 = 1.875;
23const NORMAL_SCROLL_POWER: f32 = 1.4;
24// Deterministic mean-square slope per owned wavelength octave. FFT values are
25// phase means 2 * sum(k^2 * |h0(k)|^2) for the shipped H0 realization; Gerstner values are
26// sums of 0.5 * (amplitude * wave_number)^2 for the shipped components.
27const FFT_JONSWAP_SLOPE_VARIANCE: array<f32, 5> = array(
28 0.06393624, 0.06369724, 0.06306524, 0.06170338, 0.06048288,
29);
30const GERSTNER_SLOPE_VARIANCE: array<f32, 5> = array(
31 0.0194041, 0.027484603, 0.015417861, 0.0066486476, 0.010458008,
32);
34fn direct_displacement(world_xz: vec2<f32>, lod: u32) -> vec3<f32> {
35 let sampled_xz = advected_world(world_xz);
36 let cascade = cascade_layout.cascades[lod];
37 return textureSampleLevel(
38 lod_data,
39 lod_sampler,
40 world_to_uv(sampled_xz, cascade),
41 i32(lod),
42 0.0,
43 ).xyz;
46fn direct_fft_normal_cross(world_xz: vec2<f32>, lod: u32) -> vec3<f32> {
47 let sampled_xz = advected_world(world_xz);
48 let cascade = cascade_layout.cascades[lod];
49 return textureSampleLevel(
50 fft_surface,
51 lod_sampler,
52 world_to_uv(sampled_xz, cascade),
53 i32(lod),
54 0.0,
55 ).xyz;
58fn sample_fft_normal_cross(world_xz: vec2<f32>, lod: u32, alpha: f32) -> vec3<f32> {
59 let smaller = cascade_layout.cascades[lod];
60 let bigger = cascade_layout.cascades[lod + 1u];
61 let smaller_weight = (1.0 - alpha) * smaller.weight;
62 let bigger_weight = (1.0 - smaller_weight) * bigger.weight;
63 var normal_cross = vec3(0.0);
64 var sampled_weight = 0.0;
65 if smaller_weight > MIN_SAMPLE_WEIGHT {
66 normal_cross += smaller_weight * direct_fft_normal_cross(world_xz, lod);
67 sampled_weight += smaller_weight;
68 }
69 if bigger_weight > MIN_SAMPLE_WEIGHT && lod + 1u < lod_count() {
70 normal_cross += bigger_weight * direct_fft_normal_cross(world_xz, lod + 1u);
71 sampled_weight += bigger_weight;
72 }
73 normal_cross.y += max(1.0 - sampled_weight, 0.0);
74 return normal_cross;
77fn outer_cascade_weight(world_xz: vec2<f32>) -> f32 {
78 let outer_lod = lod_count() - 1u;
79 return 1.0 - lod_alpha(world_xz, cascade_layout.cascades[outer_lod]);
82fn far_displacement(world_xz: vec2<f32>) -> vec3<f32> {
83 let weight = outer_cascade_weight(world_xz);
84 if weight <= MIN_SAMPLE_WEIGHT {
85 return vec3(0.0);
86 }
87 return weight * direct_displacement(world_xz, lod_count() - 1u);
90fn far_normal_cross(world_xz: vec2<f32>) -> vec3<f32> {
91 let outer_lod = lod_count() - 1u;
92 let cascade = cascade_layout.cascades[outer_lod];
93 let weight = outer_cascade_weight(world_xz);
94 if weight <= MIN_SAMPLE_WEIGHT {
95 return vec3(0.0, 1.0, 0.0);
96 }
97 if surface.reflection.x > 0.5 {
98 let cached = direct_fft_normal_cross(world_xz, outer_lod);
99 return mix(vec3(0.0, 1.0, 0.0), cached, weight);
100 }
101 let center = weight * direct_displacement(world_xz, outer_lod);
102 let offset_x = weight * direct_displacement(
103 world_xz + vec2(cascade.texel_width, 0.0),
104 outer_lod,
105 );
106 let offset_z = weight * direct_displacement(
107 world_xz + vec2(0.0, cascade.texel_width),
108 outer_lod,
109 );
110 let tangent_x = vec3(cascade.texel_width, 0.0, 0.0) + offset_x - center;
111 let tangent_z = vec3(0.0, 0.0, cascade.texel_width) + offset_z - center;
112 var normal_cross = cross(tangent_z, tangent_x);
113 normal_cross.y = max(normal_cross.y, MIN_NORMAL_Y);
114 return normal_cross;
116// Crest `OceanHelpersNew.hlsl::SampleDisplacementsNormals`: horizontal
117// displacement Jacobian determinant. Compression/pinch has determinant < 1.
119fn displacement_jacobian(world_xz: vec2<f32>, lod: u32) -> f32 {
120 if surface.reflection.x > 0.5 {
121 let cascade = cascade_layout.cascades[lod];
122 return textureSampleLevel(
123 fft_surface,
124 lod_sampler,
125 world_to_uv(advected_world(world_xz), cascade),
126 i32(lod),
127 0.0,
128 ).w;
129 }
130 let texel_width = cascade_layout.cascades[lod].texel_width;
131 let center = direct_displacement(world_xz, lod);
132 let offset_x = direct_displacement(world_xz + vec2(texel_width, 0.0), lod);
133 let offset_z = direct_displacement(world_xz + vec2(0.0, texel_width), lod);
134 let tangent_x = vec2(texel_width, 0.0) + offset_x.xz - center.xz;
135 let tangent_z = vec2(0.0, texel_width) + offset_z.xz - center.xz;
136 return (tangent_x.x * tangent_z.y - tangent_x.y * tangent_z.x)
137 / (texel_width * texel_width);
140fn crest_sss(world_xz: vec2<f32>, lod: u32, alpha: f32) -> f32 {
141 let smaller = cascade_layout.cascades[lod];
142 let bigger = cascade_layout.cascades[lod + 1u];
143 let smaller_weight = (1.0 - alpha) * smaller.weight;
144 let bigger_weight = (1.0 - smaller_weight) * bigger.weight;
145 var determinant = 0.0;
146 if smaller_weight > MIN_SAMPLE_WEIGHT {
147 determinant += smaller_weight * displacement_jacobian(world_xz, lod);
148 }
149 if bigger_weight > MIN_SAMPLE_WEIGHT && lod + 1u < lod_count() {
150 determinant += bigger_weight * displacement_jacobian(world_xz, lod + 1u);
151 }
152 if lod + 1u >= lod_count() {
153 determinant += 1.0 - smaller_weight;
154 }
155 return clamp(CREST_SSS_MAXIMUM - CREST_SSS_RANGE * determinant, 0.0, 1.0);
158fn sample_detail_normal(uv: vec2<f32>, lod: f32) -> vec3<f32> {
159 let packed = textureSampleLevel(detail_normal, detail_sampler, uv, lod);
160 let slope = 2.0 * packed.xy - vec2(1.0);
161 let second_moment = 2.0 * packed.z;
162 return vec3(slope, max(second_moment - dot(slope, slope), 0.0));
165// Crest `OceanNormalMapping.hlsl::SampleNormalMaps`: two opposed scroll
166// directions and a second doubled scale blended by the cascade transition.
167// z carries LEAN slope variance removed by mip filtering.
168fn detail_normal_sample(world_xz: vec2<f32>, lod: u32, alpha: f32, ripple: f32) -> vec3<f32> {
169 // Sub-grid ripples ride the current with the waves they decorate, and
170 // stretch along it inside river bodies.
171 let scrolled_xz = flow_frame(advected_world(world_xz));
172 let cascade = cascade_layout.cascades[lod];
173 let stretch = surface.detail.x * cascade.scale / 100.0;
174 let speed_near = pow(
175 log(1.0 + 2.0 * cascade.texel_width) * NORMAL_SCROLL_MULTIPLIER,
176 NORMAL_SCROLL_POWER,
177 );
178 let speed_far = pow(
179 log(1.0 + 4.0 * cascade.texel_width) * NORMAL_SCROLL_MULTIPLIER,
180 NORMAL_SCROLL_POWER,
181 );
182 let texture_width = textureDimensions(detail_normal).x;
183 let near_lod = screen_texture_lod(stretch, texture_width);
184 let near_a = sample_detail_normal(
185 (scrolled_xz + NORMAL_DIRECTION_0 * globals.time * speed_near) / stretch,
186 near_lod,
187 );
188 let near_b = sample_detail_normal(
189 (scrolled_xz + NORMAL_DIRECTION_1 * globals.time * speed_near) / stretch,
190 near_lod,
191 );
192 let far_stretch = 2.0 * stretch;
193 let far_lod = screen_texture_lod(far_stretch, texture_width);
194 let far_a = sample_detail_normal(
195 (scrolled_xz + NORMAL_DIRECTION_0 * globals.time * speed_far) / far_stretch,
196 far_lod,
197 );
198 let far_b = sample_detail_normal(
199 (scrolled_xz + NORMAL_DIRECTION_1 * globals.time * speed_far) / far_stretch,
200 far_lod,
201 );
202 let near = near_a.xy + near_b.xy;
203 let far = far_a.xy + far_b.xy;
204 let near_weight = 1.0 - alpha;
205 let variance = near_weight * near_weight * (near_a.z + near_b.z)
206 + alpha * alpha * (far_a.z + far_b.z);
207 let strength = ripple * surface.detail.y * surface.detail.z;
208 return vec3(strength * mix(near, far, alpha), strength * strength * variance);
211// GodotOceanWaves anchors this as a displacement-free fine normal cascade.
212// DIVERGENCE: Aqua reuses Crest's trilinear WaveNormals at 16x frequency and
213// its shipped 0.08 strength because minification makes it weaker than an FFT
214// normal field; rotation avoids aligning its period with the authored layers.
215fn capillary_normal_slope(world_xz: vec2<f32>, ripple: f32) -> vec2<f32> {
216 let cascade = cascade_layout.cascades[0u];
217 let stretch = surface.detail.x * cascade.scale
218 / (100.0 * surface.capillary.x);
219 let speed = pow(
220 log(1.0 + 2.0 * cascade.texel_width) * NORMAL_SCROLL_MULTIPLIER,
221 NORMAL_SCROLL_POWER,
222 );
223 let scrolled = advected_world(world_xz)
224 + CAPILLARY_DIRECTION * globals.time * speed;
225 let uv = CAPILLARY_ROTATION * scrolled / stretch;
226 let lod = screen_texture_lod(stretch, textureDimensions(detail_normal).x);
227 return ripple * surface.capillary.y * sample_detail_normal(uv, lod).xy;
Contents