A mesh is connected data#
A model’s surface is split into triangles. The vertex buffer stores attributes; the index buffer describes which three vertices form each triangle. Sharing a vertex can save memory, but a seam may need separate vertices even when their positions are identical. A normal gives the surface a direction for lighting. A UV coordinate gives it an address in a texture. UVs are usually two values that describe where on an image to sample. They are not required to match the object’s world position, and tiled textures can use coordinates outside the zero-to-one range.
Texture sampling is more than looking up a pixel#
A texture consists of texels, the image’s stored samples. A screen pixel can correspond to a fraction of a texel or to many texels. A sampler determines filtering and addressing. Nearest filtering chooses a sample; linear filtering blends neighboring samples; wrap repeats; clamp limits sampling at an edge. Mipmaps store smaller prefiltered versions of the image. The shader’s texture operation can choose a level from how quickly UVs change across the screen. This avoids reading unstable high-frequency detail for a small distant surface. Anisotropic filtering improves angled surfaces where the footprint stretches more in one direction.
Follow a texture to the GPU This illustration uses canvas. The explanation below describes the same process.
From a PNG to a texture#
A PNG must be decoded into CPU image bytes. Those bytes are copied into an upload allocation that the GPU can read. A copy command fills a GPU texture. A usage transition establishes shader access, and a descriptor tells the shader which image and subresources to use. The lab’s stages separate these responsibilities. Change the mip level to see dimensions and row sizes. Rows in CPU image data can be tightly packed while rows in DX12 upload storage must follow the returned footprint. The transfer is not a direct copy of a file into an arbitrary texture pointer.
Color versus numerical data#
A base-color image usually uses sRGB encoding, which stores display-oriented values efficiently. Sampling an sRGB view converts to linear values for lighting. A roughness image or normal map contains numerical data; it should not be decoded as display color. A normal map stores a direction relative to the surface’s tangent basis. It changes the shading normal without adding actual geometry. It will not change the silhouette of a low-detail object. Height or displacement techniques answer a different question.
Read one texture operation
Texture2D<float4> baseColor : register(t0);
SamplerState linearSampler : register(s0);
float4 sampleColor = baseColor.Sample(linearSampler, uv);The texture binding, sampler binding, and UV must all agree with the pipeline’s contract. This excerpt is an expression within a shader, not a complete standalone file. The native tutorial provides the full vertex and pixel entry points and the C++ root signature.
Inside QubicEngine#
MeshAsset and MaterialAsset keep reusable geometry and surface settings. AssetStore performs decode and upload. A material that needs a texture still loading uses a persistent fallback descriptor, then resolves the ready version at a safe boundary. Multiple instances can share both mesh and material while each has its own world transform.
Diagnose the visible result#
A checkerboard stretching suggests UV or perspective-interpolation issues. A texture with alternating corrupted rows suggests stride/pitch mismatch. A flat-looking normal map may have the wrong channel convention or be sampled as sRGB. A detailed texture shimmering far away suggests missing or poor mip filtering. Open asset lifecycle and complete DX12 texture loading to see the memory and commands underneath these concepts.