feat: Implement PBR shaders and enhance atmospheric effects in rendering

This commit is contained in:
2026-01-05 09:55:38 +00:00
parent f2e7ae3d2e
commit 27045a3a6a
15 changed files with 344 additions and 2 deletions

View File

@@ -4,6 +4,7 @@
"conan": {}
},
"include": [
"build/build/Release/generators/CMakePresets.json"
"build/build/Release/generators/CMakePresets.json",
"build/Release/generators/CMakePresets.json"
]
}

View File

@@ -60,5 +60,17 @@
"device_extensions": [
"VK_KHR_swapchain"
],
"atmospherics": {
"ambient_strength": 0.01,
"fog_density": 0.003,
"fog_color": [0.05, 0.05, 0.08],
"gamma": 2.2,
"enable_tone_mapping": true,
"enable_shadows": true,
"enable_ssgi": true,
"enable_volumetric_lighting": true,
"pbr_roughness": 0.3,
"pbr_metallic": 0.1
},
"config_file": "config/seed_runtime.json"
}

View File

@@ -538,7 +538,7 @@ local function create_static_cube(position, scale, color)
vertices = vertices,
indices = cube_indices,
compute_model_matrix = compute_model_matrix,
shader_key = "solid", -- Use solid color shader for room objects
shader_key = "pbr", -- Use PBR shader for realistic materials and lighting
}
end

169
shaders/pbr.frag Normal file
View File

@@ -0,0 +1,169 @@
#version 450
layout(location = 0) in vec3 fragColor;
layout(location = 1) in vec3 fragWorldPos;
layout(location = 2) in vec3 fragNormal;
layout(location = 3) in vec2 fragTexCoord;
layout(location = 0) out vec4 outColor;
// Material properties
layout(push_constant) uniform PushConstants {
mat4 model;
mat4 view;
mat4 proj;
mat4 lightViewProj;
vec3 cameraPos;
float time;
} pc;
// Lighting uniforms
layout(set = 0, binding = 0) uniform sampler2D shadowMap;
// Material parameters (can be extended to use textures)
const vec3 MATERIAL_ALBEDO = vec3(0.8, 0.8, 0.8);
const float MATERIAL_ROUGHNESS = 0.3;
const float MATERIAL_METALLIC = 0.1;
// Atmospheric parameters
const vec3 FOG_COLOR = vec3(0.05, 0.05, 0.08);
const float FOG_DENSITY = 0.003;
const float AMBIENT_STRENGTH = 0.01; // Much darker ambient
// Light properties
const vec3 LIGHT_COLOR = vec3(1.0, 0.9, 0.6);
const float LIGHT_INTENSITY = 1.2;
const vec3 LIGHT_POSITIONS[8] = vec3[8](
vec3(13.0, 4.5, 13.0),
vec3(-13.0, 4.5, 13.0),
vec3(13.0, 4.5, -13.0),
vec3(-13.0, 4.5, -13.0),
vec3(0.0, 4.5, 13.0),
vec3(0.0, 4.5, -13.0),
vec3(13.0, 4.5, 0.0),
vec3(-13.0, 4.5, 0.0)
);
// PBR functions
vec3 fresnelSchlick(float cosTheta, vec3 F0) {
return F0 + (1.0 - F0) * pow(clamp(1.0 - cosTheta, 0.0, 1.0), 5.0);
}
float DistributionGGX(vec3 N, vec3 H, float roughness) {
float a = roughness * roughness;
float a2 = a * a;
float NdotH = max(dot(N, H), 0.0);
float NdotH2 = NdotH * NdotH;
float num = a2;
float denom = (NdotH2 * (a2 - 1.0) + 1.0);
denom = 3.14159 * denom * denom;
return num / denom;
}
float GeometrySchlickGGX(float NdotV, float roughness) {
float r = (roughness + 1.0);
float k = (r * r) / 8.0;
float num = NdotV;
float denom = NdotV * (1.0 - k) + k;
return num / denom;
}
float GeometrySmith(vec3 N, vec3 V, vec3 L, float roughness) {
float NdotV = max(dot(N, V), 0.0);
float NdotL = max(dot(N, L), 0.0);
float ggx2 = GeometrySchlickGGX(NdotV, roughness);
float ggx1 = GeometrySchlickGGX(NdotL, roughness);
return ggx1 * ggx2;
}
float calculateShadow(vec3 worldPos) {
vec4 lightSpacePos = pc.lightViewProj * vec4(worldPos, 1.0);
vec3 projCoords = lightSpacePos.xyz / lightSpacePos.w;
projCoords = projCoords * 0.5 + 0.5;
if (projCoords.z > 1.0) return 0.0;
float closestDepth = texture(shadowMap, projCoords.xy).r;
float currentDepth = projCoords.z;
float shadow = 0.0;
vec2 texelSize = 1.0 / textureSize(shadowMap, 0);
for(int x = -1; x <= 1; ++x) {
for(int y = -1; y <= 1; ++y) {
float pcfDepth = texture(shadowMap, projCoords.xy + vec2(x, y) * texelSize).r;
shadow += currentDepth - 0.005 > pcfDepth ? 1.0 : 0.0;
}
}
shadow /= 9.0;
return shadow;
}
vec3 calculateLighting(vec3 worldPos, vec3 normal, vec3 viewDir) {
vec3 F0 = vec3(0.04);
F0 = mix(F0, MATERIAL_ALBEDO, MATERIAL_METALLIC);
vec3 Lo = vec3(0.0);
for (int i = 0; i < 8; i++) {
vec3 lightDir = normalize(LIGHT_POSITIONS[i] - worldPos);
vec3 halfway = normalize(viewDir + lightDir);
float distance = length(LIGHT_POSITIONS[i] - worldPos);
float attenuation = 1.0 / (distance * distance);
vec3 radiance = LIGHT_COLOR * LIGHT_INTENSITY * attenuation;
float NDF = DistributionGGX(normal, halfway, MATERIAL_ROUGHNESS);
float G = GeometrySmith(normal, viewDir, lightDir, MATERIAL_ROUGHNESS);
vec3 F = fresnelSchlick(max(dot(halfway, viewDir), 0.0), F0);
vec3 kS = F;
vec3 kD = vec3(1.0) - kS;
kD *= 1.0 - MATERIAL_METALLIC;
float NdotL = max(dot(normal, lightDir), 0.0);
vec3 numerator = NDF * G * F;
float denominator = 4.0 * max(dot(normal, viewDir), 0.0) * NdotL + 0.0001;
vec3 specular = numerator / denominator;
Lo += (kD * MATERIAL_ALBEDO / 3.14159 + specular) * radiance * NdotL;
}
return Lo;
}
void main() {
vec3 normal = normalize(fragNormal);
vec3 viewDir = normalize(pc.cameraPos - fragWorldPos);
// Ambient lighting
vec3 ambient = AMBIENT_STRENGTH * MATERIAL_ALBEDO;
// Direct lighting with PBR
vec3 lighting = calculateLighting(fragWorldPos, normal, viewDir);
// Shadow calculation
float shadow = calculateShadow(fragWorldPos);
lighting *= (1.0 - shadow * 0.7); // Soften shadows
// Combine lighting
vec3 finalColor = ambient + lighting;
// Fog
float fogFactor = 1.0 - exp(-FOG_DENSITY * length(pc.cameraPos - fragWorldPos));
finalColor = mix(finalColor, FOG_COLOR, fogFactor);
// Simple tone mapping
finalColor = finalColor / (finalColor + vec3(1.0));
// Gamma correction
finalColor = pow(finalColor, vec3(1.0 / 2.2));
outColor = vec4(finalColor, 1.0);
}

BIN
shaders/pbr.frag.spv Normal file

Binary file not shown.

29
shaders/pbr.vert Normal file
View File

@@ -0,0 +1,29 @@
#version 450
layout(location = 0) in vec3 inPosition;
layout(location = 1) in vec3 inNormal;
layout(location = 2) in vec2 inTexCoord;
layout(location = 0) out vec3 fragColor;
layout(location = 1) out vec3 fragWorldPos;
layout(location = 2) out vec3 fragNormal;
layout(location = 3) out vec2 fragTexCoord;
layout(push_constant) uniform PushConstants {
mat4 model;
mat4 view;
mat4 proj;
mat4 lightViewProj;
vec3 cameraPos;
float time;
} pc;
void main() {
vec4 worldPos = pc.model * vec4(inPosition, 1.0);
gl_Position = pc.proj * pc.view * worldPos;
fragWorldPos = worldPos.xyz;
fragNormal = mat3(pc.model) * inNormal;
fragTexCoord = inTexCoord;
fragColor = vec3(0.7, 0.7, 0.7); // Default color, can be from vertex color or texture
}

BIN
shaders/pbr.vert.spv Normal file

Binary file not shown.

6
shaders/shadow.frag Normal file
View File

@@ -0,0 +1,6 @@
#version 450
void main() {
// Empty fragment shader for shadow mapping
// Depth is automatically written
}

BIN
shaders/shadow.frag.spv Normal file

Binary file not shown.

14
shaders/shadow.vert Normal file
View File

@@ -0,0 +1,14 @@
#version 450
layout(location = 0) in vec3 inPosition;
layout(location = 1) in vec3 inNormal;
layout(location = 2) in vec2 inTexCoord;
layout(push_constant) uniform PushConstants {
mat4 model;
mat4 lightViewProj;
} pc;
void main() {
gl_Position = pc.lightViewProj * pc.model * vec4(inPosition, 1.0);
}

BIN
shaders/shadow.vert.spv Normal file

Binary file not shown.

65
shaders/ssgi.frag Normal file
View File

@@ -0,0 +1,65 @@
#version 450
layout(location = 0) in vec2 inPosition;
layout(location = 1) in vec2 inTexCoord;
layout(location = 0) out vec4 outColor;
layout(set = 0, binding = 0) uniform sampler2D sceneColor;
layout(set = 0, binding = 1) uniform sampler2D normalBuffer;
layout(set = 0, binding = 2) uniform sampler2D depthBuffer;
layout(push_constant) uniform PushConstants {
mat4 invProj;
mat4 invView;
vec3 cameraPos;
float intensity;
} pc;
const int NUM_SAMPLES = 16;
const float SAMPLE_RADIUS = 0.5;
// Reconstruct world position from depth
vec3 worldPosFromDepth(float depth, vec2 texCoord) {
vec4 clipSpace = vec4(texCoord * 2.0 - 1.0, depth, 1.0);
vec4 viewSpace = pc.invProj * clipSpace;
viewSpace /= viewSpace.w;
vec4 worldSpace = pc.invView * viewSpace;
return worldSpace.xyz;
}
vec3 ssao(vec2 texCoord) {
float depth = texture(depthBuffer, texCoord).r;
if (depth >= 1.0) return vec3(1.0); // Skybox
vec3 worldPos = worldPosFromDepth(depth, texCoord);
vec3 normal = normalize(texture(normalBuffer, texCoord).rgb * 2.0 - 1.0);
float occlusion = 0.0;
for (int i = 0; i < NUM_SAMPLES; i++) {
// Generate sample position in hemisphere around normal
float angle = (float(i) / float(NUM_SAMPLES)) * 6.283185;
vec2 offset = vec2(cos(angle), sin(angle)) * SAMPLE_RADIUS;
vec2 sampleTexCoord = texCoord + offset / textureSize(depthBuffer, 0);
float sampleDepth = texture(depthBuffer, sampleTexCoord).r;
if (sampleDepth < depth - 0.01) { // Occluded
occlusion += 1.0;
}
}
occlusion = 1.0 - (occlusion / float(NUM_SAMPLES));
return vec3(occlusion);
}
void main() {
vec3 sceneColor = texture(sceneColor, inTexCoord).rgb;
vec3 ao = ssao(inTexCoord);
// Apply ambient occlusion
vec3 finalColor = sceneColor * (0.3 + 0.7 * ao); // Mix AO with direct lighting
outColor = vec4(finalColor, 1.0);
}

BIN
shaders/ssgi.frag.spv Normal file

Binary file not shown.

46
shaders/volumetric.frag Normal file
View File

@@ -0,0 +1,46 @@
#version 450
layout(location = 0) in vec2 inPosition;
layout(location = 1) in vec2 inTexCoord;
layout(location = 0) out vec4 outColor;
layout(set = 0, binding = 0) uniform sampler2D sceneColor;
layout(set = 0, binding = 1) uniform sampler2D depthBuffer;
layout(push_constant) uniform PushConstants {
vec2 lightScreenPos;
float time;
float intensity;
} pc;
const int NUM_SAMPLES = 100;
const float DECAY_BASE = 0.96815;
const float WEIGHT_BASE = 0.58767;
const float EXPOSURE = 0.2;
void main() {
vec2 texCoord = inTexCoord;
vec2 deltaTexCoord = (texCoord - pc.lightScreenPos);
deltaTexCoord *= 1.0 / float(NUM_SAMPLES) * 0.5; // Scale for effect
vec3 color = texture(sceneColor, texCoord).rgb;
// Only apply god rays if we're looking towards the light
float centerDistance = length(pc.lightScreenPos - vec2(0.5, 0.5));
if (centerDistance < 0.8) { // Light is visible on screen
vec3 godRayColor = vec3(0.0);
float weight = WEIGHT_BASE;
for (int i = 0; i < NUM_SAMPLES; i++) {
texCoord -= deltaTexCoord;
vec3 sampleColor = texture(sceneColor, texCoord).rgb;
godRayColor += sampleColor * weight;
weight *= DECAY_BASE;
}
color += godRayColor * EXPOSURE * pc.intensity;
}
outColor = vec4(color, 1.0);
}

BIN
shaders/volumetric.frag.spv Normal file

Binary file not shown.