Difference between revisions of "Adding a Dynamic Scope"

From Valve Developer Community
Jump to: navigation, search
m (The Code: Fixed disconnected block of code)
m (rendertexture.h)
 
(34 intermediate revisions by 19 users not shown)
Line 1: Line 1:
== Preface ==
+
{{otherlang2
This article will deal with creating a dynamic scope, which uses a render target on weapon model to draw current players view. This code was created and run on MP EP1 source code, however it should work in other versions too.
+
|zh-cn=:Adding a Dynamic Scope:zh-cn
 +
}}
 +
[[File:Dyscope rest.jpg|right|300px]]
 +
[[File:Dyscope aim.jpg|right|300px]]
 +
 
 +
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.
 +
 
 +
{{note|1=There can apparently only be one render target on a model at a time. If you try to use this code and overlay it with another effect (e.g. refraction to simulate a lens), [https://web.archive.org/web/20170524140828/http://forums.steampowered.com/forums/showthread.php?t=1290970 only one of the targets will be drawn].}}
 +
 
 +
{{note|1=The modified crossbow model as seen on the right is available for [https://gamebanana.com/castaways/2232 download here]}}
  
 
== The Code ==
 
== The Code ==
Firstly, create two new files - in my code they are called tne_RenderTargets.cpp and tne_RenderTargets.h and add them to cl_dll project. They deal with our custom render targets interface. Moreover, I needed to add baseclientrendertargts.cpp (and subsequently the header of it) to cl_dll project in order to avoid unresolved externals. Although, other versions of SDK might already have it in.
 
Here's what you should paste in each, with proper explanation.
 
  
 +
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.
  
<big>'''tne_RenderTargets.cpp (new file)'''</big>
+
===tne_RenderTargets.cpp (new file)===
//========= Copyright © 1996-2008, The New Era team, All rights reserved. ============//
+
 
//
+
<source lang=cpp>
// Purpose: TNE Render Targets
+
#include "cbase.h"
//
+
#include "tne_RenderTargets.h"
// $NoKeywords: $
+
#include "materialsystem\imaterialsystem.h"
//=============================================================================//
+
#include "rendertexture.h"
#include "cbase.h"
+
#include "tne_RenderTargets.h"
+
ITexture* CTNERenderTargets::CreateScopeTexture( IMaterialSystem* pMaterialSystem )
#include "materialsystem\imaterialsystem.h"
+
{
#include "rendertexture.h"
+
// 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 );
 
    
 
    
ITexture* CTNERenderTargets::CreateScopeTexture( IMaterialSystem* pMaterialSystem )
+
}
{
+
 
// DevMsg("Creating Scope Render Target: _rt_Scope\n");
+
//-----------------------------------------------------------------------------
return pMaterialSystem->CreateNamedRenderTargetTextureEx2(
+
// Purpose: Called by the engine in material system init and shutdown.
"_rt_Scope",
+
// Clients should override this in their inherited version, but the base
1024, 1024, RT_SIZE_OFFSCREEN,
+
// is to init all standard render targets for use.
pMaterialSystem->GetBackBufferFormat(),
+
// Input  : pMaterialSystem - the engine's material system (our singleton is not yet inited at the time this is called)
MATERIAL_RT_DEPTH_SHARED,
+
// pHardwareConfig - the user hardware config, useful for conditional render target setup
TEXTUREFLAGS_CLAMPS | TEXTUREFLAGS_CLAMPT,
+
//-----------------------------------------------------------------------------
CREATERENDERTARGETFLAGS_HDR );
+
void CTNERenderTargets::InitClientRenderTargets( IMaterialSystem* pMaterialSystem, IMaterialSystemHardwareConfig* pHardwareConfig )
 
+
{  
}
+
m_ScopeTexture.Init( CreateScopeTexture( pMaterialSystem ) );  
+
 
//-----------------------------------------------------------------------------
+
// Water effects & camera from the base class (standard HL2 targets)
// Purpose: Called by the engine in material system init and shutdown.
+
BaseClass::InitClientRenderTargets( pMaterialSystem, pHardwareConfig );
// 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
+
// Purpose: Shut down each CTextureReference we created in InitClientRenderTargets.
//-----------------------------------------------------------------------------
+
// Called by the engine in material system shutdown.
void CTNERenderTargets::InitClientRenderTargets( IMaterialSystem* pMaterialSystem, IMaterialSystemHardwareConfig* pHardwareConfig )
+
// Input  :  -
{  
+
//-----------------------------------------------------------------------------
m_ScopeTexture.Init( CreateScopeTexture( pMaterialSystem ) );  
+
void CTNERenderTargets::ShutdownClientRenderTargets()
 +
{
 +
m_ScopeTexture.Shutdown();
 +
 
 +
// Clean up standard HL2 RTs (camera and water)
 +
BaseClass::ShutdownClientRenderTargets();
 +
}
 
   
 
   
// Water effects & camera from the base class (standard HL2 targets)
+
//add the interface!
BaseClass::InitClientRenderTargets( pMaterialSystem, pHardwareConfig );
+
static CTNERenderTargets g_TNERenderTargets;
}
+
EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CTNERenderTargets, IClientRenderTargets, CLIENTRENDERTARGETS_INTERFACE_VERSION, g_TNERenderTargets  );
 
+
CTNERenderTargets* TNERenderTargets = &g_TNERenderTargets;
//-----------------------------------------------------------------------------
+
</source>
// 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().
+
This class derives from <code>CBaseClientRenderTargets</code>, an interface made by Valve. <code>CreateScopeTexture()</code> function is called by the engine in <code>InitClientRenderTargets()</code>, in order to Init <code>m_ScopeTexture</code> (our scope render target texture) and it is also released in <code>ShutdownClientRenderTargets()</code>.
  
 +
===tne_RenderTargets.h (new file)===
  
 +
<source lang=cpp>
 +
#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 );
  
<big>'''tne_RenderTargets.h (new file)'''</big>
+
private:
//========= Copyright © 1996-2008, The New Era team, All rights reserved. ============//
+
CTextureReference m_ScopeTexture;
//
+
};
// Purpose: TNE Render Targets
 
//
 
// $NoKeywords: $
 
//=============================================================================//
 
 
   
 
   
#ifndef TNERENDERTARGETS_H_
+
extern CTNERenderTargets* TNERenderTargets;
#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:
+
#endif //TNERENDERTARGETS_H_
CTextureReference m_ScopeTexture;  
+
</source>
};
+
 
 
+
This is the header file, it's pretty self-explanatory - it overrides virtual functions from <code>CBaseClientRenderTargets</code>.
extern CTNERenderTargets* TNERenderTargets;
+
 
 
+
===rendertexture.cpp===
#endif //TNERENDERTARGETS_H_
+
 
 +
<source lang=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;
 +
}
 +
</source>
 +
 
 +
Put this somewhere near <code>GetCameraTexture</code> function. It provides as a function which finds & returns Render Target Texture;
 +
 
 +
<source lang=cpp>
 +
//Release the scope render target too
 +
s_pScopeTexture.Shutdown();
 +
</source>
 +
 
 +
Add this code to void <code>ReleaseRenderTargets( void )</code>. It handles the removal of render target.
 +
 
 +
===rendertexture.h===
 +
 
 +
Add this line after <code>ITexture *GetCameraTexture();</code>:
 +
<source lang=cpp>
 +
//Dynamic Scope Texture
 +
ITexture *GetScopeTexture();
 +
</source>
 +
 
 +
===view.cpp===
 +
 
 +
<source lang=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();
  
This is the header file, it's pretty self-explanatory - it overrides virtual functions from its CBaseClientRenderTargets.
+
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;
  
<big>'''rendertexture.cpp'''</big>
+
scopeView.m_flAspectRatio = 1.0f;
//=============================================================================
 
// 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;
+
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 );
 
 
//Release the scope render target too
+
render->PopView( m_Frustum );
s_pScopeTexture.Shutdown();
+
}
 +
</source>
  
Add this code to void ReleaseRenderTargets( void ). It handles the removal of render target.
+
This code is very similar to <code>DrawCamera</code> 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===
  
 +
<source lang=cpp>
 +
void DrawScope( const CViewSetup &cameraView );
 +
</source>
  
<big>'''rendertexture.h'''</big>
+
Put this after <code>DrawCamera</code>.
ITexture *GetScopeTexture( void );
 
  
Add this line after GetCameraTexture.
+
===view_scene.cpp===
  
 +
<source lang=cpp>
 +
//Draw the scope too
 +
if(g_pMaterialSystemHardwareConfig->GetDXSupportLevel() >= 70 )
 +
{
 +
DrawScope( view );
 +
}
 +
</source>
  
 +
It draws the scope vision if you have a DirectX 7 capable card (lowers are not supported). Put this code in <code>RenderViewEx()</code> just after the <code>#ifdef USE_MONITORS #endif</code> block in the beginning of the function.
  
<big>'''view.cpp'''</big>
+
== Episode 2 Engine Fixes ==
void CViewRender::DrawScope( const CViewSetup &viewSet )
+
 
{
+
=== viewrender.cpp ===
C_BasePlayer *localPlayer = C_BasePlayer::GetLocalPlayer();
+
Place the <code>DrawScope( ... )</code> function in '''viewrender.cpp''' after <code>DrawMonitors( ... )</code>, instead of in '''view.cpp'''.
+
 
if(!localPlayer)
+
Change:
return;
+
<source lang=cpp>
+
int nClearFlags = VIEW_CLEAR_DEPTH | VIEW_CLEAR_COLOR;
if( !localPlayer->GetActiveWeapon() )
+
bool bDrew3dSkybox = false; // bDrew3dSkybox = true turns the skybox OFF. DO NOT SET IT TO TRUE.
return;
+
bool bSkyboxVisible = true;
+
 
if( !localPlayer->GetActiveWeapon()->GetViewModel() )
+
//Set the view up and output the scene to our RenderTarget (Scope Material).
return;
+
render->Push3DView( scopeView, VIEW_CLEAR_DEPTH | VIEW_CLEAR_COLOR, true, pRenderTarget, m_Frustum );
+
Draw3dSkyboxworld( scopeView, nClearFlags, bDrew3dSkybox, bSkyboxVisible);
//Copy our current View.
+
ViewDrawScene( bDrew3dSkybox, bSkyboxVisible, scopeView, 0, VIEW_MONITOR );
CViewSetup scopeView = viewSet;
+
 
+
render->PopView( m_Frustum );
//Get our camera render target.
+
</source>
ITexture *pRenderTarget = GetScopeTexture();
+
 
+
To:
if( pRenderTarget == NULL )
+
<source lang=cpp>
return;
+
//Set the view up and output the scene to our RenderTarget (Scope Material).
+
render->Push3DView( scopeView, VIEW_CLEAR_DEPTH | VIEW_CLEAR_COLOR, pRenderTarget, GetFrustum() );
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 );
 
}
 
  
As you can see this code is very similar to DrawCamera one. Basically, it copies our current view, assigns our 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 weapon script file and it won't be elaborated in here, unless someone will ask me to show it also.
+
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 );
 +
</source>
  
 +
Change:
 +
<source lang=cpp>
 +
scopeView.fov = localPlayer->GetActiveWeapon()->GetZoomFOV();
 +
</source>
  
<big>'''viewrender.h'''</big>
+
To:
void DrawScope( const CViewSetup &cameraView );
+
<source lang=cpp>
 +
scopeView.fov = 45;
 +
</source>
  
Put this after DrawCamera.
+
{{note|GetZoomFOV() isn't a default float function. It needs to be added to <code>C_BaseCombatWeapon</code> in order to work, or hardcoded as shown above.}}
  
 +
Place this in '''viewrender.cpp''' at the end of <code>CViewRender::RenderView</code>
 +
<source lang=cpp>
 +
if(g_pMaterialSystemHardwareConfig->GetDXSupportLevel() >= 70 )
 +
{
 +
DrawScope( view );
 +
}
 +
</source>
  
 +
Right above
 +
<source lang=cpp>
 +
render->PopView( GetFrustum() );
 +
g_WorldListCache.Flush();
 +
</source>
 +
instead of '''view_scene.cpp'''
  
<big>'''view_scene.cpp'''</big>
+
The scopes should now work in any Episode 2 based Game.
//Draw the scope too
 
if(g_pMaterialSystemHardwareConfig->GetDXSupportLevel() >= 70 )
 
{
 
DrawScope( view );
 
}
 
  
It draws our scope vision if you have a DirectX 7 capable card (lowers are not supported). Put this code in RenderViewEx() just after ''#ifdef USE_MONITORS #endif'' block in the beginning of the function.
+
Special thanks to [[User:Omega|omega]] from hlcoders mailing list, who shared his code and pointed the writer in the right direction.
  
 
== The .vmt file ==
 
== The .vmt file ==
Created a new .vmt file that will setup our texture and allow it to be attached to a gun. Put inside:
+
Created a new .vmt file that will setup the texture and allow it to be attached to a gun. Put inside:
 +
<pre>
 +
UnlitGeneric
 +
{
 +
$basetexture _rt_Scope
 +
$model 1
 +
}
 +
</pre>
 +
 
 +
It takes the newly created <code>_rt_Scope</code> texture and sets it up to be useable for texturing models. Then just put the <code>_rt_Scope</code> 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:
 +
<source lang=cpp>
 +
iDefaultClip2 = pKeyValuesData->GetInt( "default_clip2", iMaxClip2 ); // amount of secondary ammo placed in the secondary clip when it's picked up
 +
</source>
 +
 
 +
 
 +
add this:
 +
<source lang=cpp>
 +
iDefaultIsFOV = pKeyValuesData->GetInt("default_dynamic_fov", 45.0f ); // изменяем FOV в скрипте
 +
</source>
 +
{{note|45.0f - this is the default value. It's very important value! 45 it's like 1x. For example 15 it's like 4x.}}
 +
 
 +
 
 +
2. - In <code>FileWeaponInfo_t::FileWeaponInfo_t()</code> after
 +
<source lang=cpp>
 +
iDefaultClip2 = 0;
 +
</source>
 +
 
 +
 
 +
add this:
 +
<source lang=cpp>
 +
iDefaultIsFOV = 0; // изменяем FOV в скрипте
 +
</source>
 +
 
 +
 
 +
=== weapon_parse.h ===
 +
After
 +
<source lang=cpp>
 +
int iDefaultClip2; // amount of secondary ammo in the gun when it's created
 +
</source>
 +
 
 +
 
 +
add this:
 +
<source lang=cpp>
 +
int iDefaultIsFOV; // изменяем FOV в скрипте
 +
</source>
 +
 
 +
 
 +
=== basecombatweapon_shared.cpp ===
 +
Find:
 +
<source lang=cpp>
 +
//-----------------------------------------------------------------------------
 +
// Purpose:
 +
//-----------------------------------------------------------------------------
 +
bool CBaseCombatWeapon::UsesClipsForAmmo2( void ) const
 +
{
 +
return ( GetMaxClip2() != WEAPON_NOCLIP );
 +
}
 +
</source>
 +
 
 +
 
 +
And after this <code>bool</code> add new <code>int</code>:
 +
<source lang=cpp>
 +
//-----------------------------------------------------------------------------
 +
// Purpose:  Изменение FOV в скрипте
 +
//-----------------------------------------------------------------------------
 +
int CBaseCombatWeapon::GetDefaultIsFOV(void) const
 +
{
 +
return GetWpnData().iDefaultIsFOV;
 +
}
 +
</source>
 +
 
 +
 
 +
=== basecombatweapon_shared.h ===
 +
After:
 +
<source lang=cpp>
 +
virtual int GetDefaultClip2( void ) const;
 +
</source>
 +
 
 +
Add this:
 +
<source lang=cpp>
 +
virtual int GetDefaultIsFOV(void) const; // изменяем FOV в скрипте
 +
</source>
 +
 
 +
 
 +
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!
 +
 
  
"UnlitGeneric"
 
{
 
"$basetexture" "_rt_Scope"
 
"$model" "1"
 
}
 
  
It takes our code-created _rt_Scope texture and sets it up to be useable for texturing models. Then you would just put the _rt_Scope texture on your weapon model and voilla!
+
=== viewrender.cpp ===
 +
 
 +
Find <code>void CViewRender::DrawScope(const CViewSetup &viewSet)</code> and change <code>scopeView.fov = 45;</code> like this:
 +
<source lang=cpp>
 +
scopeView.fov = localPlayer->GetActiveWeapon()->GetDefaultIsFOV(); // изменение FOV в скрипте
 +
</source>
 +
 
 +
 
 +
=== weapon_crossbow.txt ===
 +
For example, I'll use the crossbow. Just add after '''"secondary_ammo" "None"''' '''"default_dynamic_fov" "15"''' like this:
 +
 
 +
<source lang=cpp>
 +
...
 +
"primary_ammo" "XBowBolt"
 +
"secondary_ammo" "None"
 +
 
 +
"default_dynamic_fov" "15" // Без увеличения = 45 (Стандарт). 15 это примерно x4. Не ставить числа с "-" и "0".
 +
...
 +
</source>
  
 
== Conclusion ==
 
== Conclusion ==
If you followed the tutorial well, you should get something like that (note my code also uses the Ironsights http://developer.valvesoftware.com/wiki/Ironsights):
 
http://img397.imageshack.us/img397/9823/dmdeathstar0000pv8.jpg
 
  
 +
That's all.
 +
 +
{{note|'''"default_dynamic_fov"''' must be over '''0'''. If you will use '''"-"''' image will be turned. 1x becomes 45, 4x becomes 15 (about).}}
 +
 +
--[[User:Slam12f|Slam12f]] ([[User talk:Slam12f|talk]]) 03:37, 22 October 2015 (UTC)
  
If you find any problems with this code, please let me know so I can fix it.
 
  
Special thanks to '''omega''' from hlcoders mailing list, who shared his code and pointed me into right direction ;)
+
[[Category:Weapons programming]]
 +
[[Category:Tutorials]]

Latest revision as of 04:36, 25 May 2021

简体中文
Dyscope rest.jpg
Dyscope aim.jpg

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.

Note.png Note: There can apparently only be one render target on a model at a time. If you try to use this code and overlay it with another effect (e.g. refraction to simulate a lens), only one of the targets will be drawn.
Note.png Note: The modified crossbow model as seen on the right is available for download here

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;
Note.png Note: GetZoomFOV() isn't a default float function. It needs to be added to 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

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 ); // изменяем FOV в скрипте
Note.png Note: 45.0f - this is the default value. It's very important value! 45 it's like 1x. For example 15 it's like 4x.


2. - In FileWeaponInfo_t::FileWeaponInfo_t() after

iDefaultClip2 = 0;


add this:

iDefaultIsFOV = 0; // изменяем FOV в скрипте


weapon_parse.h

After

int iDefaultClip2; // amount of secondary ammo in the gun when it's created


add this:

int iDefaultIsFOV; // изменяем FOV в скрипте


basecombatweapon_shared.cpp

Find:

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
bool CBaseCombatWeapon::UsesClipsForAmmo2( void ) const
{
	return ( GetMaxClip2() != WEAPON_NOCLIP );
}


And after this bool add new int:

//-----------------------------------------------------------------------------
// Purpose:  Изменение FOV в скрипте
//-----------------------------------------------------------------------------
int CBaseCombatWeapon::GetDefaultIsFOV(void) const
{
	return GetWpnData().iDefaultIsFOV;
}


basecombatweapon_shared.h

After:

	virtual int GetDefaultClip2( void ) const;

Add this:

	virtual int GetDefaultIsFOV(void) const; // изменяем FOV в скрипте


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(); // изменение FOV в скрипте


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.

Note.png Note: "default_dynamic_fov" must be over 0. If you will use "-" image will be turned. 1x becomes 45, 4x becomes 15 (about).

--Slam12f (talk) 03:37, 22 October 2015 (UTC)