render-to-texture reflections on a non-trivial surface

Started by
14 comments, last by GameDev.net 18 years, 11 months ago
The following render-to-texture reflection code snippet works for the most part - the only flaw is that it produces a seemingly random mapping of the final texture, which is not what I want (seriously, I don't know how this mapping is obtained).

//first pass - draw the reflecting surface
glEnable( GL_TEXTURE_2D );
glMatrixMode(GL_MODELVIEW);

BindSurfaceTexture();

glPushMatrix();
   glTranslatef(0, 0.5f, 0);
	
   glDepthFunc( GL_LEQUAL );
   glBegin(GL_QUADS);
      for(int i = 0; i < 20; i++)
         {
         for(int j = 0; j < 20; j++)
            {
	    glTexCoord2f(i / 20.0f, j / 20.0f); glVertex3f((float)i, 0, (float)j);
	    glTexCoord2f((i + 1) / 20.0f, j / 20.0f); glVertex3f((float)i + 1, 0, (float)j);
	    glTexCoord2f((i + 1) / 20.0f, (j + 1) / 20.0f); glVertex3f((float)i + 1, 0, (float)j + 1);
	    glTexCoord2f(i / 20.0f, (j + 1) / 20.0f); glVertex3f((float)i, 0, (float)j + 1);
	    }
         }
   glEnd();

   BindReflectionTexture();

   glPushMatrix();
      glLoadIdentity();
      glTexGenfv(GL_S, GL_EYE_PLANE, IdentityPlaneS); glTexGenfv(GL_T, GL_EYE_PLANE, IdentityPlaneT); glTexGenfv(GL_R, GL_EYE_PLANE, IdentityPlaneR); glTexGenfv(GL_Q, GL_EYE_PLANE, IdentityPlaneQ);
   glPopMatrix();

   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); glTexGeni(GL_Q, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
   glEnable(GL_TEXTURE_GEN_S); glEnable(GL_TEXTURE_GEN_T); glEnable(GL_TEXTURE_GEN_R); glEnable(GL_TEXTURE_GEN_Q);		

//second pass - draw the reflection
   glMatrixMode(GL_TEXTURE);
      glPushMatrix();
         glEnable(GL_BLEND);
	 glBlendFunc(GL_ONE, GL_ONE);
	 glDepthFunc(GL_EQUAL);
	 glBegin(GL_QUADS);
	    for(int i = 0; i < 20; i++)
	       {
	       for(int j = 0; j < 20; j++)
	          {
		  glVertex3f((float)i, 0, (float)j);
		  glVertex3f((float)i + 1, 0, (float)j);
		  glVertex3f((float)i + 1, 0, (float)j + 1);
		  glVertex3f((float)i, 0, (float)j + 1);
		  }
	       }
	  glEnd();
      glPopMatrix();
   glMatrixMode( GL_MODELVIEW );	
	
   glDisable(GL_TEXTURE_GEN_S);
   glDisable(GL_TEXTURE_GEN_T);
   glDisable(GL_TEXTURE_GEN_R);
   glDisable(GL_TEXTURE_GEN_Q);
   
glPopMatrix();
glDisable(GL_BLEND);


Here's what the plane equations look like: IdentityPlaneS[0] = 1.f; IdentityPlaneT[1] = 1.f; IdentityPlaneR[2] = 1.f; IdentityPlaneQ[3] = 1.f; the rest being all zeros. This is largely my own code, although I have borrowed a lot and for that reason I do not really know what goes on at times (relating to texture projection in particular). Here is a screenshot of what I'm getting (click to enlarge): I've hilighted a tile in the texture (in red) that should map to the screen, which would then align with the actual geometry and produce a seamless reflection. No matter how I tweak the second pass in the above code by scaling the texture, I can't seem to be able to align in properly. In reality the tiled planes that are drawn will be substituted with a more elaborate surface - a little something I couldn't get the reflections working on with a single pass. [Edited by - Crispy on May 16, 2005 11:31:29 AM]
"Literally, it means that Bob is everything you can think of, but not dead; i.e., Bob is a purple-spotted, yellow-striped bumblebee/dragon/pterodactyl hybrid with a voracious addiction to Twix candy bars, but not dead."- kSquared
Advertisement
From what I can understand of that code, I think you'll never get what you want, because texture coordinates change when you move (ie. they are viewpoint-relative).

Also, you say you're performing a texture projection, but where's the texture matrix setup? I cannot see it.
--------------------------------If you can't understand what I said, it's not you, it's because my english sucks!
Alright - I'll just ask you to dumb this down for me - what kind of setup would the texture matrix need?

Quote:
... because texture coordinates change when you move ...


Isn't that the idea of texture projection - to be viewport independent.


Anyway - I found the problem, but it turns out the result isn't exactly what I'd expected.

I'm creating a planar reflection and capturing it into a texture. I am then using two passes to first draw the ripply surface (in this case water) with a diffuse map and then the second pass to draw the reflection on it. What I'm getting is a planar (flat) reflection on a bumpy surface (the reflection isn't oscillating along with the waves). What I want is the projected texture to distort on the waves. Is there a way to achieve this without resorting to extensions? Couldn't this be achieved by setting polygon normals for the water when rendering the reflection (because it's not working)?

The one line missing in the above code is a call to gluPerspective() in texture matrix mode (is this what you had in mind?).

I don't have a good screenshot handy at the moment, but I'll try to upload one if what I just wrote doesn't make much sense.

Mind you that I haven't dealt with OpenGL for a very long time and texture matrixing and advanced methods in general aren't really what I'm good at. Thanks for the reply, though.
"Literally, it means that Bob is everything you can think of, but not dead; i.e., Bob is a purple-spotted, yellow-striped bumblebee/dragon/pterodactyl hybrid with a voracious addiction to Twix candy bars, but not dead."- kSquared
I think you want projective texturing. All of the documentation I found online (nVidia's paper using the eye planes approach is the most common) made no sense to me. I finally figured it out with the help of a post that Yann made here.

Anyway, here's how I think it's best explained. When we're doing projective texturing, it's kind of like a slide projector. The texture is aligned and sized to the screen. What this means is that a vertex's XY coords in clip space is actually its texture coordinate in the projected texture. (Clip space is a cube that spans [-1,1] in all axes, and is obtained after the modelviewprojection transformation.) The only problem is that texture coordinates are [0,1] rather than [-1,1]. We fix this little problem by adding 1 to the XY and then scaling by 0.5.

So to get the projected coordinate, you need to transform the vertex into clip space and feed it into the system as a texture coordinate. There are various ways to do this, but Yann's is by far the most intuitive:
glMatrixMode( GL_TEXTURE );glLoadIdentity();glScalef( 0.5f, 0.5f, 1.0f );glTranslatef( 1.0f, 1.0f, 0.0f );glMultMatrixf( ProjectionMatrix );glMultMatrixf( ViewMatrix );//Now, when we are rendering something, we feed the vertex position as a tex coord:glTexCoord3f( 0.0f, 0.0f, 0.0f );glVertex3f( 0.0f, 0.0f, 0.0f );//you can do this with arrays too, just set your TexCoordPointer with the same params as VertexPointer


This will take your vertex position (which is in the tex coord) and transform it by the view and projection matrices, and finally remap it into [0,1] so that it's a proper texture coordinate. This is a simple, intuitive way to do things, IMO...hopefully this helps. (And in case it wasn't obvious, don't use tex coord gen.)

[Edited by - Promit on May 15, 2005 4:44:00 PM]
SlimDX | Ventspace Blog | Twitter | Diverse teams make better games. I am currently hiring capable C++ engine developers in Baltimore, MD.
Oh, and for completeness...

There are a couple issues here that aren't quite directly related to what you're doing. One, which won't affect you at all, is that projective texturing also projects an image backwards in the opposite direction, because the q coordinate (aka w, tex coords are strq, which is the same as xyzw) becomes negative.

Also, shaders make the projective texturing extremely easy to do, and they also allow you to clamp q to positive values.

Here's some sample GLSL shaders:
Vertex shader:void main(){    gl_Position = ftransform();    gl_TexCoord[0] = (gl_Position + 1.0) / 2.0;}Pixel shader:uniform sampler2D Texture;void main(){    float4 TexCoord;    TexCoord.w = max( 0.0, gl_TexCoord[0].w );    TexCoord.xyz = gl_TexCoord[0].xyz / TexCoord.w;    gl_FragColor = texture2D( Texture, TexCoord.xy );}

I think you might be able to clamp the w of the texture coordinate in the vertex shader instead of the pixel shader, but I'm not sure...if someone else cares to jump in, please do. If you do the clamping there, that'll allow the pixel shader to simply do a texture2DProj lookup, which means it'll run on PS 1.x hardware.
SlimDX | Ventspace Blog | Twitter | Diverse teams make better games. I am currently hiring capable C++ engine developers in Baltimore, MD.
Quote:Original post by Crispy
Isn't that the idea of texture projection - to be viewport independent.

Sorry, I think I can't understand. What do you mean by "viewport independent"?

Quote:
Anyway - I found the problem, but it turns out the result isn't exactly what I'd expected.

I'm creating a planar reflection and capturing it into a texture. I am then using two passes to first draw the ripply surface (in this case water) with a diffuse map and then the second pass to draw the reflection on it. What I'm getting is a planar (flat) reflection on a bumpy surface (the reflection isn't oscillating along with the waves). What I want is the projected texture to distort on the waves. Is there a way to achieve this without resorting to extensions? Couldn't this be achieved by setting polygon normals for the water when rendering the reflection (because it's not working)?

Because reflections are not generally constructed with projective texture mapping. Consider using sphere or cube mapping for simulating good-looking (like Far Cry, you know what I mean...) reflections, instead.

Quote:
The one line missing in the above code is a call to gluPerspective() in texture matrix mode (is this what you had in mind?).

Promit told you quite a bit about projective texture mapping, if you still want to use it. It seems like he is a lot more experienced of me with that technique...

--------------------------------If you can't understand what I said, it's not you, it's because my english sucks!
Here's what my projective texturing based reflection looks like:

It's a little bit cheesy IMO, but it manages to look cool...what more can you ask for? [grin]
SlimDX | Ventspace Blog | Twitter | Diverse teams make better games. I am currently hiring capable C++ engine developers in Baltimore, MD.
Aha - that certainly explained why a few lines of code are what they are. Thanks for the explanation, Promit!

Your water looks nice in a still - however it doesn't say much about animation (although you appear to be using one large quad as the reflecting surface). I'll restate my question: is it possible to properly use the projected texture on a non-flat surface? For instance, in your screenshot - provided that the water is not static, does the reflection of the mountains and the sky box ripple along with the waves? I can get a planar projection working (much like your screenshot, or my screenshot with proper tex coord mapping in the original post) - however, when I start to deform the surface on which the projected texture lies, the reflection will remain planar no matter how the surface is deformed.





This is what I gather based on your explanation - feel free to correct me: the vertex coordinates, which aren't really changing in my water, act as static texture coordinates (static on two axes, that is - why the y axis doesn't affect the texture is a little beyond my logic at this point - much like a 4D banana is, so I take comfort in ignoring it) that alone wouldn't allow me to distort the texture projected on them.

The only way for me to manipulate these coordinates (which are generated automatically), would be through vertex normals. Right?






And hopefully you can explain one other thing to me that could also make a difference - the difference between eye and object space and texture projection taking place in either. I'm using projection in eye space - however, I've encountered an example using object space to achieve a similar effect to what I'm after.
"Literally, it means that Bob is everything you can think of, but not dead; i.e., Bob is a purple-spotted, yellow-striped bumblebee/dragon/pterodactyl hybrid with a voracious addiction to Twix candy bars, but not dead."- kSquared
The nature of projective texturing is such that if the camera doesn't move, the texture won't either, regardless of the geometry on screen. A moving vertex will simply move to a different point in the projection, but its color will change accordingly and keep everything preserved.

When you're using projected reflections, the way to get the image to vary is to modify the texture coordinates dynamically. My code doesn't do so, mainly because this was part of an assignment due friday and I just plain ran out of time. But you can distort the texture coordinates either per vertex or per pixel to shimmer the image.

Also, you should change the title of this thread...as is it's a little confusing.
SlimDX | Ventspace Blog | Twitter | Diverse teams make better games. I am currently hiring capable C++ engine developers in Baltimore, MD.
Alright - I added dynamic manipulation of the texture matrix in the render code and the result is quite nice (also I changed the name of the thread - I hope this more appropriately reflects the nature of the issue - no pun intended) - again, please read and correct me if I'm wrong.

The only problem is that now simple primitives (quads or triangles) have to be used when rendering the surface because each polygon will require an individual update of the texture matrix. This is costlyish because it's no longer possible to draw the surface as a series of quad strips or as a list, plus the addition to an extra call to glTranslatef(), glPushMatrix() and glPopMatrix() per polygon. Indeed, the distortion has to be kept at a minimum as well because it's not possible to update the texture matrix per-vertex, only per-polygon (which means that the texture will be distorted in patches, not smoothly across the surface). Still - if the level of distortion is kept low, the effect is oddly realistic.

As a sidenote - is it possible to render the screen to a texture larger than the currently active resolution? Eg, render the reflection as and to a 1024x1024 texture in something like the 800x600 screen mode? This would greatly enhance the quality of the reflection up close.
"Literally, it means that Bob is everything you can think of, but not dead; i.e., Bob is a purple-spotted, yellow-striped bumblebee/dragon/pterodactyl hybrid with a voracious addiction to Twix candy bars, but not dead."- kSquared

This topic is closed to new replies.

Advertisement