Adding a Dynamic Scope
This article will deal with creating a dynamic scope, which uses a render target on the weapon model to draw the player's view. This code was created and run on the Source 2006 source code, but it should work in other versions too.
Contents
The Code
Firstly, create two new files - in the code they are called tne_RenderTargets.cpp and tne_RenderTargets.h. Add them to the client project. They deal with the custom render targets interface. Moreover, baseclientrendertargets.cpp needs to be added (and subsequently the header of it) to the client project in order to avoid unresolved externals. Although, other versions of SDK might already have it in.
Here's what should be pasted in each, with proper explanation.
tne_RenderTargets.cpp (new file)
#include "cbase.h"
#include "tne_RenderTargets.h"
#include "materialsystem\imaterialsystem.h"
#include "rendertexture.h"
ITexture* CTNERenderTargets::CreateScopeTexture( IMaterialSystem* pMaterialSystem )
{
// DevMsg("Creating Scope Render Target: _rt_Scope\n");
return pMaterialSystem->CreateNamedRenderTargetTextureEx2(
"_rt_Scope",
1024, 1024, RT_SIZE_OFFSCREEN,
pMaterialSystem->GetBackBufferFormat(),
MATERIAL_RT_DEPTH_SHARED,
TEXTUREFLAGS_CLAMPS | TEXTUREFLAGS_CLAMPT,
CREATERENDERTARGETFLAGS_HDR );
}
//-----------------------------------------------------------------------------
// Purpose: Called by the engine in material system init and shutdown.
// Clients should override this in their inherited version, but the base
// is to init all standard render targets for use.
// Input : pMaterialSystem - the engine's material system (our singleton is not yet inited at the time this is called)
// pHardwareConfig - the user hardware config, useful for conditional render target setup
//-----------------------------------------------------------------------------
void CTNERenderTargets::InitClientRenderTargets( IMaterialSystem* pMaterialSystem, IMaterialSystemHardwareConfig* pHardwareConfig )
{
m_ScopeTexture.Init( CreateScopeTexture( pMaterialSystem ) );
// Water effects & camera from the base class (standard HL2 targets)
BaseClass::InitClientRenderTargets( pMaterialSystem, pHardwareConfig );
}
//-----------------------------------------------------------------------------
// Purpose: Shut down each CTextureReference we created in InitClientRenderTargets.
// Called by the engine in material system shutdown.
// Input : -
//-----------------------------------------------------------------------------
void CTNERenderTargets::ShutdownClientRenderTargets()
{
m_ScopeTexture.Shutdown();
// Clean up standard HL2 RTs (camera and water)
BaseClass::ShutdownClientRenderTargets();
}
//add the interface!
static CTNERenderTargets g_TNERenderTargets;
EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CTNERenderTargets, IClientRenderTargets, CLIENTRENDERTARGETS_INTERFACE_VERSION, g_TNERenderTargets );
CTNERenderTargets* TNERenderTargets = &g_TNERenderTargets;
This class derives from CBaseClientRenderTargets
, an interface made by Valve. CreateScopeTexture()
function is called by the engine in InitClientRenderTargets()
, in order to Init m_ScopeTexture
(our scope render target texture) and it is also released in ShutdownClientRenderTargets()
.
tne_RenderTargets.h (new file)
#ifndef TNERENDERTARGETS_H_
#define TNERENDERTARGETS_H_
#ifdef _WIN32
#pragma once
#endif
#include "baseclientrendertargets.h" // Base class, with interfaces called by engine and inherited members to init common render targets
// externs
class IMaterialSystem;
class IMaterialSystemHardwareConfig;
class CTNERenderTargets : public CBaseClientRenderTargets
{
// no networked vars
DECLARE_CLASS_GAMEROOT( CTNERenderTargets, CBaseClientRenderTargets );
public:
virtual void InitClientRenderTargets( IMaterialSystem* pMaterialSystem, IMaterialSystemHardwareConfig* pHardwareConfig );
virtual void ShutdownClientRenderTargets();
ITexture* CreateScopeTexture( IMaterialSystem* pMaterialSystem );
private:
CTextureReference m_ScopeTexture;
};
extern CTNERenderTargets* TNERenderTargets;
#endif //TNERENDERTARGETS_H_
This is the header file, it's pretty self-explanatory - it overrides virtual functions from CBaseClientRenderTargets
.
rendertexture.cpp
//=============================================================================
// Dynamic Scope Texture
//=============================================================================
static CTextureReference s_pScopeTexture;
ITexture *GetScopeTexture( void )
{
if ( !s_pScopeTexture )
{
s_pScopeTexture.Init( materials->FindTexture( "_rt_Scope", TEXTURE_GROUP_RENDER_TARGET ) );
Assert( !IsErrorTexture( s_pScopeTexture ) );
AddReleaseFunc();
}
return s_pScopeTexture;
}
Put this somewhere near GetCameraTexture
function. It provides as a function which finds & returns Render Target Texture;
//Release the scope render target too
s_pScopeTexture.Shutdown();
Add this code to void ReleaseRenderTargets( void )
. It handles the removal of render target.
rendertexture.h
Add this line after ITexture *GetCameraTexture();
:
//Dynamic Scope Texture
ITexture *GetScopeTexture();
view.cpp
void CViewRender::DrawScope( const CViewSetup &viewSet )
{
C_BasePlayer *localPlayer = C_BasePlayer::GetLocalPlayer();
if(!localPlayer)
return;
if( !localPlayer->GetActiveWeapon() )
return;
if( !localPlayer->GetActiveWeapon()->GetViewModel() )
return;
//Copy our current View.
CViewSetup scopeView = viewSet;
//Get our camera render target.
ITexture *pRenderTarget = GetScopeTexture();
if( pRenderTarget == NULL )
return;
if( !pRenderTarget->IsRenderTarget() )
Msg(" not a render target");
//Our view information, Origin, View Direction, window size
// location on material, and visual ratios.
scopeView.width = pRenderTarget->GetActualWidth();
scopeView.height = pRenderTarget->GetActualHeight();
scopeView.x = 0;
scopeView.y = 0;
scopeView.fov = localPlayer->GetActiveWeapon()->GetZoomFOV();
scopeView.m_bOrtho = false;
scopeView.m_flAspectRatio = 1.0f;
int nClearFlags = VIEW_CLEAR_DEPTH | VIEW_CLEAR_COLOR;
bool bDrew3dSkybox = false; // bDrew3dSkybox = true turns the skybox OFF. DO NOT SET IT TO TRUE.
bool bSkyboxVisible = true;
//Set the view up and output the scene to our RenderTarget (Scope Material).
render->Push3DView( scopeView, VIEW_CLEAR_DEPTH | VIEW_CLEAR_COLOR, true, pRenderTarget, m_Frustum );
Draw3dSkyboxworld( scopeView, nClearFlags, bDrew3dSkybox, bSkyboxVisible);
ViewDrawScene( bDrew3dSkybox, bSkyboxVisible, scopeView, 0, VIEW_MONITOR );
render->PopView( m_Frustum );
}
This code is very similar to DrawCamera
one. Basically, it copies the current view, assigns the render target to a texture pointer, then sets some basic options for scopeView like width, height, position, etc. The FOV is set here with reference to a value set in the weapon's script file and be elaborated upon.
viewrender.h
void DrawScope( const CViewSetup &cameraView );
Put this after DrawCamera
.
view_scene.cpp
//Draw the scope too
if(g_pMaterialSystemHardwareConfig->GetDXSupportLevel() >= 70 )
{
DrawScope( view );
}
It draws the scope vision if you have a DirectX 7 capable card (lowers are not supported). Put this code in RenderViewEx()
just after the #ifdef USE_MONITORS #endif
block in the beginning of the function.
Episode 2 Engine Fixes
viewrender.cpp
Place the DrawScope( ... )
function in viewrender.cpp after DrawMonitors( ... )
, instead of in view.cpp.
Change:
int nClearFlags = VIEW_CLEAR_DEPTH | VIEW_CLEAR_COLOR;
bool bDrew3dSkybox = false; // bDrew3dSkybox = true turns the skybox OFF. DO NOT SET IT TO TRUE.
bool bSkyboxVisible = true;
//Set the view up and output the scene to our RenderTarget (Scope Material).
render->Push3DView( scopeView, VIEW_CLEAR_DEPTH | VIEW_CLEAR_COLOR, true, pRenderTarget, m_Frustum );
Draw3dSkyboxworld( scopeView, nClearFlags, bDrew3dSkybox, bSkyboxVisible);
ViewDrawScene( bDrew3dSkybox, bSkyboxVisible, scopeView, 0, VIEW_MONITOR );
render->PopView( m_Frustum );
To:
//Set the view up and output the scene to our RenderTarget (Scope Material).
render->Push3DView( scopeView, VIEW_CLEAR_DEPTH | VIEW_CLEAR_COLOR, pRenderTarget, GetFrustum() );
SkyboxVisibility_t nSkyboxVisible = SKYBOX_NOT_VISIBLE;
int ClearFlags = 0;
CSkyboxView *pSkyView = new CSkyboxView( this );
if( pSkyView->Setup( scopeView, &ClearFlags, &nSkyboxVisible ) != false )
AddViewToScene( pSkyView );
SafeRelease( pSkyView );
ViewDrawScene( false, SKYBOX_3DSKYBOX_VISIBLE, scopeView, VIEW_CLEAR_DEPTH, VIEW_MONITOR );
render->PopView( m_Frustum );
Change:
scopeView.fov = localPlayer->GetActiveWeapon()->GetZoomFOV();
To:
scopeView.fov = 45;
C_BaseCombatWeapon
in order to work, or hardcoded as shown above.Place this in viewrender.cpp at the end of CViewRender::RenderView
if(g_pMaterialSystemHardwareConfig->GetDXSupportLevel() >= 70 )
{
DrawScope( view );
}
Right above
render->PopView( GetFrustum() );
g_WorldListCache.Flush();
instead of view_scene.cpp
Place this in viewrender.h below Drawmonitors
void DrawScope(const CViewSetup &cameraView);
The scopes should now work in any Episode 2 based Game.
Special thanks to omega from hlcoders mailing list, who shared his code and pointed the writer in the right direction.
The .vmt file
Created a new .vmt file that will setup the texture and allow it to be attached to a gun. Put inside:
UnlitGeneric { $basetexture _rt_Scope $model 1 }
It takes the newly created _rt_Scope
texture and sets it up to be useable for texturing models. Then just put the _rt_Scope
texture on your weapon model!
Change dynamic FOV in script
This is for those who do not know how to make different FOV for different weapons.
For example, your sniper rifle has a 9-fold increase (for example the POSP 3-9×42). And ACOG increase, for example, is 4x. This big difference can be corrected by simply adding it to the weapon's script after "default_dynamic_fov". We'll be adding this below.
weapon_parse.cpp
1. After:
iDefaultClip2 = pKeyValuesData->GetInt( "default_clip2", iMaxClip2 ); // amount of secondary ammo placed in the secondary clip when it's picked up
add this:
iDefaultIsFOV = pKeyValuesData->GetInt("default_dynamic_fov", 45.0f ); //changeable FOV in a script
2. - In FileWeaponInfo_t::FileWeaponInfo_t()
after
iDefaultClip2 = 0;
add this:
iDefaultIsFOV = 0; // changeable FOV in a script
weapon_parse.h
After
int iDefaultClip2; // amount of secondary ammo in the gun when it's created
add this:
int iDefaultIsFOV; // changeable FOV in a script
Find:
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CBaseCombatWeapon::UsesClipsForAmmo2( void ) const
{
return ( GetMaxClip2() != WEAPON_NOCLIP );
}
And after this bool
add new int
:
//-----------------------------------------------------------------------------
// Purpose: changing the FOV in a script
//-----------------------------------------------------------------------------
int CBaseCombatWeapon::GetDefaultIsFOV(void) const
{
return GetWpnData().iDefaultIsFOV;
}
After:
virtual int GetDefaultClip2( void ) const;
Add this:
virtual int GetDefaultIsFOV(void) const; // changeable FOV in a script
So, now the game can read some information about "dynamic FOV" weapons in the script. And you can call this information with the help of "GetDefaultIsFOV". Let's do this!
viewrender.cpp
Find void CViewRender::DrawScope(const CViewSetup &viewSet)
and change scopeView.fov = 45;
like this:
scopeView.fov = localPlayer->GetActiveWeapon()->GetDefaultIsFOV(); // changeable FOV in a script
weapon_crossbow.txt
For example, I'll use the crossbow. Just add after "secondary_ammo" "None" "default_dynamic_fov" "15" like this:
...
"primary_ammo" "XBowBolt"
"secondary_ammo" "None"
"default_dynamic_fov" "15" // Без увеличения = 45 (Стандарт). 15 это примерно x4. Не ставить числа с "-" и "0".
...
Conclusion
That's all.