Rendering issues when using oblique near plane projection matrix

Hello,

I’m using Oblique Near Plane projection to render a view through a “portal” using FClipProjectionMatrix on a normal perspective projection matrix and then flipping the Z. It works fine normally, but there are certain things that don’t render properly. The most glaring of which is fog. I imagine this is an issue with getting the world depth of a pixel using the new projection matrix. Here are the functions that I think are causing problems:

In SceneView.cpp:

/**
 * Utility function to create the inverse depth projection transform to be used
 * by the shader system.
 * @param ProjMatrix - used to extract the scene depth ratios
 * @param InvertZ - projection calc is affected by inverted device Z
 * @return vector containing the ratios needed to convert from device Z to world Z
 */
FVector4 CreateInvDeviceZToWorldZTransform(FMatrix const & ProjMatrix)
{
	// The depth projection comes from the the following projection matrix:
	//
	// | 1  0  0  0 |
	// | 0  1  0  0 |
	// | 0  0  A  1 |
	// | 0  0  B  0 |
	//
	// Z' = (Z * A + B) / Z
	// Z' = A + B / Z
	//
	// So to get Z from Z' is just:
	// Z = B / (Z' - A)
	// 
	// Note a reversed Z projection matrix will have A=0. 
	//
	// Done in shader as:
	// Z = 1 / (Z' * C1 - C2)   --- Where C1 = 1/B, C2 = A/B
	//
	float DepthMul = ProjMatrix.M[2][2];
	float DepthAdd = ProjMatrix.M[3][2];

	if (DepthAdd == 0.f)
	{
		// Avoid dividing by 0 in this case
		DepthAdd = 0.00000001f;
	}

	float SubtractValue = DepthMul / DepthAdd;

	// Subtract a tiny number to avoid divide by 0 errors in the shader when a very far distance is decided from the depth buffer.
	// This fixes fog not being applied to the black background in the editor.
	SubtractValue -= 0.00000001f;

	return FVector4(
		0.0f,			// Unused
		0.0f,			// Unused
		1.f / DepthAdd,	
		SubtractValue
		);
}

In Common.usf:

// Converts depth buffer Z / W into W
float ConvertFromDeviceZ(float DeviceZ, float2 DeviceUV)
{
	return 1.f / (DeviceZ * View.InvDeviceZToWorldZTransform[2] - View.InvDeviceZToWorldZTransform[3]);
}

|

// inverse opteration of ConvertFromDeviceZ
float ConvertToDeviceZ(float SceneDepth, float2 DeviceUV)
{
	return 1.0f / ((SceneDepth + View.InvDeviceZToWorldZTransform[3]) * View.InvDeviceZToWorldZTransform[2]);
}

The ConvertTo/ConvertFrom function have a float2 parameter added for the DeviceUV from some tests I was doing.

I figure there should be some kind of change based on the screen location of the pixel. What I’ve tried so far is changing CreateInvDeviceZToWorldZTransform to just return the third column of the projection matrix and changed the ConvertFromDeviceZ(float DeviceZ, float2 DeviceUV) function to:

// Converts depth buffer Z / W into W
float ConvertFromDeviceZ(float DeviceZ, float2 DeviceUV)
{
//CJB!!!MODIF_BEGIN
	float x = DeviceUV.x * 2.0f - 1.0f;
	float y = DeviceUV.y * 2.0f - 1.0f;

	return ((x * View.InvDeviceZToWorldZTransform[0]) + 
			(y * View.InvDeviceZToWorldZTransform[1]) +
			View.InvDeviceZToWorldZTransform[3]) / (DeviceZ - View.InvDeviceZToWorldZTransform[2]);
//CJB!!!MODIF_END
}

This changes the artifacts a bit, but they’re still there. It could be that I’m just not doing the math correctly here.

What’s the proper way of changing these depth functions to handle oblique near plane projection? Also, are there any other issues that might come from not using the standard reversed z projection for scene capture 2d components?

Thanks,
Chris

I ran into this the other day too when trying to implement scene capture reflect for 2d planar reflections. I’m afraid I didn’t get a chance to track down all the issues with FClipProjectionMatrix and the renderer yet. I would expect a lot of problems though, especially in temporal AA and the way scene depth is decoded (as you noted).

Hi, did you solve it? I’m currently trying to implement it myself.

I got it working to some extent, but there were still visual artifacts. It’s been a while since I worked on them, but you pretty much have to go through and make sure nothing using the oblique plane clipping references the depth texture since it’ll be inaccurate. Doing this I was able to get certain dynamic lights, shadows, and decals working, but not reflections, skylights and I think a few other things.