Luxar Viewer API Documentation - v2026.9.22
    Preparing search index...

    Module rendering/mesh-texture

    Upload a decoded mesh texture to the GPU.

    The first image texture in the tree, and it differs from every existing one in ways that are easy to get wrong silently:

    • the colormap LUTs (rendering/colormap-textures.ts) are 256x1 8-bit and always LinearFilter/ClampToEdge — no wrap, no mipmaps, no colour space;
    • the element store (rendering/element-storage.ts) is float but always NearestFilter, because it carries data rather than an image.

    So this is the first texture that is simultaneously float-capable, linearly filtered, mipmapped and colour-managed, and each of those four is a place where the wrong choice renders plausibly rather than failing.

    1. Three-channel data is expanded to RGBA. Not an oversight and not laziness: WebGPU has no 3-channel texture formats at all, so RGBFormat would work under WebGL2 and break the moment the same scene loads on the WebGPU backend. rgbToRgba in colormap-textures.ts expands for the same reason. Single-channel is kept as RedFormat, which both backends do support.

    2. HDR uploads as HalfFloatType unless the device says otherwise. Core WebGL2 lets a FloatType texture exist and be sampled with NEAREST, but LINEAR filtering on one requires OES_texture_float_linear (float32-filterable on WebGPU). When it is absent the sampler silently drops to nearest, so the texture looks blocky with nothing to attribute it to. Half-float is linearly filterable in core WebGL2 and carries ~11 bits of mantissa, which is ample for an image; float32 is used only where the capability probe confirms it.

    3. v = 0 is the FIRST row of the image, on all three arms. texture.flipY is set to false explicitly rather than left at its default, because the defaults disagree AND two of them are inert or misleading:

    • DataTexture defaults flipY = false;
    • a Texture over an ImageBitmap defaults flipY = true, and WebGL silently ignores it — UNPACK_FLIP_Y_WEBGL has no effect on an ImageBitmap upload, so the flag reads true while nothing is flipped.
    • a CompressedTexture defaults flipY = false, and WebGL likewise cannot apply UNPACK_FLIP_Y_WEBGL to its already-compressed mip payloads.

    Left alone, that made texture_encoding change the MEANING of a UV: the same coordinates over the same pixels rendered upside down as raw vs as JPEG. Since the raw path takes an (h, w, c) array, the convention that matches it — v = 0 is arr[0] — is the one an author can predict, and it is also what sample_equirect uses (row 0 is +90 latitude). So flipY is pinned off and the authored v runs top-down.

    Found by eye, not by the numeric check: a UV-vs-position residual test passed because it MODELLED the shader's sampling from texture.flipY, and the flag it trusted was the thing that was wrong.

    4. sRGB is decoded HERE for float textures, and by the sampler for 8-bit ones. THREE's SRGBColorSpace maps to the hardware SRGB8_ALPHA8 sampler, which is exact and free — but only exists for 8-bit textures. Rather than depend on what THREE does with colorSpace on a float texture (which has varied across versions, and whose failure mode is a washed-out or over-dark surface that still looks plausible), the float path linearizes explicitly with the piecewise sRGB EOTF and declares itself linear. That makes the conversion a known-value unit test instead of a visual judgement — which is the whole reason to prefer it, since "looks about right" is exactly the check this class of bug passes.

    MeshTextureCapabilities
    linearizeSRGBFloat
    createMeshTexture