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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- core/DepthAndShadowPass.h : handle multiple shadow maps using a texture atlas (https://github.com/Simple-Robotics/candlewick/pull/69)
- multibody : handle two-light setup w/ shadow mapping (https://github.com/Simple-Robotics/candlewick/pull/69)
- multibody/Visualizer : use `H` key to toggle GUI (https://github.com/Simple-Robotics/candlewick/pull/70)
- shaders : Soft shadows in PBR using percentage-closer filtering (PCF) (https://github.com/Simple-Robotics/candlewick/pull/71)
- core : Tighter shadow frustum around the world-scene AABB (https://github.com/Simple-Robotics/candlewick/pull/71)
- core : add `getAABBCorners()` util function in `Collision.h` header (https://github.com/Simple-Robotics/candlewick/pull/71)

## [0.1.0] - 2025-05-21

Expand Down
94 changes: 54 additions & 40 deletions shaders/compiled/PbrBasic.frag.msl
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@ struct PbrMaterial
struct ShadowAtlasInfo
{
int4 lightRegions[2];
uint2 atlasSize;
};

struct LightBlock
Expand Down Expand Up @@ -170,57 +169,57 @@ float3 calculatePbrLighting(thread const float3& normal, thread const float3& V,
static inline __attribute__((always_inline))
bool isCoordsInRange(thread const float3& uv)
{
bool _69 = uv.x >= 0.0;
bool _76;
if (_69)
bool _72 = uv.x >= 0.0;
bool _79;
if (_72)
{
_76 = uv.y >= 0.0;
_79 = uv.y >= 0.0;
}
else
{
_76 = _69;
_79 = _72;
}
bool _83;
if (_76)
bool _86;
if (_79)
{
_83 = uv.x <= 1.0;
_86 = uv.x <= 1.0;
}
else
{
_83 = _76;
_86 = _79;
}
bool _89;
if (_83)
bool _92;
if (_86)
{
_89 = uv.y <= 1.0;
_92 = uv.y <= 1.0;
}
else
{
_89 = _83;
_92 = _86;
}
bool _96;
if (_89)
bool _99;
if (_92)
{
_96 = uv.z >= 0.0;
_99 = uv.z >= 0.0;
}
else
{
_96 = _89;
_99 = _92;
}
bool _102;
if (_96)
bool _105;
if (_99)
{
_102 = uv.z <= 1.0;
_105 = uv.z <= 1.0;
}
else
{
_102 = _96;
_105 = _99;
}
return _102;
return _105;
}

static inline __attribute__((always_inline))
float calcShadowmap(thread const int& lightIndex, thread const float& NdotL, thread spvUnsafeArray<float3, 2>& fragLightPos, constant ShadowAtlasInfo& _420, depth2d<float> shadowMap, sampler shadowMapSmplr)
float calcShadowmap(thread const int& lightIndex, thread const float& NdotL, thread const int2& atlasSize, thread spvUnsafeArray<float3, 2>& fragLightPos, constant ShadowAtlasInfo& _422, depth2d<float> shadowMap, sampler shadowMapSmplr)
{
float bias0 = 0.004999999888241291046142578125;
float3 lightSpacePos = fragLightPos[lightIndex];
Expand All @@ -233,11 +232,24 @@ float calcShadowmap(thread const int& lightIndex, thread const float& NdotL, thr
{
return 1.0;
}
int4 region = _420.lightRegions[lightIndex];
int4 region = _422.lightRegions[lightIndex];
uv = float2(region.xy) + (uv * float2(region.zw));
uv /= float2(_420.atlasSize);
float3 texCoords = float3(uv, depthRef);
return shadowMap.sample_compare(shadowMapSmplr, texCoords.xy, texCoords.z);
uv /= float2(atlasSize);
float2 regionMin = float2(region.xy) / float2(atlasSize);
float2 regionMax = float2(region.xy + region.zw) / float2(atlasSize);
float value = 0.0;
float2 offsets = float2(1.0) / float2(atlasSize);
for (int i = -1; i <= 1; i++)
{
for (int j = -1; j <= 1; j++)
{
float2 offUV = uv + (float2(float(i), float(j)) * offsets);
offUV = fast::clamp(offUV, regionMin, regionMax);
float3 texCoords = float3(offUV, depthRef);
value += (0.111111111938953399658203125 * shadowMap.sample_compare(shadowMapSmplr, texCoords.xy, texCoords.z));
}
}
return value;
}

static inline __attribute__((always_inline))
Expand All @@ -264,14 +276,15 @@ float3 uncharted2ToneMapping(thread const float3& color)
return curr * white_scale;
}

fragment main0_out main0(main0_in in [[stage_in]], constant Material& _505 [[buffer(0)]], constant LightBlock& light [[buffer(1)]], constant EffectParams& params [[buffer(2)]], constant ShadowAtlasInfo& _420 [[buffer(3)]], depth2d<float> shadowMap [[texture(0)]], texture2d<float> ssaoTex [[texture(1)]], sampler shadowMapSmplr [[sampler(0)]], sampler ssaoTexSmplr [[sampler(1)]], bool gl_FrontFacing [[front_facing]], float4 gl_FragCoord [[position]])
fragment main0_out main0(main0_in in [[stage_in]], constant Material& _571 [[buffer(0)]], constant LightBlock& light [[buffer(1)]], constant EffectParams& params [[buffer(2)]], constant ShadowAtlasInfo& _422 [[buffer(3)]], depth2d<float> shadowMap [[texture(0)]], texture2d<float> ssaoTex [[texture(1)]], sampler shadowMapSmplr [[sampler(0)]], sampler ssaoTexSmplr [[sampler(1)]], bool gl_FrontFacing [[front_facing]], float4 gl_FragCoord [[position]])
{
main0_out out = {};
spvUnsafeArray<float3, 2> fragLightPos = {};
fragLightPos[0] = in.fragLightPos_0;
fragLightPos[1] = in.fragLightPos_1;
float3 normal = fast::normalize(in.fragViewNormal);
float3 V = fast::normalize(-in.fragViewPos);
int2 atlasSize = int2(shadowMap.get_width(), shadowMap.get_height());
if (!gl_FrontFacing)
{
normal = -normal;
Expand All @@ -283,34 +296,35 @@ fragment main0_out main0(main0_in in [[stage_in]], constant Material& _505 [[buf
float3 param = normal;
float3 param_1 = V;
float3 param_2 = lightDir;
PbrMaterial _518;
_518.baseColor = _505.material.baseColor;
_518.metalness = _505.material.metalness;
_518.roughness = _505.material.roughness;
_518.ao = _505.material.ao;
PbrMaterial param_3 = _518;
PbrMaterial _584;
_584.baseColor = _571.material.baseColor;
_584.metalness = _571.material.metalness;
_584.roughness = _571.material.roughness;
_584.ao = _571.material.ao;
PbrMaterial param_3 = _584;
float3 param_4 = light.color[i];
float param_5 = light.intensity[i].x;
float3 _lo = calculatePbrLighting(param, param_1, param_2, param_3, param_4, param_5);
float NdotL = fast::max(dot(normal, lightDir), 0.0);
int param_6 = i;
float param_7 = NdotL;
float shadowValue = calcShadowmap(param_6, param_7, fragLightPos, _420, shadowMap, shadowMapSmplr);
int2 param_8 = atlasSize;
float shadowValue = calcShadowmap(param_6, param_7, param_8, fragLightPos, _422, shadowMap, shadowMapSmplr);
_lo *= shadowValue;
Lo += _lo;
}
float3 ambient = (float3(0.100000001490116119384765625) * _505.material.baseColor.xyz) * _505.material.ao;
float3 ambient = (float3(0.100000001490116119384765625) * _571.material.baseColor.xyz) * _571.material.ao;
if (params.useSsao == 1u)
{
float2 ssaoTexSize = float2(int2(ssaoTex.get_width(), ssaoTex.get_height()));
float2 ssaoUV = gl_FragCoord.xy / ssaoTexSize;
ambient *= ssaoTex.sample(ssaoTexSmplr, ssaoUV).x;
}
float3 color = ambient + Lo;
float3 param_8 = color;
color = uncharted2ToneMapping(param_8);
float3 param_9 = color;
color = uncharted2ToneMapping(param_9);
color = powr(color, float3(0.4545454680919647216796875));
out.fragColor = float4(color, _505.material.baseColor.w);
out.fragColor = float4(color, _571.material.baseColor.w);
out.outNormal = in.fragViewNormal.xy;
return out;
}
Binary file modified shaders/compiled/PbrBasic.frag.spv
Binary file not shown.
31 changes: 24 additions & 7 deletions shaders/src/PbrBasic.frag
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ layout(set=3, binding=2) uniform EffectParams {

layout(set = 3, binding = 3) uniform ShadowAtlasInfo {
ivec4 lightRegions[NUM_LIGHTS];
uvec2 atlasSize;
};

#ifdef HAS_SHADOW_MAPS
Expand All @@ -49,7 +48,7 @@ layout(location=0) out vec4 fragColor;
#endif

#ifdef HAS_SHADOW_MAPS
float calcShadowmap(int lightIndex, float NdotL) {
float calcShadowmap(int lightIndex, float NdotL, ivec2 atlasSize) {
// float bias = max(0.05 * (1.0 - NdotL), 0.005);
float bias = 0.005;
vec3 lightSpacePos = fragLightPos[lightIndex];
Expand All @@ -62,17 +61,35 @@ float calcShadowmap(int lightIndex, float NdotL) {
}

ivec4 region = lightRegions[lightIndex];
vec2 regionMin = vec2(region.xy) / atlasSize;
vec2 regionMax = vec2(region.xy + region.zw) / atlasSize;
uv = region.xy + uv * region.zw;
uv = uv / atlasSize;

vec3 texCoords = vec3(uv, depthRef);
return texture(shadowMap, texCoords);
float value = 0.0;
const vec2 offsets = 1.0 / atlasSize;
// pcf loop
const int halfKernel = 1;
const float weight = 1.0 / pow(2 * halfKernel + 1, 2);
for (int i = -halfKernel; i <= halfKernel; i++) {
for (int j = -halfKernel; j <= halfKernel; j++) {
vec2 offUV = uv + vec2(i, j) * offsets;
offUV = clamp(offUV, regionMin, regionMax);
vec3 texCoords = vec3(offUV, depthRef);
value += weight * texture(shadowMap, texCoords);
}
}

return value;
}
#endif

void main() {
vec3 normal = normalize(fragViewNormal);
vec3 V = normalize(-fragViewPos);
const vec3 V = normalize(-fragViewPos);
#ifdef HAS_SHADOW_MAPS
const ivec2 atlasSize = textureSize(shadowMap, 0).xy;
#endif

if (!gl_FrontFacing) {
// Flip normal for back faces
Expand All @@ -91,8 +108,8 @@ void main() {
light.intensity[i]
);
#ifdef HAS_SHADOW_MAPS
float NdotL = max(dot(normal, lightDir), 0.0);
float shadowValue = calcShadowmap(i, NdotL);
const float NdotL = max(dot(normal, lightDir), 0.0);
const float shadowValue = calcShadowmap(i, NdotL, atlasSize);
_lo *= shadowValue;
#endif
Lo += _lo;
Expand Down
5 changes: 2 additions & 3 deletions shaders/src/tone_mapping.glsl
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,9 @@ vec3 reinhardToneMapping(vec3 color) {
return color / (color + vec3(1.0));
}

// Exposure + Gamma (Simplified)
vec3 exposureToneMapping(vec3 color, float exposure) {
// Exposure adjustment followed by gamma correction
return pow(1.0 - exp(-color * exposure), vec3(1.0/2.2));
// Exposure adjustment
return 1.0 - exp(-color * exposure);
}

// ACES Filmic Tone Mapping (Academy Color Encoding System)
Expand Down
16 changes: 16 additions & 0 deletions src/candlewick/core/Collision.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,20 @@ inline Mat4f toTransformationMatrix(const OBB &obb) {
return T;
}

inline std::array<Float3, 8> getAABBCorners(const AABB &aabb) {
const Float3 min = aabb.min_.cast<float>();
const Float3 max = aabb.max_.cast<float>();

return {
Float3{min.x(), min.y(), min.z()}, // 000
Float3{max.x(), min.y(), min.z()}, // 100
Float3{min.x(), max.y(), min.z()}, // 010
Float3{max.x(), max.y(), min.z()}, // 110
Float3{min.x(), min.y(), max.z()}, // 001
Float3{max.x(), min.y(), max.z()}, // 101
Float3{min.x(), max.y(), max.z()}, // 011
Float3{max.x(), max.y(), max.z()}, // 111
};
}

} // namespace candlewick
27 changes: 18 additions & 9 deletions src/candlewick/core/DepthAndShadowPass.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -273,23 +273,32 @@ void renderShadowPassFromFrustum(CommandBuffer &cmdBuf, ShadowMapPass &passInfo,
void renderShadowPassFromAABB(CommandBuffer &cmdBuf, ShadowMapPass &passInfo,
std::span<const DirectionalLight> dirLight,
std::span<const OpaqueCastable> castables,
const AABB &worldSceneBounds) {
const AABB &worldAABB) {
using Eigen::Vector3d;

Float3 center = worldSceneBounds.center().cast<float>();
float radius = 0.5f * float(worldSceneBounds.size());
radius = std::ceil(radius * 16.f) / 16.f;
Float3 center = worldAABB.center().cast<float>();

for (size_t i = 0; i < passInfo.numLights(); i++) {
const Float3 eye = center - radius * dirLight[i].direction.normalized();
Float3 tmpEye = center - 100.0f * dirLight[i].direction.normalized();
Mat4f tmplightView = lookAt(tmpEye, center, Float3::UnitZ());

auto &lightView = passInfo.cam[i].view;
auto &lightProj = passInfo.cam[i].projection;
lightView = lookAt(eye, center, Float3::UnitZ());

AABB bounds{Vector3d::Constant(-radius), Vector3d::Constant(radius)};
Mat3f R = tmplightView.topLeftCorner<3, 3>();
Float3 t = tmplightView.topRightCorner<3, 1>();
AABB bounds = coal::translate(coal::rotate(worldAABB, R.cast<double>()),
t.cast<double>());

Float3 lightSpaceCenter = bounds.center().cast<float>();
float radius = float(bounds.max_.z());

Float3 finalEye = center - radius * dirLight[i].direction.normalized();

lightView = lookAt(finalEye, center, Float3::UnitZ());
lightProj = shadowOrthographicMatrix({bounds.width(), bounds.height()},
float(bounds.min_.z()),
float(bounds.max_.z()));
float(bounds.max_.z()),
float(bounds.min_.z()));
}
passInfo.render(cmdBuf, castables);
}
Expand Down
2 changes: 0 additions & 2 deletions src/candlewick/multibody/RobotScene.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ struct alignas(16) LightSpaceMatricesUbo {
struct alignas(16) ShadowAtlasInfoUbo {
using Vec4u = Eigen::Matrix<Uint32, 4, 1, Eigen::DontAlign>;
std::array<Vec4u, kNumLights> regions;
std::array<Uint32, 2> atlasSize;
};

void updateRobotTransforms(entt::registry &registry,
Expand Down Expand Up @@ -455,7 +454,6 @@ void RobotScene::renderPBRTriangleGeometry(CommandBuffer &command_buffer,
const bool enable_shadows = m_config.enable_shadows;
ShadowAtlasInfoUbo shadowAtlasUbo{
.regions{},
.atlasSize{shadowPass.atlasDims()},
};
Mat4f lightViewProj[kNumLights];
for (size_t i = 0; i < numLights; i++) {
Expand Down