How to load 3D data into 3D Texture correctly?

Hi! I am now trying to learn volume rendering using OpenGL and trying to slice a 3D volume, but I don’t know how to load data into a 3D texture. I searched on the web, but I only found many examples on legacy APIs, not on modern OpenGL APIs. Can you please tell me how to do it? Thank you very much!

My data are structured in row major. For instance, if I have a (2,2,2) voxels, then

data_arr[0] = voxels[0][0][0]
data_arr[1] = voxels[1][0][0]
data_arr[2] = voxels[0][1][0]
data_arr[3] = voxels[1][1][0]

data_arr[4] = voxels[0][0][1]
data_arr[5] = voxels[1][0][1]
data_arr[6] = voxels[0][1][1]
data_arr[7] = voxels[1][1][1]

And the value inside one voxel is unsigned short value (i.e 16bit unsigned integer).
My code for setting up the 3D texture is

GLuint tex3D;

glGenTextures(1, &tex3D);
glBindTexture(GL_TEXTURE_3D, tex3D);

glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, GL_REPEAT);

glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

// unsigned short vol_dim[3]; // dimensions of voxels
glTexImage3D(GL_TEXTURE_3D, 0, GL_R16UI,vol_dim[0], vol_dim[1], vol_dim[2], 0, GL_RED_INTEGER, GL_UNSIGNED_SHORT, data_arr);
glGenerateMipmap(GL_TEXTURE_3D);

My rendering function is

void renderFrame() {
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glActiveTexture(GL_TEXTURE0);

    program->use();
    glBindTexture(GL_TEXTURE_3D, tex3D);
    program->setUniform("Axis", axis);
    program->setUniform("MainAxisTexCoord", mainAxisCoord);

    canvas->render(); // canvas is a FBO rectangular
}

And my vertex shader is

#version 450 core

#define X_AXIS 0
#define Y_AXIS 1
#define Z_AXIS 2

uniform float MainAxisTexCoord;
uniform int Axis;

layout (location = 0) in vec3 VertexPosition;
layout(location = 2) in vec2 Tex;

out vec3 texCoord;

void main() {
    gl_Position = vec4(VertexPosition, 1.0);
    if(Axis == X_AXIS)
        texCoord = vec3(MainAxisTexCoord, Tex.s, Tex.t);
    else if(Axis == Y_AXIS)
        texCoord = vec3(Tex.s, MainAxisTexCoord, Tex.t);
    else
        texCoord = vec3(Tex, MainAxisTexCoord);
}

My fragment shader is

#version 450 core
in vec3 texCoord;

layout (binding = 0) uniform sampler3D textureMap;

layout (location = 0) out vec4 fColor;

void main()
{
    vec3 texColor = vec3(texture(textureMap, texCoord));
    fColor = vec4(texColor, 1.0);
//    fColor = vec4(texCoord, 1.0);
}

If I uncomment the last line of the code of fragment shader, I can see the result contains gradients of base colors, which means at least texCoord is correct. But if I comment the last line and want to use the texCoord to take the value of the 3D texture, I got nothing but just black. I think there must be something wrong with my code that loads data into a 3D texture, but I don’t know how to fix it.

You can’t use linear filtering with integer textures. The magnification filter must be GL_NEAREST and the minification filter either GL_NEAREST or GL_NEAREST_MIPMAP_NEAREST. Also, it’s unclear how glGenerateMipmap generates the levels for an integer texture. If the texture values are supposed to be values in the range [0,1], use GL_R16 and GL_RED for the internal and external formats. Otherwise, change the filters.

It works! Thank you very much!

Can you also be so kind to refer where I can find any documentations/books/RFC that give explanations about textures and setting up them without such incompatibilities?

The specification is the definitive reference, although it’s not the easiest thing to read. In particular, any piece of information is likely to be given in exactly one place, and not always in the place where you might expect to find it.

Tried and effective, thank you