BillboardMesh requires CNA link
Easy3D / Data & Batching / BillboardMesh
The BillboardMesh functions turn queued BillboardBatch items into plain CPU-side, camera-facing vertex/index arrays — mirroring CubeMesh's role for CubeBatch. No GPU work: no GraphicsDevice, no vertex/index buffers, no shaders — just std::vector data a caller can later upload.
#include <Easy3D/BillboardMesh.hpp>
namespace Easy3D
{
struct BillboardVertex;
void AppendBillboardMesh(const BillboardItem& item,
const Vector3& cameraRight,
const Vector3& cameraUp,
std::vector<BillboardVertex>& vertices,
std::vector<std::uint32_t>& indices);
void BuildBillboardMesh(const BillboardBatch& batch,
const Vector3& cameraRight,
const Vector3& cameraUp,
std::vector<BillboardVertex>& vertices,
std::vector<std::uint32_t>& indices);
}
<Easy3D/BillboardMesh.hpp> is not pulled in by the <Easy3D/Easy3D.hpp> umbrella header — include it directly. (CubeMesh.hpp and CubeMeshRenderer.hpp are in the umbrella; BillboardMeshRenderer.hpp is not, either.)Why billboards are different from cubes
A cube's geometry is fixed once you know its center and size. A billboard's geometry is not: a camera-facing quad's actual corner positions depend on which way the camera is currently pointing. So unlike CubeMesh (build once, cache forever, until the level changes), a BillboardMesh must be rebuilt whenever the camera moves — typically once per frame.
BillboardVertex
One mesh vertex — same shape as CubeVertex:
| Field | Type | Meaning |
|---|---|---|
Position | Microsoft::Xna::Framework::Vector3 | World position (already offset onto the camera's right/up plane). |
Uv | Microsoft::Xna::Framework::Vector2 | Normalized texture coordinate. |
AppendBillboardMesh
const Vector3& cameraRight, const Vector3& cameraUp,
std::vector<BillboardVertex>& vertices,
std::vector<std::uint32_t>& indices)
cameraRight/cameraUp (both expected normalized — e.g. an inverse view matrix's Right/Up rows). Indices are offset by the vertex count already present in vertices, so repeated calls concatenate correctly into one combined mesh.How placement works:
item.Origin(normalized,{0,0}= bottom-left,{1,1}= top-right,{0.5,0.5}= centered) places the quad relative toitem.Position.item.RotationRadiansrotates the quad within the camera's right/up plane before that placement.item.Sizescales the unit quad alongcameraRight/cameraUpindependently (X → right, Y → up).
Properties of the generated geometry:
- Per-billboard counts: 4 vertices, 6 indices (2 triangles).
- Corner order: bottom-left, bottom-right, top-right, top-left, in the camera's right/up plane.
- UVs: world "up" (the top two corners) maps to
V0(top of a top-down image) and world "down" maps toV1— the usual row-0-is-top spritesheet convention.item.Uv's corners map(U0,V1)-(U1,V1)-(U1,V0)-(U0,V0)to bottom-left/bottom-right/top-right/top-left respectively. - Winding: the two triangles are
(0,2,1)and(0,3,2)— deliberately not the textbook(0,1,2)/(0,2,3)order. That naive order is back-facing under CNA/XNA's defaultRasterizerState::CullCounterClockwisefor a billboard sitting in front of a camera looking toward it; this order renders correctly with no per-drawCullNoneoverride. See Design Principles for the same convention onCubeMesh. - Index type:
std::uint32_t, matchingCubeMesh.
BuildBillboardMesh
const Vector3& cameraRight, const Vector3& cameraUp,
std::vector<BillboardVertex>& vertices,
std::vector<std::uint32_t>& indices)
batch, in BillboardBatch::Items() order, all facing the same camera basis. Equivalent to calling AppendBillboardMesh for each item in turn against the same vertices/indices.Both functions append — they never clear the output vectors. Rebuild from scratch each frame by clearing your vectors first (or reusing their capacity with .clear() rather than reallocating).
Getting cameraRight / cameraUp
The camera basis vectors are whatever right/up axes the current Camera3D is looking along — derive them from its position/target/up the same way you would for any billboard technique (e.g. cross products of the view direction and the camera's up vector, or the appropriate rows of the inverted view matrix). Easy3D deliberately does not add a Camera3D::GetRight()/GetUp() convenience yet — no concrete consumer has asked for one; open a request if Galaxy Eggbert needs it.
Example — billboards facing the camera, rebuilt per frame
#include <Easy3D/BillboardBatch.hpp>
#include <Easy3D/BillboardMesh.hpp>
// Per frame: derive the camera's right/up axes from its current orientation
// (e.g. from the view direction cross world-up, then up = right cross forward).
const Vector3 cameraRight = /* ... */;
const Vector3 cameraUp = /* ... */;
sprites.Begin();
// ... sprites.Add() one BillboardItem per visible sprite (see BillboardBatch) ...
sprites.End();
std::vector<Easy3D::BillboardVertex> vertices;
std::vector<std::uint32_t> indices;
Easy3D::BuildBillboardMesh(sprites, cameraRight, cameraUp, vertices, indices);
// Upload with CNA and draw — see BillboardMeshRenderer, which does exactly this.
Scope and status
BillboardMesh is the second Phase 3 vertex builder (CubeMesh was first). A debug line/box vertex builder is the one remaining Phase 3 gap. See the Roadmap.
BillboardMeshRenderer — the Phase 4 adapter that uploads this output to GPU buffers and draws it.