Luk
Luk

Reputation: 78

passing a float array as a 3D Texture to GLSL fragment shader

I'm trying to implement ray casting based volume rendering and therefore I'd need to pass a float Array to the fragment shader as a Texture (Sampler3D). I've got a volume datastructure containing all the voxels. Each voxel contains a density value. So for processing I stored the values into a float Array.

//initialize glew, initialize glfw, create window, etc.

float* density;
density = new float[volume->size()];

for (int i = 0; i < volume->size(); i++){
    density[i] = volume->voxel(i).getValue();
}

Then I tried creating and binding the textures.

glGenTextures(1, &textureHandle);
glBindTexture(GL_TEXTURE_3D, textureHandle);
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
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);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glTexImage3D(GL_TEXTURE_3D, 0, GL_LUMINANCE, volume->width(),
volume->height(), volume->depth(), 0, GL_LUMINANCE, GL_FLOAT, density);

In my render loop I try to load the Texture to the uniform Sampler3D.

glClearColor(0.4f, 0.2f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

glActiveTexture(GL_TEXTURE0);
GLint gSampler = glGetUniformLocation(shader->shaderProgram, "volume");
glUniform1i(gSampler, 0);

cube->draw();

So the basic idea is to calculate the current position and direction for ray casting in the Vertex Shader.

in vec3 position;

uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;

uniform vec4 cameraPos;

out vec3 pos;
out vec3 dir;

void main(){
gl_Position = projection * view * model * vec4(position, 1.0);
pos = position;
dir = pos - (inverse(model) * cameraPos).xyz;
}

That seems to work well, so far so good. The fragment shader looks like this. I take some samples along the ray and the one with the largest density value will be taken as a color for red, green and blue.

#version 330 core

in vec3 pos;
in vec3 dir;

uniform sampler3D volume;

out vec4 color;

const float stepSize = 0.008;
const float iterations = 1000;

void main(){

vec3 rayDir = normalize(dir);
vec3 rayPos = pos;

float src;
float dst = 0;
float density = 0;

for(int i = 0; i < iterations; i++){

src = texture(volume, rayPos).r;
if(src > density){
    density = src;
}

rayPos += rayDir * stepSize;

//check whether rays are within bounds. if not -> break.

    }
color = vec4(density, density, density, 1.0f);
}

Now I've tried inserting some small debug assertions.

if(src != 0){
rayPos = vec3(1.0f);
break;
}

But src seems to be 0 at every iteration of every pixel. Which gets me to the conclusion that the Sampler isn't correctly set. Debugging the C++ code I get the correct values for the density array right before I pass it to the shader, so I guess there must be some opengl function missing. Thanks in advance!

Upvotes: 1

Views: 2234

Answers (1)

Nicol Bolas
Nicol Bolas

Reputation: 473437

glTexImage3D(GL_TEXTURE_3D, 0, GL_LUMINANCE, volume->width(), volume->height(), volume->depth(), 0, GL_LUMINANCE, GL_FLOAT, density);

Unless this density is on the range [0, 1], then this is almost certainly not doing what you intend.

GL_LUMINANCE, when used as an internal format (the third parameter to glTexImage3D, means that each pixel in OpenGL's texture data will contain a single normal integer value. So if you want a floating-point value, you're kinda out of luck.

The proper way to do this is to explicitly declare the type and pixel size of the data. Luminance was removed from the core OpenGL profile back in 3.1, so the way to do that today is to use GL_R32F as your internal format. That declares that each pixel contains one value, and that value is a 32-bit float.

If you really need to broadcast the value across the RGB channels, you can use texture swizzling to accomplish that. You can set a swizzle mask to broadcast the red component to any other channel you like.

glActiveTexture(GL_TEXTURE0);
GLint gSampler = glGetUniformLocation(shader->shaderProgram, "volume");
glUniform1i(gSampler, 0);

I've heard that binding the texture is also a good idea. You know, if you actually want to read from it ;)

Upvotes: 1

Related Questions