Depth buffer: Difference between revisions

From Valve Developer Community
Jump to navigation Jump to search
No edit summary
No edit summary
Line 7: Line 7:
{{todo|Differences between depth buffer and [[fog]].}}
{{todo|Differences between depth buffer and [[fog]].}}


== Scaling the depth output ==
==Modifying Source's depth buffer==
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 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). {{todo|Confirm the last option}}
[[Image:Source_Depthbuffer_8bit.jpg|thumb|right|150px|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). {{todo|Confirm the last option}}


The depth information drawn to the backbuffer is scaled by this factor ''( src\materialsystem\stdshaders\common_ps_fxc.h )'':
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 <code>UpdateFullScreenDepthTexture()</code> defined in view_scene.cpp, by default this happens before any translucents are drawn.
 
Firstly, navigate to
<source lang=cpp>void CRendering3dView::DrawTranslucentRenderables( bool bInSkybox, bool bShadowDepth )</source>
and move <code>UpdateFullScreenDepthTexture</code> from the switch at the beginning to the end, like this:
<source lang=cpp> [...]
// 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 );
}</source>
{{tip|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 <code>vertexlitgeneric_dx9_helper.cpp</code> search for:
<source lang=cpp> 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() );
}</source>
and replace it with:
<source lang=cpp> pShaderShadow->EnableAlphaTest( bIsAlphaTested );
if ( bIsAlphaTested  )
pShaderShadow->AlphaFunc( SHADER_ALPHAFUNC_GREATER, 0 );</source>
This will cause the pixelshader to actually draw unless its alpha output value is exactly null.
 
Now search for the line:
<source lang=cpp> pShaderShadow->EnableAlphaWrites( bFullyOpaque );</source>
and change it to:
<source lang=cpp> pShaderShadow->EnableAlphaWrites( bFullyOpaque || bIsAlphaTested );</source>
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:
<source lang=cpp> float4 color = FinalOutputConst( float4( result.rgb, alpha ), fogFactor, g_fPixelFogType, TONEMAP_SCALE_LINEAR, g_fWriteDepthToAlpha, i.projPos.z );
color.a *= round( alpha );
return color;</source>
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|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 <code>src\materialsystem\stdshaders\common_ps_fxc.h</code>:


  <span style="color:blue;">#define</span> <span style="color:brown;">OO_DESTALPHA_DEPTH_RANGE</span> (g_LinearFogColor.w)
  <span style="color:blue;">#define</span> <span style="color:brown;">OO_DESTALPHA_DEPTH_RANGE</span> (g_LinearFogColor.w)
Line 22: Line 80:


{{Note|By default the output is compressed to 8-bit precision, this will cause heavy [[w:colour banding|colour banding]] when a long range is used.}}
{{Note|By default the output is compressed to 8-bit precision, this will cause heavy [[w:colour banding|colour banding]] when a long range is used.}}
===Fixing the depth buffer texture===
The rendertarget named <code>_rt_FullFrameDepth</code> is supposed to contain the depth information, it seems to be an internal alias to the texture <code>_rt_PowerOfTwoFB</code> though. For that reason it may be better to create a new seperate rendertarget instead:
In <code>view.cpp</code> navigate to the function:
<source lang=cpp>void CViewRender::Init( void )</source>
You can add this snippet to quickly allocate a new rendertarget:
<source lang=cpp> 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();</source>
To reference this new texture properly, open <code>rendertexture.cpp</code> and go to:
<source lang=cpp> ITexture *GetFullFrameDepthTexture( void )</source>
By default this function searches for <code>_rt_FullFrameDepth</code>; change this string to read <code>_rt_FullFrameDepth_Alt</code> instead.


[[Category:Material System]]
[[Category:Material System]]
[[Category:Technical]]
[[Category:Technical]]

Revision as of 21:02, 16 January 2011

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 rendertarget (after having being generated in the alpha channel of the full backbuffer). It can be viewed with r_depthoverlay.

Todo: Differences between depth buffer and fog.

Modifying Source's depth buffer

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).

Todo: Confirm the last option

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) or 1 / projectedPosition.w. Otherwise you should use 1 / desired_range to calculate your constant, keep in mind that your interval starts at zNear (default 7).

To extend the range to maximum distance of 1024 units the definition should be changed to this:

#define OO_DESTALPHA_DEPTH_RANGE 0.00098f //(g_LinearFogColor.w)
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.