载入中...
搜索中...
未找到
RenderSystem3D.cpp
浏览该文件的文档.
6#include "graphics/Graphics.h"
7#include "graphics/Light.h"
8#include "graphics/Material.h"
9#include "graphics/Outline.h"
11#include "graphics/Shadow.h"
12
13#include <algorithm>
14#include <cmath>
15#include <glm/gtc/matrix_inverse.hpp>
16#include <glm/gtc/matrix_transform.hpp>
17#include <vector>
18
19namespace eve::graphics {
20
21namespace {
22
23std::vector<RenderSystem3D::GBufferExtraDrawer> g_gbufferDrawers;
24std::vector<RenderSystem3D::ShadowExtraDrawer> g_shadowDrawers;
25
26glm::vec3 gLightDir = glm::normalize(glm::vec3(0.4f, 1.f, 0.3f));
27glm::vec3 gLightColor = glm::vec3(1.f);
28
29glm::mat4 modelFromTransform(const Renderable3D::Transform3D &t) {
30 glm::mat4 m(1.f);
31 m = glm::translate(m, glm::vec3(t.x, t.y, t.z));
32 m = glm::rotate(m, t.yaw, glm::vec3(0.f, 1.f, 0.f));
33 m = glm::rotate(m, t.pitch, glm::vec3(1.f, 0.f, 0.f));
34 m = glm::rotate(m, t.roll, glm::vec3(0.f, 0.f, 1.f));
35 m = glm::scale(m, glm::vec3(t.sx, t.sy, t.sz));
36 return m;
37}
38
39Camera3D *findDefaultCamera3D() {
40 if (ecs::current()->getManager<Camera3D>() == nullptr) return nullptr;
41 auto camView = ecs::View<Camera3D, Camera3D::Data>();
42 for (auto it = camView.begin(); it != camView.end(); ++it) {
43 auto [data] = *it;
44 if (!data->active || !data->entity) continue;
45 return data->entity;
46 }
47 return nullptr;
48}
49
50struct PackedLight3D {
51 Light3D::Data *data = nullptr;
52 bool isPoint = true;
53};
54
55void collectLights3D(std::vector<PackedLight3D> &out, size_t maxCount) {
56 out.clear();
57 if (ecs::current()->getManager<Light3D>() == nullptr) return;
58 auto view = ecs::View<Light3D, Light3D::Data>();
59 for (auto it = view.begin(); it != view.end(); ++it) {
60 auto [d] = *it;
61 if (!d->enabled) continue;
62 PackedLight3D pl;
63 pl.data = d;
64 pl.isPoint = (d->type != "dir");
65 out.push_back(pl);
66 }
67 std::stable_sort(out.begin(), out.end(), [](const PackedLight3D &a, const PackedLight3D &b) {
68 if (a.isPoint != b.isPoint) return a.isPoint && !b.isPoint;
69 return a.data->intensity > b.data->intensity;
70 });
71 if (out.size() > maxCount) out.resize(maxCount);
72}
73
76void promoteDirectional(std::vector<PackedLight3D> &packed) {
77 for (size_t i = 0; i < packed.size(); ++i) {
78 if (packed[i].isPoint) continue;
79 if (i != 0) std::swap(packed[0], packed[i]);
80 return;
81 }
82}
83
84Lighting3DPack packLights3D(const std::vector<PackedLight3D> &lights, const Camera3D::Data *cam) {
85 Lighting3DPack pack{};
86 if (cam) {
87 pack.ambient = glm::vec4(cam->ambientR, cam->ambientG, cam->ambientB, 0.f);
88 }
89 if (lights.empty()) {
90 pack.count = 1;
91 pack.lights[0].posRadius = glm::vec4(gLightDir, 0.f);
92 pack.lights[0].color = glm::vec4(gLightColor, 1.f);
93 return pack;
94 }
95 const int n = std::min(int(lights.size()), Lighting3DPack::kMaxLights);
96 pack.count = n;
97 for (int i = 0; i < n; ++i) {
98 const auto *d = lights[size_t(i)].data;
99 Light3DGpu &g = pack.lights[i];
100 g.color = glm::vec4(d->r * d->intensity, d->g * d->intensity, d->b * d->intensity, 1.f);
101 if (lights[size_t(i)].isPoint) {
102 g.posRadius = glm::vec4(d->x, d->y, d->z, d->radius);
103 } else {
104 glm::vec3 dir(d->dx, d->dy, d->dz);
105 if (glm::length(dir) < 1e-6f) dir = glm::vec3(0.f, 1.f, 0.f);
106 else dir = glm::normalize(dir);
107 g.posRadius = glm::vec4(dir, 0.f);
108 }
109 }
110 return pack;
111}
112
113void splitLights(const std::vector<PackedLight3D> &packed, std::vector<ClusteredLightGpu> &points,
114 std::vector<ClusteredLightGpu> &dirs) {
115 points.clear();
116 dirs.clear();
117 for (const auto &pl : packed) {
118 const auto *d = pl.data;
120 g.color = glm::vec4(d->r * d->intensity, d->g * d->intensity, d->b * d->intensity, 1.f);
121 if (pl.isPoint) {
122 g.posRadius = glm::vec4(d->x, d->y, d->z, d->radius);
123 points.push_back(g);
124 } else {
125 glm::vec3 dir(d->dx, d->dy, d->dz);
126 if (glm::length(dir) < 1e-6f) dir = glm::vec3(0.f, 1.f, 0.f);
127 else dir = glm::normalize(dir);
128 g.posRadius = glm::vec4(dir, 0.f);
129 dirs.push_back(g);
130 }
131 }
132}
133
134} // namespace
135
136void Camera3D::setEye(float x, float y, float z) {
137 auto d = data();
138 d->eyeX = x;
139 d->eyeY = y;
140 d->eyeZ = z;
141}
142
143void Camera3D::setTarget(float x, float y, float z) {
144 auto d = data();
145 d->targetX = x;
146 d->targetY = y;
147 d->targetZ = z;
148}
149
150void Camera3D::setUp(float x, float y, float z) {
151 auto d = data();
152 d->upX = x;
153 d->upY = y;
154 d->upZ = z;
155}
156
157void Camera3D::setFov(float fovYDeg) { data()->fovYDeg = fovYDeg; }
158
159void Camera3D::setActive(bool active) { data()->active = active; }
160
161void Camera3D::setAmbient(float r, float g, float b) {
162 auto d = data();
163 d->ambientR = r;
164 d->ambientG = g;
165 d->ambientB = b;
166}
167
168void Camera3D::setEnvMap(Texture *cube) { data()->envMap = cube; }
169
170void Camera3D::setEnvIntensity(float intensity) {
171 data()->envIntensity = intensity < 0.f ? 0.f : intensity;
172}
173
174void Camera3D::screenToRay(float screenX, float screenY, float viewW, float viewH) {
175 auto d = data();
176 d->screenRayOx = d->eyeX;
177 d->screenRayOy = d->eyeY;
178 d->screenRayOz = d->eyeZ;
179 d->screenRayDx = 0.f;
180 d->screenRayDy = 0.f;
181 d->screenRayDz = -1.f;
182 if (viewW <= 0.f || viewH <= 0.f) return;
183
184 const glm::vec3 eye(d->eyeX, d->eyeY, d->eyeZ);
185 const glm::vec3 target(d->targetX, d->targetY, d->targetZ);
186 const glm::vec3 up(d->upX, d->upY, d->upZ);
187 const glm::mat4 viewM = glm::lookAtRH(eye, target, up);
188 const float aspect = viewW / viewH;
189 const float fovRad = d->fovYDeg * 0.017453292519943295f;
190 const glm::mat4 projM = perspectiveVulkanRH_ZO(fovRad, aspect, d->nearZ, d->farZ);
191 const glm::mat4 invVP = glm::inverse(projM * viewM);
192
193 // Screen pixel → Vulkan NDC (Y-down; matches perspectiveVulkanRH_ZO).
194 const float ndcX = (screenX / viewW) * 2.f - 1.f;
195 const float ndcY = (screenY / viewH) * 2.f - 1.f;
196 auto unproject = [&](float ndcZ) -> glm::vec3 {
197 glm::vec4 w = invVP * glm::vec4(ndcX, ndcY, ndcZ, 1.f);
198 if (std::fabs(w.w) < 1e-8f) return eye;
199 w /= w.w;
200 return glm::vec3(w);
201 };
202 // ZO depth: near = 0, far = 1.
203 const glm::vec3 nearPt = unproject(0.f);
204 const glm::vec3 farPt = unproject(1.f);
205 glm::vec3 dir = farPt - nearPt;
206 const float len = glm::length(dir);
207 if (len > 1e-8f) dir /= len;
208 else dir = glm::normalize(target - eye);
209
210 d->screenRayOx = eye.x;
211 d->screenRayOy = eye.y;
212 d->screenRayOz = eye.z;
213 d->screenRayDx = dir.x;
214 d->screenRayDy = dir.y;
215 d->screenRayDz = dir.z;
216}
217
218float Camera3D::getScreenRayOriginX() { return data()->screenRayOx; }
219float Camera3D::getScreenRayOriginY() { return data()->screenRayOy; }
220float Camera3D::getScreenRayOriginZ() { return data()->screenRayOz; }
221float Camera3D::getScreenRayDirX() { return data()->screenRayDx; }
222float Camera3D::getScreenRayDirY() { return data()->screenRayDy; }
223float Camera3D::getScreenRayDirZ() { return data()->screenRayDz; }
224
225void Renderable3D::setPosition(float x, float y, float z) {
226 auto t = transform();
227 t->x = x;
228 t->y = y;
229 t->z = z;
230}
231
232void Renderable3D::setRotation(float yaw, float pitch, float roll) {
233 auto t = transform();
234 t->yaw = yaw;
235 t->pitch = pitch;
236 t->roll = roll;
237}
238
239void Renderable3D::setYaw(float yaw) { transform()->yaw = yaw; }
240
241float Renderable3D::getYaw() { return transform()->yaw; }
242
243void Renderable3D::setScale(float sx, float sy, float sz) {
244 auto t = transform();
245 t->sx = sx;
246 t->sy = sy;
247 t->sz = sz;
248}
249
250void Renderable3D::setMesh(Mesh *mesh) { meshRenderer()->mesh = mesh; }
251
252void Renderable3D::setTexture(Texture *texture) { meshRenderer()->texture = texture; }
253
254void Renderable3D::setNormalTexture(Texture *texture) { meshRenderer()->normalTexture = texture; }
255
256void Renderable3D::setHeightTexture(Texture *texture) { meshRenderer()->heightTexture = texture; }
257
258void Renderable3D::setShader(Shader *shader) { meshRenderer()->shader = shader; }
259
260void Renderable3D::setMaterial(Material *material) { meshRenderer()->material = material; }
261
262Material *Renderable3D::getMaterial() { return meshRenderer()->material; }
263
264void Renderable3D::setXRayShader(Shader *shader) { meshRenderer()->xrayShader = shader; }
265
266Shader *Renderable3D::getXRayShader() { return meshRenderer()->xrayShader; }
267
268void Renderable3D::setXRayHighlight(bool on) { meshRenderer()->xrayHighlight = on; }
269
270bool Renderable3D::getXRayHighlight() { return meshRenderer()->xrayHighlight; }
271
272void Renderable3D::setPart(int index, const std::string &name, Mesh *mesh, Material *material) {
273 auto mr = meshRenderer();
274 if (index < 0 || index >= MeshRenderer::kMaxParts) return;
275 mr->parts[index].name = name;
276 mr->parts[index].mesh = mesh;
277 mr->parts[index].material = material;
278 if (mesh) {
279 if (mr->partCount < index + 1) mr->partCount = index + 1;
280 } else if (index + 1 == mr->partCount) {
281 while (mr->partCount > 0 && !mr->parts[mr->partCount - 1].mesh) --mr->partCount;
282 }
283}
284
286 auto mr = meshRenderer();
287 mr->partCount = 0;
288 for (int i = 0; i < MeshRenderer::kMaxParts; ++i) {
289 mr->parts[i] = ModelPart{};
290 }
291}
292
293int Renderable3D::getPartCount() { return meshRenderer()->partCount; }
294
295std::string Renderable3D::getPartName(int index) {
296 auto mr = meshRenderer();
297 if (index < 0 || index >= mr->partCount) return {};
298 return mr->parts[index].name;
299}
300
302 auto mr = meshRenderer();
303 if (index < 0 || index >= mr->partCount) return nullptr;
304 return mr->parts[index].mesh;
305}
306
308 auto mr = meshRenderer();
309 if (index < 0 || index >= mr->partCount) return nullptr;
310 return mr->parts[index].material;
311}
312
313void Renderable3D::setHair(bool hair) { meshRenderer()->isHair = hair; }
314
315bool Renderable3D::getHair() { return meshRenderer()->isHair; }
316
317void Renderable3D::setTint(float r, float g, float b, float a) {
318 auto mr = meshRenderer();
319 mr->r = r;
320 mr->g = g;
321 mr->b = b;
322 mr->a = a;
323}
324
325void Renderable3D::setMetallic(float metallic) { meshRenderer()->metallic = metallic; }
326
327void Renderable3D::setRoughness(float roughness) { meshRenderer()->roughness = roughness; }
328
329void Renderable3D::setTexCellBomb(float cellScale, float strength, float rotAmount) {
330 auto mr = meshRenderer();
331 mr->texBombScale = cellScale > 1e-3f ? cellScale : 1e-3f;
332 mr->texBombStrength = strength < 0.f ? 0.f : (strength > 1.f ? 1.f : strength);
333 mr->texBombRot = rotAmount < 0.f ? 0.f : (rotAmount > 1.f ? 1.f : rotAmount);
334}
335
336float Renderable3D::getTexCellBombScale() { return meshRenderer()->texBombScale; }
337
338float Renderable3D::getTexCellBombStrength() { return meshRenderer()->texBombStrength; }
339
340float Renderable3D::getTexCellBombRotation() { return meshRenderer()->texBombRot; }
341
342void Renderable3D::setParallax(float scale, float minLayers, float maxLayers) {
343 auto mr = meshRenderer();
344 mr->parallaxScale = scale < 0.f ? 0.f : (scale > 0.25f ? 0.25f : scale);
345 float minL = minLayers < 1.f ? 1.f : minLayers;
346 float maxL = maxLayers < minL ? minL : maxLayers;
347 if (maxL > 64.f) maxL = 64.f;
348 mr->parallaxMinLayers = minL;
349 mr->parallaxMaxLayers = maxL;
350}
351
352float Renderable3D::getParallaxScale() { return meshRenderer()->parallaxScale; }
353
354float Renderable3D::getParallaxMinLayers() { return meshRenderer()->parallaxMinLayers; }
355
356float Renderable3D::getParallaxMaxLayers() { return meshRenderer()->parallaxMaxLayers; }
357
358void Renderable3D::setVisible(bool visible) { meshRenderer()->visible = visible; }
359
360void Renderable3D::setReceiveLight(bool receive) { meshRenderer()->receiveLight = receive; }
361
362void Renderable3D::setCastShadow(bool cast) { meshRenderer()->castShadow = cast; }
363
364void Renderable3D::setReceiveShadow(bool receive) { meshRenderer()->receiveShadow = receive; }
365
366void Renderable3D::setCastOcclusion(bool cast) { meshRenderer()->castOcclusion = cast; }
367
368bool Renderable3D::getCastOcclusion() { return meshRenderer()->castOcclusion; }
369
370void Renderable3D::setCamera(Camera3D *camera) { meshRenderer()->camera = camera; }
371
372void Renderable3D::setMeshLod(int index, Mesh *mesh, float switchDistance) {
373 auto mr = meshRenderer();
374 if (index < 0 || index >= MeshRenderer::kMaxLodLevels) return;
375 mr->lodMeshes[index] = mesh;
376 if (index > 0) mr->lodDistances[index - 1] = switchDistance;
377 if (mesh) {
378 if (mr->lodCount < index + 1) mr->lodCount = index + 1;
379 } else if (index + 1 == mr->lodCount) {
380 while (mr->lodCount > 0 && !mr->lodMeshes[mr->lodCount - 1]) --mr->lodCount;
381 }
382 // Keep primary mesh in sync with LOD0 when set.
383 if (index == 0 && mesh) mr->mesh = mesh;
384}
385
387 auto mr = meshRenderer();
388 mr->lodCount = 0;
389 for (int i = 0; i < MeshRenderer::kMaxLodLevels; ++i) mr->lodMeshes[i] = nullptr;
390}
391
392int Renderable3D::getMeshLodCount() { return meshRenderer()->lodCount; }
393
395 return meshRenderer()->lodLevelForDistance(distance);
396}
397
398void RenderSystem3D::setDirectionalLight(float dx, float dy, float dz, float r, float g, float b) {
399 glm::vec3 d(dx, dy, dz);
400 if (glm::length(d) < 1e-6f) d = glm::vec3(0.f, 1.f, 0.f);
401 gLightDir = glm::normalize(d);
402 gLightColor = glm::vec3(r, g, b);
403}
404
406 if (!drawer) return;
407 g_gbufferDrawers.push_back(std::move(drawer));
408}
409
411 if (!drawer) return;
412 g_shadowDrawers.push_back(std::move(drawer));
413}
414
415namespace {
416
417Light3D::Data *findShadowCasterDir(const std::vector<PackedLight3D> &packed) {
418 Light3D::Data *best = nullptr;
419 float bestI = -1.f;
420 for (const auto &pl : packed) {
421 if (pl.isPoint || !pl.data || !pl.data->castShadow) continue;
422 if (pl.data->intensity > bestI) {
423 bestI = pl.data->intensity;
424 best = pl.data;
425 }
426 }
427 return best;
428}
429
430void prioritizeShadowCaster(std::vector<PackedLight3D> &packed, Light3D::Data *caster) {
431 if (!caster || packed.empty()) return;
432 for (size_t i = 0; i < packed.size(); ++i) {
433 if (packed[i].data != caster) continue;
434 if (i != 0) std::swap(packed[0], packed[i]);
435 return;
436 }
437}
438
443struct FrustumPlanes {
444 glm::vec4 p[6]{};
445
446 bool sphereVisible(const glm::vec3 &center, float radius) const {
447 for (const auto &pl : p) {
448 const float d = pl.x * center.x + pl.y * center.y + pl.z * center.z + pl.w;
449 if (d < -radius) return false;
450 }
451 return true;
452 }
453};
454
455FrustumPlanes extractFrustum(const glm::mat4 &m) {
456 FrustumPlanes f;
457 const glm::vec4 r0(m[0][0], m[1][0], m[2][0], m[3][0]);
458 const glm::vec4 r1(m[0][1], m[1][1], m[2][1], m[3][1]);
459 const glm::vec4 r2(m[0][2], m[1][2], m[2][2], m[3][2]);
460 const glm::vec4 r3(m[0][3], m[1][3], m[2][3], m[3][3]);
461 auto norm = [](glm::vec4 &v) {
462 const float l = glm::length(glm::vec3(v));
463 if (l > 1e-8f) v /= l;
464 };
465 f.p[0] = r3 + r0; // left
466 f.p[1] = r3 - r0; // right
467 f.p[2] = r3 + r1; // bottom
468 f.p[3] = r3 - r1; // top
469 // Vulkan clip space uses zero-to-one depth: near plane is z_clip = 0
470 // (plane r2), not the z = -w plane used by OpenGL-style [-1,1] depth.
471 f.p[4] = r2; // near
472 f.p[5] = r3 - r2; // far
473 for (auto &pl : f.p) norm(pl);
474 return f;
475}
476
482struct CameraView {
483 Camera3D::Data *data = nullptr;
484 glm::vec3 eye{0.f};
485 glm::mat4 view{1.f};
486 glm::mat4 proj{1.f};
487 glm::mat4 viewProj{1.f};
488 FrustumPlanes frustum;
489 float fovRad = 1.f;
490 Lighting3DPack lighting{};
491 ClusteredLightingUpload clustered{};
492 bool clusteredValid = false;
494 bool clusteredUploaded = false;
495};
496
497CameraView buildCameraView(Camera3D::Data *cd, const std::vector<PackedLight3D> &packed, bool useClustered,
498 float aspect, const std::vector<ClusteredLightGpu> &clusteredPoints,
499 const std::vector<ClusteredLightGpu> &clusteredDirs, Graphics &gfx) {
500 CameraView cv;
501 cv.data = cd;
502 cv.eye = glm::vec3(cd->eyeX, cd->eyeY, cd->eyeZ);
503 const glm::vec3 look(cd->targetX, cd->targetY, cd->targetZ);
504 const glm::vec3 up(cd->upX, cd->upY, cd->upZ);
505 cv.view = glm::lookAtRH(cv.eye, look, up);
506 cv.fovRad = cd->fovYDeg * 0.017453292519943295f;
507 cv.proj = perspectiveVulkanRH_ZO(cv.fovRad, aspect, cd->nearZ, cd->farZ);
508 cv.viewProj = cv.proj * cv.view;
509 cv.frustum = extractFrustum(cv.viewProj);
510 cv.lighting = packLights3D(packed, cd);
511 if (useClustered) {
512 cv.clustered = buildClusteredLighting(clusteredPoints, clusteredDirs, cv.view, cd->nearZ, cd->farZ,
513 gfx.getWidth(), gfx.getHeight(), cv.fovRad,
514 glm::vec4(cd->ambientR, cd->ambientG, cd->ambientB, 0.f));
515 cv.clusteredValid = true;
516 }
517 return cv;
518}
519
525struct CulledItem {
526 Renderable3D::MeshRenderer *mr = nullptr;
527 Mesh *mesh = nullptr;
528 Material *material = nullptr;
529 Shader *shader = nullptr; // effective mesh shader (material or legacy)
530 glm::mat4 model{1.f};
531 glm::vec3 worldC{0.f}; // world-space bounding-sphere center
532 float worldR = 0.f; // world-space bounding-sphere radius (0 = unknown → unculled)
533 float distSq = 0.f;
534 int camIdx = 0;
535 uint32_t cascadeMask = 0; // bit c set when the caster may contribute to cascade c
536 bool inView = false; // inside the item camera's frustum
537 bool inDefaultView = false; // inside the default camera's frustum (G-buffer)
538 bool hair = false;
539 bool xray = false;
540};
541
542} // namespace
543
545 eve::debug::RenderPassScope pass3d("RenderSystem3D");
546 Camera3D *defaultCam = findDefaultCamera3D();
547
549 rc->ensureCompiled();
550 const bool doShadow = rc->hasPass("shadow");
551 const bool doGBuffer = rc->hasPass("gbuffer");
552 const bool doForward = rc->hasPass("forward");
553 const bool doHair = rc->hasPass("hair");
554 const bool allowClustered = rc->isEnabled("clustered");
555
556 std::vector<PackedLight3D> packed;
557 collectLights3D(packed, size_t(ClusteredLightConfig::kMaxLights));
558 promoteDirectional(packed);
559 Light3D::Data *shadowCaster = doShadow ? findShadowCasterDir(packed) : nullptr;
560 prioritizeShadowCaster(packed, shadowCaster);
561 const bool haveExtraShadowCasters = doShadow && !g_shadowDrawers.empty();
562
563 const float aspect = (gfx.getHeight() > 0) ? float(gfx.getWidth()) / float(gfx.getHeight()) : 1.f;
564
565 ShadowUpload shadowUpload{};
566 shadowUpload.active = false;
567 // Per-cascade light frustums for caster culling (sphere vs frustum).
568 FrustumPlanes cascadeFrustums[ShadowConfig::kCascades];
569 if ((shadowCaster || haveExtraShadowCasters) && defaultCam) {
570 auto cd = defaultCam->data();
571 glm::vec3 dir = shadowCaster ? glm::vec3(shadowCaster->dx, shadowCaster->dy, shadowCaster->dz) : gLightDir;
572 if (glm::length(dir) < 1e-6f)
573 dir = glm::vec3(0.f, 1.f, 0.f);
574 else
575 dir = glm::normalize(dir);
576 const float shadowBias = shadowCaster ? shadowCaster->shadowBias : 0.003f;
577 const float shadowStrength = shadowCaster ? shadowCaster->shadowStrength : 1.f;
578 const float fovRad = cd->fovYDeg * 0.017453292519943295f;
579 shadowUpload = buildDirectionalCSM(
580 dir, glm::vec3(cd->eyeX, cd->eyeY, cd->eyeZ), glm::vec3(cd->targetX, cd->targetY, cd->targetZ),
581 glm::vec3(cd->upX, cd->upY, cd->upZ), fovRad, aspect, cd->nearZ, cd->farZ, shadowBias, shadowStrength);
582 for (int c = 0; c < ShadowConfig::kCascades; ++c)
583 cascadeFrustums[c] = extractFrustum(shadowUpload.ubo.lightVP[c]);
584 }
585 gfx.setMesh3DShadows(shadowUpload);
586
587 const bool useClustered = allowClustered && packed.size() > size_t(Lighting3DPack::kMaxLights);
588 // Light split / directional promotion is camera-independent: compute once,
589 // then each camera view bakes its own clustered table from these lists.
590 std::vector<ClusteredLightGpu> clusteredPoints;
591 std::vector<ClusteredLightGpu> clusteredDirs;
592 if (useClustered) {
593 splitLights(packed, clusteredPoints, clusteredDirs);
594 if (clusteredDirs.empty()) {
596 d.posRadius = glm::vec4(gLightDir, 0.f);
597 d.color = glm::vec4(gLightColor, 1.f);
598 clusteredDirs.push_back(d);
599 }
600 if (shadowCaster && !clusteredDirs.empty()) {
601 glm::vec3 dir(shadowCaster->dx, shadowCaster->dy, shadowCaster->dz);
602 if (glm::length(dir) < 1e-6f)
603 dir = glm::vec3(0.f, 1.f, 0.f);
604 else
605 dir = glm::normalize(dir);
606 for (size_t i = 0; i < clusteredDirs.size(); ++i) {
607 if (glm::length(glm::vec3(clusteredDirs[i].posRadius) - dir) < 1e-3f) {
608 if (i != 0) std::swap(clusteredDirs[0], clusteredDirs[i]);
609 break;
610 }
611 }
612 }
613 }
614
615 const bool haveManager = ecs::current()->getManager<Renderable3D>() != nullptr;
616 const bool shadowActive = doShadow && shadowUpload.active;
617
618 // Per-camera constants (matrices, frustum, lighting, clustered table).
619 // The default camera is slot 0 so the G-buffer pass reuses its view.
620 std::vector<CameraView> cams;
621 cams.reserve(2);
622 if (defaultCam) {
623 cams.push_back(buildCameraView(defaultCam->data().operator->(), packed, useClustered, aspect, clusteredPoints,
624 clusteredDirs, gfx));
625 }
626 auto findOrAddCam = [&](Camera3D *camEnt) -> int {
627 Camera3D::Data *d = camEnt->data().operator->();
628 for (size_t i = 0; i < cams.size(); ++i) {
629 if (cams[i].data == d) return int(i);
630 }
631 cams.push_back(buildCameraView(d, packed, useClustered, aspect, clusteredPoints, clusteredDirs, gfx));
632 return int(cams.size()) - 1;
633 };
634
635 // Single ECS traversal: one model matrix, one LOD pick, one set of
636 // sphere-vs-frustum tests per part. Passes below only replay the list.
637 std::vector<CulledItem> items;
638 items.reserve(64);
639 if (haveManager) {
640 auto view = ecs::View<Renderable3D, Renderable3D::Transform3D, Renderable3D::MeshRenderer>();
641 for (auto it = view.begin(); it != view.end(); ++it) {
642 auto [xf, mr] = *it;
643 if (!mr->visible) continue;
644 Camera3D *camEnt = mr->camera ? mr->camera : defaultCam;
645 if (!camEnt) continue;
646 const int camIdx = findOrAddCam(camEnt);
647 const CameraView &cv = cams[size_t(camIdx)];
648 const float dx = xf->x - cv.eye.x;
649 const float dy = xf->y - cv.eye.y;
650 const float dz = xf->z - cv.eye.z;
651 const float distSq = dx * dx + dy * dy + dz * dz;
652 const float dist = std::sqrt(distSq);
653 const glm::mat4 model = modelFromTransform(*xf);
654 const float maxScale = std::max(std::abs(xf->sx), std::max(std::abs(xf->sy), std::abs(xf->sz)));
655 const bool shadowOk = mr->effectiveCastShadow();
656
657 auto pushPart = [&](Mesh *drawMesh, Material *mat, bool asHair, bool castsShadow) {
658 if (!drawMesh) return;
659 CulledItem item;
660 item.mr = mr;
661 item.mesh = drawMesh;
662 item.material = mat;
663 item.shader = mat ? mat->effectiveShader() : mr->shader;
664 item.model = model;
665 item.distSq = distSq;
666 item.camIdx = camIdx;
667 item.hair = asHair;
668 item.xray = mr->xrayHighlight;
669 if (drawMesh->hasBounds()) {
670 const glm::vec4 c4 =
671 model * glm::vec4(drawMesh->boundsCx, drawMesh->boundsCy, drawMesh->boundsCz, 1.f);
672 item.worldC = glm::vec3(c4);
673 item.worldR = drawMesh->boundsRadius * maxScale;
674 item.inView = cv.frustum.sphereVisible(item.worldC, item.worldR);
675 item.inDefaultView = defaultCam ? cams[0].frustum.sphereVisible(item.worldC, item.worldR) : true;
676 if (shadowActive && castsShadow) {
677 for (int c = 0; c < ShadowConfig::kCascades; ++c) {
678 if (cascadeFrustums[c].sphereVisible(item.worldC, item.worldR))
679 item.cascadeMask |= (1u << c);
680 }
681 }
682 } else {
683 // No bounds (e.g. legacy/imported mesh): never cull.
684 item.inView = true;
685 item.inDefaultView = true;
686 if (shadowActive && castsShadow) item.cascadeMask = (1u << ShadowConfig::kCascades) - 1u;
687 }
688 items.push_back(item);
689 };
690
691 if (mr->usesParts()) {
692 for (int p = 0; p < mr->partCount; ++p) {
693 Material *mat = mr->parts[p].material ? mr->parts[p].material : mr->material;
694 const bool asHair = mat ? mat->isTransparentHair() : mr->isHair;
695 const bool casts = shadowOk && !(mat && !mat->getCastShadow());
696 pushPart(mr->parts[p].mesh, mat, asHair, casts);
697 }
698 } else {
699 Mesh *drawMesh = mr->meshForDistance(dist);
700 pushPart(drawMesh, mr->material, mr->effectiveHair(), shadowOk);
701 }
702 }
703 }
704
705 // CSM shadow passes — replay the collected casters, culled per cascade.
706 if (shadowActive && (haveManager || haveExtraShadowCasters)) {
707 auto cd = defaultCam->data();
708 for (int c = 0; c < ShadowConfig::kCascades; ++c) {
709 eve::debug::rtPassBegin("ShadowPass");
710 gfx.beginShadowPass(c);
711 for (const auto &item : items) {
712 if ((item.cascadeMask & (1u << c)) == 0) continue;
713 eve::debug::rtBind("mesh", "shadowCaster");
714 eve::debug::rtDraw("drawMeshShadow", "cascade");
715 gfx.drawMeshShadow(item.mesh, shadowUpload.ubo.lightVP[c] * item.model);
716 }
717 // Extra shadow casters (billboard/card geometry not in the ECS).
718 for (const auto &drawer : g_shadowDrawers) drawer(gfx, shadowUpload.ubo.lightVP[c], *cd);
719 gfx.endShadowPass();
720 eve::debug::rtPassEnd("ShadowPass");
721 }
722 }
723
724 // G-buffer fill (sampleable depth/normal) — before the forward swapchain pass.
725 if (doGBuffer && defaultCam && (haveManager || !g_gbufferDrawers.empty())) {
726 eve::debug::rtPassBegin("GBufferPass");
727 const CameraView &cv = cams[0]; // default camera is slot 0
728 const int gw = std::max(1, gfx.getPixelWidth() > 0 ? gfx.getPixelWidth() : gfx.getWidth());
729 const int gh = std::max(1, gfx.getPixelHeight() > 0 ? gfx.getPixelHeight() : gfx.getHeight());
730 gfx.beginGBufferPass(gw, gh);
731 for (const auto &item : items) {
732 // X-ray targets are skipped so their pixels record the occluder depth
733 // behind them; the X-ray shader samples that to detect occlusion.
734 if (item.xray) continue;
735 if (item.hair) continue;
736 if (!item.inDefaultView) continue;
737 Texture *alb = item.material ? item.material->getAlbedoTexture() : item.mr->texture;
738 const float tr = item.material ? item.material->getTintR() : item.mr->r;
739 const float tg = item.material ? item.material->getTintG() : item.mr->g;
740 const float tb = item.material ? item.material->getTintB() : item.mr->b;
741 eve::debug::rtDraw("drawMeshGBuffer", "gbuffer");
742 gfx.drawMeshGBuffer(item.mesh, cv.viewProj * item.model, item.model, cv.data->nearZ, cv.data->farZ, alb, tr,
743 tg, tb);
744 }
745 // Extra G-buffer contributors (billboard/card geometry not in the ECS).
746 for (const auto &drawer : g_gbufferDrawers) drawer(gfx, *cv.data, cv.viewProj, aspect);
747 gfx.endGBufferPass();
748 eve::debug::rtPassEnd("GBufferPass");
749 } else if (!doGBuffer) {
750 rc->getGBuffer()->clear();
751 }
752
753 if (!doForward && !doHair) return;
754
755 gfx.begin3DFrame();
756 if (!gfx.had3DThisFrame()) return;
757
758 if (!haveManager) return;
759
760 // Replay the items collected above: opaque first, hair back-to-front.
761 std::vector<const CulledItem *> opaque;
762 std::vector<const CulledItem *> hairItems;
763 opaque.reserve(items.size());
764 hairItems.reserve(items.size() / 4);
765 for (const auto &item : items) {
766 if (!item.inView) continue;
767 (item.hair ? hairItems : opaque).push_back(&item);
768 }
769 // Opaque: group by (camera, shader, material, mesh) so the backend sees
770 // long runs of identical pipeline/descriptor state instead of thrashing
771 // between materials. Hair stays sorted back-to-front by distance below.
772 std::stable_sort(opaque.begin(), opaque.end(), [](const CulledItem *a, const CulledItem *b) {
773 if (a->camIdx != b->camIdx) return a->camIdx < b->camIdx;
774 if (a->shader != b->shader) return a->shader < b->shader;
775 if (a->material != b->material) return a->material < b->material;
776 return a->mesh < b->mesh;
777 });
778 std::stable_sort(hairItems.begin(), hairItems.end(),
779 [](const CulledItem *a, const CulledItem *b) { return a->distSq > b->distSq; });
780
781 auto bindLegacyMaterial = [&](Renderable3D::MeshRenderer *mr) {
782 gfx.setMesh3DMaterial(mr->metallic, mr->roughness);
783 gfx.setMesh3DTexCellBomb(mr->texBombScale, mr->texBombStrength, mr->texBombRot);
784 gfx.setMesh3DNormalTexture(mr->normalTexture);
785 gfx.setMesh3DHeightTexture(mr->heightTexture);
786 gfx.setMesh3DParallax(mr->parallaxScale, mr->parallaxMinLayers, mr->parallaxMaxLayers);
787 gfx.setMesh3DShadowReceive(mr->receiveShadow);
788 };
789
790 // Per-camera / per-lighting state. The clustered SSBO table is uploaded at
791 // most once per camera; afterwards only the cheap active flag toggles.
792 int curCam = -1;
793 bool curLit = false;
794 bool curClustered = false;
795
796 auto drawMeshWithMaterial = [&](const CulledItem &item, CameraView &cv) {
797 auto *mr = item.mr;
798 Mesh *drawMesh = item.mesh;
799 Material *mat = item.material;
800 if (!drawMesh) return;
801
802 Texture *albedo = mr->texture;
803 Color tint(mr->r, mr->g, mr->b, mr->a);
804 Shader *shader = mr->shader;
805 if (mat) {
806 mat->bind(gfx);
807 albedo = mat->getAlbedoTexture();
808 tint = Color(mat->getTintR(), mat->getTintG(), mat->getTintB(), mat->getTintA());
809 shader = mat->effectiveShader();
810 } else {
811 bindLegacyMaterial(mr);
812 }
813
814 const bool lit = mat ? mat->getReceiveLight() : mr->receiveLight;
815 const bool clustered = lit && useClustered && !shader;
816 if (item.camIdx != curCam || lit != curLit || clustered != curClustered) {
817 if (item.camIdx != curCam) {
818 gfx.setMesh3DViewProj(cv.viewProj);
819 gfx.setMesh3DView(cv.view);
820 gfx.setMesh3DClip(cv.data->nearZ, cv.data->farZ);
821 gfx.setMesh3DCameraPos(cv.eye);
822 gfx.setMesh3DEnv(cv.data->envMap, cv.data->envIntensity);
823 curCam = item.camIdx;
824 }
825 if (clustered && cv.clusteredValid) {
826 if (!cv.clusteredUploaded) {
827 gfx.setMesh3DClusteredLighting(cv.clustered);
828 cv.clusteredUploaded = true;
829 } else {
830 gfx.setMesh3DClusteredActive(true);
831 }
832 } else {
833 gfx.setMesh3DClusteredActive(false);
834 }
835 if (lit) {
836 gfx.setMesh3DLighting(cv.lighting);
837 } else {
838 Lighting3DPack none{};
839 none.count = 0;
840 none.ambient = glm::vec4(1.f, 1.f, 1.f, 0.f);
841 gfx.setMesh3DLighting(none);
842 }
843 curLit = lit;
844 curClustered = clustered;
845 }
846
847 const glm::mat4 &model = item.model;
848 if (albedo) eve::debug::rtBind("texture", "albedo");
849 if (shader) eve::debug::rtBind("shader", item.hair ? "hair" : "mesh");
850 eve::debug::rtBind("mesh", "renderable3d");
851 eve::debug::rtDraw("drawMeshShader", shader ? "custom" : "default");
852 gfx.drawMeshShader(drawMesh, model, albedo, tint, shader);
853
854 // X-ray second pass: paint only the occluded (behind-building) part over
855 // the scene. The pipeline runs with depth test/write off + alpha blend and
856 // the shader discards visible fragments by sampling the G-buffer depth.
857 if (mr->xrayHighlight && mr->xrayShader) {
858 float sw = gfx.getPixelWidth() > 0 ? float(gfx.getPixelWidth()) : float(gfx.getWidth());
859 float shh =
860 gfx.getPixelHeight() > 0 ? float(gfx.getPixelHeight()) : float(gfx.getHeight());
861 if (mr->xrayShader->hasUniform("screenW")) mr->xrayShader->sendFloat("screenW", sw);
862 if (mr->xrayShader->hasUniform("screenH")) mr->xrayShader->sendFloat("screenH", shh);
863 eve::debug::rtBind("shader", "xray");
864 eve::debug::rtDraw("drawMeshShader", "xray");
865 gfx.drawMeshShader(drawMesh, model, albedo, tint, mr->xrayShader);
866 }
867 };
868
869 // Provide the G-buffer depth to X-ray shaders for the occlusion test.
870 if (doGBuffer && rc->getGBuffer() && rc->getGBuffer()->isValid()) {
872 }
873
874 if (doForward) {
875 for (const CulledItem *item : opaque) drawMeshWithMaterial(*item, cams[size_t(item->camIdx)]);
876 }
877 if (doHair) {
878 for (const CulledItem *item : hairItems) drawMeshWithMaterial(*item, cams[size_t(item->camIdx)]);
879 }
880
881 const bool doAO = rc->isEnabled("ao");
882 // WebGPU has no SPIR-V AO shaders; the X-ray path (below) still reads the
883 // G-buffer depth directly.
884 if (doAO && gfx.supportsGBufferPost() && defaultCam && gfx.had3DThisFrame()) {
885 GBuffer *gb = rc->getGBuffer();
886 if (gb && gb->isValid()) {
887 auto cd = defaultCam->data();
888 const float aspectSafe = aspect > 1e-4f ? aspect : 1.f;
889 auto bindCam = [&](auto *fx) {
890 fx->setCamera(cd->eyeX, cd->eyeY, cd->eyeZ, cd->targetX, cd->targetY, cd->targetZ,
891 cd->upX, cd->upY, cd->upZ, cd->fovYDeg, aspectSafe, cd->nearZ, cd->farZ);
892 };
894 ao->setQuality("medium");
895 ao->setIntensity(0.16f);
896 ao->setPower(1.1f);
897 ao->setRadius(std::clamp(cd->farZ * 0.006f, 0.18f, 0.35f));
898 bindCam(ao);
899 if (Texture *depth = gb->getHwDepthTexture())
900 ao->applyFromGBuffer(&gfx, depth, gb->getNormalTexture());
901 // Fullscreen SSGI from lit scene color reprints nearby props
902 // (curtains, planters) onto the floor as multiple swimming ghosts.
903 // Mesh shaders still add hemispheric sky/ground + wrap fill.
904 }
905 }
906
907 const bool doOutline = rc->isEnabled("outline");
908 if (doOutline && defaultCam && gfx.had3DThisFrame()) {
909 GBuffer *gb = rc->getGBuffer();
910 if (gb && gb->isValid()) {
911 Outline *outline = gfx.pipelineOutline();
912 auto cd = defaultCam->data();
913 outline->setClip(cd->nearZ, cd->farZ);
914 if (Texture *depth = gb->getHwDepthTexture())
915 outline->apply(&gfx, depth, gb->getNormalTexture());
916 }
917 }
918}
919
921 if (!target || !camera) return;
922 auto cd = camera->data();
923 const float aspect = target->getWidth() > 0
924 ? float(target->getWidth()) / float(target->getHeight())
925 : 1.f;
926
927 // Preview-quality forward pass: no shadow / G-buffer / AO passes.
928 gfx.begin3DFrameToCanvas(target);
929
930 const glm::vec3 eye(cd->eyeX, cd->eyeY, cd->eyeZ);
931 const glm::vec3 look(cd->targetX, cd->targetY, cd->targetZ);
932 const glm::vec3 up(cd->upX, cd->upY, cd->upZ);
933 const glm::mat4 viewM = glm::lookAtRH(eye, look, up);
934 const float fovRad = cd->fovYDeg * 0.017453292519943295f;
935 const glm::mat4 projM = perspectiveVulkanRH_ZO(fovRad, aspect, cd->nearZ, cd->farZ);
936 gfx.setMesh3DViewProj(projM * viewM);
937 gfx.setMesh3DView(viewM);
938 gfx.setMesh3DClip(cd->nearZ, cd->farZ);
940 gfx.setMesh3DEnv(cd->envMap, cd->envIntensity);
941
942 // Lighting: real lights (if any) + camera ambient; shadows/clustered off.
943 std::vector<PackedLight3D> packed;
944 collectLights3D(packed, size_t(ClusteredLightConfig::kMaxLights));
945 promoteDirectional(packed);
946 ClusteredLightingUpload noClustered{};
947 noClustered.active = false;
948 gfx.setMesh3DClusteredLighting(noClustered);
949 gfx.setMesh3DLighting(packLights3D(packed, cd.operator->()));
950 ShadowUpload noShadow{};
951 noShadow.active = false;
952 gfx.setMesh3DShadows(noShadow);
953
954 if (ecs::current()->getManager<Renderable3D>() != nullptr) {
955 auto view = ecs::View<Renderable3D, Renderable3D::Transform3D, Renderable3D::MeshRenderer>();
956 for (auto it = view.begin(); it != view.end(); ++it) {
957 auto [xf, mr] = *it;
958 if (!mr->visible) continue;
959 // Legacy per-entity material path (no parts / materials / hair).
960 gfx.setMesh3DMaterial(mr->metallic, mr->roughness);
961 gfx.setMesh3DTexCellBomb(mr->texBombScale, mr->texBombStrength, mr->texBombRot);
962 gfx.setMesh3DNormalTexture(mr->normalTexture);
963 gfx.setMesh3DHeightTexture(mr->heightTexture);
964 gfx.setMesh3DParallax(mr->parallaxScale, mr->parallaxMinLayers, mr->parallaxMaxLayers);
965 gfx.setMesh3DShadowReceive(false);
966 Texture *albedo = mr->texture;
967 Color tint(mr->r, mr->g, mr->b, mr->a);
968 Shader *shader = mr->shader;
969 const glm::mat4 model = modelFromTransform(*xf);
970 eve::debug::rtDraw("drawMeshShader", shader ? "custom" : "default");
971 gfx.drawMeshShader(mr->mesh, model, albedo, tint, shader);
972 }
973 }
974
975 gfx.end3DFrameToCanvas();
976}
977
978} // namespace eve::graphics
bool active
Definition CardTypes.cpp:34
int y
Definition Grass.cpp:135
int z
Definition Grass.cpp:135
int x
Definition Grass.cpp:135
glm::vec3 n
Definition Grass.cpp:64
int w
float depth
uint32_t a
uint32_t b
uint32_t c
float roughness
float tb
float tr
float tg
Texture * albedo
float metallic
float f
uint32_t cascadeMask
bool inView
bool clusteredUploaded
Whether the clustered SSBOs were uploaded for this camera this frame.
bool inDefaultView
Renderable3D::MeshRenderer * mr
glm::vec3 eye
glm::vec4 p[6]
float fovRad
FrustumPlanes frustum
Mesh * mesh
ClusteredLightingUpload clustered
int camIdx
glm::vec3 worldC
bool clusteredValid
bool xray
glm::mat4 viewProj
float worldR
Shader * shader
bool hair
Lighting3DPack lighting
float distSq
glm::mat4 view
glm::mat4 model
Material * material
glm::mat4 proj
Light2D::Data * data
bool isPoint
const char * name
Definition RockMesh.cpp:21
int d
int v
float scale
Definition TreeMesh.cpp:122
std::vector< V3 > points
Definition TreeMesh.cpp:126
V3 dir
Definition TreeMesh.cpp:121
float m[16]
RAII pass scope for C++ call sites.
Definition RenderTrace.h:56
Screen-space ambient occlusion.
void applyFromGBuffer(Graphics *gfx, Texture *hwDepth, Texture *worldNormal)
void setQuality(const std::string &quality)
"low" | "medium" | "high" (unknown → medium).
void setUp(float x, float y, float z)
void setFov(float fovYDeg)
void screenToRay(float screenX, float screenY, float viewW, float viewH)
Build a world-space picking ray from a screen pixel. Stores origin (camera eye) and normalized direct...
void setEnvIntensity(float intensity)
void setTarget(float x, float y, float z)
void setAmbient(float r, float g, float b)
void setActive(bool active)
void setEye(float x, float y, float z)
void setEnvMap(Texture *cube)
Specular IBL cubemap (Graphics::newCubemap). nullptr disables IBL.
virtual int getWidth() const =0
virtual int getHeight() const =0
Screen-space buffers for mid/post effects (AO, fog, stylize outline, …).
Definition GBuffer.h:24
Texture * getNormalTexture() const
Definition GBuffer.h:41
Texture * getHwDepthTexture() const
Definition GBuffer.h:40
bool isValid() const
Definition GBuffer.cpp:5
virtual void beginShadowPass(int cascadeIndex)=0
Depth-only shadow pass for one cascade layer (0..2). Draws are recorded into the next begin3DFrame co...
int getPixelHeight() const
Definition Graphics.h:120
virtual void setMesh3DShadows(const ShadowUpload &upload)=0
Upload CSM constants for subsequent default mesh draws (active=false disables).
virtual void endGBufferPass()=0
virtual void setMesh3DClip(float nearZ, float farZ)=0
Near/far used to pack linear depth into scene color A (SSGI).
virtual void setMesh3DMaterial(float metallic, float roughness)=0
Metallic (0..1) and roughness (0..1) for the next default mesh draw.
virtual void setMesh3DViewProj(const glm::mat4 &viewProj)=0
virtual void drawMeshGBuffer(Mesh *mesh, const glm::mat4 &mvp, const glm::mat4 &model, float nearZ, float farZ, Texture *albedo=nullptr, float tintR=1.f, float tintG=1.f, float tintB=1.f)=0
virtual void setMesh3DCameraPos(const glm::vec3 &eye)=0
Camera eye used by mesh shaders that need view/rim (stored in Mesh3DUBO).
virtual void endShadowPass()=0
virtual void setMesh3DClusteredActive(bool active)=0
Cheap per-draw toggle for the already-uploaded clustered light table. Unlike setMesh3DClusteredLighti...
virtual void begin3DFrameToCanvas(Canvas *canvas)=0
Open a 3D render pass targeting an offscreen Canvas (color + depth) at the canvas size....
virtual void setMesh3DEnv(Texture *cube, float intensity)=0
Specular IBL environment for subsequent default mesh draws. cube must be from newCubemap (or nullptr ...
virtual void setMesh3DTexCellBomb(float cellScale, float strength, float rotAmount=1.f)=0
Texture cell bombing for the next default mesh draw (breaks tiling). cellScale: cells per UV unit (ty...
virtual void drawMeshShadow(Mesh *mesh, const glm::mat4 &lightMVP)=0
virtual void beginGBufferPass(int width, int height)=0
Depth/normal(/albedo) fill pass for mid/post effects. One-shot submit (like shadow); call before begi...
virtual void setMesh3DClusteredLighting(const ClusteredLightingUpload &upload)=0
Enable clustered forward path for subsequent default mesh draws (SSBO light lists)....
virtual void setMesh3DParallax(float scale, float minLayers=8.f, float maxLayers=32.f)=0
Parallax occlusion mapping for the next default mesh draw. scale: UV displacement strength (0=off)....
Outline * pipelineOutline()
Pipeline-owned Outline used by RenderSystem3D when the "outline" feature is on.
Definition Graphics.cpp:774
virtual void setMesh3DView(const glm::mat4 &view)=0
Camera view matrix for subsequent drawMesh (view-space depth / CSM select).
bool had3DThisFrame() const
Definition Graphics.h:582
virtual void setMesh3DSceneDepth(Texture *depth)=0
Optional scene hardware depth (G-buffer hwDepth, Vulkan NDC z) bound to mesh3d shader binding 7....
AmbientOcclusion * pipelineAmbientOcclusion()
Pipeline-owned AO / GI / AA used by RenderSystem3D when features "ao" / "gi" / "aa" are enabled....
Definition Graphics.cpp:759
virtual void setMesh3DShadowReceive(bool receive)=0
Per-draw: when false, shadow sampling is forced off for the next mesh draw.
virtual bool supportsGBufferPost() const
Whether gbuffer-based post-process shaders (AO, GI) can be created on this backend....
Definition Graphics.h:104
virtual void begin3DFrame()=0
Begin a 3D frame: shadow/gbuffer (if pending) then a sampleable scene color pass (color+depth)....
virtual void drawMeshShader(Mesh *mesh, const glm::mat4 &model, Texture *texture, const Color &tint, Shader *shader)=0
Draw mesh with an explicit Mesh3D Shader (nullptr = default PBR pipeline).
RenderControl * getRenderControl()
Shared compilable 3D render control (features → pass list + GBuffer). Owned by Graphics; valid for th...
Definition Graphics.cpp:173
virtual void end3DFrameToCanvas()=0
int getPixelWidth() const
Definition Graphics.h:119
int getHeight() const
Definition Graphics.h:118
virtual void setMesh3DNormalTexture(Texture *normal)=0
Optional normal map for the next drawMesh / drawMeshShader (nullptr = flat).
virtual void setMesh3DHeightTexture(Texture *height)=0
Optional height map for parallax (R channel; nullptr = flat / off).
virtual void setMesh3DLighting(const Lighting3DPack &pack)=0
Per-frame ambient + up to 8 lights packed into Mesh3DUBO.
Packages shading method + surface parameters into one attachable asset.
Definition Material.h:26
Texture * getAlbedoTexture() const
Definition Material.h:41
float getTintR() const
Definition Material.h:54
bool isTransparentHair() const
True when this material should go through the hair transparent pass.
Definition Material.cpp:68
float getTintA() const
Definition Material.h:57
float getTintB() const
Definition Material.h:56
bool getCastShadow() const
Definition Material.h:79
float getTintG() const
Definition Material.h:55
bool getReceiveLight() const
Definition Material.h:76
void bind(Graphics &gfx) const
Push this material onto Graphics mesh3d state for the next draw. Does not issue the draw itself.
Definition Material.cpp:72
Shader * effectiveShader() const
Effective shader for Mesh3D draws (may be null → default PBR pipeline).
Definition Material.cpp:63
GPU mesh handle (+ optional CPU morph targets).
Definition Mesh.h:18
float boundsCx
Model-space bounding sphere used for view/cascade frustum culling. Computed from vertex positions at ...
Definition Mesh.h:29
bool hasBounds() const
True when a valid bounding sphere is available for culling.
Definition Mesh.h:35
float boundsRadius
Definition Mesh.h:32
Screen-space model outline (t3ssel8r-style), computed from the GBuffer hardware depth + world-normal ...
Definition Outline.h:31
void setClip(float nearZ, float farZ)
Near/far used to linearize the hardware depth.
Definition Outline.cpp:106
bool apply(Graphics *gfx, Texture *hwDepth, Texture *worldNormal)
Draw the outline over the currently bound canvas / screen. hwDepth is the D32 GBuffer (Vulkan NDC z),...
Definition Outline.cpp:143
Declarative, compilable 3D render control.
void ensureCompiled()
Ensure compiled; no-op when already clean.
bool isEnabled(const std::string &feature) const
bool hasPass(const std::string &name) const
static void addShadowExtraDrawer(ShadowExtraDrawer drawer)
static void setDirectionalLight(float dx, float dy, float dz, float r, float g, float b)
Legacy single directional light used when no enabled Light3D exists.
static void addGBufferExtraDrawer(GBufferExtraDrawer drawer)
static void renderToCanvas(Graphics &gfx, Canvas *target, Camera3D *camera)
static void render(Graphics &gfx)
std::function< void(Graphics &gfx, const glm::mat4 &lightVP, const Camera3D::Data &cam)> ShadowExtraDrawer
Register a callback that casts shadows for geometry outside the Renderable3D ECS (e....
std::function< void(Graphics &gfx, const Camera3D::Data &cam, const glm::mat4 &viewProj, float aspect)> GBufferExtraDrawer
Register a callback that fills the G-buffer (depth/normal/albedo) for geometry outside the Renderable...
void setNormalTexture(Texture *texture)
void setCamera(Camera3D *camera)
int getMeshLodLevelAtDistance(float distance)
std::string getPartName(int index)
Material * getPartMaterial(int index)
void setRotation(float yaw, float pitch, float roll)
void setHeightTexture(Texture *texture)
Height map for parallax (R channel; white = raised). nullptr disables sampling.
void setPart(int index, const std::string &name, Mesh *mesh, Material *material)
Bind a named mesh+material part (e.g. Assimp submesh / body region). index 0..kMaxParts-1....
void setTexCellBomb(float cellScale, float strength, float rotAmount=1.f)
Texture cell bombing — random per-cell UV offset/rotation blended across a 2×2 neighborhood to hide t...
void setTint(float r, float g, float b, float a=1.f)
void setMetallic(float metallic)
void setTexture(Texture *texture)
void setReceiveLight(bool receive)
void setShader(Shader *shader)
void setMaterial(Material *material)
Attach a Material that packages shading method + surface params.
void setParallax(float scale, float minLayers=8.f, float maxLayers=32.f)
Parallax occlusion mapping. scale 0 disables (default). Typical scale 0.02..0.08. Requires a height t...
void setPosition(float x, float y, float z)
void setXRayShader(Shader *shader)
Attach an X-ray mesh shader (see Shader::setXray) for occluded silhouettes.
void setXRayHighlight(bool on)
When true, this entity is an X-ray target: its G-buffer pixels are replaced by occluders and it is re...
void setMeshLod(int index, Mesh *mesh, float switchDistance=0.f)
Configure geometric LOD. index 0 = highest detail. For index > 0, switchDistance is the camera distan...
void setRoughness(float roughness)
void setScale(float sx, float sy, float sz)
void setReceiveShadow(bool receive)
Custom GPU program.
Definition Shader.h:30
GPU texture created via Graphics::newTexture. Owns GPU resources through an opaque backend handle.
Definition Texture.h:17
void rtBind(const char *kind, const char *name)
Definition RenderTrace.h:45
void rtPassBegin(const char *name)
Definition RenderTrace.h:36
void rtDraw(const char *api, const char *detail=nullptr)
Definition RenderTrace.h:48
void rtPassEnd(const char *name)
Definition RenderTrace.h:39
卡牌游戏 UI 工具模块:工厂 + 脚本绑定入口。 功能参考 ycarowr/UiCard:扇形手牌布局、抽牌/洗牌、悬浮放大、拖拽到落牌区、 敌方手牌(背面/偷看)、费用不足置灰,以及可实时调节的布局...
Definition AnimTrail.h:6
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:20
Light3DGpu ClusteredLightGpu
glm::vec4 Color
RGBA color used by every graphics draw call. Lives inside eve::graphics so including a graphics heade...
Definition Color.h:13
ClusteredLightingUpload buildClusteredLighting(const std::vector< ClusteredLightGpu > &points, const std::vector< ClusteredLightGpu > &dirs, const glm::mat4 &view, float nearZ, float farZ, int screenW, int screenH, float fovYRad, const glm::vec4 &ambient)
Build clustered tables for point lights in view space.
ShadowUpload buildDirectionalCSM(const glm::vec3 &lightDirTowardSurface, const glm::vec3 &eye, const glm::vec3 &target, const glm::vec3 &up, float fovYRad, float aspect, float nearZ, float farZ, float bias, float strength)
Build 3 cascade light view-proj matrices for a directional light.
Definition Shadow.cpp:83
CPU-built clustered lighting upload for one frame/camera. Point lights are clustered; directional lig...
GPU light packing for mesh3d / PBR (std140-friendly).
Definition Light.h:85
static constexpr int kMaxLights
Definition Light.h:91
One mesh + material slot on a multi-part model (Assimp mesh / body region). When Material* is null,...
Definition Material.h:134
static constexpr int kCascades
Definition Shadow.h:8