载入中...
搜索中...
未找到
GraphicsMesh.cpp
浏览该文件的文档.
1// Vulkan backend implementation — mesh creation and drawing.
2//
3// Re-split from the merged dev single-TU Graphics.cpp (pure move;
4// dev changes preserved). Shared helpers live in GraphicsInternal.h.
5
8#include "graphics/Light.h"
10
11#include <SDL2/SDL.h>
12#include <SDL2/SDL_vulkan.h>
13
14#include <algorithm>
15#include <array>
16#include <cmath>
17#include <cstdio>
18#include <cstdlib>
19#include <cstring>
20#include <functional>
21#include <stdexcept>
22#include <string>
23#include <vector>
24#if !defined(_WIN32)
25#include <unistd.h>
26#endif
27
28#include "common/Exception.h"
30#include "common/config.h"
32#include "image/Image.h"
33#include "image/ImageData.h"
34#include "zeroerr/assert.h"
35
36#include <memory>
37
38
39#include <assimp/mesh.h>
40#include <assimp/matrix3x3.h>
41#include <assimp/matrix4x4.h>
42#include <assimp/vector3.h>
43#include <glm/gtc/matrix_transform.hpp>
44
46
47namespace eve::graphics::vulkan {
48
49// --- Mesh creation and drawing ------------------------------------------------
50
52 ASSERT(initialized);
53 if (!initialized) throw Exception("newMeshFromAssimp: graphics not initialized");
54 if (mesh.mNumVertices == 0 || mesh.mNumFaces == 0)
55 throw Exception("newMeshFromAssimp: empty mesh");
56
57 std::vector<MeshVertex> verts;
58 verts.reserve(mesh.mNumVertices);
59 std::vector<float> basePos;
60 std::vector<float> baseNrm;
61 std::vector<float> baseUv;
62 basePos.reserve(mesh.mNumVertices * 3);
63 baseNrm.reserve(mesh.mNumVertices * 3);
64 baseUv.reserve(mesh.mNumVertices * 2);
65 for (unsigned i = 0; i < mesh.mNumVertices; ++i) {
66 MeshVertex v{};
67 v.pos = {mesh.mVertices[i].x, mesh.mVertices[i].y, mesh.mVertices[i].z};
68 if (mesh.HasNormals())
69 v.normal = {mesh.mNormals[i].x, mesh.mNormals[i].y, mesh.mNormals[i].z};
70 else
71 v.normal = {0.f, 1.f, 0.f};
72 if (mesh.HasTextureCoords(0))
73 v.uv = {mesh.mTextureCoords[0][i].x, mesh.mTextureCoords[0][i].y};
74 else
75 v.uv = {0.f, 0.f};
76 verts.push_back(v);
77 basePos.push_back(v.pos.x);
78 basePos.push_back(v.pos.y);
79 basePos.push_back(v.pos.z);
80 baseNrm.push_back(v.normal.x);
81 baseNrm.push_back(v.normal.y);
82 baseNrm.push_back(v.normal.z);
83 baseUv.push_back(v.uv.x);
84 baseUv.push_back(v.uv.y);
85 }
86
87 std::vector<uint32_t> indices;
88 indices.reserve(mesh.mNumFaces * 3);
89 for (unsigned f = 0; f < mesh.mNumFaces; ++f) {
90 const aiFace &face = mesh.mFaces[f];
91 if (face.mNumIndices != 3) continue;
92 indices.push_back(face.mIndices[0]);
93 indices.push_back(face.mIndices[1]);
94 indices.push_back(face.mIndices[2]);
95 }
96 if (indices.empty()) throw Exception("newMeshFromAssimp: no triangle faces");
97
98 std::unique_ptr<GpuMesh> gpu;
99 if (mesh.mNumVertices <= 65535u) {
100 std::vector<uint16_t> idx16;
101 idx16.reserve(indices.size());
102 for (uint32_t i : indices) idx16.push_back(uint16_t(i));
103 gpu = uploadGpuMesh16(device, frameToken(), verts, idx16);
104 } else {
105 gpu = uploadGpuMesh(device, frameToken(), verts, indices);
106 }
107 auto handle = makeMeshHandle(*gpu);
108 // Retain the CPU morph base pose only when the mesh actually has morphs;
109 // otherwise the base pos/nrm/uv copies would linger at ~32B/vertex for no reason.
110 if (mesh.mNumAnimMeshes > 0) {
111 handle->initMorphBase(int(mesh.mNumVertices), basePos.data(), baseNrm.data(), baseUv.data());
112 }
113 // Assimp morph targets (VRM / glTF blend shapes often land here).
114 for (unsigned m = 0; m < mesh.mNumAnimMeshes; ++m) {
115 const aiAnimMesh *am = mesh.mAnimMeshes[m];
116 if (!am || !am->mVertices || am->mNumVertices != mesh.mNumVertices) continue;
117 std::string name = am->mName.length ? am->mName.C_Str() : ("morph" + std::to_string(m));
118 std::vector<float> absPos(size_t(am->mNumVertices) * 3u);
119 for (unsigned i = 0; i < am->mNumVertices; ++i) {
120 absPos[size_t(i) * 3u + 0] = am->mVertices[i].x;
121 absPos[size_t(i) * 3u + 1] = am->mVertices[i].y;
122 absPos[size_t(i) * 3u + 2] = am->mVertices[i].z;
123 }
124 handle->addMorphTargetAbsolute(name, absPos.data());
125 }
126 handle->markMorphClean();
127 Mesh *raw = handle.get();
128 assignMeshBounds(raw, verts);
129 ownedGpuMeshes.push_back(std::move(gpu));
130 ownedMeshes.push_back(std::move(handle));
131 return raw;
132}
133
134Mesh *Graphics::newMeshFromAssimp(const ::aiMesh &mesh, const aiMatrix4x4 &worldTransform) {
135 ASSERT(initialized);
136 if (!initialized) throw Exception("newMeshFromAssimp: graphics not initialized");
137 if (mesh.mNumVertices == 0 || mesh.mNumFaces == 0)
138 throw Exception("newMeshFromAssimp: empty mesh");
139
140 std::vector<aiVector3D> positions(mesh.mNumVertices);
141 std::vector<aiVector3D> normals(mesh.mNumVertices);
142 aiMatrix3x3 nmat(worldTransform);
143 const float ndet = nmat.Determinant();
144 if (std::fabs(ndet) > 1e-8f) {
145 nmat.Inverse();
146 nmat.Transpose();
147 }
148 for (unsigned i = 0; i < mesh.mNumVertices; ++i) {
149 positions[i] = worldTransform * mesh.mVertices[i];
150 if (mesh.HasNormals()) {
151 normals[i] = nmat * mesh.mNormals[i];
152 normals[i].Normalize();
153 } else {
154 normals[i] = aiVector3D(0.f, 1.f, 0.f);
155 }
156 }
157
158 // Negative determinant mirrors the mesh: winding flips while inverse-transpose
159 // keeps normals consistent, so back-face cull would hide the visible side.
160 const bool flipWinding = worldTransform.Determinant() < 0.f;
161 std::vector<aiFace> flippedFaces;
162 std::vector<unsigned> flippedIdx;
163 if (flipWinding) {
164 flippedFaces.resize(mesh.mNumFaces);
165 flippedIdx.resize(size_t(mesh.mNumFaces) * 3u);
166 for (unsigned f = 0; f < mesh.mNumFaces; ++f) {
167 const aiFace &src = mesh.mFaces[f];
168 aiFace &dst = flippedFaces[f];
169 dst.mNumIndices = src.mNumIndices;
170 if (src.mNumIndices == 3 && src.mIndices) {
171 unsigned *idx = flippedIdx.data() + size_t(f) * 3u;
172 idx[0] = src.mIndices[0];
173 idx[1] = src.mIndices[2];
174 idx[2] = src.mIndices[1];
175 dst.mIndices = idx;
176 } else {
177 dst.mIndices = src.mIndices;
178 }
179 }
180 }
181
182 // Non-owning view — do not let aiMesh destructor free borrowed pointers.
183 aiMesh tmp;
184 std::memset(&tmp, 0, sizeof(tmp));
185 tmp.mPrimitiveTypes = mesh.mPrimitiveTypes;
186 tmp.mNumVertices = mesh.mNumVertices;
187 tmp.mVertices = positions.data();
188 tmp.mNormals = normals.data();
189 tmp.mNumFaces = mesh.mNumFaces;
190 tmp.mFaces = flipWinding ? flippedFaces.data() : mesh.mFaces;
191 tmp.mMaterialIndex = mesh.mMaterialIndex;
192 tmp.mNumAnimMeshes = mesh.mNumAnimMeshes;
193 tmp.mAnimMeshes = mesh.mAnimMeshes;
194 if (mesh.HasTextureCoords(0)) {
195 tmp.mTextureCoords[0] = mesh.mTextureCoords[0];
196 tmp.mNumUVComponents[0] = mesh.mNumUVComponents[0];
197 }
198 Mesh *out = newMeshFromAssimp(tmp);
199 tmp.mVertices = nullptr;
200 tmp.mNormals = nullptr;
201 tmp.mFaces = nullptr;
202 tmp.mTextureCoords[0] = nullptr;
203 tmp.mAnimMeshes = nullptr;
204 return out;
205}
206
207Mesh *Graphics::newMeshFromArrays(const float *posXYZ, const float *nrmXYZ, const float *uvST,
208 int vertexCount, const uint32_t *indices, int indexCount) {
209 ASSERT(initialized);
210 if (!initialized) throw Exception("newMeshFromArrays: graphics not initialized");
211 if (!posXYZ || vertexCount <= 0) throw Exception("newMeshFromArrays: empty positions");
212 if (!indices || indexCount < 3) throw Exception("newMeshFromArrays: empty indices");
213 if (indexCount % 3 != 0) throw Exception("newMeshFromArrays: indexCount must be multiple of 3");
214
215 std::vector<MeshVertex> verts(static_cast<size_t>(vertexCount));
216 for (int i = 0; i < vertexCount; ++i) {
217 MeshVertex &v = verts[static_cast<size_t>(i)];
218 v.pos = {posXYZ[size_t(i) * 3u], posXYZ[size_t(i) * 3u + 1u], posXYZ[size_t(i) * 3u + 2u]};
219 if (nrmXYZ)
220 v.normal = {nrmXYZ[size_t(i) * 3u], nrmXYZ[size_t(i) * 3u + 1u],
221 nrmXYZ[size_t(i) * 3u + 2u]};
222 else
223 v.normal = {0.f, 1.f, 0.f};
224 if (uvST)
225 v.uv = {uvST[size_t(i) * 2u], uvST[size_t(i) * 2u + 1u]};
226 else
227 v.uv = {0.f, 0.f};
228 }
229
230 std::vector<uint32_t> idx(indices, indices + indexCount);
231 for (uint32_t id : idx) {
232 if (int(id) >= vertexCount) throw Exception("newMeshFromArrays: index out of range");
233 }
234
235 auto gpu = uploadGpuMesh(device, frameToken(), verts, idx);
236 auto handle = makeMeshHandle(*gpu);
237 Mesh *raw = handle.get();
238 raw->computeBounds(posXYZ, vertexCount);
239 ownedGpuMeshes.push_back(std::move(gpu));
240 ownedMeshes.push_back(std::move(handle));
241 return raw;
242}
243
245 if (!mesh || !mesh->gpuHandle || !mesh->hasMorphData() || !mesh->isMorphDirty()) return false;
246 if (!initialized) return false;
247
248 std::vector<float> pos;
249 std::vector<float> nrm;
251 const int vc = mesh->getVertexCount();
252 if (vc <= 0 || int(pos.size()) < vc * 3) return false;
253 mesh->computeBounds(pos.data(), vc);
254
255 std::vector<MeshVertex> verts(static_cast<size_t>(vc));
256 const auto &uv = mesh->baseUv();
257 for (int i = 0; i < vc; ++i) {
258 MeshVertex &v = verts[static_cast<size_t>(i)];
259 v.pos = {pos[size_t(i) * 3u + 0], pos[size_t(i) * 3u + 1], pos[size_t(i) * 3u + 2]};
260 if (int(nrm.size()) >= (i + 1) * 3)
261 v.normal = {nrm[size_t(i) * 3u + 0], nrm[size_t(i) * 3u + 1], nrm[size_t(i) * 3u + 2]};
262 else
263 v.normal = {0.f, 1.f, 0.f};
264 if (int(uv.size()) >= (i + 1) * 2)
265 v.uv = {uv[size_t(i) * 2u + 0], uv[size_t(i) * 2u + 1]};
266 else
267 v.uv = {0.f, 0.f};
268 }
269
270 auto *gpu = static_cast<GpuMesh *>(mesh->gpuHandle);
271 // Ring-buffered host-visible VBO: the next copy is kDynamicVertexCopies
272 // frames old, so overwriting it never races with in-flight draws — no
273 // device-wide wait (see writeDynamicMesh).
274 ensureDynamicRing(*gpu);
275 writeDynamicMesh(*gpu, verts, getDevice(), frameToken(), nullptr, 0);
276 mesh->markMorphClean();
277 return true;
278}
279
280bool Graphics::updateMeshVertices(Mesh *mesh, const float *posXYZ, const float *nrmXYZ,
281 const float *uvST, int vertexCount, const uint32_t *indices,
282 int indexCount) {
283 if (!initialized || !mesh || !mesh->gpuHandle) return false;
284 if (!posXYZ || vertexCount <= 0) return false;
285 if (indexCount > 0 && (indexCount % 3 != 0 || !indices)) return false;
286
287 std::vector<MeshVertex> verts(static_cast<size_t>(vertexCount));
288 for (int i = 0; i < vertexCount; ++i) {
289 MeshVertex &v = verts[static_cast<size_t>(i)];
290 v.pos = {posXYZ[size_t(i) * 3u], posXYZ[size_t(i) * 3u + 1u], posXYZ[size_t(i) * 3u + 2u]};
291 if (nrmXYZ)
292 v.normal = {nrmXYZ[size_t(i) * 3u], nrmXYZ[size_t(i) * 3u + 1u],
293 nrmXYZ[size_t(i) * 3u + 2u]};
294 else
295 v.normal = {0.f, 1.f, 0.f};
296 if (uvST)
297 v.uv = {uvST[size_t(i) * 2u], uvST[size_t(i) * 2u + 1u]};
298 else
299 v.uv = {0.f, 0.f};
300 }
301
302 auto *gpu = static_cast<GpuMesh *>(mesh->gpuHandle);
303 mesh->computeBounds(posXYZ, vertexCount);
304 // Same ring-buffer approach as bakeMeshMorph: never wait on in-flight
305 // frames, just write the next copy.
306 ensureDynamicRing(*gpu);
307 writeDynamicMesh(*gpu, verts, getDevice(), frameToken(), indices, indexCount);
308 mesh->indexCount = int(gpu->indexCount);
309 return true;
310}
311
313 if (!mesh || !mesh->gpuHandle) return false;
314
315 auto *gpu = static_cast<GpuMesh *>(mesh->gpuHandle);
316 auto gpuIt = std::find_if(ownedGpuMeshes.begin(), ownedGpuMeshes.end(),
317 [&](const std::unique_ptr<GpuMesh> &g) {
318 return g.get() == gpu;
319 });
320 if (gpuIt == ownedGpuMeshes.end()) return false;
321
322 auto meshIt = std::find_if(ownedMeshes.begin(), ownedMeshes.end(),
323 [&](const std::unique_ptr<Mesh> &m) {
324 return m.get() == mesh;
325 });
326 if (meshIt == ownedMeshes.end()) return false;
327
328 // An in-flight draw may still read the vertex/index buffers; drain first.
329 waitForSharedGpuResources();
330 mesh->gpuHandle = nullptr;
331 ownedGpuMeshes.erase(gpuIt);
332 // Transfer the CPU facade to the caller instead of destroying it.
333 (void)meshIt->release();
334 ownedMeshes.erase(meshIt);
335 return true;
336}
337
338Mesh *Graphics::newMeshSphere(int slices, int stacks) {
339 ASSERT(initialized);
340 if (!initialized) throw Exception("newMeshSphere: graphics not initialized");
341 if (slices < 3) slices = 3;
342 if (stacks < 2) stacks = 2;
343 if (slices > 256) slices = 256;
344 if (stacks > 128) stacks = 128;
345
346 constexpr float kPi = 3.14159265358979323846f;
347 constexpr float kTwoPi = kPi * 2.f;
348
349 // Layout (stride = slices+1, seam column duplicated for continuous U):
350 // row 0: north pole verts (same pos, unique U)
351 // row 1..stacks-1: latitude rings
352 // row stacks: south pole verts
353 // One pole vertex per longitude avoids a single-fan UV singularity and
354 // keeps every triangle non-degenerate.
355 const int stride = slices + 1;
356 const int rows = stacks + 1; // includes both pole rows
357 std::vector<MeshVertex> verts;
358 verts.reserve(size_t(stride) * size_t(rows));
359
360 auto pushVert = [&](float px, float py, float pz, float u, float v) {
362 vert.pos = {px, py, pz};
363 vert.normal = {px, py, pz}; // unit sphere
364 vert.uv = {u, v};
365 verts.push_back(vert);
366 };
367
368 for (int y = 0; y <= stacks; ++y) {
369 const float fv = float(y) / float(stacks);
370 const float phi = fv * kPi;
371 const float sinPhi = std::sin(phi);
372 const float cosPhi = std::cos(phi);
373 for (int x = 0; x <= slices; ++x) {
374 const float u = float(x) / float(slices);
375 const float theta = u * kTwoPi;
376 // Exact poles: collapse ring to a point but keep per-slice UVs.
377 if (y == 0)
378 pushVert(0.f, 1.f, 0.f, u, 0.f);
379 else if (y == stacks)
380 pushVert(0.f, -1.f, 0.f, u, 1.f);
381 else
382 pushVert(sinPhi * std::cos(theta), cosPhi, sinPhi * std::sin(theta), u, fv);
383 }
384 }
385
386 std::vector<uint32_t> indices;
387 indices.reserve(size_t(slices) * size_t(stacks) * 6u);
388
389 // Quads between consecutive rows. Winding must be consistent for outward
390 // faces (object-space CCW; mesh pipelines use Clockwise frontFace after the
391 // Vulkan Y flip in perspectiveVulkanRH_ZO). Use the same winding that
392 // closed the south pole in-game: rowA[x], rowB[x], rowA[x+1] /
393 // rowA[x+1], rowB[x], rowB[x+1] — derived from ring→next with
394 // (i0,i2,i1)+(i1,i2,i3) which equals (lon,lat)->(lon,lat+1)->(lon+1,lat).
395 for (int y = 0; y < stacks; ++y) {
396 const uint32_t row0 = uint32_t(y * stride);
397 const uint32_t row1 = uint32_t((y + 1) * stride);
398 for (int x = 0; x < slices; ++x) {
399 const uint32_t i0 = row0 + uint32_t(x);
400 const uint32_t i1 = row0 + uint32_t(x + 1);
401 const uint32_t i2 = row1 + uint32_t(x);
402 const uint32_t i3 = row1 + uint32_t(x + 1);
403 // Outward for RH Y-up (verified against south-cap fix): i0,i2,i1 + i1,i2,i3
404 indices.push_back(i0);
405 indices.push_back(i2);
406 indices.push_back(i1);
407 indices.push_back(i1);
408 indices.push_back(i2);
409 indices.push_back(i3);
410 }
411 }
412
413 auto gpu = uploadGpuMesh(device, frameToken(), verts, indices);
414 auto handle = makeMeshHandle(*gpu);
415 Mesh *raw = handle.get();
416 assignMeshBounds(raw, verts);
417 ownedGpuMeshes.push_back(std::move(gpu));
418 ownedMeshes.push_back(std::move(handle));
419 return raw;
420}
421
422Mesh *Graphics::newMeshCylinder(int slices, int stacks, bool caps) {
423 ASSERT(initialized);
424 if (!initialized) throw Exception("newMeshCylinder: graphics not initialized");
425 if (slices < 3) slices = 3;
426 if (stacks < 1) stacks = 1;
427 if (slices > 256) slices = 256;
428 if (stacks > 128) stacks = 128;
429
430 constexpr float kPi = 3.14159265358979323846f;
431 constexpr float kTwoPi = kPi * 2.f;
432 constexpr float kRadius = 1.f;
433 constexpr float kHalfH = 1.f; // height 2, Y from -1..1
434
435 const int stride = slices + 1; // duplicated seam for continuous U
436 const int sideRows = stacks + 1;
437 std::vector<MeshVertex> verts;
438 verts.reserve(size_t(stride) * size_t(sideRows) + size_t(caps ? 2 * (slices + 2) : 0));
439
440 auto pushVert = [&](float px, float py, float pz, float nx, float ny, float nz, float u,
441 float v) {
443 vert.pos = {px, py, pz};
444 vert.normal = {nx, ny, nz};
445 vert.uv = {u, v};
446 verts.push_back(vert);
447 };
448
449 // Side wall: outward normals in XZ.
450 for (int y = 0; y <= stacks; ++y) {
451 const float fv = float(y) / float(stacks);
452 const float py = kHalfH - fv * (2.f * kHalfH);
453 for (int x = 0; x <= slices; ++x) {
454 const float u = float(x) / float(slices);
455 const float theta = u * kTwoPi;
456 const float cx = std::cos(theta);
457 const float sz = std::sin(theta);
458 pushVert(kRadius * cx, py, kRadius * sz, cx, 0.f, sz, u, fv);
459 }
460 }
461
462 std::vector<uint32_t> indices;
463 indices.reserve(size_t(slices) * size_t(stacks) * 6u +
464 size_t(caps ? slices * 2 * 3 : 0));
465
466 for (int y = 0; y < stacks; ++y) {
467 const uint32_t row0 = uint32_t(y * stride);
468 const uint32_t row1 = uint32_t((y + 1) * stride);
469 for (int x = 0; x < slices; ++x) {
470 const uint32_t i0 = row0 + uint32_t(x);
471 const uint32_t i1 = row0 + uint32_t(x + 1);
472 const uint32_t i2 = row1 + uint32_t(x);
473 const uint32_t i3 = row1 + uint32_t(x + 1);
474 indices.push_back(i0);
475 indices.push_back(i2);
476 indices.push_back(i1);
477 indices.push_back(i1);
478 indices.push_back(i2);
479 indices.push_back(i3);
480 }
481 }
482
483 if (caps) {
484 // Top cap (y = +1, normal +Y) — fan from center.
485 const uint32_t topCenter = uint32_t(verts.size());
486 pushVert(0.f, kHalfH, 0.f, 0.f, 1.f, 0.f, 0.5f, 0.5f);
487 const uint32_t topRing = uint32_t(verts.size());
488 for (int x = 0; x <= slices; ++x) {
489 const float u = float(x) / float(slices);
490 const float theta = u * kTwoPi;
491 const float cx = std::cos(theta);
492 const float sz = std::sin(theta);
493 pushVert(kRadius * cx, kHalfH, kRadius * sz, 0.f, 1.f, 0.f, 0.5f + 0.5f * cx,
494 0.5f + 0.5f * sz);
495 }
496 for (int x = 0; x < slices; ++x) {
497 indices.push_back(topCenter);
498 indices.push_back(topRing + uint32_t(x));
499 indices.push_back(topRing + uint32_t(x + 1));
500 }
501
502 // Bottom cap (y = -1, normal -Y).
503 const uint32_t botCenter = uint32_t(verts.size());
504 pushVert(0.f, -kHalfH, 0.f, 0.f, -1.f, 0.f, 0.5f, 0.5f);
505 const uint32_t botRing = uint32_t(verts.size());
506 for (int x = 0; x <= slices; ++x) {
507 const float u = float(x) / float(slices);
508 const float theta = u * kTwoPi;
509 const float cx = std::cos(theta);
510 const float sz = std::sin(theta);
511 pushVert(kRadius * cx, -kHalfH, kRadius * sz, 0.f, -1.f, 0.f, 0.5f + 0.5f * cx,
512 0.5f + 0.5f * sz);
513 }
514 for (int x = 0; x < slices; ++x) {
515 // CW when viewed from below so outward (-Y) faces are CCW from outside.
516 indices.push_back(botCenter);
517 indices.push_back(botRing + uint32_t(x + 1));
518 indices.push_back(botRing + uint32_t(x));
519 }
520 }
521
522 auto gpu = uploadGpuMesh(device, frameToken(), verts, indices);
523 auto handle = makeMeshHandle(*gpu);
524 Mesh *raw = handle.get();
525 assignMeshBounds(raw, verts);
526 ownedGpuMeshes.push_back(std::move(gpu));
527 ownedMeshes.push_back(std::move(handle));
528 return raw;
529}
530
531void Graphics::drawMesh(Mesh *mesh, const glm::mat4 &model, Texture *texture, const Color &tint) {
532 drawMeshShader(mesh, model, texture, tint, nullptr);
533}
534
535void Graphics::drawMeshShader(Mesh *mesh, const glm::mat4 &model, Texture *texture, const Color &tint,
536 Shader *shader) {
537 ASSERT(initialized);
538 ASSERT(mesh != nullptr);
539 if (!initialized) throw Exception("drawMesh: graphics not initialized");
540 if (!mesh || !mesh->gpuHandle) throw Exception("drawMesh: null mesh");
541 if (!swapchainPassOpen && !offscreen3DPassOpen)
542 throw Exception("drawMesh: call begin3DFrame first");
543 if (!mesh3dPipeline) throw Exception("drawMesh: mesh3d pipeline missing");
544
545 if (shader) {
546 if (shader->getKind() != Shader::Kind::eMesh3D)
547 throw Exception("drawMesh: shader is not a Mesh3D shader (use newMeshShader*)");
548 if (!shader->gpuHandle) throw Exception("drawMesh: shader has no GPU pipeline");
549 }
550
551 auto *gpuMesh = static_cast<GpuMesh *>(mesh->gpuHandle);
552 Texture *tex = texture ? texture : whiteTexture;
553 if (!tex || !tex->gpuHandle) throw Exception("drawMesh: missing texture");
554 auto *gpuTex = static_cast<GpuTexture *>(tex->gpuHandle);
555
556 ensureFlatNormalTexture3D();
557 Texture *ntex = mesh3dNormalTexture ? mesh3dNormalTexture : flatNormalTexture3D;
558 if (!ntex || !ntex->gpuHandle) throw Exception("drawMesh: missing normal texture");
559 auto *gpuNormal = static_cast<GpuTexture *>(ntex->gpuHandle);
560
561 ensureFlatHeightTexture3D();
562 Texture *htex = mesh3dHeightTexture ? mesh3dHeightTexture : flatHeightTexture3D;
563 if (!htex || !htex->gpuHandle) throw Exception("drawMesh: missing height texture");
564 auto *gpuHeight = static_cast<GpuTexture *>(htex->gpuHandle);
565
566 Texture *depthTex = mesh3dSceneDepthTexture ? mesh3dSceneDepthTexture : whiteTexture;
567 if (!depthTex || !depthTex->gpuHandle) throw Exception("drawMesh: missing scene depth texture");
568 auto *gpuDepth = static_cast<GpuTexture *>(depthTex->gpuHandle);
569
570 ensureDefaultEnvCubemap();
571 Texture *envTex = mesh3dEnvTexture ? mesh3dEnvTexture : defaultEnvCubemap;
572 if (!envTex || !envTex->gpuHandle) throw Exception("drawMesh: missing env cubemap");
573 auto *gpuEnv = static_cast<GpuTexture *>(envTex->gpuHandle);
574 if (!gpuEnv->isCube) throw Exception("drawMesh: env texture is not a cubemap");
575 const float envIntensity = (mesh3dEnvTexture && mesh3dEnvIntensity > 0.f) ? mesh3dEnvIntensity : 0.f;
576
577 const bool useClustered = mesh3dClusteredActive && !shader && mesh3dClusteredPipeline;
578 auto &cb = currentPresentCb();
579
580 auto makeShadowUbo = [&]() {
581 ShadowUBO s = mesh3dShadows.ubo;
582 if (!mesh3dShadows.active) {
583 s.bias.y = 0.f;
584 s.splits.w = 0.f;
585 }
586 s.bias.z = mesh3dShadowReceive ? 1.f : 0.f;
587 return s;
588 };
589
590 if (useClustered) {
591 Mesh3DClusteredUBO ubo{};
592 ubo.model = model;
593 ubo.mvp = mesh3dFrameUbo.mvp * model;
594 ubo.view = mesh3dClustered.view;
595 ubo.lightDir = mesh3dClustered.primaryDir;
596 ubo.lightColor = glm::vec4(glm::vec3(mesh3dClustered.primaryColor), envIntensity);
597 ubo.tint = glm::vec4(tint.r, tint.g, tint.b, tint.a);
598 ubo.cameraPos = glm::vec4(glm::vec3(mesh3dFrameUbo.cameraPos), mesh3dRoughness);
599 ubo.ambient = glm::vec4(glm::vec3(mesh3dClustered.ambient), mesh3dMetallic);
600 ubo.gridInfo = mesh3dClustered.gridInfo;
601 ubo.clipInfo = mesh3dClustered.clipInfo;
602 ubo.texBomb = glm::vec4(mesh3dTexBombScale, mesh3dTexBombStrength, mesh3dTexBombRot, 0.f);
603 ubo.parallax =
604 glm::vec4(mesh3dParallaxScale, mesh3dParallaxMinLayers, mesh3dParallaxMaxLayers, 0.f);
605
606 auto &cfslots = currentMesh3dClusteredFrameSlots();
607 if (cfslots.drawIndex >= cfslots.capacity) {
608 std::fprintf(stderr,
609 "[vulkan] clustered mesh3d UBO ring exhausted (%zu draws); draw skipped\n",
610 cfslots.capacity);
611 return;
612 }
613 const size_t slot = cfslots.drawIndex++;
614 ensureMesh3dStrides();
615 const uint32_t uboOffset = uint32_t(slot) * mesh3dClusteredUboStride;
616 const uint32_t shadowOffset = uint32_t(slot) * shadowUboStride;
617 updateRingLocal(cfslots.uboRing, uboOffset, &ubo, sizeof(ubo));
618 const ShadowUBO shadow = makeShadowUbo();
619 updateRingLocal(cfslots.shadowRing, shadowOffset, &shadow, sizeof(shadow));
620 vk::DescriptorSet set =
621 mesh3dClusteredSetFor(gpuTex, gpuNormal, gpuEnv, gpuHeight, cfslots);
622 const uint32_t dynOffsets[2] = {uboOffset, shadowOffset};
623
624 if (mesh3dClusteredPipeline != lastMesh3dClusteredPipeline) {
625 cb.bindPipeline(vk::PipelineBindPoint::eGraphics, mesh3dClusteredPipeline);
626 lastMesh3dClusteredPipeline = mesh3dClusteredPipeline;
627 }
628 cb.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, mesh3dClusteredPipelineLayout, 0, 1,
629 &set, 2, dynOffsets);
630 drawIndexedMesh(cb, *gpuMesh);
631 return;
632 }
633
634 Mesh3DUBO ubo = mesh3dFrameUbo;
635 ubo.model = model;
636 ubo.mvp = mesh3dFrameUbo.mvp * model;
637 ubo.tint = glm::vec4(tint.r, tint.g, tint.b, tint.a);
638 ubo.ambient = glm::vec4(glm::vec3(mesh3dLighting.ambient), mesh3dMetallic);
639 const int lightCount = std::max(0, std::min(mesh3dLighting.count, Lighting3DPack::kMaxLights));
640 ubo.lightDir.w = float(lightCount);
641 ubo.cameraPos.w = mesh3dRoughness;
642 ubo.lightColor.w = envIntensity;
643 ubo.texBomb = glm::vec4(mesh3dTexBombScale, mesh3dTexBombStrength, mesh3dTexBombRot, 0.f);
644 ubo.parallax =
645 glm::vec4(mesh3dParallaxScale, mesh3dParallaxMinLayers, mesh3dParallaxMaxLayers, 0.f);
646 for (int i = 0; i < lightCount; ++i) ubo.lights[i] = mesh3dLighting.lights[i];
647 int dirI = -1;
648 for (int i = 0; i < lightCount; ++i) {
649 if (mesh3dLighting.lights[i].posRadius.w <= 0.f) {
650 dirI = i;
651 break;
652 }
653 }
654 if (dirI >= 0) {
655 glm::vec3 d(mesh3dLighting.lights[dirI].posRadius);
656 if (glm::length(d) < 1e-6f) d = glm::vec3(0.f, 1.f, 0.f);
657 else d = glm::normalize(d);
658 ubo.lightDir = glm::vec4(d, float(lightCount));
659 ubo.lightColor = glm::vec4(glm::vec3(mesh3dLighting.lights[dirI].color), envIntensity);
660 } else {
661 ubo.lightDir = glm::vec4(0.f, 1.f, 0.f, float(lightCount));
662 ubo.lightColor = glm::vec4(0.f, 0.f, 0.f, envIntensity);
663 }
664
665 auto &fslots = currentMesh3dFrameSlots();
666 if (fslots.drawIndex >= fslots.capacity) {
667 std::fprintf(stderr, "[vulkan] mesh3d UBO ring exhausted (%zu draws); draw skipped\n",
668 fslots.capacity);
669 return;
670 }
671 const size_t slot = fslots.drawIndex++;
672 ensureMesh3dStrides();
673 const uint32_t uboOffset = uint32_t(slot) * mesh3dUboStride;
674 const uint32_t shadowOffset = uint32_t(slot) * shadowUboStride;
675 updateRingLocal(fslots.uboRing, uboOffset, &ubo, sizeof(ubo));
676 const ShadowUBO shadow = makeShadowUbo();
677 updateRingLocal(fslots.shadowRing, shadowOffset, &shadow, sizeof(shadow));
678 vk::DescriptorSet set = mesh3dSetFor(gpuTex, gpuNormal, gpuEnv, gpuHeight, gpuDepth, fslots);
679 const uint32_t dynOffsets[2] = {uboOffset, shadowOffset};
680
681 if (shader) {
682 if (offscreen3DPassOpen)
683 throw Exception("drawMeshShader: custom mesh shader in offscreen 3D pass is unsupported");
684 auto *gs = static_cast<GpuShader *>(shader->gpuHandle);
685 vk::Pipeline pipeline = gs->mesh3dPipeline;
686 if (shader->isXray()) {
687 // X-ray silhouette pass: depth test/write off + alpha blend so the
688 // occluded part paints over the building. The pipeline is created
689 // with the shader (see newMeshShaderFromSpv); do not compile it
690 // here — a render pass is already open.
691 pipeline = gs->mesh3dXrayPipeline;
692 }
693 if (!pipeline) return;
694 if (pipeline != lastMesh3dPipeline) {
695 cb.bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline);
696 lastMesh3dPipeline = pipeline;
697 }
698 cb.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, mesh3dShaderPipelineLayout, 0, 1, &set,
699 2, dynOffsets);
700 cb.pushConstants(mesh3dShaderPipelineLayout,
701 vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment, 0,
702 Shader::kPushConstantBytes, shader->pushConstantData());
703 } else {
704 const vk::Pipeline pipe = offscreen3DPassOpen ? offscreen3DMeshPipeline : mesh3dPipeline;
705 if (pipe != lastMesh3dPipeline) {
706 cb.bindPipeline(vk::PipelineBindPoint::eGraphics, pipe);
707 lastMesh3dPipeline = pipe;
708 }
709 cb.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, mesh3dPipelineLayout, 0, 1, &set, 2,
710 dynOffsets);
711 }
712 drawIndexedMesh(cb, *gpuMesh);
713}
714
715
716} // namespace eve::graphics::vulkan
std::vector< std::uint32_t > verts
Definition Builder.cpp:27
float cx
Definition CardTypes.cpp:31
vk::ShaderModule vert
int y
Definition Grass.cpp:135
uint32_t i1
Definition Grass.cpp:62
uint32_t i2
Definition Grass.cpp:62
uint32_t i0
Definition Grass.cpp:62
float u
Definition Grass.cpp:234
int x
Definition Grass.cpp:135
std::vector< Colorf > px
int idx
float f
Mesh * mesh
Shader * shader
glm::mat4 model
const char * name
Definition RockMesh.cpp:21
int d
int v
float m[16]
uint32_t s
Definition Weather.cpp:28
GPU mesh handle (+ optional CPU morph targets).
Definition Mesh.h:18
void computeBounds(const float *posXYZ, int vertexCount)
Compute the bounding sphere (centroid + max radius) from positions.
Definition Mesh.cpp:10
void computeMorphedPositions(std::vector< float > &outPos, std::vector< float > &outNrm) const
Bake current weights into outPos / outNrm (xyz packed).
Definition Mesh.cpp:142
Custom GPU program.
Definition Shader.h:30
static constexpr uint32_t kPushConstantBytes
Definition Shader.h:33
GPU texture created via Graphics::newTexture. Owns GPU resources through an opaque backend handle.
Definition Texture.h:17
bool releaseMesh(Mesh *mesh) override
Eagerly releases a mesh created by this Graphics.
Mesh * newMeshSphere(int slices=32, int stacks=16) override
Procedural UV sphere (radius 1, Y-up). Owned by Graphics. slices = longitude divisions,...
bool bakeMeshMorph(Mesh *mesh) override
If mesh morph weights are dirty, bake blended positions and upload to the GPU VBO....
Mesh * newMeshCylinder(int slices=32, int stacks=1, bool caps=true) override
Procedural Y-up cylinder (radius 1, height 2 centered at origin). slices = longitude divisions; stack...
Mesh * newMeshFromArrays(const float *posXYZ, const float *nrmXYZ, const float *uvST, int vertexCount, const uint32_t *indices, int indexCount) override
Upload a triangle mesh from packed CPU arrays. Owned by Graphics. posXYZ required (vertexCount*3)....
Mesh * newMeshFromAssimp(const ::aiMesh &mesh) override
void drawMeshShader(Mesh *mesh, const glm::mat4 &model, Texture *texture, const Color &tint, Shader *shader) override
Draw mesh with an explicit Mesh3D Shader (nullptr = default PBR pipeline).
bool updateMeshVertices(Mesh *mesh, const float *posXYZ, const float *nrmXYZ, const float *uvST, int vertexCount, const uint32_t *indices, int indexCount) override
In-place update of a mesh's vertex/index data (CPU -> host-visible VBO). Mirrors bakeMeshMorph: the u...
void drawMesh(Mesh *mesh, const glm::mat4 &model, Texture *texture, const Color &tint) override
Draw one mesh with model matrix. Requires begin3DFrame() (or an open swapchain pass).
glm::vec4 Color
RGBA color used by every graphics draw call. Lives inside eve::graphics so including a graphics heade...
Definition Color.h:13
static constexpr int kMaxLights
Definition Light.h:91
Light3DGpu lights[kMaxLights]
Definition Light.h:93
Per-frame / per-draw CSM constants (std140). Binding separate from Mesh3D Frame UBO.
Definition Shadow.h:17
Light3DGpu lights[Lighting3DPack::kMaxLights]
Definition Graphics.h:95