Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ test_output.txt
*.swp
.DS_Store
*.spv
!assets/shaders/vulkan/lpv_inject.comp.spv
!assets/shaders/vulkan/lpv_propagate.comp.spv
wiki/
*.exr
*.hdr
Expand Down
2 changes: 2 additions & 0 deletions assets/shaders/vulkan/g_pass.frag
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ layout(set = 0, binding = 0) uniform GlobalUniforms {
vec4 pbr_params;
vec4 volumetric_params;
vec4 viewport_size;
vec4 lpv_params;
vec4 lpv_origin;
} global;

// 4x4 Bayer matrix for dithered LOD transitions
Expand Down
49 changes: 49 additions & 0 deletions assets/shaders/vulkan/lpv_inject.comp
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#version 450

layout(local_size_x = 4, local_size_y = 4, local_size_z = 4) in;

struct LightData {
vec4 pos_radius;
vec4 color;
};

layout(set = 0, binding = 0, rgba32f) uniform writeonly image2D lpv_out;
layout(set = 0, binding = 1) readonly buffer Lights {
LightData lights[];
} light_buffer;

layout(push_constant) uniform InjectPush {
vec4 grid_origin_cell;
vec4 grid_params;
uint light_count;
} push_data;

ivec2 atlasUV(ivec3 cell, int gridSize) {
return ivec2(cell.x, cell.y + cell.z * gridSize);
}

void main() {
int gridSize = int(push_data.grid_params.x);
ivec3 cell = ivec3(gl_GlobalInvocationID.xyz);
if (any(greaterThanEqual(cell, ivec3(gridSize)))) {
return;
}

vec3 world_pos = push_data.grid_origin_cell.xyz + vec3(cell) * push_data.grid_origin_cell.w + vec3(0.5 * push_data.grid_origin_cell.w);

vec3 accum = vec3(0.0);
for (uint i = 0; i < push_data.light_count; i++) {
vec3 light_pos = light_buffer.lights[i].pos_radius.xyz;
float radius = max(light_buffer.lights[i].pos_radius.w, 0.001);
vec3 light_color = light_buffer.lights[i].color.rgb;

float d = length(world_pos - light_pos);
if (d < radius) {
float att = 1.0 - (d / radius);
att *= att;
accum += light_color * att;
}
}

imageStore(lpv_out, atlasUV(cell, gridSize), vec4(accum, 1.0));
}
Binary file added assets/shaders/vulkan/lpv_inject.comp.spv
Binary file not shown.
48 changes: 48 additions & 0 deletions assets/shaders/vulkan/lpv_propagate.comp
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#version 450

layout(local_size_x = 4, local_size_y = 4, local_size_z = 4) in;

layout(set = 0, binding = 0, rgba32f) uniform readonly image2D lpv_src;
layout(set = 0, binding = 1, rgba32f) uniform writeonly image2D lpv_dst;

layout(push_constant) uniform PropPush {
uint grid_size;
vec4 propagation;
} push_data;

ivec2 atlasUV(ivec3 cell, int gridSize) {
return ivec2(cell.x, cell.y + cell.z * gridSize);
}

vec3 sampleCell(ivec3 cell, int gridSize) {
return imageLoad(lpv_src, atlasUV(cell, gridSize)).rgb;
}

void main() {
int gridSize = int(push_data.grid_size);
ivec3 cell = ivec3(gl_GlobalInvocationID.xyz);
if (any(greaterThanEqual(cell, ivec3(gridSize)))) {
return;
}

// propagation.x = neighbor propagation factor, propagation.y = center retention
vec3 center = sampleCell(cell, gridSize) * push_data.propagation.y;
vec3 accum = center;
float f = push_data.propagation.x;

ivec3 off[6] = ivec3[6](
ivec3(-1, 0, 0), ivec3(1, 0, 0),
ivec3(0, -1, 0), ivec3(0, 1, 0),
ivec3(0, 0, -1), ivec3(0, 0, 1)
);

for (int i = 0; i < 6; i++) {
ivec3 n = cell + off[i];
if (any(lessThan(n, ivec3(0))) || any(greaterThanEqual(n, ivec3(gridSize)))) {
continue;
}
accum += sampleCell(n, gridSize) * f;
}

imageStore(lpv_dst, atlasUV(cell, gridSize), vec4(accum, 1.0));
}
Binary file added assets/shaders/vulkan/lpv_propagate.comp.spv
Binary file not shown.
53 changes: 50 additions & 3 deletions assets/shaders/vulkan/terrain.frag
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ layout(set = 0, binding = 0) uniform GlobalUniforms {
vec4 pbr_params; // x = pbr_quality, y = exposure, z = saturation, w = ssao_strength
vec4 volumetric_params; // x = enabled, y = density, z = steps, w = scattering
vec4 viewport_size; // xy = width/height
vec4 lpv_params; // x = enabled, y = intensity, z = cell_size, w = grid_size
vec4 lpv_origin; // xyz = world origin
} global;

// Constants
Expand Down Expand Up @@ -100,6 +102,7 @@ layout(set = 0, binding = 7) uniform sampler2D uRoughnessMap; // Roughness ma
layout(set = 0, binding = 8) uniform sampler2D uDisplacementMap; // Displacement map (unused for now)
layout(set = 0, binding = 9) uniform sampler2D uEnvMap; // Environment Map (EXR)
layout(set = 0, binding = 10) uniform sampler2D uSSAOMap; // SSAO Map
layout(set = 0, binding = 11) uniform sampler2D uLPVGrid; // LPV 3D atlas (Z slices packed in Y)

layout(set = 0, binding = 2) uniform ShadowUniforms {
mat4 light_space_matrices[4];
Expand Down Expand Up @@ -277,6 +280,47 @@ vec3 computeIBLAmbient(vec3 N, float roughness) {
return textureLod(uEnvMap, envUV, envMipLevel).rgb;
}

vec3 sampleLPVVoxel(vec3 voxel, float gridSize) {
float u = (voxel.x + 0.5) / gridSize;
float v = (voxel.y + voxel.z * gridSize + 0.5) / (gridSize * gridSize);
return texture(uLPVGrid, vec2(u, v)).rgb;
}

vec3 sampleLPVAtlas(vec3 worldPos) {
if (global.lpv_params.x < 0.5) return vec3(0.0);

float gridSize = max(global.lpv_params.w, 1.0);
float cellSize = max(global.lpv_params.z, 0.001);
vec3 local = (worldPos - global.lpv_origin.xyz) / cellSize;

if (any(lessThan(local, vec3(0.0))) || any(greaterThanEqual(local, vec3(gridSize)))) {
return vec3(0.0);
}

vec3 base = floor(local);
vec3 frac = fract(local);

vec3 p0 = clamp(base, vec3(0.0), vec3(gridSize - 1.0));
vec3 p1 = clamp(base + vec3(1.0), vec3(0.0), vec3(gridSize - 1.0));

vec3 c000 = sampleLPVVoxel(vec3(p0.x, p0.y, p0.z), gridSize);
vec3 c100 = sampleLPVVoxel(vec3(p1.x, p0.y, p0.z), gridSize);
vec3 c010 = sampleLPVVoxel(vec3(p0.x, p1.y, p0.z), gridSize);
vec3 c110 = sampleLPVVoxel(vec3(p1.x, p1.y, p0.z), gridSize);
vec3 c001 = sampleLPVVoxel(vec3(p0.x, p0.y, p1.z), gridSize);
vec3 c101 = sampleLPVVoxel(vec3(p1.x, p0.y, p1.z), gridSize);
vec3 c011 = sampleLPVVoxel(vec3(p0.x, p1.y, p1.z), gridSize);
vec3 c111 = sampleLPVVoxel(vec3(p1.x, p1.y, p1.z), gridSize);

vec3 c00 = mix(c000, c100, frac.x);
vec3 c10 = mix(c010, c110, frac.x);
vec3 c01 = mix(c001, c101, frac.x);
vec3 c11 = mix(c011, c111, frac.x);
vec3 c0 = mix(c00, c10, frac.y);
vec3 c1 = mix(c01, c11, frac.y);
return mix(c0, c1, frac.z) * global.lpv_params.y;
}

vec3 computeBRDF(vec3 albedo, vec3 N, vec3 V, vec3 L, float roughness) {
vec3 H = normalize(V + L);
vec3 F0 = mix(vec3(DIELECTRIC_F0), albedo, 0.0);
Expand Down Expand Up @@ -307,22 +351,25 @@ vec3 computePBR(vec3 albedo, vec3 N, vec3 V, vec3 L, float roughness, float tota
vec3 Lo = brdf * sunColor * NdotL_final * (1.0 - totalShadow);
vec3 envColor = computeIBLAmbient(N, roughness);
float shadowAmbientFactor = mix(1.0, 0.2, totalShadow);
vec3 ambientColor = albedo * (max(min(envColor, IBL_CLAMP) * skyLight * 0.8, vec3(global.lighting.x * 0.8)) + blockLight) * ao * ssao * shadowAmbientFactor;
vec3 indirect = sampleLPVAtlas(vFragPosWorld);
vec3 ambientColor = albedo * (max(min(envColor, IBL_CLAMP) * skyLight * 0.8, vec3(global.lighting.x * 0.8)) + blockLight + indirect) * ao * ssao * shadowAmbientFactor;
return ambientColor + Lo;
}

vec3 computeNonPBR(vec3 albedo, vec3 N, float nDotL, float totalShadow, float skyLight, vec3 blockLight, float ao, float ssao) {
vec3 envColor = computeIBLAmbient(N, NON_PBR_ROUGHNESS);
float shadowAmbientFactor = mix(1.0, 0.2, totalShadow);
vec3 ambientColor = albedo * (max(min(envColor, IBL_CLAMP) * skyLight * 0.8, vec3(global.lighting.x * 0.8)) + blockLight) * ao * ssao * shadowAmbientFactor;
vec3 indirect = sampleLPVAtlas(vFragPosWorld);
vec3 ambientColor = albedo * (max(min(envColor, IBL_CLAMP) * skyLight * 0.8, vec3(global.lighting.x * 0.8)) + blockLight + indirect) * ao * ssao * shadowAmbientFactor;
vec3 sunColor = global.sun_color.rgb * global.params.w * SUN_RADIANCE_TO_IRRADIANCE / PI;
vec3 directColor = albedo * sunColor * nDotL * (1.0 - totalShadow);
return ambientColor + directColor;
}

vec3 computeLOD(vec3 albedo, float nDotL, float totalShadow, float skyLightVal, vec3 blockLight, float ao, float ssao) {
float shadowAmbientFactor = mix(1.0, 0.2, totalShadow);
vec3 ambientColor = albedo * (max(vec3(skyLightVal * 0.8), vec3(global.lighting.x * 0.4)) + blockLight) * ao * ssao * shadowAmbientFactor;
vec3 indirect = sampleLPVAtlas(vFragPosWorld);
vec3 ambientColor = albedo * (max(vec3(skyLightVal * 0.8), vec3(global.lighting.x * 0.4)) + blockLight + indirect) * ao * ssao * shadowAmbientFactor;
vec3 sunColor = global.sun_color.rgb * global.params.w * SUN_VOLUMETRIC_INTENSITY / PI;
vec3 directColor = albedo * sunColor * nDotL * (1.0 - totalShadow);
return ambientColor + directColor;
Expand Down
Binary file modified assets/shaders/vulkan/terrain.frag.spv
Binary file not shown.
2 changes: 2 additions & 0 deletions assets/shaders/vulkan/terrain.vert
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ layout(set = 0, binding = 0) uniform GlobalUniforms {
vec4 pbr_params; // x = pbr_quality, y = exposure, z = saturation, w = ssao_strength
vec4 volumetric_params; // x = enabled, y = density, z = steps, w = scattering
vec4 viewport_size; // xy = width/height
vec4 lpv_params; // x = enabled, y = intensity, z = cell_size, w = grid_size
vec4 lpv_origin; // xyz = world origin
} global;

layout(push_constant) uniform ModelUniforms {
Expand Down
Binary file modified assets/shaders/vulkan/terrain.vert.spv
Binary file not shown.
6 changes: 5 additions & 1 deletion build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ pub fn build(b: *std.Build) void {

b.installArtifact(exe);

const shader_cmd = b.addSystemCommand(&.{ "sh", "-c", "for f in assets/shaders/vulkan/*.vert assets/shaders/vulkan/*.frag; do glslangValidator -V \"$f\" -o \"$f.spv\"; done" });
const shader_cmd = b.addSystemCommand(&.{ "sh", "-c", "for f in assets/shaders/vulkan/*.vert assets/shaders/vulkan/*.frag assets/shaders/vulkan/*.comp; do glslangValidator -V \"$f\" -o \"$f.spv\"; done" });

const run_cmd = b.addRunArtifact(exe);
run_cmd.step.dependOn(b.getInstallStep());
Expand Down Expand Up @@ -176,6 +176,8 @@ pub fn build(b: *std.Build) void {
const validate_vulkan_ssao_frag = b.addSystemCommand(&.{ "glslangValidator", "-V", "assets/shaders/vulkan/ssao.frag" });
const validate_vulkan_ssao_blur_frag = b.addSystemCommand(&.{ "glslangValidator", "-V", "assets/shaders/vulkan/ssao_blur.frag" });
const validate_vulkan_g_pass_frag = b.addSystemCommand(&.{ "glslangValidator", "-V", "assets/shaders/vulkan/g_pass.frag" });
const validate_vulkan_lpv_inject_comp = b.addSystemCommand(&.{ "glslangValidator", "-V", "assets/shaders/vulkan/lpv_inject.comp" });
const validate_vulkan_lpv_propagate_comp = b.addSystemCommand(&.{ "glslangValidator", "-V", "assets/shaders/vulkan/lpv_propagate.comp" });

test_step.dependOn(&validate_vulkan_terrain_vert.step);
test_step.dependOn(&validate_vulkan_terrain_frag.step);
Expand All @@ -195,4 +197,6 @@ pub fn build(b: *std.Build) void {
test_step.dependOn(&validate_vulkan_ssao_frag.step);
test_step.dependOn(&validate_vulkan_ssao_blur_frag.step);
test_step.dependOn(&validate_vulkan_g_pass_frag.step);
test_step.dependOn(&validate_vulkan_lpv_inject_comp.step);
test_step.dependOn(&validate_vulkan_lpv_propagate_comp.step);
}
Loading
Loading