Adding a Dynamic Scope

From Valve Developer Community
Revision as of 18:59, 17 March 2008 by Millz (talk | contribs)

(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search

Preface

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.

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.


tne_RenderTargets.cpp (new file)

//========= Copyright © 1996-2008, The New Era team, All rights reserved. ============//
//
// Purpose: TNE Render Targets
//
// $NoKeywords: $
//=============================================================================//
#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)

//========= Copyright © 1996-2005, The New Era team, All rights reserved. ============//
//
// Purpose: TNE Render Targets
//
// $NoKeywords: $
//=============================================================================//

#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 its CBaseClientRenderTargets.


rendertexture.cpp

//=============================================================================
// 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 out Render Target Texture;


//Release the scope render target too
s_pScopeTexture.Shutdown();[/pre][/code]

Add this code to void ReleaseRenderTargets( void ). It handles the removal of render target.


rendertexture.h

ITexture *GetScopeTexture( void );[/pre][/code]

Add this line after GetCameraTexture.


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.origin = localPlayer->EyePosition();
	scopeView.angles = localPlayer->GetAbsAngles();
	scopeView.fov = localPlayer->GetActiveWeapon()->GetZoomFOV();
	scopeView.m_bOrtho = false;

	scopeView.m_flAspectRatio = 1.0f;

	int nClearFlags = VIEW_CLEAR_DEPTH | VIEW_CLEAR_COLOR;
	bool bDrew3dSkybox = true;
	bool bSkyboxVisible = true;

	//Set the view up and output the scene to our RenderTarget (Scope Material).
 	render->Push3DView( mirrorView, VIEW_CLEAR_DEPTH | VIEW_CLEAR_COLOR, true, pRenderTarget, m_Frustum );
	Draw3dSkyboxworld(mirrorView, nClearFlags, bDrew3dSkybox, bSkyboxVisible);
	ViewDrawScene( true, true, mirrorView, 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.


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


The .vmt file

Created a new .vmt file that will setup our texture and allow it to be attached to a gun. Put inside:

"UnlitTwoTexture"
{
	"$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!

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): ctftestroom0004iz7.jpg


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