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
.
r_depthoverlay
is broken in Alien Swarm. To fix it, save the line "showz {}
" to materials/debug/showz.vmt
. [todo tested in?]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.