Rear View Mirror

From Valve Developer Community
Jump to: navigation, search
Rearmirror.jpg

Introduction

This simple tutorial will show you how to get a working Rear View Mirror in your game. Most preferably for a racing mod. This was created on SDK from scratch. It can work on HL2DM SDK just change the folder's referred to as "SDK" with "HL2MP" or what you desire.

Getting started

First it'd be best if you understand what is going on, and not just copy-paste what's written. The camera view you have is part of the CViewSetup class, this defines its origin and angles and window size to display on your screen, along with FOV and AspectRatios for the type of display. You want to create a new view facing behind the player and set the window size to fit on the screen nicely. Once you have your view, we want to display it on a RenderTarget (basically just a material we have on a HUD Panel).

Setup the render scene

Open cl_dll/viewrender.h

Add after DrawWorld and before DrawMonitors functions declarations.

void			DrawMirror( const CViewSetup &cameraView );

Open cl_dll/view.cpp

Add after CViewRender::DrawMonitors

void CViewRender::DrawMirror( const CViewSetup &viewSet )
{
	C_BasePlayer *localPlayer = C_BasePlayer::GetLocalPlayer();
	if(!localPlayer)
		return;

	//Copy our current View.
	CViewSetup mirrorView = viewSet;

	//Get our camera render target.
	ITexture *pRenderTarget = GetCameraTexture();

	//Fog data, enables for this view, and reverts back to default.
	fogparams_t oldFogParams;
	float flOldZFar = 0.0f;
	bool fogEnabled = true;
	if ( fogEnabled )
	{	
		oldFogParams = localPlayer->m_Local.m_fog;
		flOldZFar = mirrorView.zFar;
		localPlayer->m_Local.m_fog.enable = true;
		localPlayer->m_Local.m_fog.start = 2048;
		localPlayer->m_Local.m_fog.end = 4096;
		localPlayer->m_Local.m_fog.farz = 4096;
		localPlayer->m_Local.m_fog.colorPrimary.SetR( 0 );
		localPlayer->m_Local.m_fog.colorPrimary.SetG( 0 );
		localPlayer->m_Local.m_fog.colorPrimary.SetB( 0 );
		mirrorView.zFar = 4096;
	}

	//Our view information, Origin, View Direction, window size
	//	location on material, and visual ratios.
	mirrorView.width = 256;
	mirrorView.height = 128;
	mirrorView.x = 0;
	mirrorView.y = 0;
	mirrorView.origin = localPlayer->GetAbsOrigin() + Vector( 0, 0, 50);
	mirrorView.angles = localPlayer->GetRenderAngles();
	mirrorView.angles.x += 30;
	mirrorView.angles.y = AngleNormalize( mirrorView.angles.y + 180 );
	mirrorView.fov = localPlayer->GetFOV();
	mirrorView.m_bOrtho = false;
	mirrorView.m_flAspectRatio = 1.0f;

	//Set the view up and output the scene to our RenderTarget (Camera Material).
 	render->Push3DView( mirrorView, VIEW_CLEAR_DEPTH | VIEW_CLEAR_COLOR, true, pRenderTarget, m_Frustum );
	ViewDrawScene( false, true, mirrorView, 0, VIEW_MONITOR );
 	render->PopView( m_Frustum );

	// Reset the world fog parameters.
	if ( fogEnabled )
	{
		localPlayer->m_Local.m_fog = oldFogParams;
		mirrorView.zFar = flOldZFar;
	}
}

This is the core function that does the view stuff. It copies our current Camera View of our screen, and changes its member variables to rotate the camera and fit it on a small area. Then you send it to the engine to render our entire scene facing the angles we defined and origin specified. Notice the 30 added to the angle's X value to rotate the camera downward. You could play with different angles and origins for different results. However, if you stray away from your player, you will have to add PVS locations through the server to render the entities not seen at your location.

Next let the render function run this along side the main view.

Open cl_dll/view_scene.cpp Find CViewRender::RenderViewEx and inside the function add after the DrawMonitor code block section.

if(g_pMaterialSystemHardwareConfig->GetDXSupportLevel() >= 70 )
{
	DrawMirror( view );
}

This technology requires DirectX 7.0 so you have to add the check in for it.

Setting up the HUD

Now you create a simple HUD class to display the scene we created.

Create a new file cl_dll/sdk/sdk_hud_mirror.cpp

Add it to your solution preferably in the SDK subsection folder of the client solution.

#include "cbase.h"
#include "hudelement.h"
#include "iclientmode.h"
#include <vgui_controls/ImagePanel.h>

using namespace vgui;

// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"

//.vmt location + filename
#define IMAGE_MIRROR "mirror/mirror"

//-----------------------------------------------------------------------------
// Purpose: HUD Mirror panel
//-----------------------------------------------------------------------------
class CHudMirror : public CHudElement, public vgui::Panel
{
	DECLARE_CLASS_SIMPLE( CHudMirror, vgui::Panel );

public:
	CHudMirror( const char *pElementName );

protected: 
	virtual void Paint();
	
private:
	vgui::ImagePanel *m_Mirror;
};	

extern CHud gHUD;

DECLARE_HUDELEMENT( CHudMirror );

//-----------------------------------------------------------------------------
// Purpose: Constructor
//-----------------------------------------------------------------------------
CHudMirror::CHudMirror( const char *pElementName ) : CHudElement( pElementName ), vgui::Panel(NULL, "HudMirror")
{
	vgui::Panel *pParent = g_pClientMode->GetViewport();
	SetParent( pParent );	//Our parent is the screen itself.	

	m_Mirror = new ImagePanel(this, "RearViewMirror");
	m_Mirror->SetImage( IMAGE_MIRROR );

	SetPaintBackgroundEnabled(false);
}

//-----------------------------------------------------------------------------
// Purpose: Paint
//-----------------------------------------------------------------------------
void CHudMirror::Paint() 
{
        //Set position Top Right corner
	SetPos( ScreenWidth() - 270 , 25 );

        //Set Mirror to 256x128 pixels
	m_Mirror->SetSize( 256, 128 );
	m_Mirror->SetVisible( true );
}

This class simply defines a HUD class called "HudMirror" and creates an ImagePanel with image "mirror/mirror.vmt" which has the "_rt_Camera" we need to display our scene. The Paint() function sets the location and size to display our mirror!

Next Open up MOD/scripts/HudLayout.res

Add this at the top.

HudMirror
{
	"wide"	"256"
	"tall"  "128"
	"PaintBackgroundType"	"1"
}

This is the bare minimum needed for a HUD, you can do everything else in the HUD class.

Adding mirror material

You need to create a simple mirror.vmt file so you can use in your HUD.

Create mirror.vmt in your MOD folder @ materials/VGUI/mirror/mirror.vmt

Add this inside.

"UnlitGeneric"
{
	"$basetexture" "_rt_Camera"
}

There are ways of adding a texture that overlays on what your camera displays, it's used for TV scan lines and other types of effects, maybe a dirty mirror?

In that case you use this instead of what is above.

"Unlittwotexture"
{
	"$basetexture" "_rt_Camera"
	"$texture2"	"dev/dev_combinescanline"
	"$additive" "1"
}

However since you're dealing with HUDs, it is much wiser to create a different ImagePanel and manually overlap it onto your Mirror panel to create the desired effect.

Conclusion

If you noticed the render function DrawMirror is almost an exact copy of DrawOneMonitor inside view.cpp, however that uses the camera entities to display their location. One thing that still require clarification was how it can have multiple monitors and display it to the correct material. For a guide on setting up multiple render targets, see this tutorial.

There could be better ways of doing this, research is still in progress and if a method is found this page will be updated. Don't be shy on editing what exists, this just gives you the barebones of what's needed to get it to work properly, it's up to you to masterfully manipulate it!

Notes:: In the newer SDK (at least the beta) the functions Push3DView() and ViewDrawScene() have parameters that differ from those used in this tutorial. Also view_scene.cpp is no longer used for rendering so the function calling your view should be moved.