Merge branch 'CSM-new'
CSM-dev is clipmap shadow map and it works
This commit is contained in:
@@ -8,7 +8,8 @@ layout(location=0) out vec4 outColor;
|
|||||||
layout(set=1, binding=0) uniform sampler2D posTex;
|
layout(set=1, binding=0) uniform sampler2D posTex;
|
||||||
layout(set=1, binding=1) uniform sampler2D normalTex;
|
layout(set=1, binding=1) uniform sampler2D normalTex;
|
||||||
layout(set=1, binding=2) uniform sampler2D albedoTex;
|
layout(set=1, binding=2) uniform sampler2D albedoTex;
|
||||||
layout(set=2, binding=0) uniform sampler2D shadowTex[MAX_CASCADES];
|
// Mixed near + CSM: shadowTex[0] is the near/simple map, 1..N-1 are cascades
|
||||||
|
layout(set=2, binding=0) uniform sampler2D shadowTex[4];
|
||||||
|
|
||||||
const float PI = 3.14159265359;
|
const float PI = 3.14159265359;
|
||||||
|
|
||||||
@@ -29,22 +30,32 @@ vec2(0.0281, -0.2468), vec2(-0.2104, 0.0573),
|
|||||||
vec2(0.1197, 0.0779), vec2(-0.0905, -0.1203)
|
vec2(0.1197, 0.0779), vec2(-0.0905, -0.1203)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Clipmap selection: choose the smallest level whose light-space XY NDC contains the point.
|
||||||
|
uint selectCascadeIndex(vec3 worldPos)
|
||||||
|
{
|
||||||
|
for (uint i = 0u; i < 4u; ++i)
|
||||||
|
{
|
||||||
|
vec4 lclip = sceneData.lightViewProjCascades[i] * vec4(worldPos, 1.0);
|
||||||
|
vec3 ndc = lclip.xyz / max(lclip.w, 1e-6);
|
||||||
|
if (abs(ndc.x) <= 1.0 && abs(ndc.y) <= 1.0 && ndc.z >= 0.0 && ndc.z <= 1.0)
|
||||||
|
{
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 3u; // fallback to farthest level
|
||||||
|
}
|
||||||
|
|
||||||
float calcShadowVisibility(vec3 worldPos, vec3 N, vec3 L)
|
float calcShadowVisibility(vec3 worldPos, vec3 N, vec3 L)
|
||||||
{
|
{
|
||||||
// Choose cascade based on view-space depth
|
uint ci = selectCascadeIndex(worldPos);
|
||||||
float viewDepth = - (sceneData.view * vec4(worldPos, 1.0)).z;// positive
|
mat4 lightMat = sceneData.lightViewProjCascades[ci];
|
||||||
int ci = 0;
|
|
||||||
if (viewDepth > sceneData.cascadeSplitsView.x) ci = 1;
|
|
||||||
if (viewDepth > sceneData.cascadeSplitsView.y) ci = 2;
|
|
||||||
if (viewDepth > sceneData.cascadeSplitsView.z) ci = 3;
|
|
||||||
ci = clamp(ci, 0, MAX_CASCADES-1);
|
|
||||||
|
|
||||||
vec4 lclip = sceneData.lightViewProjCascades[ci] * vec4(worldPos, 1.0);
|
vec4 lclip = lightMat * vec4(worldPos, 1.0);
|
||||||
vec3 ndc = lclip.xyz / lclip.w;
|
vec3 ndc = lclip.xyz / lclip.w;
|
||||||
vec2 suv = ndc.xy * 0.5 + 0.5;
|
vec2 suv = ndc.xy * 0.5 + 0.5;
|
||||||
|
|
||||||
if (any(lessThan(suv, vec2(0.0))) || any(greaterThan(suv, vec2(1.0))))
|
if (any(lessThan(suv, vec2(0.0))) || any(greaterThan(suv, vec2(1.0))))
|
||||||
return 1.0;
|
return 1.0;
|
||||||
|
|
||||||
float current = clamp(ndc.z, 0.0, 1.0);
|
float current = clamp(ndc.z, 0.0, 1.0);
|
||||||
|
|
||||||
@@ -60,15 +71,16 @@ float calcShadowVisibility(vec3 worldPos, vec3 N, vec3 L)
|
|||||||
vec2 texelSize = 1.0 / vec2(dim);
|
vec2 texelSize = 1.0 / vec2(dim);
|
||||||
|
|
||||||
float baseRadius = 1.25;
|
float baseRadius = 1.25;
|
||||||
float radius = mix(baseRadius, baseRadius * 4.0, current);
|
// Slightly increase filter for farther cascades
|
||||||
|
float radius = mix(baseRadius, baseRadius * 3.0, float(ci) / 3.0);
|
||||||
|
|
||||||
float ang = hash12(suv * 4096.0) * 6.2831853;
|
float ang = hash12(suv * 4096.0) * 6.2831853;
|
||||||
vec2 r = vec2(cos(ang), sin(ang));
|
vec2 r = vec2(cos(ang), sin(ang));
|
||||||
mat2 rot = mat2(r.x, -r.y, r.y, r.x);
|
mat2 rot = mat2(r.x, -r.y, r.y, r.x);
|
||||||
|
|
||||||
const int TAP_COUNT = 16;
|
const int TAP_COUNT = 16;
|
||||||
float occluded = 0.0;
|
float visible = 0.0;
|
||||||
float wsum = 0.0;
|
float wsum = 0.0;
|
||||||
|
|
||||||
for (int i = 0; i < TAP_COUNT; ++i)
|
for (int i = 0; i < TAP_COUNT; ++i)
|
||||||
{
|
{
|
||||||
@@ -79,19 +91,14 @@ float calcShadowVisibility(vec3 worldPos, vec3 N, vec3 L)
|
|||||||
float w = 1.0 - smoothstep(0.0, 0.65, pr);
|
float w = 1.0 - smoothstep(0.0, 0.65, pr);
|
||||||
|
|
||||||
float mapD = texture(shadowTex[ci], suv + off).r;
|
float mapD = texture(shadowTex[ci], suv + off).r;
|
||||||
|
float vis = step(mapD, current + bias);
|
||||||
|
|
||||||
// Forward-Z depth shadow map:
|
visible += vis * w;
|
||||||
// - Occluded when current + bias > mapD
|
wsum += w;
|
||||||
// - Unoccluded otherwise
|
|
||||||
// Use step(edge, x): returns 1 when x >= edge. Make occ=1 for occluded.
|
|
||||||
float occ = step(mapD + bias, current);
|
|
||||||
|
|
||||||
occluded += occ * w;
|
|
||||||
wsum += w;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
float shadow = (wsum > 0.0) ? (occluded / wsum) : 0.0;
|
float visibility = (wsum > 0.0) ? (visible / wsum) : 1.0;
|
||||||
return 1.0 - shadow;
|
return visibility;
|
||||||
}
|
}
|
||||||
|
|
||||||
vec3 fresnelSchlick(float cosTheta, vec3 F0)
|
vec3 fresnelSchlick(float cosTheta, vec3 F0)
|
||||||
|
|||||||
@@ -6,13 +6,16 @@ layout(set = 0, binding = 0) uniform SceneData{
|
|||||||
mat4 view;
|
mat4 view;
|
||||||
mat4 proj;
|
mat4 proj;
|
||||||
mat4 viewproj;
|
mat4 viewproj;
|
||||||
mat4 lightViewProj; // legacy single-shadow for fallback
|
// Legacy single shadow matrix (used for near range in mixed mode)
|
||||||
|
mat4 lightViewProj;
|
||||||
vec4 ambientColor;
|
vec4 ambientColor;
|
||||||
vec4 sunlightDirection; //w for sun power
|
vec4 sunlightDirection; //w for sun power
|
||||||
vec4 sunlightColor;
|
vec4 sunlightColor;
|
||||||
// CSM data
|
|
||||||
mat4 lightViewProjCascades[MAX_CASCADES];
|
// Cascaded shadow matrices (0 = near/simple map, 1..N-1 = CSM)
|
||||||
vec4 cascadeSplitsView; // positive view-space distances of far plane per cascade
|
mat4 lightViewProjCascades[4];
|
||||||
|
// View-space split distances for selecting cascades (x,y,z,w)
|
||||||
|
vec4 cascadeSplitsView;
|
||||||
} sceneData;
|
} sceneData;
|
||||||
|
|
||||||
layout(set = 1, binding = 0) uniform GLTFMaterialData{
|
layout(set = 1, binding = 0) uniform GLTFMaterialData{
|
||||||
|
|||||||
@@ -18,14 +18,16 @@ layout(buffer_reference, std430) readonly buffer VertexBuffer{
|
|||||||
layout(push_constant) uniform PushConsts {
|
layout(push_constant) uniform PushConsts {
|
||||||
mat4 render_matrix;
|
mat4 render_matrix;
|
||||||
VertexBuffer vertexBuffer;
|
VertexBuffer vertexBuffer;
|
||||||
uint cascadeIndex;
|
uint cascadeIndex; // which cascade this pass renders
|
||||||
|
// pad to 16-byte boundary implicitly
|
||||||
} PC;
|
} PC;
|
||||||
|
|
||||||
void main()
|
void main()
|
||||||
{
|
{
|
||||||
Vertex v = PC.vertexBuffer.vertices[gl_VertexIndex];
|
Vertex v = PC.vertexBuffer.vertices[gl_VertexIndex];
|
||||||
vec4 worldPos = PC.render_matrix * vec4(v.position, 1.0);
|
vec4 worldPos = PC.render_matrix * vec4(v.position, 1.0);
|
||||||
uint ci = min(PC.cascadeIndex, uint(MAX_CASCADES-1));
|
// Use cascaded matrix; cascade 0 is the legacy near/simple map
|
||||||
|
uint ci = min(PC.cascadeIndex, uint(3));
|
||||||
gl_Position = sceneData.lightViewProjCascades[ci] * worldPos;
|
gl_Position = sceneData.lightViewProjCascades[ci] * worldPos;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,10 +10,18 @@ inline constexpr bool kUseValidationLayers = true;
|
|||||||
// Shadow mapping configuration
|
// Shadow mapping configuration
|
||||||
inline constexpr int kShadowCascadeCount = 4;
|
inline constexpr int kShadowCascadeCount = 4;
|
||||||
// Maximum shadow distance for CSM in view-space units
|
// Maximum shadow distance for CSM in view-space units
|
||||||
inline constexpr float kShadowCSMFar = 50.0f;
|
inline constexpr float kShadowCSMFar = 400.0f;
|
||||||
// Shadow map resolution used for stabilization (texel snapping). Must match actual image size.
|
// Shadow map resolution used for stabilization (texel snapping). Must match actual image size.
|
||||||
inline constexpr float kShadowMapResolution = 4096.0f;
|
inline constexpr float kShadowMapResolution = 2048.0f;
|
||||||
// Extra XY expansion for cascade footprint (safety against FOV/aspect changes)
|
// Extra XY expansion for cascade footprint (safety against FOV/aspect changes)
|
||||||
inline constexpr float kShadowCascadeRadiusScale = 1.15f;
|
inline constexpr float kShadowCascadeRadiusScale = 2.5f;
|
||||||
// Additive XY margin in world units (light-space) beyond scaled radius
|
// Additive XY margin in world units (light-space) beyond scaled radius
|
||||||
inline constexpr float kShadowCascadeRadiusMargin = 10.0f;
|
inline constexpr float kShadowCascadeRadiusMargin = 40.0f;
|
||||||
|
|
||||||
|
// Clipmap shadow configuration (used when cascades operate in clipmap mode)
|
||||||
|
// Base coverage radius of level 0 around the camera (world units). Each level doubles the radius.
|
||||||
|
inline constexpr float kShadowClipBaseRadius = 20.0f;
|
||||||
|
// Pullback distance of the light eye from the clipmap center along the light direction (world units)
|
||||||
|
inline constexpr float kShadowClipLightPullback = 160.0f;
|
||||||
|
// Additional Z padding for the orthographic frustum along light direction
|
||||||
|
inline constexpr float kShadowClipZPadding = 80.0f;
|
||||||
|
|||||||
@@ -18,6 +18,8 @@
|
|||||||
#include "render/vk_pipelines.h"
|
#include "render/vk_pipelines.h"
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <glm/gtx/transform.hpp>
|
#include <glm/gtx/transform.hpp>
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
#include "render/primitives.h"
|
#include "render/primitives.h"
|
||||||
|
|
||||||
#include "vk_mem_alloc.h"
|
#include "vk_mem_alloc.h"
|
||||||
@@ -126,7 +128,7 @@ void VulkanEngine::init()
|
|||||||
auto imguiPass = std::make_unique<ImGuiPass>();
|
auto imguiPass = std::make_unique<ImGuiPass>();
|
||||||
_renderPassManager->setImGuiPass(std::move(imguiPass));
|
_renderPassManager->setImGuiPass(std::move(imguiPass));
|
||||||
|
|
||||||
const std::string structurePath = _assetManager->modelPath("seoul_high.glb");
|
const std::string structurePath = _assetManager->modelPath("police_office.glb");
|
||||||
const auto structureFile = _assetManager->loadGLTF(structurePath);
|
const auto structureFile = _assetManager->loadGLTF(structurePath);
|
||||||
|
|
||||||
assert(structureFile.has_value());
|
assert(structureFile.has_value());
|
||||||
@@ -317,6 +319,7 @@ void VulkanEngine::draw()
|
|||||||
RGImageHandle hGBufferAlbedo = _renderGraph->import_gbuffer_albedo();
|
RGImageHandle hGBufferAlbedo = _renderGraph->import_gbuffer_albedo();
|
||||||
RGImageHandle hSwapchain = _renderGraph->import_swapchain_image(swapchainImageIndex);
|
RGImageHandle hSwapchain = _renderGraph->import_swapchain_image(swapchainImageIndex);
|
||||||
|
|
||||||
|
// Create a transient shadow depth target (fixed resolution for now)
|
||||||
// Create transient depth targets for cascaded shadow maps
|
// Create transient depth targets for cascaded shadow maps
|
||||||
const VkExtent2D shadowExtent{2048, 2048};
|
const VkExtent2D shadowExtent{2048, 2048};
|
||||||
std::array<RGImageHandle, kShadowCascadeCount> hShadowCascades{};
|
std::array<RGImageHandle, kShadowCascadeCount> hShadowCascades{};
|
||||||
|
|||||||
@@ -35,11 +35,9 @@ void SamplerManager::init(DeviceManager *deviceManager)
|
|||||||
sh.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER;
|
sh.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER;
|
||||||
sh.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER;
|
sh.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER;
|
||||||
sh.compareEnable = VK_FALSE; // manual PCF
|
sh.compareEnable = VK_FALSE; // manual PCF
|
||||||
// Depth shadow maps are single-level; keep base LOD only and avoid mip filtering.
|
|
||||||
sh.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST;
|
|
||||||
sh.maxLod = 0.0f;
|
|
||||||
sh.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE;
|
sh.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE;
|
||||||
vkCreateSampler(_deviceManager->device(), &sh, nullptr, &_shadowLinearClamp);
|
vkCreateSampler(_deviceManager->device(), &sh, nullptr, &_shadowLinearClamp);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SamplerManager::cleanup()
|
void SamplerManager::cleanup()
|
||||||
@@ -56,6 +54,7 @@ void SamplerManager::cleanup()
|
|||||||
vkDestroySampler(_deviceManager->device(), _defaultSamplerLinear, nullptr);
|
vkDestroySampler(_deviceManager->device(), _defaultSamplerLinear, nullptr);
|
||||||
_defaultSamplerLinear = VK_NULL_HANDLE;
|
_defaultSamplerLinear = VK_NULL_HANDLE;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_shadowLinearClamp)
|
if (_shadowLinearClamp)
|
||||||
{
|
{
|
||||||
vkDestroySampler(_deviceManager->device(), _shadowLinearClamp, nullptr);
|
vkDestroySampler(_deviceManager->device(), _shadowLinearClamp, nullptr);
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ public:
|
|||||||
VkSampler defaultNearest() const { return _defaultSamplerNearest; }
|
VkSampler defaultNearest() const { return _defaultSamplerNearest; }
|
||||||
VkSampler shadowLinearClamp() const { return _shadowLinearClamp; }
|
VkSampler shadowLinearClamp() const { return _shadowLinearClamp; }
|
||||||
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
DeviceManager *_deviceManager = nullptr;
|
DeviceManager *_deviceManager = nullptr;
|
||||||
VkSampler _defaultSamplerLinear = VK_NULL_HANDLE;
|
VkSampler _defaultSamplerLinear = VK_NULL_HANDLE;
|
||||||
|
|||||||
@@ -676,15 +676,17 @@ void RenderGraph::execute(VkCommandBuffer cmd)
|
|||||||
if (rec && rec->imageView != VK_NULL_HANDLE)
|
if (rec && rec->imageView != VK_NULL_HANDLE)
|
||||||
{
|
{
|
||||||
depthInfo = vkinit::depth_attachment_info(rec->imageView, VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL);
|
depthInfo = vkinit::depth_attachment_info(rec->imageView, VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL);
|
||||||
if (p.depthAttachment.clearOnLoad)
|
if (p.depthAttachment.clearOnLoad)
|
||||||
{
|
{
|
||||||
depthInfo.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
|
depthInfo.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
|
||||||
depthInfo.clearValue = p.depthAttachment.clear;
|
depthInfo.clearValue = p.depthAttachment.clear;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
depthInfo.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
|
depthInfo.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
|
||||||
}
|
}
|
||||||
|
if (p.depthAttachment.clearOnLoad) depthInfo.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
|
||||||
|
else depthInfo.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
|
||||||
if (!p.depthAttachment.store) depthInfo.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
if (!p.depthAttachment.store) depthInfo.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
||||||
hasDepth = true;
|
hasDepth = true;
|
||||||
if (rec->extent.width && rec->extent.height) chosenExtent = clamp_min(chosenExtent, rec->extent);
|
if (rec->extent.width && rec->extent.height) chosenExtent = clamp_min(chosenExtent, rec->extent);
|
||||||
|
|||||||
@@ -19,8 +19,7 @@ public:
|
|||||||
RGImageHandle drawHandle,
|
RGImageHandle drawHandle,
|
||||||
RGImageHandle gbufferPosition,
|
RGImageHandle gbufferPosition,
|
||||||
RGImageHandle gbufferNormal,
|
RGImageHandle gbufferNormal,
|
||||||
RGImageHandle gbufferAlbedo,
|
RGImageHandle gbufferAlbedo, std::span<RGImageHandle> shadowCascades);
|
||||||
std::span<RGImageHandle> shadowCascades);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
EngineContext *_context = nullptr;
|
EngineContext *_context = nullptr;
|
||||||
|
|||||||
@@ -45,11 +45,11 @@ void ShadowPass::init(EngineContext *context)
|
|||||||
b.set_cull_mode(VK_CULL_MODE_BACK_BIT, VK_FRONT_FACE_CLOCKWISE);
|
b.set_cull_mode(VK_CULL_MODE_BACK_BIT, VK_FRONT_FACE_CLOCKWISE);
|
||||||
b.set_multisampling_none();
|
b.set_multisampling_none();
|
||||||
b.disable_blending();
|
b.disable_blending();
|
||||||
// Forward-Z for shadow maps only (engine uses reversed-Z elsewhere)
|
|
||||||
// We clear depth to 1.0 and use LESS_OR_EQUAL so the nearest depth wins.
|
b.enable_depthtest(true, VK_COMPARE_OP_GREATER_OR_EQUAL);
|
||||||
b.enable_depthtest(true, VK_COMPARE_OP_LESS_OR_EQUAL);
|
|
||||||
b.set_depth_format(VK_FORMAT_D32_SFLOAT);
|
b.set_depth_format(VK_FORMAT_D32_SFLOAT);
|
||||||
|
|
||||||
|
// Static depth bias to help with surface acne (tune later)
|
||||||
b._rasterizer.depthBiasEnable = VK_TRUE;
|
b._rasterizer.depthBiasEnable = VK_TRUE;
|
||||||
b._rasterizer.depthBiasConstantFactor = 2.0f;
|
b._rasterizer.depthBiasConstantFactor = 2.0f;
|
||||||
b._rasterizer.depthBiasSlopeFactor = 2.0f;
|
b._rasterizer.depthBiasSlopeFactor = 2.0f;
|
||||||
@@ -85,8 +85,7 @@ void ShadowPass::register_graph(RenderGraph *graph, std::span<RGImageHandle> cas
|
|||||||
RGPassType::Graphics,
|
RGPassType::Graphics,
|
||||||
[shadowDepth](RGPassBuilder &builder, EngineContext *ctx)
|
[shadowDepth](RGPassBuilder &builder, EngineContext *ctx)
|
||||||
{
|
{
|
||||||
// Forward-Z in shadow pass: clear depth to 1.0 (far)
|
VkClearValue clear{}; clear.depthStencil = {0.f, 0};
|
||||||
VkClearValue clear{}; clear.depthStencil = {1.f, 0};
|
|
||||||
builder.write_depth(shadowDepth, true, clear);
|
builder.write_depth(shadowDepth, true, clear);
|
||||||
|
|
||||||
// Ensure index/vertex buffers are tracked as reads (like Geometry)
|
// Ensure index/vertex buffers are tracked as reads (like Geometry)
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
#include "vk_swapchain.h"
|
#include "vk_swapchain.h"
|
||||||
#include "core/engine_context.h"
|
#include "core/engine_context.h"
|
||||||
|
#include "core/config.h"
|
||||||
#include "glm/gtx/transform.hpp"
|
#include "glm/gtx/transform.hpp"
|
||||||
#include <glm/gtc/matrix_transform.hpp>
|
#include <glm/gtc/matrix_transform.hpp>
|
||||||
|
|
||||||
@@ -106,123 +107,64 @@ void SceneManager::update_scene()
|
|||||||
sceneData.proj = projection;
|
sceneData.proj = projection;
|
||||||
sceneData.viewproj = projection * view;
|
sceneData.viewproj = projection * view;
|
||||||
|
|
||||||
// Build cascaded directional light view-projection matrices
|
// Clipmap shadow setup (directional). Each level i covers a square region
|
||||||
|
// around the camera in the light's XY plane with radius R_i = R0 * 2^i.
|
||||||
|
// The region center is snapped to the light-space texel grid for stability.
|
||||||
{
|
{
|
||||||
using namespace glm;
|
const glm::mat4 invView = glm::inverse(view);
|
||||||
const vec3 camPos = vec3(inverse(view)[3]);
|
const glm::vec3 camPos = glm::vec3(invView[3]);
|
||||||
// Use light-ray direction (from light to scene).
|
|
||||||
// Shaders compute per-fragment L as -sunlightDirection (vector to light).
|
|
||||||
vec3 L = normalize(vec3(sceneData.sunlightDirection));
|
|
||||||
if (!glm::all(glm::isfinite(L)) || glm::length2(L) < 1e-10f)
|
|
||||||
L = glm::vec3(0.0f, -1.0f, 0.0f);
|
|
||||||
|
|
||||||
const glm::vec3 worldUp(0, 1, 0), altUp(0, 0, 1);
|
glm::vec3 L = glm::normalize(-glm::vec3(sceneData.sunlightDirection));
|
||||||
glm::vec3 upPick = (std::abs(glm::dot(worldUp, L)) > 0.99f) ? altUp : worldUp;
|
if (glm::length(L) < 1e-5f) L = glm::vec3(0.0f, -1.0f, 0.0f);
|
||||||
glm::vec3 right = glm::normalize(glm::cross(upPick, L));
|
const glm::vec3 worldUp(0.0f, 1.0f, 0.0f);
|
||||||
glm::vec3 up = glm::normalize(glm::cross(L, right));
|
glm::vec3 right = glm::cross(L, worldUp);
|
||||||
|
if (glm::length2(right) < 1e-6f) right = glm::vec3(1, 0, 0);
|
||||||
|
right = glm::normalize(right);
|
||||||
|
glm::vec3 up = glm::normalize(glm::cross(right, L));
|
||||||
|
|
||||||
const float csmFar = kShadowCSMFar;
|
auto level_radius = [](int level) {
|
||||||
const float lambda = 0.5f;
|
return kShadowClipBaseRadius * powf(2.0f, float(level));
|
||||||
const int cascades = kShadowCascadeCount;
|
|
||||||
|
|
||||||
float splits[4] = {0, 0, 0, 0};
|
|
||||||
for (int i = 1; i <= cascades; ++i)
|
|
||||||
{
|
|
||||||
float p = (float) i / (float) cascades;
|
|
||||||
float logd = nearPlane * std::pow(csmFar / nearPlane, p);
|
|
||||||
float lind = nearPlane + (csmFar - nearPlane) * p;
|
|
||||||
float d = glm::mix(lind, logd, lambda);
|
|
||||||
if (i - 1 < 4) splits[i - 1] = d;
|
|
||||||
}
|
|
||||||
sceneData.cascadeSplitsView = vec4(splits[0], splits[1], splits[2], splits[3]);
|
|
||||||
|
|
||||||
mat4 invView = inverse(view);
|
|
||||||
|
|
||||||
float baseWorldTexel = 0.0f;
|
|
||||||
|
|
||||||
auto buildCascade = [&](int idx, float nearD, float farD) -> mat4 {
|
|
||||||
float tanHalf = tanf(fov * 0.5f);
|
|
||||||
float yf = tanHalf * farD;
|
|
||||||
float xf = yf * aspect;
|
|
||||||
float rStable = 1.05f * sqrtf(xf * xf + yf * yf);
|
|
||||||
|
|
||||||
float texelWorld;
|
|
||||||
if (idx == 0)
|
|
||||||
{
|
|
||||||
baseWorldTexel = (2.0f * rStable) / kShadowMapResolution;
|
|
||||||
baseWorldTexel = powf(2.0f, ceilf(log2f(baseWorldTexel)));
|
|
||||||
texelWorld = baseWorldTexel;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
texelWorld = baseWorldTexel * (1 << idx);
|
|
||||||
rStable = 0.5f * texelWorld * kShadowMapResolution;
|
|
||||||
}
|
|
||||||
|
|
||||||
vec3 cornersV[8]; {
|
|
||||||
float tanHalfFov = tanf(fov * 0.5f);
|
|
||||||
float yn = tanHalfFov * nearD, xn = yn * aspect;
|
|
||||||
float yf = tanHalfFov * farD, xf = yf * aspect;
|
|
||||||
cornersV[0] = {-xn, -yn, -nearD};
|
|
||||||
cornersV[1] = {xn, -yn, -nearD};
|
|
||||||
cornersV[2] = {xn, yn, -nearD};
|
|
||||||
cornersV[3] = {-xn, yn, -nearD};
|
|
||||||
cornersV[4] = {-xf, -yf, -farD};
|
|
||||||
cornersV[5] = {xf, -yf, -farD};
|
|
||||||
cornersV[6] = {xf, yf, -farD};
|
|
||||||
cornersV[7] = {-xf, yf, -farD};
|
|
||||||
}
|
|
||||||
mat4 invView = inverse(view);
|
|
||||||
vec3 cornersW[8], centerWS(0);
|
|
||||||
for (int i = 0; i < 8; ++i)
|
|
||||||
{
|
|
||||||
cornersW[i] = vec3(invView * vec4(cornersV[i], 1));
|
|
||||||
centerWS += cornersW[i];
|
|
||||||
}
|
|
||||||
centerWS *= 1.0f / 8.0f;
|
|
||||||
|
|
||||||
float lightDist = rStable + 50.0f;
|
|
||||||
vec3 lightPos = centerWS - L * lightDist;
|
|
||||||
mat4 viewLight = lookAtRH(lightPos, centerWS, up);
|
|
||||||
|
|
||||||
vec2 centerLS = vec2(viewLight * vec4(centerWS, 1));
|
|
||||||
vec2 snapped = floor(centerLS / texelWorld) * texelWorld;
|
|
||||||
vec2 deltaLS = snapped - centerLS;
|
|
||||||
vec3 shiftWS = right * deltaLS.x + up * deltaLS.y;
|
|
||||||
vec3 centerSnapped = centerWS + shiftWS;
|
|
||||||
|
|
||||||
lightPos = centerSnapped - L * lightDist;
|
|
||||||
viewLight = lookAtRH(lightPos, centerSnapped, up);
|
|
||||||
|
|
||||||
float radius = ceil(rStable / texelWorld) * texelWorld;
|
|
||||||
vec2 cLS = vec2(viewLight * vec4(centerSnapped, 1));
|
|
||||||
float left = cLS.x - radius, rightE = cLS.x + radius;
|
|
||||||
float bottom = cLS.y - radius, top = cLS.y + radius;
|
|
||||||
|
|
||||||
float minZ = 1e9f, maxZ = -1e9f;
|
|
||||||
for (int i = 0; i < 8; ++i)
|
|
||||||
{
|
|
||||||
vec3 p = vec3(viewLight * vec4(cornersW[i], 1));
|
|
||||||
minZ = std::min(minZ, p.z);
|
|
||||||
maxZ = std::max(maxZ, p.z);
|
|
||||||
}
|
|
||||||
float sliceLen = farD - nearD;
|
|
||||||
float zPad = std::max(10.0f, 0.2f * sliceLen);
|
|
||||||
float casterExtrude = 100.0f;
|
|
||||||
float nearLS = 0.01f;
|
|
||||||
float farLS = -minZ + zPad + casterExtrude;
|
|
||||||
|
|
||||||
mat4 projLight = orthoRH_ZO(left, rightE, bottom, top, nearLS, farLS);
|
|
||||||
return projLight * viewLight;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
for (int i = 0; i < cascades; ++i)
|
// Keep a copy of level radii in cascadeSplitsView for debug/visualization
|
||||||
|
sceneData.cascadeSplitsView = glm::vec4(
|
||||||
|
level_radius(0), level_radius(1), level_radius(2), level_radius(3));
|
||||||
|
|
||||||
|
for (int ci = 0; ci < kShadowCascadeCount; ++ci)
|
||||||
{
|
{
|
||||||
float nearD = (i == 0) ? nearPlane : splits[i - 1];
|
const float radius = level_radius(ci);
|
||||||
float farD = splits[i];
|
|
||||||
sceneData.lightViewProjCascades[i] = buildCascade(i, nearD, farD);
|
// Compute camera coordinates in light's orthonormal basis (world -> light XY)
|
||||||
|
const float u = glm::dot(camPos, right);
|
||||||
|
const float v = glm::dot(camPos, up);
|
||||||
|
|
||||||
|
// Texel size in light-space at this level
|
||||||
|
const float texel = (2.0f * radius) / float(kShadowMapResolution);
|
||||||
|
const float uSnapped = floorf(u / texel) * texel;
|
||||||
|
const float vSnapped = floorf(v / texel) * texel;
|
||||||
|
const float du = uSnapped - u;
|
||||||
|
const float dv = vSnapped - v;
|
||||||
|
|
||||||
|
// World-space snapped center of this clip level
|
||||||
|
const glm::vec3 center = camPos + right * du + up * dv;
|
||||||
|
|
||||||
|
// Build light view matrix looking at the snapped center
|
||||||
|
const glm::vec3 eye = center - L * kShadowClipLightPullback;
|
||||||
|
const glm::mat4 V = glm::lookAtRH(eye, center, up);
|
||||||
|
|
||||||
|
// Conservative Z range along light direction
|
||||||
|
const float zNear = 0.1f;
|
||||||
|
const float zFar = kShadowClipLightPullback + kShadowClipZPadding;
|
||||||
|
|
||||||
|
const glm::mat4 P = glm::orthoRH_ZO(-radius, radius, -radius, radius, zNear, zFar);
|
||||||
|
const glm::mat4 lightVP = P * V;
|
||||||
|
|
||||||
|
sceneData.lightViewProjCascades[ci] = lightVP;
|
||||||
|
if (ci == 0)
|
||||||
|
{
|
||||||
|
sceneData.lightViewProj = lightVP;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
sceneData.lightViewProj = sceneData.lightViewProjCascades[0];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto end = std::chrono::system_clock::now();
|
auto end = std::chrono::system_clock::now();
|
||||||
|
|||||||
Reference in New Issue
Block a user