{"id":541,"date":"2026-09-05T16:59:21","date_gmt":"2026-09-05T16:59:21","guid":{"rendered":"https:\/\/x.sheep-mine.ts.net\/index.php\/best-practices-for-vertex-packing\/"},"modified":"2026-09-05T16:59:21","modified_gmt":"2026-09-05T16:59:21","slug":"best-practices-for-vertex-packing","status":"publish","type":"post","link":"https:\/\/x.sheep-mine.ts.net\/index.php\/best-practices-for-vertex-packing\/","title":{"rendered":"Best practices for vertex packing"},"content":{"rendered":"<p><br \/>\n<\/p>\n<p>I am exploring the use of compacted vertex formats that may be modified with a geometry shaders. For geometry shaders data must be 4 bytes aligned.<\/p>\n<p>My current vertex structure was :<\/p>\n<pre><code>typedef struct _Vert\n{\n    XMFLOAT3 Pos;\n    XMFLOAT3 Normal;\n    XMFLOAT4 Color;\n    XMFLOAT2 Tex;\n    UINT ID;\n}Vert, *LPVert;\n<\/code><\/pre>\n<p>with the following<\/p>\n<pre><code>const D3D11_INPUT_ELEMENT_DESC PNCTILayout[] =\n{\n    { \"POSITION\", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },\n    { \"NORMAL\", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0 },    \n    { \"COLOR\", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 24, D3D11_INPUT_PER_VERTEX_DATA, 0 \n    { \"TEXCOORD\", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 40, D3D11_INPUT_PER_VERTEX_DATA, 0 },\n    { \"BUFFERID\", 0, DXGI_FORMAT_R32_UINT, 0, 48, D3D11_INPUT_PER_VERTEX_DATA, 0 }\n};\n<\/code><\/pre>\n<p>As color in <code>R8G8B8A8_UNORM<\/code> format can be natively decoded to float4 in the vertex shader I first changed <code>XMFLOAT4 Color<\/code> to <code>UINT<\/code> (or <code>XMCOLOR<\/code>) <code>Color<\/code> for creating the vertex on the CPU. Then I changed the UV coordinates from <code>XMFLOAT2<\/code> to <code>XMHALF2<\/code> with the use of <code>XMConvertFloat2toHalf<\/code> when creating the vertex data for my mesh, giving the following:<\/p>\n<pre><code>typedef struct _Vert\n{\n    XMFLOAT3 Pos;\n    XMFLOAT3 Normal;\n    XMCOLOR Color;\n    XMHALF2 Tex;\n    UINT    ID16;\n}Vert, *LPVert;\n\nconst D3D11_INPUT_ELEMENT_DESC PNCTILayout[] =\n{\n    { \"POSITION\", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },\n    { \"NORMAL\", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0 },\n    { \"COLOR\", 0, DXGI_FORMAT_R8G8B8A8_UNORM, 0, 24, D3D11_INPUT_PER_VERTEX_DATA, 0 },\n    { \"TEXCOORD\", 0, DXGI_FORMAT_R32_UINT, 0, 28, D3D11_INPUT_PER_VERTEX_DATA, 0 },\n    { \"BUFFERID\", 0, DXGI_FORMAT_R32_UINT, 0, 32, D3D11_INPUT_PER_VERTEX_DATA, 0 }\n};\n<\/code><\/pre>\n<p>The <code>TEXCOORD<\/code> is not <code>R16G16_FLOAT<\/code> because it does not work for geometry shaders where each component must be 4 bytes. The hack is to use a uint and pack in the CPU the two components in <code>0xFFFF<\/code> and <code>0x0000FFFF<\/code> bits, what <code>XMHALF2<\/code> do. In the shaders we need to unpack the UV UINT to two floats :<\/p>\n<pre><code>float2 UnpackUV(uint packed)\n{\n    uint2 h = uint2(packed & 0xFFFF, packed >> 16);\n        return f16tof32(h);\n}\n<\/code><\/pre>\n<p>It works fine but what is the real practice for games for PCs? Compaction is a regular strategy for textures but I don&#8217;t see too much about vertex. In particular if I want to also pack position or normal, what should work the best regarding percision for application in regular 3D world settings where the world is not aligned to any grid (no voxels)?<\/p>\n<p><br \/>\n<br \/><a href=\"https:\/\/gamedev.stackexchange.com\/questions\/217415\/best-practices-for-vertex-packing\">Source link <\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>I am exploring the use of compacted vertex formats that may be modified with a&#8230;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1230,1232,1231],"tags":[],"class_list":["post-541","post","type-post","status-publish","format-standard","hentry","category-directx11","category-packing","category-vertex-buffer"],"_links":{"self":[{"href":"https:\/\/x.sheep-mine.ts.net\/index.php\/wp-json\/wp\/v2\/posts\/541","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/x.sheep-mine.ts.net\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/x.sheep-mine.ts.net\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/x.sheep-mine.ts.net\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/x.sheep-mine.ts.net\/index.php\/wp-json\/wp\/v2\/comments?post=541"}],"version-history":[{"count":0,"href":"https:\/\/x.sheep-mine.ts.net\/index.php\/wp-json\/wp\/v2\/posts\/541\/revisions"}],"wp:attachment":[{"href":"https:\/\/x.sheep-mine.ts.net\/index.php\/wp-json\/wp\/v2\/media?parent=541"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/x.sheep-mine.ts.net\/index.php\/wp-json\/wp\/v2\/categories?post=541"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/x.sheep-mine.ts.net\/index.php\/wp-json\/wp\/v2\/tags?post=541"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}