diff --git a/CMakeUserPresets.json b/CMakeUserPresets.json index 889fff9..e2739a5 100644 --- a/CMakeUserPresets.json +++ b/CMakeUserPresets.json @@ -4,6 +4,7 @@ "conan": {} }, "include": [ - "build/build/Release/generators/CMakePresets.json" + "build/build/Release/generators/CMakePresets.json", + "build/Release/generators/CMakePresets.json" ] } \ No newline at end of file diff --git a/config/seed_runtime.json b/config/seed_runtime.json index eaaa67c..a3159c6 100644 --- a/config/seed_runtime.json +++ b/config/seed_runtime.json @@ -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" } diff --git a/scripts/cube_logic.lua b/scripts/cube_logic.lua index 590cc34..52ab6f9 100644 --- a/scripts/cube_logic.lua +++ b/scripts/cube_logic.lua @@ -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 diff --git a/shaders/pbr.frag b/shaders/pbr.frag new file mode 100644 index 0000000..53a3391 --- /dev/null +++ b/shaders/pbr.frag @@ -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); +} \ No newline at end of file diff --git a/shaders/pbr.frag.spv b/shaders/pbr.frag.spv new file mode 100644 index 0000000..5458f9a Binary files /dev/null and b/shaders/pbr.frag.spv differ diff --git a/shaders/pbr.vert b/shaders/pbr.vert new file mode 100644 index 0000000..c10847c --- /dev/null +++ b/shaders/pbr.vert @@ -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 +} \ No newline at end of file diff --git a/shaders/pbr.vert.spv b/shaders/pbr.vert.spv new file mode 100644 index 0000000..9d701d2 Binary files /dev/null and b/shaders/pbr.vert.spv differ diff --git a/shaders/shadow.frag b/shaders/shadow.frag new file mode 100644 index 0000000..b7217da --- /dev/null +++ b/shaders/shadow.frag @@ -0,0 +1,6 @@ +#version 450 + +void main() { + // Empty fragment shader for shadow mapping + // Depth is automatically written +} \ No newline at end of file diff --git a/shaders/shadow.frag.spv b/shaders/shadow.frag.spv new file mode 100644 index 0000000..757dd51 Binary files /dev/null and b/shaders/shadow.frag.spv differ diff --git a/shaders/shadow.vert b/shaders/shadow.vert new file mode 100644 index 0000000..b60bd9d --- /dev/null +++ b/shaders/shadow.vert @@ -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); +} \ No newline at end of file diff --git a/shaders/shadow.vert.spv b/shaders/shadow.vert.spv new file mode 100644 index 0000000..30ad42f Binary files /dev/null and b/shaders/shadow.vert.spv differ diff --git a/shaders/ssgi.frag b/shaders/ssgi.frag new file mode 100644 index 0000000..ea3f34b --- /dev/null +++ b/shaders/ssgi.frag @@ -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); +} \ No newline at end of file diff --git a/shaders/ssgi.frag.spv b/shaders/ssgi.frag.spv new file mode 100644 index 0000000..149e0a5 Binary files /dev/null and b/shaders/ssgi.frag.spv differ diff --git a/shaders/volumetric.frag b/shaders/volumetric.frag new file mode 100644 index 0000000..39a442c --- /dev/null +++ b/shaders/volumetric.frag @@ -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); +} \ No newline at end of file diff --git a/shaders/volumetric.frag.spv b/shaders/volumetric.frag.spv new file mode 100644 index 0000000..bffac78 Binary files /dev/null and b/shaders/volumetric.frag.spv differ