Depth buffer

From Valve Developer Community
Jump to: navigation, search
Source's depth buffer is very limited

The depth buffer is a texture in which each on-screen pixel is assigned a greyscale value depending on its distance from the camera. This allows visual effects to easily alter with distance. It is used for soft particles.

Source's depth buffer has a very short range of just 192 units, and is also squashed down to fit into a power of two render target (after having being generated in the alpha channel of the full backbuffer). It can be viewed with r_depthoverlay.

Icon-Bug.pngBug:Alien Swarm r_depthoverlay is broken in Alien Swarm. To fix it, save the line "showz {}" to materials/debug/showz.vmt.

Alien Swarm allows you to easily modify the range of the depth buffer with the cvar mat_dest_alpha_range, but the bit-depth and behaviour in regards to alpha tested geometry stays the same.

Modifying Source's depth buffer texture

Depth buffer with alpha tested geometry and a range of 20000 world units

The available output range of the depth buffer can be adjusted in every mod that is utilizing a custom shader library. A requirement is to solely render all opaque and alpha tested geometry in a scene with modified shaders; this can either be achieved by manually replacing all shaders used in every VMT file (optimal solution), reloading all materials at runtime (slow) or overriding the actual shader files used by a default shader dll (probably unstable).

Blank image.pngTodo: Confirm the last option
Note.pngNote:Source SDK 2013 contains a procedure originally built for Pyrovision that renders a full depth buffer usable by mod shaders. The code is in viewrender.cpp in a method named CBaseWorldView::SSAO_DepthPass(). For an example of how to modify the source to take advantage of this code, see this commit.

The following paragraphs will document how you can alter the public shaders to output a different depth buffer.


Fixing alpha testing behaviour

If you want to let alpha tested objects draw depth, you will have to change several things first. The Client copies the depth information from the backbuffer with a call to UpdateFullScreenDepthTexture() defined in view_scene.cpp, by default this happens before any translucents are drawn.

Firstly, navigate to

void CRendering3dView::DrawTranslucentRenderables( bool bInSkybox, bool bShadowDepth )

and move UpdateFullScreenDepthTexture from the switch at the beginning to the end, like this:

	[...]
	// Draw the rest of the surfaces in world leaves
	DrawTranslucentWorldAndDetailPropsInLeaves( iPrevLeaf, 0, nEngineDrawFlags, nDetailLeafCount, pDetailLeafList, bShadowDepth );

	if ( g_CurrentViewID == VIEW_MAIN )
		UpdateFullScreenDepthTexture();

	// Draw any queued-up detail props from previously visited leaves
	DetailObjectSystem()->RenderTranslucentDetailObjects( CurrentViewOrigin(), CurrentViewForward(), CurrentViewRight(), CurrentViewUp(), nDetailLeafCount, pDetailLeafList );

	// Reset the blend state.
	render->SetBlend( 1 );
}
Tip.pngTip:This will break soft particles. Instead, you should split the function in half; either draw alpha tested objects first or particles at the very end and do the depth buffer copy in-between.


To make alpha tested objects draw to the alpha channel in the first place, you need to modify the implementation and the actual shader file of each shader in question first. The following examples will explain it with the help of the vertexlitgeneric shader:


In vertexlitgeneric_dx9_helper.cpp search for:

			pShaderShadow->EnableAlphaTest( bIsAlphaTested );

			if ( info.m_nAlphaTestReference != -1 && params[info.m_nAlphaTestReference]->GetFloatValue() > 0.0f )
			{
				pShaderShadow->AlphaFunc( SHADER_ALPHAFUNC_GEQUAL, params[info.m_nAlphaTestReference]->GetFloatValue() );
			}

and replace it with:

			pShaderShadow->EnableAlphaTest( bIsAlphaTested );
			if ( bIsAlphaTested  )
				pShaderShadow->AlphaFunc( SHADER_ALPHAFUNC_GREATER, 0 );

This will cause the pixelshader to actually draw unless its alpha output value is exactly null.

Now search for the line:

	pShaderShadow->EnableAlphaWrites( bFullyOpaque );

and change it to:

	pShaderShadow->EnableAlphaWrites( bFullyOpaque || bIsAlphaTested );

This change will allow writing to the alpha channel of the current target for alpha tested materials.


The shader file, in this case SDK_vertexlit_and_unlit_generic_ps2x.fxc, needs to reflect the new behaviour; change the very end to look like this:

	float4 color = FinalOutputConst( float4( result.rgb, alpha ), fogFactor, g_fPixelFogType, TONEMAP_SCALE_LINEAR, g_fWriteDepthToAlpha, i.projPos.z );
	color.a *= round( alpha );
	return color;

This replaces the alpha testing reference, if the actual alpha value from the texture is smaller than 0.5f, we will have transparency at this pixel, otherwise the value stays unmodified and represents the depth range instead.

Tip.pngTip:Add a new alpha testing reference per shader constant if you want it to be variable again.

Scaling the depth output

The depth information drawn to the backbuffer is scaled by the following factor found in src\materialsystem\stdshaders\common_ps_fxc.h:

#define OO_DESTALPHA_DEPTH_RANGE (g_LinearFogColor.w)

Changing the definition of the constant g_LinearFogColor would also result in a different range, but it seems to be inaccessible from the publicly available source code. The value describes how the homogenous depth is transformed into normalized space, so if one was to describe the whole scene in the alpha channel, the value should be 1 / (zFar - zNear), otherwise you should use 1 / desired_range to calculate your constant, keep in mind that your interval starts at zNear (default 7).

Scaling the depth output to the whole scene


To make this value flexible and automatically describe your whole range, you can add a float rendering parameter to renderparm.h:

//#define MAX_FLOAT_RENDER_PARMS 20
enum RenderParamFloat_t
{
	FLOAT_RENDERPARM_ZDELTA = 0,

	MAX_FLOAT_RENDER_PARMS = 20
};

In viewrender.cpp find the function

void CViewRender::DrawWorldAndEntities( bool bDrawSkybox, const CViewSetup &viewIn, int nClearFlags, ViewCustomVisibility_t *pCustomVisibility )

and add this to the top:

	CMatRenderContextPtr pRenderContext( materials );
	pRenderContext->SetFloatRenderingParameter( FLOAT_RENDERPARM_ZDELTA, (viewIn.zFar - viewIn.zNear) );

Since pixelshader constants are mostly not consistent across individual shaders, you might have to to define a constant for each shader by hand. For SDK_vertexlit_and_unlit_generic_ps2x.fxc it could be done like this:

common_ps_fxc.h

#ifdef DEPTH_GET_CUSTOM_RANGE
#	define OO_DESTALPHA_DEPTH_RANGE DEPTHRANGE_CONSTANT
#else
#	define OO_DESTALPHA_DEPTH_RANGE (g_LinearFogColor.w)
#endif

SDK_vertexlit_and_unlit_generic_ps2x.fxc

#define DEPTH_GET_CUSTOM_RANGE
const float g_DepthRange			: register( c14 ); //you can also add the float to another existing constant this way, depends on the shader
#define DEPTHRANGE_CONSTANT g_DepthRange
#include "common_fxc.h"

vertexlitgeneric_dx9_helper.cpp

DYNAMIC_STATE
{
	[...]
	float zDelta = pShaderAPI->GetFloatRenderingParameter( FLOAT_RENDERPARM_ZDELTA );
	float g_const14[4] = { zDelta, 0.0f, 0.0f, 0.0f };
	DynamicCmdsOut.SetPixelShaderConstant( 14, g_const14, 1 );

	DynamicCmdsOut.End();
}
Note.pngNote:By default the output is compressed to 8-bit precision, this will cause heavy colour banding when a long range is used.


Fixing the depth buffer texture

The rendertarget named _rt_FullFrameDepth is supposed to contain the depth information, it seems to be an internal alias to the texture _rt_PowerOfTwoFB though. For that reason it may be better to create a new seperate rendertarget instead: In view.cpp navigate to the function:

void CViewRender::Init( void )

You can add this snippet to quickly allocate a new rendertarget:

		ITexture *depthOld = materials->FindTexture( "_rt_FullFrameDepth", TEXTURE_GROUP_RENDER_TARGET );
		static int flags = TEXTUREFLAGS_NOMIP | TEXTUREFLAGS_NOLOD | TEXTUREFLAGS_RENDERTARGET;
		if ( depthOld )
			flags = depthOld->GetFlags();

		int iW, iH;
		materials->GetBackBufferDimensions( iW, iH );
		materials->BeginRenderTargetAllocation();
		ITexture *p = materials->CreateNamedRenderTargetTextureEx(
				"_rt_FullFrameDepth_Alt",
				iW, iH, RT_SIZE_NO_CHANGE,
				IMAGE_FORMAT_A8,
				MATERIAL_RT_DEPTH_NONE,
				flags,
				0);
		materials->EndRenderTargetAllocation();

To reference this new texture properly, open rendertexture.cpp and go to:

	ITexture *GetFullFrameDepthTexture( void )

By default this function searches for _rt_FullFrameDepth; change this string to read _rt_FullFrameDepth_Alt instead.