载入中...
搜索中...
未找到
ShadowScheme.cpp
浏览该文件的文档.
2
3#include "common/Diagnostic.h"
5
6#include <algorithm>
7#include <cmath>
8#include <glm/gtc/matrix_transform.hpp>
9
10namespace eve::graphics {
11namespace {
12
13ShadowSchemeSettings g_shadowSchemeSettings{};
14
15struct LocalPageCacheEntry {
16 Light3D::Data* light = nullptr;
17 glm::mat4 lightVP{1.f};
18 float bias = 0.002f;
19 float x = 0.f, y = 0.f, z = 0.f;
20 float dx = 0.f, dy = 0.f, dz = 0.f;
21 float radius = 0.f;
22 float spotAngleDeg = 0.f;
23 float shadowStrength = 1.f;
24 bool occupied = false;
25};
26
27struct LocalPageCache {
28 LocalPageCacheEntry slots[ShadowConfig::kLocalSlots]{};
29};
30
31LocalPageCache g_localPageCache{};
32
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;
39 }
40 return true;
41}
42
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;
46}
47
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;
55}
56
57void storeCacheEntry(LocalPageCacheEntry& e, Light3D::Data* d, const glm::mat4& vp, float bias) {
58 e = LocalPageCacheEntry{};
59 e.occupied = d != nullptr;
60 e.light = d;
61 e.lightVP = vp;
62 e.bias = bias;
63 if (!d) return;
64 e.x = d->x;
65 e.y = d->y;
66 e.z = d->z;
67 e.dx = d->dx;
68 e.dy = d->dy;
69 e.dz = d->dz;
70 e.radius = d->radius;
71 e.spotAngleDeg = d->spotAngleDeg;
72 e.shadowStrength = d->shadowStrength;
73}
74
75int findCachedSlot(Light3D::Data* light) {
76 if (!light) return -1;
77 for (int s = 0; s < ShadowConfig::kLocalSlots; ++s) {
78 if (g_localPageCache.slots[s].occupied && g_localPageCache.slots[s].light == light) return s;
79 }
80 return -1;
81}
82
83} // namespace
84
85ShadowSchemeSettings& ShadowSchemeSettings::current() { return g_shadowSchemeSettings; }
86
88 for (auto& slot : g_localPageCache.slots) slot = LocalPageCacheEntry{};
89}
90
92 if (equalsIgnoreCase(name, "auto")) return Result<ShadowMethod>::success(ShadowMethod::Auto);
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"))
104 Diagnostic::error(DiagnosticCode::InvalidArgument, "Unknown shadow method", "graphics.shadow"));
105}
106
108 switch (method) {
110 return "auto";
112 return "none";
114 return "csm";
116 return "perspective";
118 return "cube";
119 }
120 return "none";
121}
122
123Result<ShadowMethod> resolveShadowMethod(std::string_view lightType, ShadowMethod requested) {
124 ShadowMethod method = requested;
125 if (method == ShadowMethod::Auto) {
126 if (lightType == "dir") method = ShadowMethod::CascadedDirectional;
127 else if (lightType == "spot")
129 else if (lightType == "point")
131 else
133 }
135 if (lightType == "dir" && method != ShadowMethod::CascadedDirectional) {
137 DiagnosticCode::Unsupported, "Directional lights only support csm shadows", "graphics.shadow"));
138 }
139 if (lightType == "spot" && method != ShadowMethod::PerspectiveSpot) {
141 DiagnosticCode::Unsupported, "Spot lights only support perspective shadows", "graphics.shadow"));
142 }
143 if (lightType == "point" && method != ShadowMethod::CubePoint) {
145 DiagnosticCode::Unsupported, "Point lights only support cube shadows", "graphics.shadow"));
146 }
148}
149
150glm::mat4 buildSpotShadowVP(const glm::vec3& position, const glm::vec3& direction, float range,
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;
158 const glm::mat4 view = glm::lookAtRH(position, position + forward, up);
159 const glm::mat4 proj = perspectiveVulkanRH_ZO(fovY, 1.f, nearZ, farZ);
160 return proj * view;
161}
162
163Result<glm::mat4> buildPointShadowFaceVP(const glm::vec3& position, int faceIndex, float range) {
164 if (faceIndex < 0 || faceIndex > 5) {
166 "Point shadow face must be 0..5",
167 "graphics.shadow"));
168 }
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},
172 };
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},
176 };
177 const float farZ = std::max(range, 1e-2f);
178 const float nearZ = std::max(farZ * 0.01f, 0.05f);
179 const glm::mat4 view = glm::lookAtRH(position, position + kForward[faceIndex], kUp[faceIndex]);
180 const glm::mat4 proj = perspectiveVulkanRH_ZO(1.5707963267948966f, 1.f, nearZ, farZ);
182}
183
185 const ShadowSchemeSettings& settings, bool hadSlot) {
186 float score = std::max(light.intensity, 0.f);
187 if (settings.enableLocalPaging && view.valid) {
188 const glm::vec3 pos(light.x, light.y, light.z);
189 const float dist = std::max(glm::length(pos - view.eye), 0.1f);
190 const float range = std::max(light.radius, 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);
194 float frustum = 0.02f;
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 &&
202 ndcZ < 1.f + 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);
205 } else {
206 frustum = 0.05f;
207 }
208 }
209 score = light.intensity * atten * (0.35f + 0.65f * std::min(coverage, 1.5f) / 1.5f) *
210 (0.25f + 0.75f * frustum);
211 }
212 if (hadSlot) score *= (1.f + std::max(settings.hysteresisBonus, 0.f));
213 return score;
214}
215
216void selectShadowCasters(const std::vector<Light3D::Data*>& lights, const std::vector<bool>& isPointFlags,
218 Light3D::Data*& directionalCaster, std::vector<LocalShadowSlot>& localSlots) {
219 directionalCaster = nullptr;
220 localSlots.clear();
221 float bestDirI = -1.f;
222
223 struct SpotCandidate {
224 Light3D::Data* light = nullptr;
225 size_t index = 0;
226 float score = 0.f;
227 int prevSlot = -1;
228 };
229 std::vector<SpotCandidate> spots;
230 spots.reserve(lights.size());
231
232 for (size_t i = 0; i < lights.size(); ++i) {
233 Light3D::Data* d = lights[i];
234 if (!d) continue;
235 d->shadowLocalSlot = -1;
236 if (!d->enabled || !d->castShadow || d->volumetricOnly) continue;
237 const bool isPoint = i < isPointFlags.size() ? isPointFlags[i] : (d->type != "dir");
238 auto resolved = resolveShadowMethod(d->type, static_cast<ShadowMethod>(d->shadowMethod));
239 if (!resolved.ok()) continue;
240 const ShadowMethod method = resolved.value();
241 if (method == ShadowMethod::None) continue;
242
244 if (!settings.enableDirectionalCsm) continue;
245 if (d->intensity > bestDirI) {
246 bestDirI = d->intensity;
247 directionalCaster = d;
248 }
249 continue;
250 }
251 if (d->type == "spot" && method == ShadowMethod::PerspectiveSpot) {
252 if (!settings.enableSpotPerspective) continue;
253 const int prev = findCachedSlot(d);
254 const float score = scoreSpotShadowCandidate(*d, view, settings, prev >= 0);
255 if (settings.enableLocalPaging && view.valid && score < settings.minPageScore) continue;
256 spots.push_back({d, i, score, prev});
257 }
258 if (d->type == "point" && method == ShadowMethod::CubePoint && settings.enablePointCube &&
259 settings.maxPointShadowCasters > 0) {
260 // Reserved for the cube-atlas pass.
261 }
262 }
263
264 std::stable_sort(spots.begin(), spots.end(),
265 [](const SpotCandidate& a, const SpotCandidate& b) { return a.score > b.score; });
266 const int maxSpots = std::max(0, std::min(settings.maxSpotShadowCasters, ShadowConfig::kLocalSlots));
267 if (maxSpots == 0 || spots.empty()) {
269 return;
270 }
271
272 const size_t winnerCount = std::min(size_t(maxSpots), spots.size());
273 std::vector<bool> winnerTaken(winnerCount, false);
274
276 float assignedScore[ShadowConfig::kLocalSlots]{};
277 size_t assignedIndex[ShadowConfig::kLocalSlots]{};
278
279 // Sticky: keep previous physical slot when that light is still a winner.
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;
288 }
289
290 for (size_t w = 0; w < winnerCount; ++w) {
291 if (winnerTaken[w]) continue;
292 int slot = -1;
293 for (int s = 0; s < maxSpots; ++s) {
294 if (!assigned[s]) {
295 slot = s;
296 break;
297 }
298 }
299 if (slot < 0) {
300 int victim = -1;
301 float worst = 1e30f;
302 for (int s = 0; s < maxSpots; ++s) {
303 if (assignedScore[s] < worst) {
304 worst = assignedScore[s];
305 victim = s;
306 }
307 }
308 if (victim < 0 || assignedScore[victim] >= spots[w].score) continue;
309 slot = victim;
310 }
311 assigned[slot] = spots[w].light;
312 assignedScore[slot] = spots[w].score;
313 assignedIndex[slot] = spots[w].index;
314 winnerTaken[w] = true;
315 }
316
317 struct UpdateCandidate {
318 int slot = -1;
319 int priority = 0;
320 float score = 0.f;
321 };
322 std::vector<UpdateCandidate> updates;
323 updates.reserve(size_t(maxSpots));
324 LocalPageCacheEntry nextCache[ShadowConfig::kLocalSlots]{};
325 // Map physical slot → localSlots index for later update-budget edits.
326 int slotToLocalIndex[ShadowConfig::kLocalSlots];
327 for (int& v : slotToLocalIndex) v = -1;
328
329 for (int s = 0; s < maxSpots; ++s) {
330 Light3D::Data* d = assigned[s];
331 if (!d) continue;
332
333 const float bias = d->shadowBias > 0.f ? d->shadowBias : settings.defaultSpotBias;
334 const glm::vec3 dir = safeNormalize(glm::vec3(d->dx, d->dy, d->dz), glm::vec3(0.f, -1.f, 0.f));
335 const glm::mat4 vp =
336 buildSpotShadowVP(glm::vec3(d->x, d->y, d->z), dir, std::max(d->radius, 0.1f), d->spotAngleDeg);
337
338 LocalShadowSlot slot;
339 slot.light = d;
342 slot.lightIndex = int(assignedIndex[s]);
343 slot.lightVP = vp;
344 slot.bias = bias;
345 slot.strength = d->shadowStrength;
346 slot.score = assignedScore[s];
347 slot.needsUpdate = true;
348
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);
352 if (paramsSame) {
353 slot.needsUpdate = false;
354 slot.lightVP = prev.lightVP;
355 slot.bias = prev.bias;
356 } else {
357 int priority = sameLight ? 2 : 3;
358 updates.push_back({s, priority, slot.score});
359 }
360
361 d->shadowLocalSlot = s;
362 storeCacheEntry(nextCache[s], d, slot.lightVP, slot.bias);
363 slotToLocalIndex[s] = int(localSlots.size());
364 localSlots.push_back(slot);
365 }
366
367 const int updateBudget =
368 std::max(0, std::min(settings.maxLocalUpdatesPerFrame, ShadowConfig::kLocalSlots));
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;
372 });
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;
377 LocalShadowSlot& slot = localSlots[size_t(li)];
378 slot.needsUpdate = false;
379 const LocalPageCacheEntry& prev = g_localPageCache.slots[phys];
380 if (prev.occupied && prev.light == slot.light) {
381 slot.lightVP = prev.lightVP;
382 slot.bias = prev.bias;
383 nextCache[phys].lightVP = prev.lightVP;
384 nextCache[phys].bias = prev.bias;
385 }
386 }
387
388 for (int s = 0; s < ShadowConfig::kLocalSlots; ++s) g_localPageCache.slots[s] = nextCache[s];
389}
390
391} // namespace eve::graphics
double score
Definition Agent.cpp:50
float w
Definition AnimClip.cpp:738
float y
Definition AnimClip.cpp:738
float x
Definition AnimClip.cpp:738
float z
Definition AnimClip.cpp:738
const std::string & s
int priority
Stable, structured diagnostics shared by engine modules.
std::uint16_t method
std::vector< eve::ProcgenProbeDesc > lights
glm::vec4 clip
float v
HexVec3 up
Range range
std::array< float, 3 > position
std::string name
MeleePoint3 b
Definition MeleeHit.cpp:41
MeleePoint3 a
Definition MeleeHit.cpp:40
float radius
float d
FrustumPlanes frustum
glm::mat4 view
glm::mat4 proj
bool isPoint
RoadLaneDirection direction
const RoadEdge * edge
float shadowStrength
float dz
glm::mat4 lightVP
float spotAngleDeg
Light3D::Data * light
float dy
float dx
float bias
bool occupied
LocalPageCacheEntry slots[ShadowConfig::kLocalSlots]
TerrainThermalSettings settings
V3 dir
Definition TreeMesh.cpp:150
uint32_t index
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.
Definition Diagnostic.h:125
Move-only operation result carrying either a value or Status.
Definition Result.h:155
static Result success(T value)
Construct a successful result owning value.
Definition Result.h:164
static Result failure(Status status)
Construct a failed result from a structured status.
Definition Result.h:175
卡牌游戏 UI 工具模块:工厂 + 脚本绑定入口。 功能参考 ycarowr/UiCard:扇形手牌布局、抽牌/洗牌、悬浮放大、拖拽到落牌区、 敌方手牌(背面/偷看)、费用不足置灰,以及可实时调节的布局...
Definition Animation.h:25
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.
Definition ClipSpace.h:22
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,...
Definition Shadow.h:14
Result< ShadowMethod > parseShadowMethod(std::string_view name)
Parse a script/editor method name into ShadowMethod.
Data public API.
Definition Light.h:200
int shadowLocalSlot
Frame-transient local shadow atlas slot (0..kLocalSlots-1), or -1. Written by selectShadowCasters; pa...
Definition Light.h:224
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.
Definition Shadow.h:26
static constexpr int kCascades
Definition Shadow.h:24
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.
uint32_t pad[2]