Search code examples
typescriptglslshaderwebglwebgl2

problems using internal formats other than R8 for texture data


I'm using GLSL shaders with 3D texture data in WebGL2 code via TypeScript. My texture data contains single-channel samples, with different data sources using samples with different bit widths (u8, u16, u32, f32). Unfortunately, I cannot get texture formats other than R8 to work (64 bit Chrome v104+ on Windows 10).

I see no GLSL shader/program compilation errors, and no WebGL runtime errors on the console or via return values from WebGL calls.

When I upload texture data from a Uint8Array as R8 format, everything works fine. However, when I switch from R8 to R8UI format (ostensibly identical data, but usampler in the shader vs sampler to return raw unsigned values rather than normalized floats) I get ... nothing.

All the values returned by the sampler are zero, everywhere in the 3D texture data

I checked this by modifying the shader to simply output a gray pixel wherever the sampled texture data is non-zero - no gray pixels are created.

I also tried R16UI and R32F texture formats (source data passed via e.g., Uint8Array or Float32Array ); these formats also result in textures full of zero values when the shader runs. It seems that only R8 produces anything other than textures full of 0.

I could perhaps try to smear 16-bit values across 2 x 8-bit values via some sort of RG8 internal texture format, but the "correct" data types are apparently available by default in WebGL2 - I just can't seem to get them to work.

Any ideas?

Code snippets follow, with no error checking for concision:

Main program (R8 example)

// R8 - this seems to work
const data = new Uint8Array(W*H*N)
internal_format = sys.gl.R8
< ... setup data array ... >
setDataTexture(W,H,N, data, internal_format)

Main program (R8UI example)

// R8UI - this doesn't seem to work, despite being ostensibly
// identical to the R8 source data
const data = new Uint8Array(W*H*N)
internal_format = sys.gl.R8UI
< ... setup data array ... >
setDataTexture(W,H,N, data, internal_format)

setDataTexture()

setDataTexture(X: number, Y: number, Z: number, data: any, internal_format: GLenum) {
    const gl = this.gl
    const params: Record<GLenum, any> = {}
    params[gl.R8]    = ["R8",    gl.RED,         gl.UNSIGNED_BYTE]
    params[gl.R8UI]  = ["R8UI",  gl.RED_INTEGER, gl.UNSIGNED_BYTE]
    params[gl.R16UI] = ["R16UI", gl.RED_INTEGER, gl.UNSIGNED_SHORT]
    params[gl.R16I]  = ["R16I",  gl.RED_INTEGER, gl.SHORT]
    params[gl.R32F]  = ["R32F",  gl.RED,         gl.FLOAT]
    
    gl.activeTexture(gl.TEXTURE0) // bind data to texture 0

    if (this.dataTex !== null) {
        gl.deleteTexture(this.dataTex)
        this.dataTex = null
    }

    if (!params[internal_format]) {
        console.log(`Unknown internal format ${internal_format}`)
        return
    }

    const [str, fmt, typ] = params[internal_format]

    this.dataTex = gl.createTexture()
    gl.bindTexture(gl.TEXTURE_3D, this.dataTex)
    // UNPACK_ALIGNMENT : https://stackoverflow.com/questions/51582282/error-when-creating-textures-in-webgl-with-the-rgb-format
    gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1)
    gl.texStorage3D(gl.TEXTURE_3D, 1, internal_format, X,Y,Z)
    // LINEAR filtering doesn't work for some data types, default to NEAREST for testing
    gl.texParameteri(gl.TEXTURE_3D, gl.TEXTURE_MIN_FILTER, gl.NEAREST)
    gl.texParameteri(gl.TEXTURE_3D, gl.TEXTURE_WRAP_R, gl.CLAMP_TO_EDGE)
    gl.texParameteri(gl.TEXTURE_3D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
    gl.texParameteri(gl.TEXTURE_3D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)    
    gl.texSubImage3D(gl.TEXTURE_3D, 0, 0,0,0, X,Y,Z, fmt, typ, data)
}

Fragment shader (R8)

#version 300 es

precision highp int;
precision highp float;

uniform highp sampler3D volume;

< ... etc, then loop calculating position "pos" ... >

// Assume only using red channel in texture data
float val = texture(volume, pos).r;
// ... now do something with "val"

Fragment shader (R8UI)

#version 300 es

precision highp int;
precision highp float;

uniform highp usampler3D volume;

< ... etc, then loop calculating position "pos" ... >

// Assume only using red channel in texture data
uint val_ = texture(volume, pos).r;
if (val_ > 0u) {
    // write gray pixel data
}

Solution

  • Your texture is incomplete. R8UI is a format that is not texture filterable because integer values cannot be interpolated. Therefore, both the minification filter and the magnification filter must be "nearest" value filters. You have set the minification filter

    gl.texParameteri(gl.TEXTURE_3D, gl.TEXTURE_MIN_FILTER, gl.NEAREST)
    

    however you have forgotten the magnification filter

    gl.texParameteri(gl.TEXTURE_3D, gl.TEXTURE_MAG_FILTER, gl.NEAREST)