Sign in to follow this  

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

This topic is 4595 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

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]

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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]

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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...

Share this post


Link to post
Share on other sites
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]

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
Yann L has wrote an article about Water Reflections some time ago. I assure you it will explain everything(it did for me):
http://www.gamedev.net/reference/articles/article2138.asp

Quote:

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.


Actually, local reflections are implemented using projective texture mapping. You just offset the proj. texture coords using the normal vector. Not exactly physically correct, but it gives a pretty good effect. I'm pretty sure Far Cry and every other game does something similar. Cube mapping is used to reflect objects that are(in theory) infinitely far away, like the skybox.

Share this post


Link to post
Share on other sites
If the normal-based reflection map offsetting is supposed to just work, then I have no idea what I'm doing wrong - specifying vertex normals has absolutely no effect whatsoever on the reflection.

Thanks for the link, though.

Share this post


Link to post
Share on other sites
Quote:
Original post by Crispy
If the normal-based reflection map offsetting is supposed to just work, then I have no idea what I'm doing wrong - specifying vertex normals has absolutely no effect whatsoever on the reflection.

Thanks for the link, though.


It seems like what I have said before about texture projections was just plain wrong. This is the result of trying to talk about something I'm totally inexperienced...

Anyway, Crispy, If you didn't changed the code, it is normal that changing the normal vectors has no effect on the texture coordinates. I think that, in your case, the best way to distort the texture coordinates is to use a vertex program.

Share this post


Link to post
Share on other sites
Crispy, I'd strongly recommend using a vertex shader. Even on hardware that doesn't support it, it can be done in software by the driver quite efficiently, and it will allow you do to per vertex distortion of the water surface.

BTW, how are you distorting the texture matrix? I wanted to do the same, but have yet to think of a good method for doing so...

Share this post


Link to post
Share on other sites
Aight - a vertex program it'll be then. I have a GF2, though, so I have no idea what the performance hit might be, even if done in software. Without having Googled for it, does anyone have link to a simple tutorial on the subject?

Quote:

BTW, how are you distorting the texture matrix? I wanted to do the same, but have yet to think of a good method for doing so...


Simple:


for(int j = 0, i0 = 0, i1 = 1, i2 = ex + 1, i3 = ex; j < ez - 1; j++)
{
for(int i = 0; i < ex - 1; i++, i0++, i1++, i2++, i3++)
{
if(!texcoords)
{
glMatrixMode(GL_TEXTURE);
glPushMatrix();
glTranslatef(0, oscmap[i0].y, 0);
}

glBegin(GL_QUADS);
if(texcoords) glTexCoord2f(texcoord[i0].u, texcoord[i0].v);
glVertex3fv(oscmap[i0]);

if(texcoords) glTexCoord2f(texcoord[i1].u, texcoord[i1].v);
glVertex3fv(oscmap[i1]);

if(texcoords) glTexCoord2f(texcoord[i2].u, texcoord[i2].v);
glVertex3fv(oscmap[i2]);

if(texcoords) glTexCoord2f(texcoord[i3].u, texcoord[i3].v);
glVertex3fv(oscmap[i3]);
glEnd();

if(!texcoords)
glPopMatrix();
}
i0++; i1++; i2++; i3++;
}




Ignore the indexes - they're optimized to not include any array offset calculation inside the loop. This loop could be optimized a little further still by taking the call to glMatrixMode() out.

Otherwise it's meant to be called for either the diffuse map pass or with texcoords set to true or the reflection pass otherwise.

Note that by making oscmap[n].y (wave height) too large, the individual quads will start to form a shifting mosaic pattern since each four adjacent vertices entail an identical texture matrix shift. This is why a vertex program would indeed be a lot better solution - the only problem being that I've never even seen a bit of shader code before.

Share this post


Link to post
Share on other sites
Guest Anonymous Poster
Sorry, I don't know any good tutorials on vertex programs usage...
I learned them the hard way, reading the OpenGL specification!

Search on google, there should be plenty of code examples and tutorials.

Share this post


Link to post
Share on other sites

This topic is 4595 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this