Oblique near-plane clipping

Hi all, I’ve gone through Mr Eric Lengyel’s tutorial on Oblique near-plane clipping, but I can’t get it working. Here’s what I’m doing:

void HT_Graphics::ModifyProjectionMatrix(CVector4& clipPlane)
{
    float       matrix[16];
	CVector4    q;
	
	float mat1[16];
	float mat2[16];

	glGetFloatv(GL_MODELVIEW_MATRIX, mat1);

	CMatrix::mat4_inverseOrth(mat2, mat1);
	CMatrix::mat4_transpose(mat1, mat2);

	float xplane[4];

	CMatrix::plane_transform(xplane, clipPlane.v, mat1);
	CVector4 resVec(xplane[0], xplane[1], xplane[2], xplane[3]);

    // Grab the current projection matrix from OpenGL
    glGetFloatv(GL_PROJECTION_MATRIX, matrix);

    // Calculate the clip-space corner point opposite the clipping plane
    // as (sgn(clipPlane.x), sgn(clipPlane.y), 1, 1) and
    // transform it into camera space by multiplying it
    // by the inverse of the projection matrix
    
    q.x = (sgn(resVec.x) + matrix[8]) / matrix[0];
	q.y = (sgn(resVec.y) + matrix[9]) / matrix[5];
    q.z = -1.0f;
    q.w = (1.0f + matrix[10]) / matrix[14];
    
    // Calculate the scaled plane vector
	CVector4 c = resVec * (2.0f / (resVec * q));
    
    // Replace the third row of the projection matrix
    matrix[2] = c.x;
    matrix[6] = c.y;
    matrix[10] = c.z + 1.0f;
    matrix[14] = c.w;

    // Load it back into OpenGL
    glMatrixMode(GL_PROJECTION);
	glLoadMatrixf(matrix);

	glMatrixMode(GL_MODELVIEW);
}
		static inline void plane_transform(float* dst, float* src, float* matrix)
		{
			#define M(ROW,COL) matrix[((COL)*4)+(ROW)]

			dst[0] = src[0] * M(0,0) + src[1] * M(1,0) + src[2] * M(2,0) + src[3] * M(3,0);
			dst[1] = src[0] * M(0,1) + src[1] * M(1,1) + src[2] * M(2,1) + src[3] * M(3,1);
			dst[2] = src[0] * M(0,2) + src[1] * M(1,2) + src[2] * M(2,2) + src[3] * M(3,2);
			dst[3] = src[0] * M(0,3) + src[1] * M(1,3) + src[2] * M(2,3) + src[3] * M(3,3);

			#undef M
		}

		static inline void mat4_transpose(float* dst, float* src)
		{
			dst[0]=src[0];
			dst[1]=src[4];
			dst[2]=src[8];
			dst[3]=src[12];
			dst[4]=src[1];
			dst[5]=src[5];
			dst[6]=src[9];
			dst[7]=src[13];
			dst[8]=src[2];
			dst[9]=src[6];
			dst[10]=src[10];
			dst[11]=src[14];
			dst[12]=src[3];
			dst[13]=src[7];
			dst[14]=src[11];
			dst[15]=src[15];
		}

		static inline void mat4_inverseOrth(float* dst, float* src)
		{
			dst[0]=src[0];
			dst[1]=src[4];
			dst[2]=src[8];
			dst[3]=0.0f;
			dst[4]=src[1];
			dst[5]=src[5];
			dst[6]=src[9];
			dst[7]=0.0f;
			dst[8]=src[2];
			dst[9]=src[6];
			dst[10]=src[10];
			dst[11]=0.0f;
			dst[12]=-src[12]*src[0]-src[13]*src[1]-src[14]*src[2];
			dst[13]=-src[12]*src[4]-src[13]*src[5]-src[14]*src[6];
			dst[14]=-src[12]*src[8]-src[13]*src[9]-src[14]*src[10];
			dst[15]=1.0f;
		}
...
		isReflection = true;
		framesComputeReflection = 0;

		// *** calculate reflection ***********************************************************
		glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, frameBufferReflection);

		glViewport(0,0, REFLECTION_TEXTURE_SIZE, REFLECTION_TEXTURE_SIZE);
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
		glLoadIdentity();

		gluLookAt(pos.x, pos.y, pos.z,
		  view.x, view.y, view.z,
		  up.x, up.y, up.z);

		glPushMatrix();
		if(pos.y > waterHeight)
		{
			glTranslatef(0.0f, waterHeight * 2.0f, 0.0f);
			glScalef(1.0, -1.0, 1.0);

			glCullFace(GL_FRONT);
	
			ModifyProjectionMatrix(CVector4(0.0, 1.0, 0.0, -waterHeight));
			
			// render scene to reflection texture
			HT_Graphics::SetActiveShaderProgram(HT_Constants::SH_NONE);
			m_skybox->Render(pos.x, pos.y, pos.z, engineconfig->farPlane-10.0f, engineconfig->farPlane-10.0f, engineconfig->farPlane-10.0f);

			if(engineconfig->reflectionsFull)
			{
				m_mapI->Render();
			}

			ResetProjectionMatrix();

			glCullFace(GL_BACK);
		}
		else
		{
			ModifyProjectionMatrix(CVector4(0.0, 1.0, 0.0, waterHeight));

			if(engineconfig->reflectionsFull)
			{
				m_mapI->Render();
			}

			ResetProjectionMatrix();
		}

		glPopMatrix();

		glBindTexture(GL_TEXTURE_2D, reflTex);
		glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
...

I don’t know what’s gone wrong :confused:

What I do is I pass the clipping plane equation as world coordinates (as for the glClipPlane function), and I transform the coordinates to eye coords using inverse transpose modelview matrix. Then I modify the projection matrix accordingly as described in Mr Lengyel’s article.

Here’s a shot of the problem

The plane still seems to “follow” the camera, and doesn’t clip along the surface of the water (the brick wall is actually a “reflection” of the bottom of the pool, and the black thing is a clipped region)

Anybody got a clue?

Thanks a lot

HardTop

You need to multiply the plane’s normal (in your case [0 -1 0 1]) by the inverse view matrix and then normalize it (only the xyz part leave the w out of it since you need it set to 1.0f for later).
Then you need to transform a point on the water plan again by the inverse view matrix.
Once you have those two under your belly, the new water plane will be something like (normal.xyz, -dot4(transformedPointOnAPlane, normal)).
Use that plane to modify your projection matrix =-)

that didnt fully work, jcd. only mistake was that one time the transposed and one time the normal viewmatrix is used.

but I could fix my issues with your hints, and this here works

// transform clipplane to eyespace
MatrixTranspose(mviewinvT,mviewinv);
// transform normal
Vector3Transform(eyeplane,clipPlane,mviewinvT);
// transform point on normal
Vector3Scale(q,clipPlane,clipPlane[3]);
Vector3Transform1(q,mview);
eyeplane[3] = -Vector3Dot(q,eyeplane);

I’ll give that a try. thanks guys. Anymore clues are welcome :slight_smile:

OK but what are Vector3Transform, Vector3Scale and Vector3Transform1 supposed to do, anyway?

I’m not sure exactly, cause it’s not my code )))

but it is seems to me, that

  • Vector3Transform() multiplies 3-component vector by 3x3 matrix, saving the result in other vector (b = M*a)
  • Vector3Scale() scales 3-component vector by constant value, saving the result in other vector (b = t*a)
  • Vector3Transform1() does the same, as simple version, but the result is stored in the input vector (a = M*a)

I think Eric’s code needs the world-space clipplane transformed into eye-space (using the inverse transpose of the view matrix).

SeanG: exactly, that’s what I’m trying to do, but I don’t seem to get the correct coordinates computed when I multiply my world-space clipplane by the inverse transpose modelview matrix, to obtain eye-space coords

Jackis: this makes sense :slight_smile:

I’m just wondering what went wrong with my calculation of the Inv-T-MVMatrix, and with my conversion from world-space to eye-space…

Thanks for your feedback! Don’t hesitate to post anymore thoughts

HT

Hmmm, this just works for me.

I think maybe he’s got this thing wired for the eye on one side of the clipplane, as is the case for reflection clipping. If you want to use this as a generic clipplane (which looks to be what’s going on in your scene), you may need to handle the more general case.

Try this minor modification:

// ...

Plane q;
//q.X = (Sign(clipPlane.X) + projMat[8]) / projMat[0];
//q.Y = (Sign(clipPlane.Y) + projMat[9]) / projMat[5];
//q.Z = -1;
//q.W = (1.0F + projMat[10]) / (projMat[14]);

q.X = Sign(clipPlane.X);
q.Y = Sign(clipPlane.Y);
q.Z = -Sign(clipPlane.Z);
q.W = 1;
q = q.Transform(projMat.Inverse());
 
// ...

This is pretty much what he already has in his comments; the code is just optimized for the common case.

Please correct me if I hosed something.

Hi –

After a quick look at your code, here are the things that come to mind:

  1. It looks like you’re specifying a clip plane in world space and then transforming it into eye space with the inverse transpose of the model-view matrix. Does the model-view matrix actually go from world space to eye space in your case, or does it transform from some local object space?

  2. The clip plane needs to face away from the camera, and I don’t think it does in your code. Try negating it. In eye space, the clip plane’s w-coordinate should be negative.

  3. The planes that you’re using for above water and below water don’t match up. I think you want to use (0, -1, 0, waterHeight) above the water and (0, 1, 0, -waterHeight) below the water.

– Eric Lengyel

SeanG: tried your solution but to no avail

Mr Lengyel:

  1. I tried initializing a variable (mvm) containing the modelview matrix just after the glulookat call, but it didn’t work
  2. didn’t work either
  3. I’ll try and have the “above water” case working first :slight_smile:

but thanks a lot anyway. Here’s another screenshot representing the issue. You can see two intersecting clipping planes in the reflection. One plane is “camera” dependent (the one producing the black background), where the other seems “fixed”, but not parallel to the surface of the water

sorry, last screenshot was not relevant.

Here’s an exact version of what’s happening:

I can’t get anywhere… f*cking clipping planes! I’m getting desperate :s

oooof solved at last…

It was probably an issue about that matrix column or row major order stuff… I disabled the transposition of the inverse MV matrix, and it’s working at last.

Here’s the code:

void HT_Graphics::ClipPlane(CVector4& clipPlane)
{
    float       matrix[16];
	CVector4    q;
	
	float mat1[16];
	float mat2[16];
	float xplane[4];

    // Grab the current projection matrix from OpenGL
	glGetFloatv(GL_MODELVIEW_MATRIX, mvm);
    glGetFloatv(GL_PROJECTION_MATRIX, matrix);

	CMatrix::mat4_inverseOrth(mat2, mvm);
	//CMatrix::mat4_transpose(mat1, mat2);

	CMatrix::plane_transform(xplane, clipPlane.v, mat2);
	CVector4 resVec(xplane);

    // Calculate the clip-space corner point opposite the clipping plane
    // as (sgn(clipPlane.x), sgn(clipPlane.y), 1, 1) and
    // transform it into camera space by multiplying it
    // by the inverse of the projection matrix
	q.x = (sgn(resVec.x) + matrix[8]) / matrix[0];
	q.y = (sgn(resVec.y) + matrix[9]) / matrix[5];
	q.z = -1.0f;
	q.w = (1.0f + matrix[10]) / matrix[14];

    // Calculate the scaled plane vector
	CVector4 c = resVec * (2.0f / (resVec * q));
    
    // Replace the third row of the projection matrix
    matrix[2] = c.x;
    matrix[6] = c.y;
    matrix[10] = c.z + 1.0f;
    matrix[14] = c.w;

    // Load it back into OpenGL
    glMatrixMode(GL_PROJECTION);
	glLoadMatrixf(matrix);

	glMatrixMode(GL_MODELVIEW);
}

Thanks to everybody for your help! I still got an issue on the nVidia board, where the fresnel term is obviously wrong, but I’ll try and fix that :slight_smile:

On the ATI, it’s looking OK, but on the nVidia, the fresnel seems wrong

If you’re using GLSL, try using -reflect(view, normal) instead of reflect(view, normal); that should fix it on NVIDIA cards =-/

Originally posted by CrazyButcher:
[b] that didnt fully work, jcd. only mistake was that one time the transposed and one time the normal viewmatrix is used.

but I could fix my issues with your hints, and this here works

// transform clipplane to eyespace
MatrixTranspose(mviewinvT,mviewinv);
// transform normal
Vector3Transform(eyeplane,clipPlane,mviewinvT);
// transform point on normal
Vector3Scale(q,clipPlane,clipPlane[3]);
Vector3Transform1(q,mview);
eyeplane[3] = -Vector3Dot(q,eyeplane); [/b]
You need to rotate and translate your point, that would make a Vector4Transform =-/
Also -Vector4Dot instead of Vector3Dot…

The proper transform for planes/normals is the inverse transpose, so something is amiss if an inverse is working.

Check you matrix/vector routines, carefully. If the math is done correctly, this should just work.

By the way, the general clipplane transform will work, but it’ll hose your depth as you swing around the other (positive) side of the plane. Nvidia has a demo of a slightly different technique for a generalized clipplane, but it makes no attempt to maximize depth precision.

thanks jcd, my vector3transform actually does translation, too. so the first eyeplane transform is actually incorrect, but the transposed matrix4x4 in my case contains 0 as translation so it works. but why would eyeplane.w need a vector4dot ?

SeanG: I think I’ll perform another clipping if I get to the other side of the plane. I have an IF on the height on the plane.

HT

JCD, I tested your option to negate the reflect term, but I get a kind of “inverted” effect on the underwater textures, and a saturated effect on the reflection:

...
vec4 viewReflection = normalize( -reflect(-viewTangetSpace, normalVector) );
vec4 invertedFresnel = vec4( dot(normalVector, viewReflection ) );
vec4 fresnelTerm = 1.0 - invertedFresnel;
...

And here’s the shot:

It was tested on both ATI and nVidia

HT