#[compute]
#version 450
layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
layout(set = 0, binding = 0) uniform sampler2D depth_texture;
layout(set = 0, binding = 1, rgba16f) uniform restrict writeonly image2D cloud_texture;
layout(set = 1, binding = 0) uniform sampler3D perlin_worley_noise;
layout(set = 1, binding = 1) uniform sampler3D detail_noise;
layout(push_constant, std430) uniform Params {
vec4 camera_pos_ymin;
vec4 sun_dir_ymax;
vec4 screen_time;
vec4 cam_right; // xyz = right, w = half_fov_tan
vec4 cam_up_aspect; // xyz = up, w = aspect
vec4 cam_forward_pad; // xyz = forward
} p;
// --- Height gradient presets (Nubis / GPU Pro 7) ---
float height_gradient(float h, vec4 g) {
return smoothstep(g.x, g.y, h) - smoothstep(g.z, g.w, h);
}
const vec4 STRATOCUMULUS = vec4(0.02, 0.20, 0.48, 0.625);
const vec4 CUMULUS = vec4(0.01, 0.0625, 0.78, 1.0);
float remap(float v, float lo, float hi, float new_lo, float new_hi) {
return new_lo + (v - lo) / (hi - lo) * (new_hi - new_lo);
}
vec2 intersect_slab(vec3 ro, vec3 rd, float y_lo, float y_hi) {
if (abs(rd.y) < 1e-6) {
if (ro.y >= y_lo && ro.y <= y_hi) return vec2(0.0, 1e20);
else return vec2(1.0, 0.0);
}
float t0 = (y_lo - ro.y) / rd.y;
float t1 = (y_hi - ro.y) / rd.y;
if (t0 > t1) { float tmp = t0; t0 = t1; t1 = tmp; }
return vec2(max(t0, 0.0), max(t1, 0.0));
}
// --- Procedural 3D noise ---
float hash31(vec3 p) {
p = fract(p * vec3(443.897, 441.423, 437.195));
p += dot(p, p.yzx + 19.19);
return fract((p.x + p.y) * p.z);
}
float value_noise_3d(vec3 p) {
vec3 i = floor(p);
vec3 f = fract(p);
f = f * f * (3.0 - 2.0 * f);
return mix(
mix(mix(hash31(i), hash31(i+vec3(1,0,0)), f.x),
mix(hash31(i+vec3(0,1,0)), hash31(i+vec3(1,1,0)), f.x), f.y),
mix(mix(hash31(i+vec3(0,0,1)), hash31(i+vec3(1,0,1)), f.x),
mix(hash31(i+vec3(0,1,1)), hash31(i+vec3(1,1,1)), f.x), f.y),
f.z);
}
float fbm_3d(vec3 p, int octaves) {
float sum = 0.0, amp = 0.5, freq = 1.0;
for (int i = 0; i < octaves; i++) {
sum += value_noise_3d(p * freq) * amp;
freq *= 2.0; amp *= 0.5;
}
return sum;
}
float worley_3d(vec3 p) {
vec3 i = floor(p);
vec3 f = fract(p);
float md = 1.0;
for (int z = -1; z <= 1; z++)
for (int y = -1; y <= 1; y++)
for (int x = -1; x <= 1; x++) {
vec3 off = vec3(float(x), float(y), float(z));
vec3 cell = i + off;
vec3 pt = vec3(hash31(cell), hash31(cell+100.0), hash31(cell+200.0));
md = min(md, length(off + pt - f));
}
return md;
}
// --- Cloud density ---
float sample_cloud_density(vec3 pos, float y_min, float y_max, float weather) {
float slab_thickness = y_max - y_min;
float height_frac = clamp((pos.y - y_min) / slab_thickness, 0.0, 1.0);
vec3 sp = pos * 0.002;
float perlin = fbm_3d(sp, 4);
float worley = 1.0 - worley_3d(sp * 2.0);
float base_cloud = perlin * 0.7 + worley * 0.3;
float gradient = mix(
height_gradient(height_frac, CUMULUS),
height_gradient(height_frac, STRATOCUMULUS),
weather);
base_cloud *= gradient;
float coverage = mix(0.2, 0.85, weather);
base_cloud = remap(base_cloud, 1.0 - coverage, 1.0, 0.0, 1.0);
base_cloud *= coverage;
if (base_cloud > 0.0) {
float detail = fbm_3d(pos * 0.01, 3);
float erosion = mix(detail, 1.0 - detail, clamp(height_frac * 3.0, 0.0, 1.0));
base_cloud = remap(base_cloud, erosion * 0.15, 1.0, 0.0, 1.0);
}
return clamp(base_cloud, 0.0, 1.0);
}
// --- Lighting ---
float beer_powder(float d) {
return 2.0 * exp(-d) * (1.0 - exp(-2.0 * d));
}
float hg_phase(float cos_theta, float g) {
float g2 = g * g;
return (1.0 - g2) / (4.0 * 3.14159265 * pow(1.0 + g2 - 2.0 * g * cos_theta, 1.5));
}
void main() {
ivec2 pixel = ivec2(gl_GlobalInvocationID.xy);
ivec2 screen = ivec2(p.screen_time.xy);
if (pixel.x >= screen.x || pixel.y >= screen.y) return;
vec3 cam_pos = p.camera_pos_ymin.xyz;
float y_min = p.camera_pos_ymin.w;
float y_max = p.sun_dir_ymax.w;
vec3 sun_dir = p.sun_dir_ymax.xyz;
float weather = p.screen_time.w;
// Reconstruct world-space ray from camera basis vectors.
vec2 uv = (vec2(pixel) + 0.5) / vec2(screen);
vec2 ndc = uv * 2.0 - 1.0;
ndc.y = -ndc.y; // Vulkan Y-flip: UV top=0 but NDC top=+1
float half_fov_tan = p.cam_right.w;
float aspect = p.cam_up_aspect.w;
vec3 rd = normalize(
p.cam_forward_pad.xyz
+ p.cam_right.xyz * ndc.x * half_fov_tan * aspect
+ p.cam_up_aspect.xyz * ndc.y * half_fov_tan
);
vec2 t = intersect_slab(cam_pos, rd, y_min, y_max);
if (t.x >= t.y || t.y <= 0.0) {
imageStore(cloud_texture, pixel, vec4(0.0));
return;
}
t.y = min(t.y, 5000.0);
int steps = 64;
float step_size = (t.y - t.x) / float(steps);
float transmittance = 1.0;
vec3 light_energy = vec3(0.0);
float extinction = 0.04;
float cos_theta = dot(rd, sun_dir);
float phase = hg_phase(cos_theta, 0.6);
for (int i = 0; i < steps; i++) {
if (transmittance < 0.01) break;
float tc = t.x + (float(i) + 0.5) * step_size;
vec3 pos = cam_pos + rd * tc;
float density = sample_cloud_density(pos, y_min, y_max, weather);
if (density < 0.001) continue;
float sample_ext = extinction * density;
float height_frac = clamp((pos.y - y_min) / (y_max - y_min), 0.0, 1.0);
// Light march (5 steps toward sun)
float light_density = 0.0;
float light_step = (y_max - y_min) * 0.15;
for (int j = 0; j < 5; j++) {
vec3 lp = pos + sun_dir * light_step * float(j + 1);
light_density += sample_cloud_density(lp, y_min, y_max, weather);
}
float lt = beer_powder(light_density * extinction * light_step);
float ms = lt + 0.5 * beer_powder(light_density * extinction * light_step * 0.5);
float ambient = mix(0.15, 0.4, height_frac);
vec3 sun_col = vec3(1.0, 0.95, 0.85);
vec3 amb_col = vec3(0.4, 0.5, 0.7);
vec3 col = sun_col * ms * phase + amb_col * ambient;
float st = exp(-sample_ext * step_size);
light_energy += col * transmittance * (1.0 - st);
transmittance *= st;
}
imageStore(cloud_texture, pixel, vec4(light_energy, 1.0 - transmittance));
}