feat: Add PBR shader support and atmospherics configuration

This commit is contained in:
2026-01-05 10:05:52 +00:00
parent 27045a3a6a
commit 2fef2b14ca
21 changed files with 262 additions and 47 deletions

View File

@@ -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 = {

View File

@@ -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() {

Binary file not shown.

View File

@@ -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);
}

Binary file not shown.

View File

@@ -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
}

Binary file not shown.

View File

@@ -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() {

Binary file not shown.

View File

@@ -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() {

Binary file not shown.

View File

@@ -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;
}

Binary file not shown.

View File

@@ -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);

Binary file not shown.

View File

@@ -297,6 +297,7 @@ void ServiceBasedApp::RegisterServices() {
registry_.GetService<services::ISwapchainService>(),
registry_.GetService<services::IPipelineService>(),
registry_.GetService<services::IBufferService>(),
registry_.GetService<services::IConfigService>(),
registry_.GetService<services::ILogger>());
// Graphics service (facade)

View File

@@ -13,9 +13,25 @@ struct Vertex {
struct PushConstants {
std::array<float, 16> model;
std::array<float, 16> viewProj;
// Extended fields for PBR/atmospherics (optional for basic shaders)
std::array<float, 16> view;
std::array<float, 16> proj;
std::array<float, 16> lightViewProj;
std::array<float, 3> cameraPos;
float time;
// Atmospherics parameters
float ambientStrength;
float fogDensity;
float fogStart;
float fogEnd;
std::array<float, 3> 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

View File

@@ -293,6 +293,62 @@ RuntimeConfig JsonConfigService::LoadFromJson(std::shared_ptr<ILogger> 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<float>(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<float, 3>& 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<float>(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;
}

View File

@@ -9,11 +9,13 @@ RenderCommandService::RenderCommandService(std::shared_ptr<IVulkanDeviceService>
std::shared_ptr<ISwapchainService> swapchainService,
std::shared_ptr<IPipelineService> pipelineService,
std::shared_ptr<IBufferService> bufferService,
std::shared_ptr<IConfigService> configService,
std::shared_ptr<ILogger> 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);

View File

@@ -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 <memory>
@@ -25,6 +26,7 @@ public:
std::shared_ptr<ISwapchainService> swapchainService,
std::shared_ptr<IPipelineService> pipelineService,
std::shared_ptr<IBufferService> bufferService,
std::shared_ptr<IConfigService> configService,
std::shared_ptr<ILogger> logger);
~RenderCommandService() override;
@@ -55,6 +57,7 @@ private:
std::shared_ptr<ISwapchainService> swapchainService_;
std::shared_ptr<IPipelineService> pipelineService_;
std::shared_ptr<IBufferService> bufferService_;
std::shared_ptr<IConfigService> configService_;
std::shared_ptr<ILogger> logger_;
VkCommandPool commandPool_ = VK_NULL_HANDLE;

View File

@@ -1,5 +1,6 @@
#pragma once
#include <array>
#include <cstdint>
#include <filesystem>
#include <string>
@@ -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<float, 3> 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