53 if (!initialized)
throw Exception(
"newMeshFromAssimp: graphics not initialized");
54 if (
mesh.mNumVertices == 0 ||
mesh.mNumFaces == 0)
55 throw Exception(
"newMeshFromAssimp: empty mesh");
57 std::vector<MeshVertex>
verts;
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) {
68 if (
mesh.HasNormals())
69 v.normal = {
mesh.mNormals[i].x,
mesh.mNormals[i].y,
mesh.mNormals[i].z};
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};
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);
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]);
96 if (indices.empty())
throw Exception(
"newMeshFromAssimp: no triangle faces");
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);
105 gpu = uploadGpuMesh(device, frameToken(),
verts, indices);
107 auto handle = makeMeshHandle(*gpu);
110 if (
mesh.mNumAnimMeshes > 0) {
111 handle->initMorphBase(
int(
mesh.mNumVertices), basePos.data(), baseNrm.data(), baseUv.data());
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;
124 handle->addMorphTargetAbsolute(
name, absPos.data());
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));
136 if (!initialized)
throw Exception(
"newMeshFromAssimp: graphics not initialized");
137 if (
mesh.mNumVertices == 0 ||
mesh.mNumFaces == 0)
138 throw Exception(
"newMeshFromAssimp: empty mesh");
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) {
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();
154 normals[i] = aiVector3D(0.f, 1.f, 0.f);
160 const bool flipWinding = worldTransform.Determinant() < 0.f;
161 std::vector<aiFace> flippedFaces;
162 std::vector<unsigned> flippedIdx;
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];
177 dst.mIndices = src.mIndices;
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];
199 tmp.mVertices =
nullptr;
200 tmp.mNormals =
nullptr;
201 tmp.mFaces =
nullptr;
202 tmp.mTextureCoords[0] =
nullptr;
203 tmp.mAnimMeshes =
nullptr;
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;
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;
435 const int stride = slices + 1;
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));
440 auto pushVert = [&](
float px,
float py,
float pz,
float nx,
float ny,
float nz,
float u,
444 vert.normal = {nx, ny, nz};
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);
462 std::vector<uint32_t> indices;
463 indices.reserve(
size_t(slices) *
size_t(stacks) * 6u +
464 size_t(caps ? slices * 2 * 3 : 0));
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);
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,
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));
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,
514 for (
int x = 0;
x < slices; ++
x) {
516 indices.push_back(botCenter);
517 indices.push_back(botRing + uint32_t(
x + 1));
518 indices.push_back(botRing + uint32_t(
x));
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));
538 ASSERT(
mesh !=
nullptr);
539 if (!initialized)
throw Exception(
"drawMesh: graphics not initialized");
541 if (!swapchainPassOpen && !offscreen3DPassOpen)
542 throw Exception(
"drawMesh: call begin3DFrame first");
543 if (!mesh3dPipeline)
throw Exception(
"drawMesh: mesh3d pipeline missing");
547 throw Exception(
"drawMesh: shader is not a Mesh3D shader (use newMeshShader*)");
548 if (!
shader->gpuHandle)
throw Exception(
"drawMesh: shader has no GPU pipeline");
551 auto *gpuMesh =
static_cast<GpuMesh *
>(
mesh->gpuHandle);
552 Texture *tex = texture ? texture : whiteTexture;
556 ensureFlatNormalTexture3D();
557 Texture *ntex = mesh3dNormalTexture ? mesh3dNormalTexture : flatNormalTexture3D;
561 ensureFlatHeightTexture3D();
562 Texture *htex = mesh3dHeightTexture ? mesh3dHeightTexture : flatHeightTexture3D;
566 Texture *depthTex = mesh3dSceneDepthTexture ? mesh3dSceneDepthTexture : whiteTexture;
567 if (!depthTex || !depthTex->
gpuHandle)
throw Exception(
"drawMesh: missing scene depth texture");
570 ensureDefaultEnvCubemap();
571 Texture *envTex = mesh3dEnvTexture ? mesh3dEnvTexture : defaultEnvCubemap;
574 if (!gpuEnv->isCube)
throw Exception(
"drawMesh: env texture is not a cubemap");
575 const float envIntensity = (mesh3dEnvTexture && mesh3dEnvIntensity > 0.f) ? mesh3dEnvIntensity : 0.f;
577 const bool useClustered = mesh3dClusteredActive && !
shader && mesh3dClusteredPipeline;
578 auto &cb = currentPresentCb();
580 auto makeShadowUbo = [&]() {
582 if (!mesh3dShadows.
active) {
586 s.bias.z = mesh3dShadowReceive ? 1.f : 0.f;
593 ubo.mvp = mesh3dFrameUbo.
mvp *
model;
594 ubo.view = mesh3dClustered.
view;
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);
604 glm::vec4(mesh3dParallaxScale, mesh3dParallaxMinLayers, mesh3dParallaxMaxLayers, 0.f);
606 auto &cfslots = currentMesh3dClusteredFrameSlots();
607 if (cfslots.drawIndex >= cfslots.capacity) {
609 "[vulkan] clustered mesh3d UBO ring exhausted (%zu draws); draw skipped\n",
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};
624 if (mesh3dClusteredPipeline != lastMesh3dClusteredPipeline) {
625 cb.bindPipeline(vk::PipelineBindPoint::eGraphics, mesh3dClusteredPipeline);
626 lastMesh3dClusteredPipeline = mesh3dClusteredPipeline;
628 cb.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, mesh3dClusteredPipelineLayout, 0, 1,
629 &set, 2, dynOffsets);
630 drawIndexedMesh(cb, *gpuMesh);
637 ubo.
tint = glm::vec4(tint.r, tint.g, tint.b, tint.a);
638 ubo.
ambient = glm::vec4(glm::vec3(mesh3dLighting.
ambient), mesh3dMetallic);
643 ubo.
texBomb = glm::vec4(mesh3dTexBombScale, mesh3dTexBombStrength, mesh3dTexBombRot, 0.f);
645 glm::vec4(mesh3dParallaxScale, mesh3dParallaxMinLayers, mesh3dParallaxMaxLayers, 0.f);
646 for (
int i = 0; i < lightCount; ++i) ubo.
lights[i] = mesh3dLighting.
lights[i];
648 for (
int i = 0; i < lightCount; ++i) {
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));
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);
665 auto &fslots = currentMesh3dFrameSlots();
666 if (fslots.drawIndex >= fslots.capacity) {
667 std::fprintf(stderr,
"[vulkan] mesh3d UBO ring exhausted (%zu draws); draw skipped\n",
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};
682 if (offscreen3DPassOpen)
683 throw Exception(
"drawMeshShader: custom mesh shader in offscreen 3D pass is unsupported");
685 vk::Pipeline pipeline = gs->mesh3dPipeline;
691 pipeline = gs->mesh3dXrayPipeline;
693 if (!pipeline)
return;
694 if (pipeline != lastMesh3dPipeline) {
695 cb.bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline);
696 lastMesh3dPipeline = pipeline;
698 cb.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, mesh3dShaderPipelineLayout, 0, 1, &set,
700 cb.pushConstants(mesh3dShaderPipelineLayout,
701 vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment, 0,
704 const vk::Pipeline pipe = offscreen3DPassOpen ? offscreen3DMeshPipeline : mesh3dPipeline;
705 if (pipe != lastMesh3dPipeline) {
706 cb.bindPipeline(vk::PipelineBindPoint::eGraphics, pipe);
707 lastMesh3dPipeline = pipe;
709 cb.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, mesh3dPipelineLayout, 0, 1, &set, 2,
712 drawIndexedMesh(cb, *gpuMesh);
GPU texture created via Graphics::newTexture. Owns GPU resources through an opaque backend handle.