1
0
forked from 0ad/0ad
0ad/source/renderer/Renderer.cpp
pyrolink f174796540 Added fresnel effect for water.
This was SVN commit r3194.
2005-12-06 06:42:49 +00:00

1818 lines
49 KiB
C++
Executable File

///////////////////////////////////////////////////////////////////////////////
//
// Name: Renderer.cpp
// Author: Rich Cross
// Contact: rich@wildfiregames.com
//
// Description: OpenGL renderer class; a higher level interface
// on top of OpenGL to handle rendering the basic visual games
// types - terrain, models, sprites, particles etc
//
///////////////////////////////////////////////////////////////////////////////
#include "precompiled.h"
#include <map>
#include <set>
#include <algorithm>
#include "Renderer.h"
#include "Terrain.h"
#include "Matrix3D.h"
#include "MathUtil.h"
#include "Camera.h"
#include "PatchRData.h"
#include "Texture.h"
#include "LightEnv.h"
#include "Terrain.h"
#include "CLogger.h"
#include "ps/Game.h"
#include "Profile.h"
#include "Game.h"
#include "World.h"
#include "Player.h"
#include "LOSManager.h"
#include "Model.h"
#include "ModelDef.h"
#include "ogl.h"
#include "lib/res/res.h"
#include "lib/res/file/file.h"
#include "lib/res/graphics/tex.h"
#include "lib/res/graphics/ogl_tex.h"
#include "ps/Loader.h"
#include "ps/ProfileViewer.h"
#include "renderer/FixedFunctionModelRenderer.h"
#include "renderer/HWLightingModelRenderer.h"
#include "renderer/InstancingModelRenderer.h"
#include "renderer/ModelRenderer.h"
#include "renderer/PlayerRenderer.h"
#include "renderer/RenderModifiers.h"
#include "renderer/RenderPathVertexShader.h"
#include "renderer/TransparencyRenderer.h"
#define LOG_CATEGORY "graphics"
///////////////////////////////////////////////////////////////////////////////////
// CRendererStatsTable - Profile display of rendering stats
/**
* Class CRendererStatsTable: Implementation of AbstractProfileTable to
* display the renderer stats in-game.
*
* Accesses CRenderer::m_Stats by keeping the reference passed to the
* constructor.
*/
class CRendererStatsTable : public AbstractProfileTable
{
public:
CRendererStatsTable(const CRenderer::Stats& st);
// Implementation of AbstractProfileTable interface
CStr GetName();
CStr GetTitle();
uint GetNumberRows();
const std::vector<ProfileColumn>& GetColumns();
CStr GetCellText(uint row, uint col);
AbstractProfileTable* GetChild(uint row);
private:
/// Reference to the renderer singleton's stats
const CRenderer::Stats& Stats;
/// Column descriptions
std::vector<ProfileColumn> columnDescriptions;
enum {
Row_Counter = 0,
Row_DrawCalls,
Row_TerrainTris,
Row_ModelTris,
Row_BlendSplats,
// Must be last to count number of rows
NumberRows
};
};
// Construction
CRendererStatsTable::CRendererStatsTable(const CRenderer::Stats& st)
: Stats(st)
{
columnDescriptions.push_back(ProfileColumn("Name", 230));
columnDescriptions.push_back(ProfileColumn("Value", 100));
}
// Implementation of AbstractProfileTable interface
CStr CRendererStatsTable::GetName()
{
return "renderer";
}
CStr CRendererStatsTable::GetTitle()
{
return "Renderer statistics";
}
uint CRendererStatsTable::GetNumberRows()
{
return NumberRows;
}
const std::vector<ProfileColumn>& CRendererStatsTable::GetColumns()
{
return columnDescriptions;
}
CStr CRendererStatsTable::GetCellText(uint row, uint col)
{
char buf[256];
switch(row)
{
case Row_Counter:
if (col == 0)
return "counter";
snprintf(buf, sizeof(buf), "%d", Stats.m_Counter);
return buf;
case Row_DrawCalls:
if (col == 0)
return "# draw calls";
snprintf(buf, sizeof(buf), "%d", Stats.m_DrawCalls);
return buf;
case Row_TerrainTris:
if (col == 0)
return "# terrain tris";
snprintf(buf, sizeof(buf), "%d", Stats.m_TerrainTris);
return buf;
case Row_ModelTris:
if (col == 0)
return "# model tris";
snprintf(buf, sizeof(buf), "%d", Stats.m_ModelTris);
return buf;
case Row_BlendSplats:
if (col == 0)
return "# blend splats";
snprintf(buf, sizeof(buf), "%d", Stats.m_BlendSplats);
return buf;
default:
return "???";
}
}
AbstractProfileTable* CRendererStatsTable::GetChild(uint UNUSED(row))
{
return 0;
}
///////////////////////////////////////////////////////////////////////////////////
// CRenderer implementation
/**
* Struct CRendererInternals: Truly hide data that is supposed to be hidden
* in this structure so it won't even appear in header files.
*/
struct CRendererInternals
{
/// Table to display renderer stats in-game via profile system
CRendererStatsTable profileTable;
CRendererInternals()
: profileTable(g_Renderer.m_Stats)
{
}
};
///////////////////////////////////////////////////////////////////////////////////
// CRenderer destructor
CRenderer::CRenderer()
{
m = new CRendererInternals;
g_ProfileViewer.AddRootTable(&m->profileTable);
m_Width=0;
m_Height=0;
m_Depth=0;
m_FrameCounter=0;
m_TerrainRenderMode=SOLID;
m_ModelRenderMode=SOLID;
m_ClearColor[0]=m_ClearColor[1]=m_ClearColor[2]=m_ClearColor[3]=0;
m_ShadowMap=0;
m_SortAllTransparent = false;
m_FastNormals = true;
m_VertexShader = 0;
m_Options.m_NoVBO=false;
m_Options.m_Shadows=true;
m_Options.m_ShadowColor=RGBAColor(0.4f,0.4f,0.4f,1.0f);
m_Options.m_RenderPath = RP_DEFAULT;
for (uint i=0;i<MaxTextureUnits;i++) {
m_ActiveTextures[i]=0;
}
// Must query card capabilities before creating renderers that depend
// on card capabilities.
EnumCaps();
m_VertexShader = new RenderPathVertexShader;
if (!m_VertexShader->Init())
{
delete m_VertexShader;
m_VertexShader = 0;
}
// model rendering
m_Models.VertexFF = ModelVertexRendererPtr(new FixedFunctionModelRenderer);
if (HWLightingModelRenderer::IsAvailable())
m_Models.VertexHWLit = ModelVertexRendererPtr(new HWLightingModelRenderer);
if (InstancingModelRenderer::IsAvailable())
m_Models.VertexInstancing = ModelVertexRendererPtr(new InstancingModelRenderer);
m_Models.VertexPolygonSort = ModelVertexRendererPtr(new PolygonSortModelRenderer);
m_Models.NormalFF = new BatchModelRenderer(m_Models.VertexFF);
m_Models.PlayerFF = new BatchModelRenderer(m_Models.VertexFF);
m_Models.TranspFF = new SortModelRenderer(m_Models.VertexFF);
if (m_Models.VertexHWLit)
{
m_Models.NormalHWLit = new BatchModelRenderer(m_Models.VertexHWLit);
m_Models.PlayerHWLit = new BatchModelRenderer(m_Models.VertexHWLit);
m_Models.TranspHWLit = new SortModelRenderer(m_Models.VertexHWLit);
}
else
{
m_Models.NormalHWLit = NULL;
m_Models.PlayerHWLit = NULL;
m_Models.TranspHWLit = NULL;
}
if (m_Models.VertexInstancing)
{
m_Models.NormalInstancing = new BatchModelRenderer(m_Models.VertexInstancing);
m_Models.PlayerInstancing = new BatchModelRenderer(m_Models.VertexInstancing);
}
else
{
m_Models.NormalInstancing = NULL;
m_Models.PlayerInstancing = NULL;
}
m_Models.TranspSortAll = new SortModelRenderer(m_Models.VertexPolygonSort);
m_Models.ModWireframe = RenderModifierPtr(new WireframeRenderModifier);
m_Models.ModPlain = RenderModifierPtr(new PlainRenderModifier);
SetFastPlayerColor(true);
m_Models.ModSolidColor = RenderModifierPtr(new SolidColorRenderModifier);
m_Models.ModTransparent = RenderModifierPtr(new TransparentRenderModifier);
m_Models.ModTransparentShadow = RenderModifierPtr(new TransparentShadowRenderModifier);
// water
m_RenderWater = true;
m_WaterHeight = 5.0f;
m_WaterColor = CColor(0.3f, 0.35f, 0.7f, 1.0f);
m_WaterFullDepth = 4.0f;
m_WaterMaxAlpha = 0.85f;
m_WaterAlphaOffset = -0.05f;
m_SWaterTrans=0;
m_TWaterTrans=0;
m_SWaterSpeed=0.0015f;
m_TWaterSpeed=0.0015f;
m_SWaterScrollCounter=0;
m_TWaterScrollCounter=0;
m_WaterCurrentTex=0;
cur_loading_water_tex = 0;
ONCE( ScriptingInit(); );
}
///////////////////////////////////////////////////////////////////////////////////
// CRenderer destructor
CRenderer::~CRenderer()
{
// model rendering
delete m_Models.NormalFF;
delete m_Models.PlayerFF;
delete m_Models.TranspFF;
delete m_Models.NormalHWLit;
delete m_Models.PlayerHWLit;
delete m_Models.TranspHWLit;
delete m_Models.NormalInstancing;
delete m_Models.PlayerInstancing;
delete m_Models.TranspSortAll;
// general
delete m_VertexShader;
m_VertexShader = 0;
// we no longer UnloadAlphaMaps / UnloadWaterTextures here -
// that is the responsibility of the module that asked for
// them to be loaded (i.e. CGameView).
delete m;
}
///////////////////////////////////////////////////////////////////////////////////
// EnumCaps: build card cap bits
void CRenderer::EnumCaps()
{
// assume support for nothing
m_Caps.m_VBO=false;
m_Caps.m_TextureBorderClamp=false;
m_Caps.m_GenerateMipmaps=false;
m_Caps.m_VertexShader=false;
// now start querying extensions
if (!m_Options.m_NoVBO) {
if (oglHaveExtension("GL_ARB_vertex_buffer_object")) {
m_Caps.m_VBO=true;
}
}
if (oglHaveExtension("GL_ARB_texture_border_clamp")) {
m_Caps.m_TextureBorderClamp=true;
}
if (oglHaveExtension("GL_SGIS_generate_mipmap")) {
m_Caps.m_GenerateMipmaps=true;
}
if (0 == oglHaveExtensions(0, "GL_ARB_shader_objects", "GL_ARB_shading_language_100", 0))
{
if (oglHaveExtension("GL_ARB_vertex_shader"))
m_Caps.m_VertexShader=true;
}
}
bool CRenderer::Open(int width, int height, int depth)
{
m_Width = width;
m_Height = height;
m_Depth = depth;
// set packing parameters
glPixelStorei(GL_PACK_ALIGNMENT,1);
glPixelStorei(GL_UNPACK_ALIGNMENT,1);
// setup default state
glDepthFunc(GL_LEQUAL);
glEnable(GL_DEPTH_TEST);
glCullFace(GL_BACK);
glFrontFace(GL_CCW);
glEnable(GL_CULL_FACE);
GLint bits;
glGetIntegerv(GL_DEPTH_BITS,&bits);
LOG(NORMAL, LOG_CATEGORY, "CRenderer::Open: depth bits %d",bits);
glGetIntegerv(GL_STENCIL_BITS,&bits);
LOG(NORMAL, LOG_CATEGORY, "CRenderer::Open: stencil bits %d",bits);
glGetIntegerv(GL_ALPHA_BITS,&bits);
LOG(NORMAL, LOG_CATEGORY, "CRenderer::Open: alpha bits %d",bits);
if (m_Options.m_RenderPath == RP_DEFAULT)
SetRenderPath(m_Options.m_RenderPath);
return true;
}
// resize renderer view
void CRenderer::Resize(int width,int height)
{
if (m_ShadowMap && (width>m_Width || height>m_Height)) {
glDeleteTextures(1,(GLuint*) &m_ShadowMap);
m_ShadowMap=0;
}
m_Width=width;
m_Height=height;
}
//////////////////////////////////////////////////////////////////////////////////////////
// SetOptionBool: set boolean renderer option
void CRenderer::SetOptionBool(enum Option opt,bool value)
{
switch (opt) {
case OPT_NOVBO:
m_Options.m_NoVBO=value;
break;
case OPT_SHADOWS:
m_Options.m_Shadows=value;
break;
case OPT_NOPBUFFER:
// NOT IMPLEMENTED
break;
default:
debug_warn("CRenderer::SetOptionBool: unknown option");
break;
}
}
//////////////////////////////////////////////////////////////////////////////////////////
// GetOptionBool: get boolean renderer option
bool CRenderer::GetOptionBool(enum Option opt) const
{
switch (opt) {
case OPT_NOVBO:
return m_Options.m_NoVBO;
case OPT_SHADOWS:
return m_Options.m_Shadows;
default:
debug_warn("CRenderer::GetOptionBool: unknown option");
break;
}
return false;
}
//////////////////////////////////////////////////////////////////////////////////////////
// SetOptionColor: set color renderer option
void CRenderer::SetOptionColor(enum Option opt,const RGBAColor& value)
{
switch (opt) {
case OPT_SHADOWCOLOR:
m_Options.m_ShadowColor=value;
break;
default:
debug_warn("CRenderer::SetOptionColor: unknown option");
break;
}
}
void CRenderer::SetOptionFloat(enum Option opt, float val)
{
switch(opt)
{
case OPT_LODBIAS:
m_Options.m_LodBias = val;
break;
default:
debug_warn("CRenderer::SetOptionFloat: unknown option");
break;
}
}
//////////////////////////////////////////////////////////////////////////////////////////
// GetOptionColor: get color renderer option
const RGBAColor& CRenderer::GetOptionColor(enum Option opt) const
{
static const RGBAColor defaultColor(1.0f,1.0f,1.0f,1.0f);
switch (opt) {
case OPT_SHADOWCOLOR:
return m_Options.m_ShadowColor;
default:
debug_warn("CRenderer::GetOptionColor: unknown option");
break;
}
return defaultColor;
}
//////////////////////////////////////////////////////////////////////////////////////////
// SetRenderPath: Select the preferred render path.
// This may only be called before Open(), because the layout of vertex arrays and other
// data may depend on the chosen render path.
void CRenderer::SetRenderPath(RenderPath rp)
{
if (rp == RP_DEFAULT)
{
if (m_Models.NormalHWLit && m_Models.PlayerHWLit)
rp = RP_VERTEXSHADER;
else
rp = RP_FIXED;
}
if (rp == RP_VERTEXSHADER)
{
if (!m_Models.NormalHWLit || !m_Models.PlayerHWLit)
{
LOG(WARNING, LOG_CATEGORY, "Falling back to fixed function\n");
rp = RP_FIXED;
}
}
m_Options.m_RenderPath = rp;
}
CStr CRenderer::GetRenderPathName(RenderPath rp)
{
switch(rp) {
case RP_DEFAULT: return "default";
case RP_FIXED: return "fixed";
case RP_VERTEXSHADER: return "vertexshader";
default: return "(invalid)";
}
}
CRenderer::RenderPath CRenderer::GetRenderPathByName(CStr name)
{
if (name == "fixed")
return RP_FIXED;
if (name == "vertexshader")
return RP_VERTEXSHADER;
if (name == "default")
return RP_DEFAULT;
LOG(WARNING, LOG_CATEGORY, "Unknown render path name '%hs', assuming 'default'", name.c_str());
return RP_DEFAULT;
}
//////////////////////////////////////////////////////////////////////////////////////////
// SetFastPlayerColor
void CRenderer::SetFastPlayerColor(bool fast)
{
m_FastPlayerColor = fast;
if (m_FastPlayerColor)
{
if (!FastPlayerColorRender::IsAvailable())
{
LOG(WARNING, LOG_CATEGORY, "Falling back to slower player color rendering.");
m_FastPlayerColor = false;
}
}
if (m_FastPlayerColor)
m_Models.ModPlayer = RenderModifierPtr(new FastPlayerColorRender);
else
m_Models.ModPlayer = RenderModifierPtr(new SlowPlayerColorRender);
}
//////////////////////////////////////////////////////////////////////////////////////////
// BeginFrame: signal frame start
void CRenderer::BeginFrame()
{
#ifndef SCED
if(!g_Game || !g_Game->IsGameStarted())
return;
#endif
// bump frame counter
m_FrameCounter++;
if (m_VertexShader)
m_VertexShader->BeginFrame();
// zero out all the per-frame stats
m_Stats.Reset();
// calculate coefficients for terrain and unit lighting
m_SHCoeffsUnits.Clear();
m_SHCoeffsTerrain.Clear();
if (m_LightEnv) {
m_SHCoeffsUnits.AddDirectionalLight(m_LightEnv->m_SunDir, m_LightEnv->m_SunColor);
m_SHCoeffsTerrain.AddDirectionalLight(m_LightEnv->m_SunDir, m_LightEnv->m_SunColor);
m_SHCoeffsUnits.AddAmbientLight(m_LightEnv->m_UnitsAmbientColor);
m_SHCoeffsTerrain.AddAmbientLight(m_LightEnv->m_TerrainAmbientColor);
}
// init per frame stuff
m_ShadowRendered=false;
m_ShadowBound.SetEmpty();
}
//////////////////////////////////////////////////////////////////////////////////////////
// SetClearColor: set color used to clear screen in BeginFrame()
void CRenderer::SetClearColor(u32 color)
{
m_ClearColor[0]=float(color & 0xff)/255.0f;
m_ClearColor[1]=float((color>>8) & 0xff)/255.0f;
m_ClearColor[2]=float((color>>16) & 0xff)/255.0f;
m_ClearColor[3]=float((color>>24) & 0xff)/255.0f;
}
static int RoundUpToPowerOf2(int x)
{
if ((x & (x-1))==0) return x;
int d=x;
while (d & (d-1)) {
d&=(d-1);
}
return d<<1;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////
// BuildTransformation: build transformation matrix from a position and standard basis vectors
void CRenderer::BuildTransformation(const CVector3D& pos,const CVector3D& right,const CVector3D& up,
const CVector3D& dir,CMatrix3D& result)
{
// build basis
result._11=right.X;
result._12=right.Y;
result._13=right.Z;
result._14=0;
result._21=up.X;
result._22=up.Y;
result._23=up.Z;
result._24=0;
result._31=dir.X;
result._32=dir.Y;
result._33=dir.Z;
result._34=0;
result._41=0;
result._42=0;
result._43=0;
result._44=1;
CMatrix3D trans;
trans.SetTranslation(-pos.X,-pos.Y,-pos.Z);
result=result*trans;
}
///////////////////////////////////////////////////////////////////////////////////////////////////
// ConstructLightTransform: build transformation matrix for light at given position casting in
// given direction
void CRenderer::ConstructLightTransform(const CVector3D& pos,const CVector3D& dir,CMatrix3D& result)
{
CVector3D right,up;
CVector3D viewdir=m_Camera.m_Orientation.GetIn();
if (fabs(dir.Y)>0.01f) {
up=CVector3D(viewdir.X,(-dir.Z*viewdir.Z-dir.X*dir.X)/dir.Y,viewdir.Z);
} else {
up=CVector3D(0,0,1);
}
up.Normalize();
right=dir.Cross(up);
right.Normalize();
BuildTransformation(pos,right,up,dir,result);
}
///////////////////////////////////////////////////////////////////////////////////////////////////
// CalcShadowMatrices: calculate required matrices for shadow map generation - the light's
// projection and transformation matrices
void CRenderer::CalcShadowMatrices()
{
int i;
// get bounds of shadow casting objects
const CBound& bounds=m_ShadowBound;
// get centre of bounds
CVector3D centre;
bounds.GetCentre(centre);
// get sunlight direction
// ??? RC more optimal light placement?
CVector3D lightpos=centre-(m_LightEnv->m_SunDir * 1000);
// make light transformation matrix
ConstructLightTransform(lightpos,m_LightEnv->m_SunDir,m_LightTransform);
// transform shadow bounds to light space, calculate near and far bounds
CVector3D vp[8];
m_LightTransform.Transform(CVector3D(bounds[0].X,bounds[0].Y,bounds[0].Z),vp[0]);
m_LightTransform.Transform(CVector3D(bounds[1].X,bounds[0].Y,bounds[0].Z),vp[1]);
m_LightTransform.Transform(CVector3D(bounds[0].X,bounds[1].Y,bounds[0].Z),vp[2]);
m_LightTransform.Transform(CVector3D(bounds[1].X,bounds[1].Y,bounds[0].Z),vp[3]);
m_LightTransform.Transform(CVector3D(bounds[0].X,bounds[0].Y,bounds[1].Z),vp[4]);
m_LightTransform.Transform(CVector3D(bounds[1].X,bounds[0].Y,bounds[1].Z),vp[5]);
m_LightTransform.Transform(CVector3D(bounds[0].X,bounds[1].Y,bounds[1].Z),vp[6]);
m_LightTransform.Transform(CVector3D(bounds[1].X,bounds[1].Y,bounds[1].Z),vp[7]);
float left=vp[0].X;
float right=vp[0].X;
float top=vp[0].Y;
float bottom=vp[0].Y;
float znear=vp[0].Z;
float zfar=vp[0].Z;
for (i=1;i<8;i++) {
if (vp[i].X<left) left=vp[i].X;
else if (vp[i].X>right) right=vp[i].X;
if (vp[i].Y<bottom) bottom=vp[i].Y;
else if (vp[i].Y>top) top=vp[i].Y;
if (vp[i].Z<znear) znear=vp[i].Z;
else if (vp[i].Z>zfar) zfar=vp[i].Z;
}
// shift near and far clip planes slightly to avoid artifacts with points
// exactly on the clip planes
znear=(znear<m_Camera.GetNearPlane()+0.01f) ? m_Camera.GetNearPlane() : znear-0.01f;
zfar+=0.01f;
m_LightProjection.SetZero();
m_LightProjection._11=2/(right-left);
m_LightProjection._22=2/(top-bottom);
m_LightProjection._33=2/(zfar-znear);
m_LightProjection._14=-(right+left)/(right-left);
m_LightProjection._24=-(top+bottom)/(top-bottom);
m_LightProjection._34=-(zfar+znear)/(zfar-znear);
m_LightProjection._44=1;
#if 0
#if 0
// TODO, RC - trim against frustum?
// get points of view frustum in world space
CVector3D frustumPts[8];
m_Camera.GetFrustumPoints(frustumPts);
// transform to light space
for (i=0;i<8;i++) {
m_LightTransform.Transform(frustumPts[i],vp[i]);
}
float left1=vp[0].X;
float right1=vp[0].X;
float top1=vp[0].Y;
float bottom1=vp[0].Y;
float znear1=vp[0].Z;
float zfar1=vp[0].Z;
for (int i=1;i<8;i++) {
if (vp[i].X<left1) left1=vp[i].X;
else if (vp[i].X>right1) right1=vp[i].X;
if (vp[i].Y<bottom1) bottom1=vp[i].Y;
else if (vp[i].Y>top1) top1=vp[i].Y;
if (vp[i].Z<znear1) znear1=vp[i].Z;
else if (vp[i].Z>zfar1) zfar1=vp[i].Z;
}
left=max(left,left1);
right=min(right,right1);
top=min(top,top1);
bottom=max(bottom,bottom1);
znear=max(znear,znear1);
zfar=min(zfar,zfar1);
#endif
// experimental stuff, do not use ..
// TODO, RC - desperately need to improve resolution here if we're using shadow maps; investigate
// feasibility of PSMs
// transform light space bounds to image space - TODO, RC: safe to just use 3d transform here?
CVector4D vph[8];
for (i=0;i<8;i++) {
CVector4D tmp(vp[i].X,vp[i].Y,vp[i].Z,1.0f);
m_LightProjection.Transform(tmp,vph[i]);
vph[i][0]/=vph[i][2];
vph[i][1]/=vph[i][2];
}
// find the two points furthest apart
int p0,p1;
float maxdistsqrd=-1;
for (i=0;i<8;i++) {
for (int j=i+1;j<8;j++) {
float dx=vph[i][0]-vph[j][0];
float dy=vph[i][1]-vph[j][1];
float distsqrd=dx*dx+dy*dy;
if (distsqrd>maxdistsqrd) {
p0=i;
p1=j;
maxdistsqrd=distsqrd;
}
}
}
// now we want to rotate the camera such that the longest axis lies the diagonal at 45 degrees -
// get angle between points
float angle=atan2(vph[p0][1]-vph[p1][1],vph[p0][0]-vph[p1][0]);
float rotation=-angle;
// build rotation matrix
CQuaternion quat;
quat.FromAxisAngle(lightdir,rotation);
CMatrix3D m;
quat.ToMatrix(m);
// rotate up vector by given rotation
CVector3D up(m_LightTransform._21,m_LightTransform._22,m_LightTransform._23);
up=m.Rotate(up);
up.Normalize(); // TODO, RC - required??
// rebuild right vector
CVector3D rightvec;
rightvec=lightdir.Cross(up);
rightvec.Normalize();
BuildTransformation(lightpos,rightvec,up,lightdir,m_LightTransform);
// retransform points
m_LightTransform.Transform(CVector3D(bounds[0].X,bounds[0].Y,bounds[0].Z),vp[0]);
m_LightTransform.Transform(CVector3D(bounds[1].X,bounds[0].Y,bounds[0].Z),vp[1]);
m_LightTransform.Transform(CVector3D(bounds[0].X,bounds[1].Y,bounds[0].Z),vp[2]);
m_LightTransform.Transform(CVector3D(bounds[1].X,bounds[1].Y,bounds[0].Z),vp[3]);
m_LightTransform.Transform(CVector3D(bounds[0].X,bounds[0].Y,bounds[1].Z),vp[4]);
m_LightTransform.Transform(CVector3D(bounds[1].X,bounds[0].Y,bounds[1].Z),vp[5]);
m_LightTransform.Transform(CVector3D(bounds[0].X,bounds[1].Y,bounds[1].Z),vp[6]);
m_LightTransform.Transform(CVector3D(bounds[1].X,bounds[1].Y,bounds[1].Z),vp[7]);
// recalculate projection
left=vp[0].X;
right=vp[0].X;
top=vp[0].Y;
bottom=vp[0].Y;
znear=vp[0].Z;
zfar=vp[0].Z;
for (i=1;i<8;i++) {
if (vp[i].X<left) left=vp[i].X;
else if (vp[i].X>right) right=vp[i].X;
if (vp[i].Y<bottom) bottom=vp[i].Y;
else if (vp[i].Y>top) top=vp[i].Y;
if (vp[i].Z<znear) znear=vp[i].Z;
else if (vp[i].Z>zfar) zfar=vp[i].Z;
}
// shift near and far clip planes slightly to avoid artifacts with points
// exactly on the clip planes
znear-=0.01f;
zfar+=0.01f;
m_LightProjection.SetZero();
m_LightProjection._11=2/(right-left);
m_LightProjection._22=2/(top-bottom);
m_LightProjection._33=2/(zfar-znear);
m_LightProjection._14=-(right+left)/(right-left);
m_LightProjection._24=-(top+bottom)/(top-bottom);
m_LightProjection._34=-(zfar+znear)/(zfar-znear);
m_LightProjection._44=1;
#endif
}
void CRenderer::CreateShadowMap()
{
// get shadow map size as next power of two up from view width and height
m_ShadowMapWidth=m_Width;
m_ShadowMapWidth=RoundUpToPowerOf2(m_ShadowMapWidth);
m_ShadowMapHeight=m_Height;
m_ShadowMapHeight=RoundUpToPowerOf2(m_ShadowMapHeight);
// create texture object - initially filled with white, so clamp to edge clamps to correct color
glGenTextures(1,(GLuint*) &m_ShadowMap);
BindTexture(0,(GLuint) m_ShadowMap);
u32 size=m_ShadowMapWidth*m_ShadowMapHeight;
u32* buf=new u32[size];
for (uint i=0;i<size;i++) buf[i]=0x00ffffff;
glTexImage2D(GL_TEXTURE_2D,0,GL_RGBA8,m_ShadowMapWidth,m_ShadowMapHeight,0,GL_RGBA,GL_UNSIGNED_BYTE,buf);
delete[] buf;
// set texture parameters
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
}
void CRenderer::RenderShadowMap()
{
PROFILE( "render shadow map" );
// create shadow map if we haven't already got one
if (!m_ShadowMap) CreateShadowMap();
// clear buffers
glClearColor(1,1,1,0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
// build required matrices
CalcShadowMatrices();
// setup viewport
glViewport(0,0,m_Width,m_Height);
glMatrixMode(GL_PROJECTION);
glPushMatrix();
glLoadMatrixf(&m_LightProjection._11);
glMatrixMode(GL_MODELVIEW);
glPushMatrix();
glLoadMatrixf(&m_LightTransform._11);
#if 0
// debug aid - render actual bounds of shadow casting objects; helps see where
// the lights projection/transform can be optimised
glColor3f(1.0,0.0,0.0);
const CBound& bounds=m_ShadowBound;
glBegin(GL_LINE_LOOP);
glVertex3f(bounds[0].X,bounds[0].Y,bounds[0].Z);
glVertex3f(bounds[0].X,bounds[0].Y,bounds[1].Z);
glVertex3f(bounds[0].X,bounds[1].Y,bounds[1].Z);
glVertex3f(bounds[0].X,bounds[1].Y,bounds[0].Z);
glEnd();
glBegin(GL_LINE_LOOP);
glVertex3f(bounds[1].X,bounds[0].Y,bounds[0].Z);
glVertex3f(bounds[1].X,bounds[0].Y,bounds[1].Z);
glVertex3f(bounds[1].X,bounds[1].Y,bounds[1].Z);
glVertex3f(bounds[1].X,bounds[1].Y,bounds[0].Z);
glEnd();
glBegin(GL_LINE_LOOP);
glVertex3f(bounds[0].X,bounds[0].Y,bounds[0].Z);
glVertex3f(bounds[0].X,bounds[0].Y,bounds[1].Z);
glVertex3f(bounds[1].X,bounds[0].Y,bounds[1].Z);
glVertex3f(bounds[1].X,bounds[0].Y,bounds[0].Z);
glEnd();
glBegin(GL_LINE_LOOP);
glVertex3f(bounds[0].X,bounds[1].Y,bounds[0].Z);
glVertex3f(bounds[0].X,bounds[1].Y,bounds[1].Z);
glVertex3f(bounds[1].X,bounds[1].Y,bounds[1].Z);
glVertex3f(bounds[1].X,bounds[1].Y,bounds[0].Z);
glEnd();
#endif // 0
glEnable(GL_SCISSOR_TEST);
glScissor(1,1,m_Width-2,m_Height-2);
glColor4fv(m_Options.m_ShadowColor);
glDisable(GL_CULL_FACE);
m_Models.NormalFF->Render(m_Models.ModSolidColor, MODELFLAG_CASTSHADOWS);
m_Models.PlayerFF->Render(m_Models.ModSolidColor, MODELFLAG_CASTSHADOWS);
if (m_Models.NormalHWLit)
m_Models.NormalHWLit->Render(m_Models.ModSolidColor, MODELFLAG_CASTSHADOWS);
if (m_Models.PlayerHWLit)
m_Models.PlayerHWLit->Render(m_Models.ModSolidColor, MODELFLAG_CASTSHADOWS);
if (m_Models.NormalInstancing)
m_Models.NormalInstancing->Render(m_Models.ModSolidColor, MODELFLAG_CASTSHADOWS);
if (m_Models.PlayerInstancing)
m_Models.PlayerInstancing->Render(m_Models.ModSolidColor, MODELFLAG_CASTSHADOWS);
m_Models.TranspFF->Render(m_Models.ModTransparentShadow, MODELFLAG_CASTSHADOWS);
if (m_Models.TranspHWLit)
m_Models.TranspHWLit->Render(m_Models.ModTransparentShadow, MODELFLAG_CASTSHADOWS);
m_Models.TranspSortAll->Render(m_Models.ModTransparentShadow, MODELFLAG_CASTSHADOWS);
glEnable(GL_CULL_FACE);
glColor3f(1.0f,1.0f,1.0f);
glDisable(GL_SCISSOR_TEST);
// copy result into shadow map texture
BindTexture(0,m_ShadowMap);
glCopyTexSubImage2D(GL_TEXTURE_2D,0,0,0,0,0,m_Width,m_Height);
// restore matrix stack
glPopMatrix();
glMatrixMode(GL_PROJECTION);
glPopMatrix();
glMatrixMode(GL_MODELVIEW);
#if 0
// debug aid - dump generated shadow map to file; helps verify shadow map
// space being well used (not that it is at the minute .. (TODO, RC))
unsigned char* data=new unsigned char[m_ShadowMapWidth*m_ShadowMapHeight*3];
glGetTexImage(GL_TEXTURE_2D,0,GL_BGR_EXT,GL_UNSIGNED_BYTE,data);
saveTGA("d:\\test4.tga",m_ShadowMapWidth,m_ShadowMapHeight,24,data);
delete[] data;
#endif // 0
#if 0
unsigned char* data=new unsigned char[m_Width*m_Height*4];
glReadBuffer(GL_BACK);
glReadPixels(0,0,m_Width,m_Height,GL_BGRA_EXT,GL_UNSIGNED_BYTE,data);
saveTGA("d:\\test3.tga",m_Width,m_Height,32,data);
delete[] data;
#endif // 0
}
void CRenderer::ApplyShadowMap()
{
PROFILE( "applying shadows" );
CMatrix3D tmp2;
CMatrix3D texturematrix;
float dx=0.5f*float(m_Width)/float(m_ShadowMapWidth);
float dy=0.5f*float(m_Height)/float(m_ShadowMapHeight);
texturematrix.SetTranslation(dx,dy,0); // transform (-0.5, 0.5) to (0,1) - texture space
tmp2.SetScaling(dx,dy,0); // scale (-1,1) to (-0.5,0.5)
texturematrix=texturematrix*tmp2;
texturematrix=texturematrix*m_LightProjection; // transform light -> projected light space (-1 to 1)
texturematrix=texturematrix*m_LightTransform; // transform world -> light space
glMatrixMode(GL_TEXTURE);
glLoadMatrixf(&texturematrix._11);
glMatrixMode(GL_MODELVIEW);
CPatchRData::ApplyShadowMap(m_ShadowMap);
glMatrixMode(GL_TEXTURE);
glLoadIdentity();
glMatrixMode(GL_MODELVIEW);
}
void CRenderer::RenderPatches()
{
PROFILE(" render patches ");
// switch on wireframe if we need it
if (m_TerrainRenderMode==WIREFRAME) {
MICROLOG(L"wireframe on");
glPolygonMode(GL_FRONT_AND_BACK,GL_LINE);
}
// render all the patches, including blend pass
MICROLOG(L"render patch submissions");
RenderPatchSubmissions();
if (m_TerrainRenderMode==WIREFRAME) {
// switch wireframe off again
MICROLOG(L"wireframe off");
glPolygonMode(GL_FRONT_AND_BACK,GL_FILL);
} else if (m_TerrainRenderMode==EDGED_FACES) {
// edged faces: need to make a second pass over the data:
// first switch on wireframe
glPolygonMode(GL_FRONT_AND_BACK,GL_LINE);
// setup some renderstate ..
glDepthMask(0);
SetTexture(0,0);
glColor4f(1,1,1,0.35f);
glLineWidth(2.0f);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
// .. and some client states
glEnableClientState(GL_VERTEX_ARRAY);
CPatchRData::RenderStreamsAll(STREAM_POS);
// set color for outline
glColor3f(0,0,1);
glLineWidth(4.0f);
// render outline of each patch
CPatchRData::RenderOutlines();
// .. and switch off the client states
glDisableClientState(GL_VERTEX_ARRAY);
// .. and restore the renderstates
glDisable(GL_BLEND);
glDepthMask(1);
// restore fill mode, and we're done
glPolygonMode(GL_FRONT_AND_BACK,GL_FILL);
}
}
void CRenderer::RenderWater()
{
PROFILE(" render water ");
if(!m_RenderWater)
{
return;
}
//Fresnel effect
CCamera* Camera=g_Game->GetView()->GetCamera();
CVector3D CamFace=Camera->m_Orientation.GetIn();
CamFace.Normalize();
float FresnelScalar = CamFace.Dot( CVector3D(0.0f, -1.0f, 0.0f) );
//Invert and set boundaries
FresnelScalar = (1 - FresnelScalar) * 0.4f + 0.6f;
const int DX[] = {1,1,0,0};
const int DZ[] = {0,1,1,0};
CTerrain* terrain = g_Game->GetWorld()->GetTerrain();
int mapSize = terrain->GetVerticesPerSide();
CLOSManager* losMgr = g_Game->GetWorld()->GetLOSManager();
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
glDepthMask(GL_FALSE);
double time = get_time();
double period = 1.6;
int curTex = (int)(time*60/period) % 60;
ogl_tex_bind(m_WaterTexture[curTex], 0);
glMatrixMode(GL_TEXTURE);
glLoadIdentity();
float tx = -fmod(time, 20.0)/20.0;
float ty = fmod(time, 35.0)/35.0;
glTranslatef(tx, ty, 0);
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE);
glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_MODULATE);
glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_TEXTURE);
glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB_ARB, GL_SRC_COLOR);
glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB_ARB, GL_PRIMARY_COLOR_ARB);
glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB_ARB, GL_SRC_COLOR);
glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA_ARB, GL_REPLACE);
glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_ARB, GL_PRIMARY_COLOR_ARB);
glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA_ARB, GL_SRC_ALPHA);
// Set the proper LOD bias
glTexEnvf(GL_TEXTURE_FILTER_CONTROL, GL_TEXTURE_LOD_BIAS, m_Options.m_LodBias);
glBegin(GL_QUADS);
for(size_t i=0; i<m_VisiblePatches.size(); i++)
{
CPatch* patch = m_VisiblePatches[i];
for(int dx=0; dx<PATCH_SIZE; dx++)
{
for(int dz=0; dz<PATCH_SIZE; dz++)
{
int x = (patch->m_X*PATCH_SIZE + dx);
int z = (patch->m_Z*PATCH_SIZE + dz);
// is any corner of the tile below the water height? if not, no point rendering it
bool shouldRender = false;
for(int j=0; j<4; j++)
{
float terrainHeight = terrain->getVertexGroundLevel(x + DX[j], z + DZ[j]);
if(terrainHeight < m_WaterHeight)
{
shouldRender = true;
break;
}
}
if(!shouldRender)
{
continue;
}
for(int j=0; j<4; j++)
{
int ix = x + DX[j];
int iz = z + DZ[j];
float vertX = ix * CELL_SIZE;
float vertZ = iz * CELL_SIZE;
float terrainHeight = terrain->getVertexGroundLevel(ix, iz);
float alpha = clamp((m_WaterHeight - terrainHeight) / m_WaterFullDepth + m_WaterAlphaOffset,
-100.0f, m_WaterMaxAlpha);
float losMod = 1.0f;
for(int k=0; k<4; k++)
{
int tx = ix - DX[k];
int tz = iz - DZ[k];
if(tx >= 0 && tz >= 0 && tx <= mapSize-2 && tz <= mapSize-2)
{
ELOSStatus s = losMgr->GetStatus(tx, tz, g_Game->GetLocalPlayer());
if(s == LOS_EXPLORED && losMod > 0.7f)
losMod = 0.7f;
else if(s==LOS_UNEXPLORED && losMod > 0.0f)
losMod = 0.0f;
}
}
glColor4f(m_WaterColor.r*losMod, m_WaterColor.g*losMod, m_WaterColor.b*losMod, alpha * FresnelScalar);
pglMultiTexCoord2fARB(GL_TEXTURE0, vertX/16.0f, vertZ/16.0f);
glVertex3f(vertX, m_WaterHeight, vertZ);
}
} //end of x loop
} //end of z loop
}
glEnd();
glLoadIdentity();
glMatrixMode(GL_MODELVIEW);
glDepthMask(GL_TRUE);
glDisable(GL_BLEND);
glDisable(GL_TEXTURE_2D);
}
void CRenderer::RenderModels()
{
PROFILE( "render models ");
// switch on wireframe if we need it
if (m_ModelRenderMode==WIREFRAME) {
glPolygonMode(GL_FRONT_AND_BACK,GL_LINE);
}
m_Models.NormalFF->Render(m_Models.ModPlain, 0);
m_Models.PlayerFF->Render(m_Models.ModPlayer, 0);
if (m_Models.NormalHWLit)
m_Models.NormalHWLit->Render(m_Models.ModPlain, 0);
if (m_Models.PlayerHWLit)
m_Models.PlayerHWLit->Render(m_Models.ModPlayer, 0);
if (m_Models.NormalInstancing)
m_Models.NormalInstancing->Render(m_Models.ModPlain, 0);
if (m_Models.PlayerInstancing)
m_Models.PlayerInstancing->Render(m_Models.ModPlayer, 0);
if (m_ModelRenderMode==WIREFRAME) {
// switch wireframe off again
glPolygonMode(GL_FRONT_AND_BACK,GL_FILL);
} else if (m_ModelRenderMode==EDGED_FACES) {
m_Models.NormalFF->Render(m_Models.ModWireframe, 0);
m_Models.PlayerFF->Render(m_Models.ModWireframe, 0);
if (m_Models.NormalHWLit)
m_Models.NormalHWLit->Render(m_Models.ModWireframe, 0);
if (m_Models.PlayerHWLit)
m_Models.PlayerHWLit->Render(m_Models.ModWireframe, 0);
if (m_Models.NormalInstancing)
m_Models.NormalInstancing->Render(m_Models.ModWireframe, 0);
if (m_Models.PlayerInstancing)
m_Models.PlayerInstancing->Render(m_Models.ModWireframe, 0);
}
}
void CRenderer::RenderTransparentModels()
{
PROFILE( "render transparent models ");
// switch on wireframe if we need it
if (m_ModelRenderMode==WIREFRAME) {
glPolygonMode(GL_FRONT_AND_BACK,GL_LINE);
}
m_Models.TranspFF->Render(m_Models.ModTransparent, 0);
if (m_Models.TranspHWLit)
m_Models.TranspHWLit->Render(m_Models.ModTransparent, 0);
m_Models.TranspSortAll->Render(m_Models.ModTransparent, 0);
if (m_ModelRenderMode==WIREFRAME) {
// switch wireframe off again
glPolygonMode(GL_FRONT_AND_BACK,GL_FILL);
} else if (m_ModelRenderMode==EDGED_FACES) {
m_Models.TranspFF->Render(m_Models.ModWireframe, 0);
if (m_Models.TranspHWLit)
m_Models.TranspHWLit->Render(m_Models.ModWireframe, 0);
m_Models.TranspSortAll->Render(m_Models.ModWireframe, 0);
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////
// FlushFrame: force rendering of any batched objects
void CRenderer::FlushFrame()
{
#ifndef SCED
if(!g_Game || !g_Game->IsGameStarted())
return;
#endif
oglCheck();
// Prepare model renderers
PROFILE_START("prepare models");
m_Models.NormalFF->PrepareModels();
m_Models.PlayerFF->PrepareModels();
m_Models.TranspFF->PrepareModels();
if (m_Models.NormalHWLit)
m_Models.NormalHWLit->PrepareModels();
if (m_Models.PlayerHWLit)
m_Models.PlayerHWLit->PrepareModels();
if (m_Models.TranspHWLit)
m_Models.TranspHWLit->PrepareModels();
if (m_Models.NormalInstancing)
m_Models.NormalInstancing->PrepareModels();
if (m_Models.PlayerInstancing)
m_Models.PlayerInstancing->PrepareModels();
m_Models.TranspSortAll->PrepareModels();
PROFILE_END("prepare models");
if (!m_ShadowRendered) {
if (m_Options.m_Shadows) {
MICROLOG(L"render shadows");
RenderShadowMap();
}
// clear buffers
glClearColor(m_ClearColor[0],m_ClearColor[1],m_ClearColor[2],m_ClearColor[3]);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
}
oglCheck();
// render submitted patches and models
MICROLOG(L"render patches");
RenderPatches();
oglCheck();
MICROLOG(L"render models");
RenderModels();
oglCheck();
if (m_Options.m_Shadows && !m_ShadowRendered) {
MICROLOG(L"apply shadows");
ApplyShadowMap();
oglCheck();
}
m_ShadowRendered=true;
// call on the transparency renderer to render all the transparent stuff
MICROLOG(L"render transparent");
RenderTransparentModels();
oglCheck();
// render water (note: we're assuming there's no transparent stuff over water...
// we could also do this above render transparent if we assume there's no transparent
// stuff underwater)
MICROLOG(L"render water");
RenderWater();
oglCheck();
// empty lists
MICROLOG(L"empty lists");
CPatchRData::ClearSubmissions();
m_VisiblePatches.clear();
// Finish model renderers
m_Models.NormalFF->EndFrame();
m_Models.PlayerFF->EndFrame();
m_Models.TranspFF->EndFrame();
if (m_Models.NormalHWLit)
m_Models.NormalHWLit->EndFrame();
if (m_Models.PlayerHWLit)
m_Models.PlayerHWLit->EndFrame();
if (m_Models.TranspHWLit)
m_Models.TranspHWLit->EndFrame();
if (m_Models.NormalInstancing)
m_Models.NormalInstancing->EndFrame();
if (m_Models.PlayerInstancing)
m_Models.PlayerInstancing->EndFrame();
m_Models.TranspSortAll->EndFrame();
}
///////////////////////////////////////////////////////////////////////////////////////////////////
// EndFrame: signal frame end; implicitly flushes batched objects
void CRenderer::EndFrame()
{
#ifndef SCED
if(!g_Game || !g_Game->IsGameStarted())
return;
#endif
FlushFrame();
g_Renderer.SetTexture(0,0);
static bool once=false;
if (!once && glGetError()) {
LOG(ERROR, LOG_CATEGORY, "CRenderer::EndFrame: GL errors occurred");
once=true;
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////
// SetCamera: setup projection and transform of camera and adjust viewport to current view
void CRenderer::SetCamera(CCamera& camera)
{
CMatrix3D view;
camera.m_Orientation.GetInverse(view);
const CMatrix3D& proj=camera.GetProjection();
glMatrixMode(GL_PROJECTION);
glLoadMatrixf(&proj._11);
glMatrixMode(GL_MODELVIEW);
glLoadMatrixf(&view._11);
SetViewport(camera.GetViewPort());
m_Camera=camera;
}
void CRenderer::SetViewport(const SViewPort &vp)
{
glViewport(vp.m_X,vp.m_Y,vp.m_Width,vp.m_Height);
}
void CRenderer::Submit(CPatch* patch)
{
CPatchRData::Submit(patch);
m_VisiblePatches.push_back(patch);
}
void CRenderer::Submit(CModel* model)
{
if (model->GetFlags() & MODELFLAG_CASTSHADOWS) {
PROFILE( "updating shadow bounds" );
m_ShadowBound += model->GetBounds();
}
// Tricky: The call to GetBounds() above can invalidate the position
model->ValidatePosition();
bool canUseInstancing = false;
if (model->GetModelDef()->GetNumBones() == 0)
canUseInstancing = true;
if (model->GetMaterial().IsPlayer())
{
if (m_Options.m_RenderPath == RP_VERTEXSHADER)
{
if (canUseInstancing && m_Models.PlayerInstancing)
m_Models.PlayerInstancing->Submit(model);
else
m_Models.PlayerHWLit->Submit(model);
}
else
m_Models.PlayerFF->Submit(model);
}
else if (model->GetMaterial().UsesAlpha())
{
if (m_SortAllTransparent)
m_Models.TranspSortAll->Submit(model);
else if (m_Options.m_RenderPath == RP_VERTEXSHADER)
m_Models.TranspHWLit->Submit(model);
else
m_Models.TranspFF->Submit(model);
}
else
{
if (m_Options.m_RenderPath == RP_VERTEXSHADER)
{
if (canUseInstancing && m_Models.NormalInstancing)
m_Models.NormalInstancing->Submit(model);
else
m_Models.NormalHWLit->Submit(model);
}
else
m_Models.NormalFF->Submit(model);
}
}
void CRenderer::Submit(CSprite* UNUSED(sprite))
{
}
void CRenderer::Submit(CParticleSys* UNUSED(psys))
{
}
void CRenderer::Submit(COverlay* UNUSED(overlay))
{
}
void CRenderer::RenderPatchSubmissions()
{
// switch on required client states
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_COLOR_ARRAY);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
// render everything
CPatchRData::RenderBaseSplats();
CPatchRData::RenderBlendSplats();
// switch off all client states
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
glDisableClientState(GL_COLOR_ARRAY);
glDisableClientState(GL_VERTEX_ARRAY);
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// LoadTexture: try and load the given texture; set clamp/repeat flags on texture object if necessary
bool CRenderer::LoadTexture(CTexture* texture,u32 wrapflags)
{
const Handle errorhandle = -1;
Handle h=texture->GetHandle();
// already tried to load this texture
if (h)
{
// nothing to do here - just return success according to
// whether this is a valid handle or not
return h==errorhandle ? true : false;
}
h=ogl_tex_load(texture->GetName());
if (h <= 0)
{
LOG(ERROR, LOG_CATEGORY, "LoadTexture failed on \"%s\"",(const char*) texture->GetName());
texture->SetHandle(errorhandle);
return false;
}
if(!wrapflags)
wrapflags = GL_CLAMP_TO_EDGE;
(void)ogl_tex_set_wrap(h, wrapflags);
(void)ogl_tex_set_filter(h, GL_LINEAR_MIPMAP_LINEAR);
// (this also verifies that the texture is a power-of-two)
if(ogl_tex_upload(h) < 0)
{
LOG(ERROR, LOG_CATEGORY, "LoadTexture failed on \"%s\" : upload failed",(const char*) texture->GetName());
ogl_tex_free(h);
texture->SetHandle(errorhandle);
return false;
}
texture->SetHandle(h);
return true;
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// BindTexture: bind a GL texture object to current active unit
void CRenderer::BindTexture(int unit,GLuint tex)
{
pglActiveTextureARB(GL_TEXTURE0+unit);
glBindTexture(GL_TEXTURE_2D,tex);
if (tex) {
glEnable(GL_TEXTURE_2D);
} else {
glDisable(GL_TEXTURE_2D);
}
m_ActiveTextures[unit]=tex;
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// SetTexture: set the given unit to reference the given texture; pass a null texture to disable texturing on any unit
void CRenderer::SetTexture(int unit,CTexture* texture)
{
Handle h = texture? texture->GetHandle() : 0;
ogl_tex_bind(h, unit);
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// IsTextureTransparent: return true if given texture is transparent, else false - note texture must be loaded
// beforehand
bool CRenderer::IsTextureTransparent(CTexture* texture)
{
if (!texture) return false;
Handle h=texture->GetHandle();
uint flags = 0; // assume no alpha on failure
(void)ogl_tex_get_format(h, &flags, 0);
return (flags & TEX_ALPHA) != 0;
}
static inline void CopyTriple(unsigned char* dst,const unsigned char* src)
{
dst[0]=src[0];
dst[1]=src[1];
dst[2]=src[2];
}
///////////////////////////////////////////////////////////////////////////////////////////////////
// LoadAlphaMaps: load the 14 default alpha maps, pack them into one composite texture and
// calculate the coordinate of each alphamap within this packed texture
int CRenderer::LoadAlphaMaps()
{
const char* const key = "(alpha map composite)";
Handle ht = ogl_tex_find(key);
// alpha map texture had already been created and is still in memory:
// reuse it, do not load again.
if(ht > 0)
{
m_hCompositeAlphaMap = ht;
return 0;
}
//
// load all textures and store Handle in array
//
Handle textures[NumAlphaMaps] = {0};
PathPackage pp;
(void)pp_set_dir(&pp, "art/textures/terrain/alphamaps/special");
const char* fnames[NumAlphaMaps] = {
"blendcircle.dds",
"blendlshape.dds",
"blendedge.dds",
"blendedgecorner.dds",
"blendedgetwocorners.dds",
"blendfourcorners.dds",
"blendtwooppositecorners.dds",
"blendlshapecorner.dds",
"blendtwocorners.dds",
"blendcorner.dds",
"blendtwoedges.dds",
"blendthreecorners.dds",
"blendushape.dds",
"blendbad.dds"
};
uint base = 0; // texture width/height (see below)
// for convenience, we require all alpha maps to be of the same BPP
// (avoids another ogl_tex_get_size call, and doesn't hurt)
uint bpp = 0;
for(uint i=0;i<NumAlphaMaps;i++)
{
(void)pp_append_file(&pp, fnames[i]);
// note: these individual textures can be discarded afterwards;
// we cache the composite.
textures[i] = ogl_tex_load(pp.path, RES_NO_CACHE);
RETURN_ERR(textures[i]);
// quick hack: we require plain RGB(A) format, so convert to that.
// ideally the texture would be in uncompressed form; then this wouldn't
// be necessary.
uint flags;
ogl_tex_get_format(textures[i], &flags, 0);
ogl_tex_transform_to(textures[i], flags & ~TEX_DXT);
// get its size and make sure they are all equal.
// (the packing algo assumes this)
uint this_width = 0, this_bpp = 0; // fail-safe
(void)ogl_tex_get_size(textures[i], &this_width, 0, &this_bpp);
// .. first iteration: establish size
if(i == 0)
{
base = this_width;
bpp = this_bpp;
}
// .. not first: make sure texture size matches
else if(base != this_width || bpp != this_bpp)
DISPLAY_ERROR(L"Alpha maps are not identically sized (including pixel depth)");
}
//
// copy each alpha map (tile) into one buffer, arrayed horizontally.
//
uint tile_w = 2+base+2; // 2 pixel border (avoids bilinear filtering artifacts)
uint total_w = RoundUpToPowerOf2(tile_w * NumAlphaMaps);
uint total_h = base; debug_assert(is_pow2(total_h));
u8* data=new u8[total_w*total_h*3];
// for each tile on row
for(uint i=0;i<NumAlphaMaps;i++)
{
// get src of copy
const u8* src = 0;
(void)ogl_tex_get_data(textures[i], (void**)&src);
uint srcstep=bpp/8;
// get destination of copy
u8* dst=data+3*(i*tile_w);
// for each row of image
for (uint j=0;j<base;j++) {
// duplicate first pixel
CopyTriple(dst,src);
dst+=3;
CopyTriple(dst,src);
dst+=3;
// copy a row
for (uint k=0;k<base;k++) {
CopyTriple(dst,src);
dst+=3;
src+=srcstep;
}
// duplicate last pixel
CopyTriple(dst,(src-srcstep));
dst+=3;
CopyTriple(dst,(src-srcstep));
dst+=3;
// advance write pointer for next row
dst+=3*(total_w-tile_w);
}
m_AlphaMapCoords[i].u0=float(i*tile_w+2)/float(total_w);
m_AlphaMapCoords[i].u1=float((i+1)*tile_w-2)/float(total_w);
m_AlphaMapCoords[i].v0=0.0f;
m_AlphaMapCoords[i].v1=1.0f;
}
for (uint i=0;i<NumAlphaMaps;i++)
(void)ogl_tex_free(textures[i]);
// upload the composite texture
Tex t;
(void)tex_wrap(total_w, total_h, 24, 0, data, &t);
m_hCompositeAlphaMap = ogl_tex_wrap(&t, key);
(void)ogl_tex_set_filter(m_hCompositeAlphaMap, GL_LINEAR);
(void)ogl_tex_set_wrap (m_hCompositeAlphaMap, GL_CLAMP_TO_EDGE);
int ret = ogl_tex_upload(m_hCompositeAlphaMap, 0, 0, GL_INTENSITY);
delete[] data;
return ret;
}
///////////////////////////////////////////////////////////////////////////////////////////////////
// UnloadAlphaMaps: frees the resources allocates by LoadAlphaMaps
void CRenderer::UnloadAlphaMaps()
{
ogl_tex_free(m_hCompositeAlphaMap);
m_hCompositeAlphaMap = 0;
}
int CRenderer::LoadWaterTextures()
{
const uint num_textures = ARRAY_SIZE(m_WaterTexture);
// yield after this time is reached. balances increased progress bar
// smoothness vs. slowing down loading.
const double end_time = get_time() + 100e-3;
// initialize to 0 in case something fails below
// (we then abort the loop, but don't want undefined values in here)
if (cur_loading_water_tex == 0)
{
for (uint i = 0; i < num_textures; i++)
m_WaterTexture[i] = 0;
}
while (cur_loading_water_tex < num_textures)
{
char waterName[VFS_MAX_PATH];
// TODO: add a member variable and setter for this. (can't make this
// a parameter because this function is called via delay-load code)
const char* water_type = "animation2";
snprintf(waterName, ARRAY_SIZE(waterName), "art/textures/terrain/types/water/%s/water%02d.dds", water_type, cur_loading_water_tex+1);
Handle ht = ogl_tex_load(waterName);
if (ht <= 0)
{
LOG(ERROR, LOG_CATEGORY, "LoadWaterTextures failed on \"%s\"", waterName);
return ht;
}
m_WaterTexture[cur_loading_water_tex]=ht;
RETURN_ERR(ogl_tex_upload(ht));
cur_loading_water_tex++;
LDR_CHECK_TIMEOUT(cur_loading_water_tex, num_textures);
}
return 0;
}
void CRenderer::UnloadWaterTextures()
{
for (uint i = 0; i < ARRAY_SIZE(m_WaterTexture); i++)
{
ogl_tex_free(m_WaterTexture[i]);
m_WaterTexture[i] = 0;
}
cur_loading_water_tex = 0; // so they will be reloaded if LoadWaterTextures is called again
}
///////////////////////////////////////////////////////////////////////////////////////////////////
// Scripting Interface
jsval CRenderer::JSI_GetFastPlayerColor(JSContext*)
{
return ToJSVal(m_FastPlayerColor);
}
void CRenderer::JSI_SetFastPlayerColor(JSContext* ctx, jsval newval)
{
bool fast;
if (!ToPrimitive(ctx, newval, fast))
return;
SetFastPlayerColor(fast);
}
jsval CRenderer::JSI_GetRenderPath(JSContext*)
{
return ToJSVal(GetRenderPathName(m_Options.m_RenderPath));
}
void CRenderer::JSI_SetRenderPath(JSContext* ctx, jsval newval)
{
CStr name;
if (!ToPrimitive(ctx, newval, name))
return;
SetRenderPath(GetRenderPathByName(name));
}
void CRenderer::ScriptingInit()
{
AddProperty(L"fastPlayerColor", &CRenderer::JSI_GetFastPlayerColor, &CRenderer::JSI_SetFastPlayerColor);
AddProperty(L"renderpath", &CRenderer::JSI_GetRenderPath, &CRenderer::JSI_SetRenderPath);
AddProperty(L"sortAllTransparent", &CRenderer::m_SortAllTransparent);
AddProperty(L"fastNormals", &CRenderer::m_FastNormals);
CJSObject<CRenderer>::ScriptingInit("Renderer");
}