8#include <glm/gtc/matrix_transform.hpp>
13ShadowSchemeSettings g_shadowSchemeSettings{};
15struct LocalPageCacheEntry {
19 float x = 0.f,
y = 0.f,
z = 0.f;
27struct LocalPageCache {
31LocalPageCache g_localPageCache{};
33bool equalsIgnoreCase(std::string_view
a, std::string_view
b) {
34 if (
a.size() !=
b.size())
return false;
35 for (
size_t i = 0; i <
a.size(); ++i) {
36 const char ca = (
a[i] >=
'A' &&
a[i] <=
'Z') ?
char(
a[i] -
'A' +
'a') :
a[i];
37 const char cb = (
b[i] >=
'A' &&
b[i] <=
'Z') ?
char(
b[i] -
'A' +
'a') :
b[i];
38 if (ca != cb)
return false;
43glm::vec3 safeNormalize(
const glm::vec3&
v,
const glm::vec3& fallback) {
44 const float len = glm::length(
v);
45 return len > 1e-6f ?
v / len : fallback;
48bool lightParamsMatchCache(
const Light3D::Data&
d,
const LocalPageCacheEntry& e,
float resolvedBias) {
49 constexpr float kEps = 1e-4f;
50 return e.occupied && e.light == &
d && std::abs(e.x -
d.x) < kEps && std::abs(e.y -
d.y) < kEps &&
51 std::abs(e.z -
d.z) < kEps && std::abs(e.dx -
d.dx) < kEps && std::abs(e.dy -
d.dy) < kEps &&
52 std::abs(e.dz -
d.dz) < kEps && std::abs(e.radius -
d.radius) < kEps &&
53 std::abs(e.spotAngleDeg -
d.spotAngleDeg) < kEps &&
54 std::abs(e.shadowStrength -
d.shadowStrength) < kEps && std::abs(e.bias - resolvedBias) < kEps;
57void storeCacheEntry(LocalPageCacheEntry& e, Light3D::Data*
d,
const glm::mat4& vp,
float bias) {
58 e = LocalPageCacheEntry{};
59 e.occupied =
d !=
nullptr;
71 e.spotAngleDeg =
d->spotAngleDeg;
72 e.shadowStrength =
d->shadowStrength;
75int findCachedSlot(Light3D::Data*
light) {
76 if (!
light)
return -1;
78 if (g_localPageCache.slots[
s].occupied && g_localPageCache.slots[
s].light ==
light)
return s;
88 for (
auto& slot : g_localPageCache.slots) slot = LocalPageCacheEntry{};
93 if (equalsIgnoreCase(
name,
"none") || equalsIgnoreCase(
name,
"off"))
95 if (equalsIgnoreCase(
name,
"csm") || equalsIgnoreCase(
name,
"cascaded") ||
96 equalsIgnoreCase(
name,
"directional"))
98 if (equalsIgnoreCase(
name,
"perspective") || equalsIgnoreCase(
name,
"spot"))
100 if (equalsIgnoreCase(
name,
"cube") || equalsIgnoreCase(
name,
"cubemap") ||
101 equalsIgnoreCase(
name,
"point"))
116 return "perspective";
127 else if (lightType ==
"spot")
129 else if (lightType ==
"point")
151 float outerAngleDeg) {
152 const glm::vec3 forward = safeNormalize(
direction, glm::vec3(0.f, -1.f, 0.f));
153 glm::vec3
up(0.f, 1.f, 0.f);
154 if (std::abs(glm::dot(forward,
up)) > 0.95f)
up = glm::vec3(0.f, 0.f, 1.f);
155 const float farZ = std::max(
range, 1e-2f);
156 const float nearZ = std::max(farZ * 0.01f, 0.05f);
157 const float fovY = std::clamp(outerAngleDeg, 0.1f, 89.f) * 2.f * 0.017453292519943295f;
164 if (faceIndex < 0 || faceIndex > 5) {
166 "Point shadow face must be 0..5",
169 static const glm::vec3 kForward[6] = {
170 {1.f, 0.f, 0.f}, {-1.f, 0.f, 0.f}, {0.f, 1.f, 0.f},
171 {0.f, -1.f, 0.f}, {0.f, 0.f, 1.f}, {0.f, 0.f, -1.f},
173 static const glm::vec3 kUp[6] = {
174 {0.f, -1.f, 0.f}, {0.f, -1.f, 0.f}, {0.f, 0.f, 1.f},
175 {0.f, 0.f, -1.f}, {0.f, -1.f, 0.f}, {0.f, -1.f, 0.f},
177 const float farZ = std::max(
range, 1e-2f);
178 const float nearZ = std::max(farZ * 0.01f, 0.05f);
189 const float dist = std::max(glm::length(
pos -
view.eye), 0.1f);
191 const float atten = std::clamp(1.f - dist / (
range * 2.f + dist), 0.f, 1.f);
192 const float coverage = std::clamp(
range / dist, 0.f, 4.f);
193 const glm::vec4
clip =
view.viewProj * glm::vec4(
pos, 1.f);
195 if (
clip.w > 1e-5f) {
196 const float invW = 1.f /
clip.w;
197 const float ndcX =
clip.x * invW;
198 const float ndcY =
clip.y * invW;
199 const float ndcZ =
clip.z * invW;
200 const float pad = coverage * 0.5f;
201 if (std::abs(ndcX) < 1.f +
pad && std::abs(ndcY) < 1.f +
pad && ndcZ > -
pad &&
203 const float edge = std::max(std::abs(ndcX), std::abs(ndcY));
204 frustum = std::clamp(1.f - 0.5f *
edge, 0.15f, 1.f);
212 if (hadSlot)
score *= (1.f + std::max(
settings.hysteresisBonus, 0.f));
218 Light3D::Data*& directionalCaster, std::vector<LocalShadowSlot>& localSlots) {
219 directionalCaster =
nullptr;
221 float bestDirI = -1.f;
223 struct SpotCandidate {
229 std::vector<SpotCandidate> spots;
230 spots.reserve(
lights.size());
232 for (
size_t i = 0; i <
lights.size(); ++i) {
236 if (!
d->enabled || !
d->castShadow ||
d->volumetricOnly)
continue;
237 const bool isPoint = i < isPointFlags.size() ? isPointFlags[i] : (
d->type !=
"dir");
239 if (!resolved.ok())
continue;
244 if (!
settings.enableDirectionalCsm)
continue;
245 if (
d->intensity > bestDirI) {
246 bestDirI =
d->intensity;
247 directionalCaster =
d;
252 if (!
settings.enableSpotPerspective)
continue;
253 const int prev = findCachedSlot(
d);
256 spots.push_back({
d, i,
score, prev});
259 settings.maxPointShadowCasters > 0) {
264 std::stable_sort(spots.begin(), spots.end(),
265 [](
const SpotCandidate&
a,
const SpotCandidate&
b) { return a.score > b.score; });
267 if (maxSpots == 0 || spots.empty()) {
272 const size_t winnerCount = std::min(
size_t(maxSpots), spots.size());
273 std::vector<bool> winnerTaken(winnerCount,
false);
280 for (
size_t w = 0;
w < winnerCount; ++
w) {
281 const int prev = spots[
w].prevSlot;
282 if (prev < 0 || prev >= maxSpots)
continue;
283 if (assigned[prev])
continue;
284 assigned[prev] = spots[
w].light;
285 assignedScore[prev] = spots[
w].score;
286 assignedIndex[prev] = spots[
w].index;
287 winnerTaken[
w] =
true;
290 for (
size_t w = 0;
w < winnerCount; ++
w) {
291 if (winnerTaken[
w])
continue;
293 for (
int s = 0;
s < maxSpots; ++
s) {
302 for (
int s = 0;
s < maxSpots; ++
s) {
303 if (assignedScore[
s] < worst) {
304 worst = assignedScore[
s];
308 if (victim < 0 || assignedScore[victim] >= spots[
w].
score)
continue;
311 assigned[slot] = spots[
w].light;
312 assignedScore[slot] = spots[
w].score;
313 assignedIndex[slot] = spots[
w].index;
314 winnerTaken[
w] =
true;
317 struct UpdateCandidate {
322 std::vector<UpdateCandidate> updates;
323 updates.reserve(
size_t(maxSpots));
327 for (
int&
v : slotToLocalIndex)
v = -1;
329 for (
int s = 0;
s < maxSpots; ++
s) {
334 const glm::vec3
dir = safeNormalize(glm::vec3(
d->dx,
d->dy,
d->dz), glm::vec3(0.f, -1.f, 0.f));
346 slot.
score = assignedScore[
s];
349 const LocalPageCacheEntry& prev = g_localPageCache.slots[
s];
350 const bool sameLight = prev.occupied && prev.light ==
d;
351 const bool paramsSame = sameLight && lightParamsMatchCache(*
d, prev,
bias);
355 slot.
bias = prev.bias;
361 d->shadowLocalSlot =
s;
363 slotToLocalIndex[
s] = int(localSlots.size());
364 localSlots.push_back(slot);
367 const int updateBudget =
369 std::stable_sort(updates.begin(), updates.end(), [](
const UpdateCandidate&
a,
const UpdateCandidate&
b) {
370 if (a.priority != b.priority) return a.priority > b.priority;
371 return a.score > b.score;
373 for (
size_t i =
size_t(updateBudget); i < updates.size(); ++i) {
374 const int phys = updates[i].slot;
375 const int li = slotToLocalIndex[phys];
376 if (li < 0)
continue;
379 const LocalPageCacheEntry& prev = g_localPageCache.slots[phys];
380 if (prev.occupied && prev.light == slot.
light) {
382 slot.
bias = prev.bias;
383 nextCache[phys].lightVP = prev.lightVP;
384 nextCache[phys].bias = prev.bias;
Stable, structured diagnostics shared by engine modules.
std::vector< eve::ProcgenProbeDesc > lights
std::array< float, 3 > position
RoadLaneDirection direction
LocalPageCacheEntry slots[ShadowConfig::kLocalSlots]
TerrainThermalSettings settings
static Diagnostic error(DiagnosticCode code, std::string message, std::string path={}, DiagnosticDetails details={}, std::string source={})
Construct an error diagnostic with the standard error severity.
Move-only operation result carrying either a value or Status.
static Result success(T value)
Construct a successful result owning value.
static Result failure(Status status)
Construct a failed result from a structured status.
卡牌游戏 UI 工具模块:工厂 + 脚本绑定入口。 功能参考 ycarowr/UiCard:扇形手牌布局、抽牌/洗牌、悬浮放大、拖拽到落牌区、 敌方手牌(背面/偷看)、费用不足置灰,以及可实时调节的布局...
void selectShadowCasters(const std::vector< Light3D::Data * > &lights, const std::vector< bool > &isPointFlags, const ShadowSchemeSettings &settings, const ShadowPagingView &view, Light3D::Data *&directionalCaster, std::vector< LocalShadowSlot > &localSlots)
Select directional CSM caster + paged local spot slots for one frame.
glm::mat4 perspectiveVulkanRH_ZO(float fovyRad, float aspect, float zNear, float zFar)
Right-handed, zero-to-one depth perspective for Vulkan swapchains.
glm::mat4 buildSpotShadowVP(const glm::vec3 &position, const glm::vec3 &direction, float range, float outerAngleDeg)
Build a perspective light VP for a spot caster (Vulkan RH, ZO depth).
float scoreSpotShadowCandidate(const Light3D::Data &light, const ShadowPagingView &view, const ShadowSchemeSettings &settings, bool hadSlot)
Score a spot caster for local-shadow paging (higher = more important).
void resetShadowLocalPageCache()
Clear sticky local-page cache (tests / scheme resets).
Result< ShadowMethod > resolveShadowMethod(std::string_view lightType, ShadowMethod requested)
Resolve Auto / validate an explicit method for a concrete light type.
const char * shadowMethodName(ShadowMethod method)
Canonical lowercase name for a ShadowMethod (auto, none, csm, …).
Result< glm::mat4 > buildPointShadowFaceVP(const glm::vec3 &position, int faceIndex, float range)
Build one cube-face light VP for a point caster.
ShadowMethod
How a Light3D produces real-time shadows. Auto resolves per light type (dir→CSM, spot→perspective,...
Result< ShadowMethod > parseShadowMethod(std::string_view name)
Parse a script/editor method name into ShadowMethod.
int shadowLocalSlot
Frame-transient local shadow atlas slot (0..kLocalSlots-1), or -1. Written by selectShadowCasters; pa...
One allocated local (spot/point-face) shadow slot for the frame. @ownership Ephemeral CPU descriptor;...
bool needsUpdate
When false, skip the shadow pass and keep the cached depth layer.
static constexpr int kLocalSlots
Extra depth-array layers for spot (perspective) local shadows.
static constexpr int kCascades
Camera context used to score local shadow candidates for atlas paging. @ownership Ephemeral; valid on...
Process-wide defaults for creating shadows per light family.
static ShadowSchemeSettings & current()
Borrow the process-wide settings object.