volumetric rendering with 3D Textures

I’m trying to use 3D textures for volumetric rendering…

I’ve created the texture in memory; I’m not too sure how to actually use it (show it) for volumetric rendering. I’ve found this http://www.opengl.org/developers/code/sig99/advanced99/notes/node299.html but it seems not very efficient.

I was thinking more like creating a cube (or box) and bind the 3D texture to the cube - not just surfaces but the whole volumetric texture. Is such way possible?

Thanks.

[This message has been edited by PhilY (edited 06-10-2002).]

gl draws triangles.
textured triangles.

this should answer your question…

That’s not much of an answer. This is not a simple texturing operation.

For volume rendering you must draw many slices through the volume to sample the texture along the ray paths and accumulate the final color to the framebuffer.

This page should help:
http://www.dorbie.com/volume.html

Here’s another link:
http://www.sgi.com/software/opengl/advanced97/notes/node179.html#SECTION000150000 000000000000

There are more sophisticated approaches now which improve on the simple slice approach using multitexture and dependent reads, I’m not sure you need that but here’s a link:
http://www9.informatik.uni-erlangen.de/Persons/Rezk/paper/paper_HWWS00.pdf

P.S. your notes link didn’t work for me. The link I gave is probably similar but earlier. It is not efficient, but it is faster than other methods due to the hardware acceleration. You need a prodigeous ammount of textured blended pixel fill performance for OpenGL volume rendering and generally you try and keep the resolution low. You shouldn’t need a high resolution result because the volume typically isn’t that high resolution anyway.

[This message has been edited by dorbie (edited 06-10-2002).]

Thanks for your replies.

I accidentally put a comma at the end of the link - it’s fixed now. However, it seems like they’re about the same, as you noted, dorbie. I actually checked out your site a few days ago searching through google. Your page is very informative.

I think I’ll read through the paper that you’ve linked. It seems like something I might actually implement. Thanks for your help, dorbie.

Originally posted by dorbie:
That’s not much of an answer. This is not a simple texturing operation.

actually it IS the answer…

all you can do to render a 3d volume is render textured triangles. as slices. as mentoyed above from himself. he asked for a bether way but there is no really bether way wich works fast on hardware. and why? because all gl does is drawing triangles. textured triangles…

(or points or lines)

For more source, you may also want to check out the RadeonVolVis sample, which will run on any part supporting 3D textures including any chips in the RADEON 7000 and 8000 series (and beyond).

-Jason

I’ve implemented the 3D volume thingy and it almost works correctly

I’m rotating the model in respect to the X and the Y axis. When rotation about one single axis, everything is fine. However, if I rotate it about x and y axes, the texture and clipping planes rotate about different axes

I can’t seem to find what is causing this. I’m not even using the MODELVIEW matrix, just the texture matrix. Any help will be greatly appreciated.

I’ve closely followed dorbie’s example; here are some main parts of the code:

//-------------------------Texture Creation------------------------
glGenTextures(1,&ThreeDTexture);
glBindTexture(GL_TEXTURE_3D_EXT,ThreeDTexture);
glTexImage3DEXT(GL_TEXTURE_3D_EXT,0,GL_RGBA,64,64,64,0,GL_RGBA,GL_UNSIGNED_BYTE,data);

glTexParameteri(GL_TEXTURE_3D_EXT,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
glTexParameteri(GL_TEXTURE_3D_EXT,GL_TEXTURE_MAG_FILTER,GL_LINEAR);

glTexParameteri(GL_TEXTURE_3D_EXT,GL_TEXTURE_WRAP_S, GL_CLAMP);
glTexParameteri(GL_TEXTURE_3D_EXT,GL_TEXTURE_WRAP_T, GL_CLAMP);
glTexParameteri(GL_TEXTURE_3D_EXT,GL_TEXTURE_WRAP_R_EXT, GL_CLAMP);

glEnable(GL_TEXTURE_GEN_S);
glEnable(GL_TEXTURE_GEN_T);
glEnable(GL_TEXTURE_GEN_R);

glEnable(GL_TEXTURE_3D_EXT);

//----------------------Rotation and Clipping----------------------

double eqnxy1[] = {0.0, 0.0, 1.0, 0};
double eqnxy2[] = {0.0, 0.0, -1.0, 1};
double eqnxz1[] = {0.0, 1.0, 0.0, 0};
double eqnxz2[] = {0.0, -1.0, 0.0, 1};
double eqnzy1[] = {1.0, 0.0, 0.0, 0};
double eqnzy2[] = {-1.0, 0.0, 0.0, 1};

glMatrixMode(GL_TEXTURE);

glLoadIdentity();

glTranslatef(.5, .5, .5);

glRotatef(rotateY, 1,0,0);
glRotatef(rotateX, 0,1,0);

float textureRotationMatrix[16];
glGetFloatv(GL_TEXTURE_MATRIX, textureRotationMatrix);

for(i=0; i<4; i++) {
clipEquation[i]=0.0;
for(j=0; j<4; j++)
clipEquation[i]+=textureRotationMatrix[i*4+j]*eqnxy1[j];
}

for(i=0; i<3; i++)
clipEquation[i]/=clipEquation[3];

clipEquation[3] = 1.0;

glClipPlane(GL_CLIP_PLANE0, &clipEquation[0]);
if (clipping) glEnable(GL_CLIP_PLANE0);

//…and so on for all 6 clipping planes

//----------------------Finally, Rendering----------------------------

float planeS[] = {1,0,0,1};
float planeT[] = {0,1,0,1};
float planeR[] = {0,0,1,1};

glBindTexture(GL_TEXTURE_3D_EXT, ThreeDTexture);
glBegin(GL_QUADS);

for (float a = 0.0; a &lt; 1.0; a += .02){ 

	glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
	glTexGenfv(GL_S, GL_EYE_PLANE, planeS);

	glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
	glTexGenfv(GL_T, GL_EYE_PLANE, planeT);

	glTexGeni(GL_R, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
	glTexGenfv(GL_R, GL_EYE_PLANE, planeR);

	glTexParameteri(GL_TEXTURE_3D_EXT,GL_TEXTURE_WRAP_S, GL_CLAMP);
	glTexParameteri(GL_TEXTURE_3D_EXT,GL_TEXTURE_WRAP_T, GL_CLAMP);
	glTexParameteri(GL_TEXTURE_3D_EXT,GL_TEXTURE_WRAP_R_EXT, GL_CLAMP);
		
	glVertex3f(-1 ,-1 ,a);
	glVertex3f(-1 ,1 ,a);
	glVertex3f(1 ,1 ,a);
	glVertex3f(1 ,-1 ,a);
}

glEnd();

[This message has been edited by PhilY (edited 06-13-2002).]

[This message has been edited by PhilY (edited 06-14-2002).]

ehhh… correction…

I just found out that nothing really works in the above code The texture coordinate generation only works if there is no rotation at all --> I guess I have to rotate plane[S,T,R] by the rotation matrix as well?

Again, any help will be greatly appreciated

Yes S,T,R has to be rotated.

You can use the modelview to transform the clip planes and the texgen equations. I suggest you do this, it’s easier to tie them together.

The other way is to keep the whole thing fixed and use the modelview to rotate the viewer. This is completely equivalent but just a different way of conceptualizing things.

So apply the transformation to the modelview (inverse view or model, pick your poison). Then apply the clip planes and texgen. Then pop the modelview stack and draw the slice planes at the right depth in eye space (I think this is correct but it’s untested). Applying the texgen in object space after the modelview manipulations means they will be transformed with the volume so no need for a texture matrix or manual manipulations.

I’d isolate each effect and try to get it working, for example get the planes fixed in eyespace under transform. Next get the texgen working with rotation on these planes. Next get the clip planes working.

P.S. when I say ‘planes’ on it’s own in the last paragraph I mean the textured polygon slices through the data. Sorry if this caused confusion with “clip planes”.

Hi, dorbie, thanks once again for your help.

First, the clipping problem was due to me not rendering the quads along the z-axis far enough. I was rendering them from -1 to 1, but when the cube gets rotated , it goes beyond [-1,1], obviously. I feel pretty stupid about that

I wrote a simple code to follow what you’ve suggested - quite an elegant way! The clipping planes are transformed fine but the texture does not want to get transformed no matter that I try to do. Even if I explicitly set the current matrix mode to texture and load the model transform matrix (commented out in the code below), I do not see any transformation. So basically, the texture stays the same no matter what the rotation but the clipping planes are working fine.

The below is my code, yet again, quite different this time. Do you see anything obvious that I’ve missed?

Another thing. I don’t think I understand the automatic texture coordinate generation very well. For one thing, when the planes cross the 3D texture, the 3D texture can be projected on to the planes with as many as 6 vertices. But the quads only have 4 vertices and texture coordinates and the vertices are one-to-one match, correct? I’m a bit confused Would anyone know any good source to learn this from? (I’ve tried Red/Blue book, MSDN, and searching this forum)

glGenTextures(1,&ThreeDTexture);
glBindTexture(GL_TEXTURE_3D_EXT,ThreeDTexture);
glTexImage3DEXT(GL_TEXTURE_3D_EXT,0,GL_RGBA,SIZEX,SIZEY,SIZEZ,0,GL_RGBA,GL_UNSIGNED_BYTE,data);

glTexParameteri(GL_TEXTURE_3D_EXT,GL_TEXTURE_MIN_FILTER,GL_NEAREST);
glTexParameteri(GL_TEXTURE_3D_EXT,GL_TEXTURE_MAG_FILTER,GL_NEAREST);

glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);

glTexParameteri(GL_TEXTURE_3D_EXT,GL_TEXTURE_WRAP_S, GL_CLAMP);
glTexParameteri(GL_TEXTURE_3D_EXT,GL_TEXTURE_WRAP_T, GL_CLAMP);
glTexParameteri(GL_TEXTURE_3D_EXT,GL_TEXTURE_WRAP_R_EXT, GL_CLAMP);


glEnable(GL_TEXTURE_GEN_S);
glEnable(GL_TEXTURE_GEN_T);
glEnable(GL_TEXTURE_GEN_R);

glEnable(GL_TEXTURE_3D_EXT);	

double eqnxy1[] = {0.0, 0.0, 1.0, 0};
double eqnxy2[] = {0.0, 0.0, -1.0, 1};
double eqnxz1[] = {0.0, 1.0, 0.0, 0};
double eqnxz2[] = {0.0, -1.0, 0.0, 1};
double eqnzy1[] = {1.0, 0.0, 0.0, 0};
double eqnzy2[] = {-1.0, 0.0, 0.0, 1};

float planeS[] = {1,0,0,1};
float planeT[] = {0,1,0,1};
float planeR[] = {0,0,1,1};

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);	


glMatrixMode(GL_MODELVIEW);

glLoadIdentity();

//Rotations
glTranslatef(.5, .5, .5);
glRotatef(rotateY, 1,0,0);
glRotatef(rotateX, 0,1,0);
glTranslatef(-.5, -.5, -.5);

//Store the MODELVIEW matrix into memory
float modelMatrix[16];
glGetFloatv(GL_MODELVIEW_MATRIX, modelMatrix);

//Set clipping planes
glClipPlane(GL_CLIP_PLANE0, &eqnxy1[0]);
glClipPlane(GL_CLIP_PLANE1, &eqnxy2[0]);
glClipPlane(GL_CLIP_PLANE2, &eqnxz1[0]);
glClipPlane(GL_CLIP_PLANE3, &eqnxz2[0]);
glClipPlane(GL_CLIP_PLANE4, &eqnzy1[0]);
glClipPlane(GL_CLIP_PLANE5, &eqnzy2[0]);

//Enable to the clipping planes
if (clipping){
	glEnable(GL_CLIP_PLANE0);
	glEnable(GL_CLIP_PLANE1);
	glEnable(GL_CLIP_PLANE2);
	glEnable(GL_CLIP_PLANE3);
	glEnable(GL_CLIP_PLANE4);
	glEnable(GL_CLIP_PLANE5);
}

glBindTexture(GL_TEXTURE_3D_EXT,ThreeDTexture);

glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
glTexGeni(GL_R, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);

//Start drawing the QUADs
glBegin(GL_QUADS);

	for (float a = -2.0; a &lt; 2.0; a += DISTANCE){ 

		planeS[3] = planeT[3] = planeR[3] = a;

		//glMatrixMode(GL_TEXTURE);
		//glLoadMatrixf(modelMatrix);

		//Generate the texture coordinates
		glTexGenfv(GL_S, GL_EYE_PLANE, planeS);
		glTexGenfv(GL_T, GL_EYE_PLANE, planeT);
		glTexGenfv(GL_R, GL_EYE_PLANE, planeR);
	
		//Render the QUADs using identity matrix
		glPushMatrix();
			glLoadIdentity();
			glVertex3f(-2 ,-2 ,a);
			glVertex3f(-2 ,2 ,a);
			glVertex3f(2 ,2 ,a);
			glVertex3f(2 ,-2 ,a);
		glPopMatrix();

		}

glEnd();

The biggest problem is you don’t switch back to modelview matrix after you load on the texture matrix.

Equally bad, you’re not allowed to do this stuff inside begin and end, that will be sure to cause problems esp. with the matrix stuff. Move begin end around your primitives.

Your code around the quad drawing is nasty. You also manipulate the plane equations with ‘a’ DON’T.

Move the push pop outside the loop.

Move the texgen stuff outside the loop.

One area I’m rusty on is the transformation of the plane equations through the modelview when specified. I dunno if this happens and whether it’s object_plane or eye_plane do this.

This might make the texture matrix manips redundant, just a thought. For now apply them when the modelview is ident and go with the texture matrix manipulations. Experiment later if you want to eliminate texgen.

Try this, it’s untested but close I think, and probably more elegant than you thought possible.

glMatrixMode(GL_TEXTURE);
glLoadMatrixf(modelMatrix);
glMatrixMode(GL_MODELVIEW);

glPushMatrix();
glLoadIdentity();

//Generate the texture coordinates
glTexGenfv(GL_S, GL_EYE_PLANE, planeS);
glTexGenfv(GL_T, GL_EYE_PLANE, planeT);
glTexGenfv(GL_R, GL_EYE_PLANE, planeR);

//Start drawing the QUADs
glBegin(GL_QUADS);

for (float a = -2.0; a < 2.0; a += DISTANCE){
glVertex3f(-2 ,-2 ,a);
glVertex3f(-2 ,2 ,a);
glVertex3f(2 ,2 ,a);
glVertex3f(2 ,-2 ,a);
}

glEnd();

glPopMatrix();

I explain texgen here:
http://www.dorbie.com/aqua.html

[This message has been edited by dorbie (edited 06-18-2002).]

While your answer was “true” it was nowhere near complete. It is obvious that OpenGL draws primarily textured triangles and not volumes. What is not obvious to everybody is how to use textured triangles to render a volume.

It’s also obvious from your other posts that you have alot of experience with OpenGL, but alot of your comments (like this one) come off as rude, even when you are “right.” It is not reasonable to expect Phil to conclude what you know about volumetric rendering from your curt suggestion, particularly if he’s asking the question in the first place.

-Won

–EDIT–

Whoops! I should’ve indicated that this messages was intended for Dave. Sorry for the confusion!

[This message has been edited by Won (edited 06-19-2002).]

Won,

Phew, I thought you were talking to me when I started to read this, I wish you’d address it to the intended recipient :-).

PhilY, one other thing with identity on the modelview you are rendering in eyespace when you draw your quad slices. This means that you don’t need to draw from -2 to 2. Itterate ‘a’ from -2 to -near_clip (or -4 to -near_clip), I’m unclear on your cube dimensions, and you need to ensure rotated corners don’t get clipped on the far end of the volume.

If you want to translate the volume a distance away from the viewer you must ensure the slices cover the projected region in eyespace. Making this automatic would require adding a translation component to remain on the modelview and it probably wouldn’t complicate the other stuff because your texgen etc is in eye space, but leaving it in eyespace would be important. All you’d do is leave the cube translate on the modelview when drawing the slice quads without the rotates. You then itterate ‘a’ from -1 to 1. (or -2 to 2 depending on the cube size I suppose).

Maybe you currently just draw it around the eye with you in the middle in that case you just need to add a translation immediately before the quad rendering loop (I think that’s right).

[This message has been edited by dorbie (edited 06-19-2002).]

Thanks again, dorbie, for your help.

Won, thanks for your comment; I thought it was directed towards dorbie as well until I read dorbie’s response

I’m trying to process all this information right now… I haven’t been doing OpenGL (or graphics in general) for long, maybe 6 months or so; I’m kinda slow

I lost a little interest in this right now; I thought it was a real quick thing -> just change all gl…2D to gl…3D I was wrong.

I AM going to get this working in the near future, however, and I’ll give you follow up on that. Once again, thank you very much, dorbie, for your help.

This is pretty advanced stuff you’re attempting. Also, I don’t think anyone does 3D volume rendering quite the same way I do :-). This is the “fully automatic use OpenGL to do as much of the work as possible” approach. You’ll notice that when you get this working OpenGL will pretty much be doing ALL the calculations including slice clipping. Your hand coded transformations will be virtually non existant, you’ll be using the modelview matrix and texture matrix for all transformations with no complex slice generation.

All – sorry for the confusion from before! The post is now editted.

There are some other approaches for volume rendering, too. If the volume data is sparse, you might be able to just get away with drawing each of the voxels as points or imposter sprites.

Also, you can try to polygonalize the data using marching cubes. There are libraries that can do this for you like VTK (www.kitware.com).

I know that neither of these have anything to do with using volume textures, but thought I’d suggest some possible alternatives.

-Won

Won, the first approach you suggested is called “splatting”.

The slice sampling of 3D texture in not unique but I haven’t seen anyone else implement it as automatically in OpenGL. Typically there’s a lot of CPU code generating slices and even texcoords in some cases. None of that is required if it’s implemented as I’m describing. It’s no big deal really but some implementations end up doing the whole thing entirely in hardware which is nice. It’s just interesting to exercise OpenGL and keep the application code this simple. Ultimately it’s equivalent to all other slicing approaches.

[This message has been edited by dorbie (edited 06-19-2002).]

Originally posted by Won:
…your message…(saving space)

sorry for sounding rude. i just don’t have much time to write messages currently, thats all.
well, why i gave no more answers is simple:

a) he states the solution himself and asks if there is a direct volume rendering way. my answer in so far is an indirect but quite clear no. he knows yet the slice way, as he posts the link with the slice way. so i don’t need to mentoin it

b) i have no practical expirience with 3dtextures. i have a gf2mx only… can’t wait to play with 3dtextures myself, thought… (they simplify perpixellightingsetups quite much…)

i try to not sound rude anymore. had a little war with dorbie, i bet that gave a bad view to both me and dorbie (at least, dorbie first thought you ment him, and others, too ). my statements about cg are not sweet neighter but well, thats just mather of opinion .

won: about the drawing voxels as points or imposter sprites. well, this idea is quite good. with VAR you get that quite fast even on my fillratelimited gf2mx. and you can use the volumetric texture there as well (well… i can’t ) and bind this to colorize the voxels automatically. its possibly the most easy approach (and the most “static” one, set up the vertexarray and just plot it down). but slicing is the fastest i guess.

marching cubes, well yeah. but it doesn’t look like a typical volumetric object (they look so ghostlike you know )

Won, thanks for your suggestions. “Splatting” was something I had thought about - a bunch of 0 dimensional textures (actually a two dimensional texture that is 1X1) freely rotating depending on the view. I’m trying to learn 3D textures right now, tho.

Dorbie, I have another question for you, hopefully the last one - at least on this matter

All seemed well; then, I hit the same wall I had hit before, back when I was rotating the texture matrix manually - week ago?

The problem is that when the object is rotated about the y-axis and then about the x-axis, the clipping planes rotate in absolute manner (rotates about the axes of the view) while the texture rotates about its own axes.

I have assured and verified again and yet again that the MODELVIEW matrix and the TEXTURE matrix are identical. I guess I could manipulate either of the matrices so that the behaviors are the same but I prolly missed something obvious - order ?, or maybe there’s yet another elegant way to fix this in GL?

I can’t thank you enough, dorbie.

Here is the code: (I just figured out that UBB codes are enabled here )

glMatrixMode(GL_MODELVIEW);
glLoadIdentity();

//Rotations
glTranslatef(.5, .5, .5);
glRotatef(rotateY, 1,0,0);
glRotatef(rotateX, 0,1,0);
glTranslatef(-.5, -.5, -.5);

//Store the MODELVIEW matrix into memory
float modelMatrix[16];
glGetFloatv(GL_MODELVIEW_MATRIX, modelMatrix);

glMatrixMode(GL_TEXTURE);
glLoadMatrixf(modelMatrix);

glTranslatef(-1,-1,-1);

//Set clipping planes
glClipPlane(GL_CLIP_PLANE0, &eqnxy1[0]);
glClipPlane(GL_CLIP_PLANE1, &eqnxy2[0]);
glClipPlane(GL_CLIP_PLANE2, &eqnxz1[0]);
glClipPlane(GL_CLIP_PLANE3, &eqnxz2[0]);
glClipPlane(GL_CLIP_PLANE4, &eqnzy1[0]);
glClipPlane(GL_CLIP_PLANE5, &eqnzy2[0]);

//Enable to the clipping planes
if (clipping){
glEnable(GL_CLIP_PLANE0);
glEnable(GL_CLIP_PLANE1);
glEnable(GL_CLIP_PLANE2);
glEnable(GL_CLIP_PLANE3);
glEnable(GL_CLIP_PLANE4);
glEnable(GL_CLIP_PLANE5);
}

glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
glTexGeni(GL_R, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);

glMatrixMode(GL_MODELVIEW);
glPushMatrix();
glLoadIdentity();

  //Generate the texture coordinates
  glTexGenfv(GL_S, GL_EYE_PLANE, planeS);
  glTexGenfv(GL_T, GL_EYE_PLANE, planeT);
  glTexGenfv(GL_R, GL_EYE_PLANE, planeR);

  //Start drawing the QUADs
  glBegin(GL_QUADS);
  for (float a = -2.0; a < 2.0; a += DISTANCE){   
  	glVertex3f(-2 ,-2 ,a);  
  	glVertex3f(-2 ,2 ,a);  
  	glVertex3f(2 ,2 ,a);  
  	glVertex3f(2 ,-2 ,a);
  }
  glEnd();

glPopMatrix();

[This message has been edited by PhilY (edited 06-20-2002).]