Depth buffer
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.
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
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).
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 );
}
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.
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();
}
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.
INTZ Depth
As an alternative, you can unbind Alias and use _rt_FullFrameDepth for its intended purpose, as in Counter-Strike: Global Offensive engine branch, using IMAGE_FORMAT_NV_INTZ for Team Fortress 2 engine branch and IMAGE_FORMAT_INTZ for Counter-Strike: Global Offensive engine branch. INTZ is a D3D9 HACK that will allow you to write hardware depth as a color texture. You also won't have to rewrite shaders. For example, depth is typically obtained through Multi Render Target (MRT), using Deferred renderer. But INTZ will help you to get depth concisely even without an MRT. See implementation on Source SDK 2013.
materials->RemoveTextureAlias("_rt_FullFrameDepth"); // _rt_FullFrameDepth is Alias of _rt_PowerOfTwoFB on PC
materials->BeginRenderTargetAllocation();
materials->CreateNamedRenderTargetTextureEx("_rt_FullFrameDepth", 1, 1,
RT_SIZE_FULL_FRAME_BUFFER,
IMAGE_FORMAT_NV_INTZ,
MATERIAL_RT_DEPTH_NONE,
TEXTUREFLAGS_CLAMPS | TEXTUREFLAGS_CLAMPT | TEXTUREFLAGS_POINTSAMPLE,
CREATERENDERTARGETFLAGS_NOEDRAM
);
materials->EndRenderTargetAllocation();
INTZ pros:
- Soft particles. It is a very old engine feature. It requires high precision depth buffer which Source always had problems with. It implemented in
Military Conflict: Vietnam.
used high-precision 24-bit hardware INTZ depth buffer with 8 additional bits reserved for stencil.
- Works both with Forward rendered and Deferred renderer. This way you can save 1 channel of MRT texture.
- This will allow you to obtain a depth buffer for shaders without resorting to double-rendering of scene, as in Pyro Vision on
_rt_ResolvedFullFrameDepth.
INTZ disadvantages:
- Depth storing without MSAA. As
_rt_ResolvedFullFrameDepthand as any other depth. - MSAA is disabled for the main scene. This can be avoided if you're an engine developer. By using
NVCSas NvAPI_D3D9_StretchRectEx from NVAPI SDK. For Intel and AMD, you can useRESZtexture format.