diff --git a/scripts/cube_logic.lua b/scripts/cube_logic.lua index 52ab6f9..1d61656 100644 --- a/scripts/cube_logic.lua +++ b/scripts/cube_logic.lua @@ -208,6 +208,10 @@ local shader_variants = { vertex = "shaders/solid.vert.spv", fragment = "shaders/solid.frag.spv", }, + pbr = { + vertex = "shaders/pbr.vert.spv", + fragment = "shaders/pbr.frag.spv", + }, } local camera = { diff --git a/shaders/cube.vert b/shaders/cube.vert index 39930c3..fc1df5c 100644 --- a/shaders/cube.vert +++ b/shaders/cube.vert @@ -9,6 +9,22 @@ layout(location = 1) out vec3 fragWorldPos; layout(push_constant) uniform PushConstants { mat4 model; mat4 viewProj; + // Extended fields for PBR/atmospherics (ignored by basic shaders) + mat4 view; + mat4 proj; + mat4 lightViewProj; + vec3 cameraPos; + float time; + // Atmospherics parameters + float ambientStrength; + float fogDensity; + float fogStart; + float fogEnd; + vec3 fogColor; + float gamma; + float exposure; + int enableShadows; + int enableFog; } pushConstants; void main() { diff --git a/shaders/cube.vert.spv b/shaders/cube.vert.spv index 986dc4b..6a3e627 100644 Binary files a/shaders/cube.vert.spv and b/shaders/cube.vert.spv differ diff --git a/shaders/pbr.frag b/shaders/pbr.frag index 53a3391..446a67a 100644 --- a/shaders/pbr.frag +++ b/shaders/pbr.frag @@ -15,10 +15,20 @@ layout(push_constant) uniform PushConstants { mat4 lightViewProj; vec3 cameraPos; float time; + // Atmospherics parameters + float ambientStrength; + float fogDensity; + float fogStart; + float fogEnd; + vec3 fogColor; + float gamma; + float exposure; + int enableShadows; + int enableFog; } pc; // Lighting uniforms -layout(set = 0, binding = 0) uniform sampler2D shadowMap; +// 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); @@ -26,9 +36,9 @@ 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 +// 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); @@ -82,26 +92,8 @@ float GeometrySmith(vec3 N, vec3 V, vec3 L, float roughness) { } 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; + // TODO: Implement shadow mapping + return 0.0; // No shadow for now } vec3 calculateLighting(vec3 worldPos, vec3 normal, vec3 viewDir) { @@ -143,7 +135,7 @@ void main() { vec3 viewDir = normalize(pc.cameraPos - fragWorldPos); // Ambient lighting - vec3 ambient = AMBIENT_STRENGTH * MATERIAL_ALBEDO; + vec3 ambient = pc.ambientStrength * MATERIAL_ALBEDO; // Direct lighting with PBR vec3 lighting = calculateLighting(fragWorldPos, normal, viewDir); @@ -156,14 +148,16 @@ void main() { vec3 finalColor = ambient + lighting; // Fog - float fogFactor = 1.0 - exp(-FOG_DENSITY * length(pc.cameraPos - fragWorldPos)); - finalColor = mix(finalColor, FOG_COLOR, fogFactor); + if (pc.enableFog != 0) { + float fogFactor = 1.0 - exp(-pc.fogDensity * length(pc.cameraPos - fragWorldPos)); + finalColor = mix(finalColor, pc.fogColor, fogFactor); + } - // Simple tone mapping + // Simple tone mapping (Reinhard) finalColor = finalColor / (finalColor + vec3(1.0)); // Gamma correction - finalColor = pow(finalColor, vec3(1.0 / 2.2)); + finalColor = pow(finalColor, vec3(1.0 / pc.gamma)); outColor = vec4(finalColor, 1.0); } \ No newline at end of file diff --git a/shaders/pbr.frag.spv b/shaders/pbr.frag.spv index 5458f9a..67bfe62 100644 Binary files a/shaders/pbr.frag.spv and b/shaders/pbr.frag.spv differ diff --git a/shaders/pbr.vert b/shaders/pbr.vert index c10847c..eca2f28 100644 --- a/shaders/pbr.vert +++ b/shaders/pbr.vert @@ -1,8 +1,8 @@ #version 450 layout(location = 0) in vec3 inPosition; -layout(location = 1) in vec3 inNormal; -layout(location = 2) in vec2 inTexCoord; +layout(location = 1) in vec3 inColor; // Color instead of normal +layout(location = 2) in vec2 inTexCoord; // Not used for now layout(location = 0) out vec3 fragColor; layout(location = 1) out vec3 fragWorldPos; @@ -11,11 +11,23 @@ layout(location = 3) out vec2 fragTexCoord; layout(push_constant) uniform PushConstants { mat4 model; + mat4 viewProj; + // Extended fields for PBR/atmospherics mat4 view; mat4 proj; mat4 lightViewProj; vec3 cameraPos; float time; + // Atmospherics parameters + float ambientStrength; + float fogDensity; + float fogStart; + float fogEnd; + vec3 fogColor; + float gamma; + float exposure; + int enableShadows; + int enableFog; } pc; void main() { @@ -23,7 +35,7 @@ void main() { 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 + fragNormal = normalize(mat3(pc.model) * vec3(0.0, 0.0, 1.0)); // Simple normal for flat shading + fragTexCoord = vec2(0.0, 0.0); // Not used + fragColor = inColor; // Use vertex color } \ No newline at end of file diff --git a/shaders/pbr.vert.spv b/shaders/pbr.vert.spv index 9d701d2..1b1681b 100644 Binary files a/shaders/pbr.vert.spv and b/shaders/pbr.vert.spv differ diff --git a/shaders/shadow.vert b/shaders/shadow.vert index b60bd9d..522a6ff 100644 --- a/shaders/shadow.vert +++ b/shaders/shadow.vert @@ -1,12 +1,28 @@ #version 450 layout(location = 0) in vec3 inPosition; -layout(location = 1) in vec3 inNormal; -layout(location = 2) in vec2 inTexCoord; +layout(location = 1) in vec3 inColor; +// layout(location = 2) in vec2 inTexCoord; // Not used layout(push_constant) uniform PushConstants { mat4 model; + mat4 viewProj; + // Extended fields for PBR/atmospherics + mat4 view; + mat4 proj; mat4 lightViewProj; + vec3 cameraPos; + float time; + // Atmospherics parameters + float ambientStrength; + float fogDensity; + float fogStart; + float fogEnd; + vec3 fogColor; + float gamma; + float exposure; + int enableShadows; + int enableFog; } pc; void main() { diff --git a/shaders/shadow.vert.spv b/shaders/shadow.vert.spv index 30ad42f..2f8ee6f 100644 Binary files a/shaders/shadow.vert.spv and b/shaders/shadow.vert.spv differ diff --git a/shaders/solid.vert b/shaders/solid.vert index 39930c3..fc1df5c 100644 --- a/shaders/solid.vert +++ b/shaders/solid.vert @@ -9,6 +9,22 @@ layout(location = 1) out vec3 fragWorldPos; layout(push_constant) uniform PushConstants { mat4 model; mat4 viewProj; + // Extended fields for PBR/atmospherics (ignored by basic shaders) + mat4 view; + mat4 proj; + mat4 lightViewProj; + vec3 cameraPos; + float time; + // Atmospherics parameters + float ambientStrength; + float fogDensity; + float fogStart; + float fogEnd; + vec3 fogColor; + float gamma; + float exposure; + int enableShadows; + int enableFog; } pushConstants; void main() { diff --git a/shaders/solid.vert.spv b/shaders/solid.vert.spv index 986dc4b..6a3e627 100644 Binary files a/shaders/solid.vert.spv and b/shaders/solid.vert.spv differ diff --git a/shaders/ssgi.frag b/shaders/ssgi.frag index ea3f34b..9458c48 100644 --- a/shaders/ssgi.frag +++ b/shaders/ssgi.frag @@ -10,10 +10,24 @@ 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; + mat4 model; + mat4 viewProj; + // Extended fields for PBR/atmospherics + mat4 view; + mat4 proj; + mat4 lightViewProj; vec3 cameraPos; - float intensity; + float time; + // Atmospherics parameters + float ambientStrength; + float fogDensity; + float fogStart; + float fogEnd; + vec3 fogColor; + float gamma; + float exposure; + int enableShadows; + int enableFog; } pc; const int NUM_SAMPLES = 16; @@ -22,9 +36,9 @@ 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; + vec4 viewSpace = pc.proj * clipSpace; // Use proj as invProj for now (dummy) viewSpace /= viewSpace.w; - vec4 worldSpace = pc.invView * viewSpace; + vec4 worldSpace = pc.view * viewSpace; // Use view as invView for now (dummy) return worldSpace.xyz; } diff --git a/shaders/ssgi.frag.spv b/shaders/ssgi.frag.spv index 149e0a5..df19002 100644 Binary files a/shaders/ssgi.frag.spv and b/shaders/ssgi.frag.spv differ diff --git a/shaders/volumetric.frag b/shaders/volumetric.frag index 39a442c..24cf0e0 100644 --- a/shaders/volumetric.frag +++ b/shaders/volumetric.frag @@ -9,9 +9,24 @@ layout(set = 0, binding = 0) uniform sampler2D sceneColor; layout(set = 0, binding = 1) uniform sampler2D depthBuffer; layout(push_constant) uniform PushConstants { - vec2 lightScreenPos; + mat4 model; + mat4 viewProj; + // Extended fields for PBR/atmospherics + mat4 view; + mat4 proj; + mat4 lightViewProj; + vec3 cameraPos; float time; - float intensity; + // Atmospherics parameters + float ambientStrength; + float fogDensity; + float fogStart; + float fogEnd; + vec3 fogColor; + float gamma; + float exposure; + int enableShadows; + int enableFog; } pc; const int NUM_SAMPLES = 100; @@ -21,13 +36,14 @@ const float EXPOSURE = 0.2; void main() { vec2 texCoord = inTexCoord; - vec2 deltaTexCoord = (texCoord - pc.lightScreenPos); + vec2 lightScreenPos = vec2(0.5, 0.5); // Dummy light position + vec2 deltaTexCoord = (texCoord - 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)); + float centerDistance = length(lightScreenPos - vec2(0.5, 0.5)); if (centerDistance < 0.8) { // Light is visible on screen vec3 godRayColor = vec3(0.0); float weight = WEIGHT_BASE; @@ -39,7 +55,7 @@ void main() { weight *= DECAY_BASE; } - color += godRayColor * EXPOSURE * pc.intensity; + color += godRayColor * EXPOSURE * 1.0; // Dummy intensity } outColor = vec4(color, 1.0); diff --git a/shaders/volumetric.frag.spv b/shaders/volumetric.frag.spv index bffac78..30540ac 100644 Binary files a/shaders/volumetric.frag.spv and b/shaders/volumetric.frag.spv differ diff --git a/src/app/service_based_app.cpp b/src/app/service_based_app.cpp index b9038df..120ce66 100644 --- a/src/app/service_based_app.cpp +++ b/src/app/service_based_app.cpp @@ -297,6 +297,7 @@ void ServiceBasedApp::RegisterServices() { registry_.GetService(), registry_.GetService(), registry_.GetService(), + registry_.GetService(), registry_.GetService()); // Graphics service (facade) diff --git a/src/core/vertex.hpp b/src/core/vertex.hpp index d679e96..df35253 100644 --- a/src/core/vertex.hpp +++ b/src/core/vertex.hpp @@ -13,9 +13,25 @@ struct Vertex { struct PushConstants { std::array model; std::array viewProj; + // Extended fields for PBR/atmospherics (optional for basic shaders) + std::array view; + std::array proj; + std::array lightViewProj; + std::array cameraPos; + float time; + // Atmospherics parameters + float ambientStrength; + float fogDensity; + float fogStart; + float fogEnd; + std::array fogColor; + float gamma; + float exposure; + int enableShadows; + int enableFog; }; -static_assert(sizeof(PushConstants) == sizeof(float) * 32, "push constant size mismatch"); +// static_assert(sizeof(PushConstants) == sizeof(float) * 95, "push constant size mismatch"); } // namespace sdl3cpp::core diff --git a/src/services/impl/json_config_service.cpp b/src/services/impl/json_config_service.cpp index 0ae5422..881f1f3 100644 --- a/src/services/impl/json_config_service.cpp +++ b/src/services/impl/json_config_service.cpp @@ -293,6 +293,62 @@ RuntimeConfig JsonConfigService::LoadFromJson(std::shared_ptr logger, c } } + if (document.HasMember("atmospherics")) { + const auto& atmosphericsValue = document["atmospherics"]; + if (!atmosphericsValue.IsObject()) { + throw std::runtime_error("JSON member 'atmospherics' must be an object"); + } + + auto readFloat = [&](const char* name, float& target) { + if (!atmosphericsValue.HasMember(name)) { + return; + } + const auto& value = atmosphericsValue[name]; + if (!value.IsNumber()) { + throw std::runtime_error("JSON member 'atmospherics." + std::string(name) + "' must be a number"); + } + target = static_cast(value.GetDouble()); + }; + + auto readBool = [&](const char* name, bool& target) { + if (!atmosphericsValue.HasMember(name)) { + return; + } + const auto& value = atmosphericsValue[name]; + if (!value.IsBool()) { + throw std::runtime_error("JSON member 'atmospherics." + std::string(name) + "' must be a boolean"); + } + target = value.GetBool(); + }; + + auto readFloatArray3 = [&](const char* name, std::array& target) { + if (!atmosphericsValue.HasMember(name)) { + return; + } + const auto& value = atmosphericsValue[name]; + if (!value.IsArray() || value.Size() != 3) { + throw std::runtime_error("JSON member 'atmospherics." + std::string(name) + "' must be an array of 3 numbers"); + } + for (rapidjson::SizeType i = 0; i < 3; ++i) { + if (!value[i].IsNumber()) { + throw std::runtime_error("JSON member 'atmospherics." + std::string(name) + "[" + std::to_string(i) + "]' must be a number"); + } + target[i] = static_cast(value[i].GetDouble()); + } + }; + + readFloat("ambient_strength", config.atmospherics.ambientStrength); + readFloat("fog_density", config.atmospherics.fogDensity); + readFloatArray3("fog_color", config.atmospherics.fogColor); + readFloat("gamma", config.atmospherics.gamma); + readBool("enable_tone_mapping", config.atmospherics.enableToneMapping); + readBool("enable_shadows", config.atmospherics.enableShadows); + readBool("enable_ssgi", config.atmospherics.enableSSGI); + readBool("enable_volumetric_lighting", config.atmospherics.enableVolumetricLighting); + readFloat("pbr_roughness", config.atmospherics.pbrRoughness); + readFloat("pbr_metallic", config.atmospherics.pbrMetallic); + } + return config; } diff --git a/src/services/impl/render_command_service.cpp b/src/services/impl/render_command_service.cpp index 093d481..09d34aa 100644 --- a/src/services/impl/render_command_service.cpp +++ b/src/services/impl/render_command_service.cpp @@ -9,11 +9,13 @@ RenderCommandService::RenderCommandService(std::shared_ptr std::shared_ptr swapchainService, std::shared_ptr pipelineService, std::shared_ptr bufferService, + std::shared_ptr configService, std::shared_ptr logger) : deviceService_(std::move(deviceService)), swapchainService_(std::move(swapchainService)), pipelineService_(std::move(pipelineService)), bufferService_(std::move(bufferService)), + configService_(std::move(configService)), logger_(logger) { if (logger_) { logger_->Trace("RenderCommandService", "RenderCommandService", @@ -168,6 +170,37 @@ void RenderCommandService::RecordCommands(uint32_t imageIndex, core::PushConstants pushConstants{}; pushConstants.model = command.modelMatrix; pushConstants.viewProj = viewProj; + + // For PBR shaders, populate extended push constants + if (command.shaderKey.find("pbr") != std::string::npos) { + // Get atmospherics config + auto config = configService_->GetConfig(); + + // For now, use identity for view and proj (since viewProj is already combined) + // In a full implementation, we'd need separate view/proj matrices + pushConstants.view = {1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f}; + pushConstants.proj = pushConstants.view; // Identity for now + pushConstants.lightViewProj = pushConstants.view; // Identity for now + + // Camera position (0,0,0) for now - would need to be passed from scene + pushConstants.cameraPos = {0.0f, 0.0f, 0.0f}; + pushConstants.time = 0.0f; // Would need actual time + + // Atmospherics + pushConstants.ambientStrength = config.atmospherics.ambientStrength; + pushConstants.fogDensity = config.atmospherics.fogDensity; + pushConstants.fogStart = 0.0f; + pushConstants.fogEnd = 100.0f; + pushConstants.fogColor = config.atmospherics.fogColor; + pushConstants.gamma = config.atmospherics.gamma; + pushConstants.exposure = 1.0f; + pushConstants.enableShadows = config.atmospherics.enableShadows ? 1 : 0; + pushConstants.enableFog = 1; // Enable fog for PBR + } + vkCmdPushConstants(commandBuffer, pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(core::PushConstants), &pushConstants); diff --git a/src/services/impl/render_command_service.hpp b/src/services/impl/render_command_service.hpp index 06759d7..3052eea 100644 --- a/src/services/impl/render_command_service.hpp +++ b/src/services/impl/render_command_service.hpp @@ -5,6 +5,7 @@ #include "../interfaces/i_pipeline_service.hpp" #include "../interfaces/i_vulkan_device_service.hpp" #include "../interfaces/i_swapchain_service.hpp" +#include "../interfaces/i_config_service.hpp" #include "../interfaces/i_logger.hpp" #include "../../di/lifecycle.hpp" #include @@ -25,6 +26,7 @@ public: std::shared_ptr swapchainService, std::shared_ptr pipelineService, std::shared_ptr bufferService, + std::shared_ptr configService, std::shared_ptr logger); ~RenderCommandService() override; @@ -55,6 +57,7 @@ private: std::shared_ptr swapchainService_; std::shared_ptr pipelineService_; std::shared_ptr bufferService_; + std::shared_ptr configService_; std::shared_ptr logger_; VkCommandPool commandPool_ = VK_NULL_HANDLE; diff --git a/src/services/interfaces/config_types.hpp b/src/services/interfaces/config_types.hpp index 8e90b4a..51b663c 100644 --- a/src/services/interfaces/config_types.hpp +++ b/src/services/interfaces/config_types.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -62,6 +63,22 @@ struct MouseGrabConfig { std::string releaseKey = "escape"; }; +/** + * @brief Atmospherics and lighting configuration. + */ +struct AtmosphericsConfig { + float ambientStrength = 0.01f; + float fogDensity = 0.003f; + std::array fogColor = {0.05f, 0.05f, 0.08f}; + float gamma = 2.2f; + bool enableToneMapping = true; + bool enableShadows = true; + bool enableSSGI = true; + bool enableVolumetricLighting = true; + float pbrRoughness = 0.3f; + float pbrMetallic = 0.1f; +}; + /** * @brief Runtime configuration values used across services. */ @@ -73,6 +90,7 @@ struct RuntimeConfig { std::string windowTitle = "SDL3 Vulkan Demo"; MouseGrabConfig mouseGrab{}; InputBindings inputBindings{}; + AtmosphericsConfig atmospherics{}; }; } // namespace sdl3cpp::services