1
0
forked from 0ad/0ad
0ad/source/renderer/Renderer.cpp

1832 lines
52 KiB
C++
Raw Normal View History

///////////////////////////////////////////////////////////////////////////////
//
// 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 "graphics/Terrain.h"
#include "maths/Matrix3D.h"
#include "maths/MathUtil.h"
#include "graphics/Camera.h"
#include "graphics/Texture.h"
#include "graphics/LightEnv.h"
#include "graphics/Terrain.h"
#include "ps/Pyrogenesis.h" // MICROLOG
#include "ps/CLogger.h"
#include "ps/Game.h"
#include "ps/Profile.h"
#include "ps/Game.h"
#include "ps/World.h"
#include "ps/Player.h"
#include "simulation/LOSManager.h"
#include "simulation/TerritoryManager.h"
#include "graphics/Model.h"
#include "graphics/ModelDef.h"
#include "lib/ogl.h"
#include "lib/path_util.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 "graphics/ParticleEngine.h"
#include "graphics/DefaultEmitter.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/ShadowMap.h"
#include "renderer/TerrainOverlay.h"
#include "renderer/TerrainRenderer.h"
#include "renderer/TransparencyRenderer.h"
#include "renderer/WaterManager.h"
#include "renderer/SkyManager.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();
size_t GetNumberRows();
const std::vector<ProfileColumn>& GetColumns();
CStr GetCellText(size_t row, size_t col);
AbstractProfileTable* GetChild(size_t 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
};
NO_COPY_CTOR(CRendererStatsTable);
};
// 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";
}
size_t CRendererStatsTable::GetNumberRows()
{
return NumberRows;
}
const std::vector<ProfileColumn>& CRendererStatsTable::GetColumns()
{
return columnDescriptions;
}
CStr CRendererStatsTable::GetCellText(size_t row, size_t 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(size_t UNUSED(row))
{
return 0;
}
///////////////////////////////////////////////////////////////////////////////////
// CRenderer implementation
enum {
AmbientDiffuse = 0,
OnlyDiffuse,
NumVertexTypes
};
/**
* 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
{
/// true if CRenderer::Open has been called
bool IsOpen;
/// Table to display renderer stats in-game via profile system
CRendererStatsTable profileTable;
/// Water manager
WaterManager waterManager;
/// Sky manager
SkyManager skyManager;
/// Terrain renderer
TerrainRenderer* terrainRenderer;
/// Shadow map
ShadowMap* shadow;
/// Various model renderers
struct Models {
// The following model renderers are aliases for the appropriate real_*
// model renderers (depending on hardware availability and current settings)
// and must be used for actual model submission and rendering
ModelRenderer* Normal;
ModelRenderer* NormalInstancing;
ModelRenderer* Player;
ModelRenderer* PlayerInstancing;
ModelRenderer* Transp;
// "Palette" of available ModelRenderers. Do not use these directly for
// rendering and submission; use the aliases above instead.
ModelRenderer* pal_NormalFF[NumVertexTypes];
ModelRenderer* pal_PlayerFF[NumVertexTypes];
ModelRenderer* pal_NormalHWLit[NumVertexTypes];
ModelRenderer* pal_PlayerHWLit[NumVertexTypes];
ModelRenderer* pal_NormalInstancing[NumVertexTypes];
ModelRenderer* pal_PlayerInstancing[NumVertexTypes];
ModelRenderer* pal_TranspFF[NumVertexTypes];
ModelRenderer* pal_TranspHWLit[NumVertexTypes];
ModelRenderer* pal_TranspSortAll;
ModelVertexRendererPtr VertexFF[NumVertexTypes];
ModelVertexRendererPtr VertexHWLit[NumVertexTypes];
ModelVertexRendererPtr VertexInstancing[NumVertexTypes];
ModelVertexRendererPtr VertexPolygonSort;
// generic RenderModifiers that are supposed to be used directly
RenderModifierPtr ModWireframe;
RenderModifierPtr ModSolidColor;
RenderModifierPtr ModTransparentShadow;
RenderModifierPtr ModTransparentDepthShadow;
// RenderModifiers that are selected from the palette below
RenderModifierPtr ModNormal;
RenderModifierPtr ModPlayer;
RenderModifierPtr ModTransparent;
// Palette of available RenderModifiers
RenderModifierPtr ModPlain;
LitRenderModifierPtr ModPlainLit;
RenderModifierPtr ModPlayerUnlit;
LitRenderModifierPtr ModPlayerLit;
RenderModifierPtr ModTransparentUnlit;
LitRenderModifierPtr ModTransparentLit;
} Model;
CRendererInternals()
: IsOpen(false), profileTable(g_Renderer.m_Stats)
{
terrainRenderer = new TerrainRenderer();
shadow = new ShadowMap();
for(int vertexType = 0; vertexType < NumVertexTypes; ++vertexType)
{
Model.pal_NormalFF[vertexType] = 0;
Model.pal_PlayerFF[vertexType] = 0;
Model.pal_TranspFF[vertexType] = 0;
Model.pal_NormalHWLit[vertexType] = 0;
Model.pal_PlayerHWLit[vertexType] = 0;
Model.pal_TranspHWLit[vertexType] = 0;
Model.pal_NormalInstancing[vertexType] = 0;
Model.pal_PlayerInstancing[vertexType] = 0;
}
Model.pal_TranspSortAll = 0;
Model.Normal = 0;
Model.NormalInstancing = 0;
Model.Player = 0;
Model.PlayerInstancing = 0;
Model.Transp = 0;
}
~CRendererInternals()
{
delete shadow;
delete terrainRenderer;
}
bool CanUseRenderPathVertexShader()
{
for(int vertexType = 0; vertexType < NumVertexTypes; ++vertexType)
{
if (!Model.pal_NormalHWLit[vertexType] ||
!Model.pal_PlayerHWLit[vertexType])
return false;
}
return true;
}
};
///////////////////////////////////////////////////////////////////////////////////
// CRenderer destructor
CRenderer::CRenderer()
{
m = new CRendererInternals;
m_WaterManager = &m->waterManager;
m_SkyManager = &m->skyManager;
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_SortAllTransparent = false;
m_FastNormals = true;
m_DisplayFrustum = false;
m_DisableCopyShadow = false;
m_FastPlayerColor = true;
m_VertexShader = 0;
m_Options.m_NoVBO=false;
m_Options.m_NoFramebufferObject = false;
m_Options.m_Shadows=true;
m_Options.m_RenderPath = RP_DEFAULT;
m_Options.m_FancyWater = false;
m_ShadowZBias = 0.02f;
for (uint i=0;i<MaxTextureUnits;i++) {
m_ActiveTextures[i]=0;
}
ONCE( ScriptingInit(); );
}
///////////////////////////////////////////////////////////////////////////////////
// CRenderer destructor
CRenderer::~CRenderer()
{
// model rendering
for(int vertexType = 0; vertexType < NumVertexTypes; ++vertexType)
{
delete m->Model.pal_NormalFF[vertexType];
delete m->Model.pal_PlayerFF[vertexType];
delete m->Model.pal_TranspFF[vertexType];
delete m->Model.pal_NormalHWLit[vertexType];
delete m->Model.pal_PlayerHWLit[vertexType];
delete m->Model.pal_TranspHWLit[vertexType];
delete m->Model.pal_NormalInstancing[vertexType];
delete m->Model.pal_PlayerInstancing[vertexType];
}
delete m->Model.pal_TranspSortAll;
// general
delete m_VertexShader;
m_VertexShader = 0;
CParticleEngine::GetInstance()->cleanup();
// 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;
m_Caps.m_DepthTextureShadows = false;
m_Caps.m_FramebufferObject = 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;
}
if (0 == oglHaveExtensions(0, "GL_ARB_shadow", "GL_ARB_depth_texture", 0)) {
// According to Delphi3d.net, all relevant graphics chips that support depth textures
// (i.e. Geforce3+, Radeon9500+, even i915) also have >= 4 TMUs, so this restriction
// isn't actually a restriction, and it helps with integrating depth texture
// shadows into rendering paths.
if (ogl_max_tex_units >= 4)
m_Caps.m_DepthTextureShadows = true;
}
if (!m_Options.m_NoFramebufferObject)
{
if (oglHaveExtension("GL_EXT_framebuffer_object"))
m_Caps.m_FramebufferObject = true;
}
}
bool CRenderer::Open(int width, int height, int depth)
{
m->IsOpen = true;
// Must query card capabilities before creating renderers that depend
// on card capabilities.
EnumCaps();
m->shadow->SetUseDepthTexture(true);
m_VertexShader = new RenderPathVertexShader;
if (!m_VertexShader->Init())
{
delete m_VertexShader;
m_VertexShader = 0;
}
// model rendering
m->Model.VertexFF[AmbientDiffuse] = ModelVertexRendererPtr(new FixedFunctionModelRenderer(false));
m->Model.VertexFF[OnlyDiffuse] = ModelVertexRendererPtr(new FixedFunctionModelRenderer(true));
if (HWLightingModelRenderer::IsAvailable())
{
m->Model.VertexHWLit[AmbientDiffuse] = ModelVertexRendererPtr(new HWLightingModelRenderer(false));
m->Model.VertexHWLit[OnlyDiffuse] = ModelVertexRendererPtr(new HWLightingModelRenderer(true));
}
if (InstancingModelRenderer::IsAvailable())
{
m->Model.VertexInstancing[AmbientDiffuse] = ModelVertexRendererPtr(new InstancingModelRenderer(false));
m->Model.VertexInstancing[OnlyDiffuse] = ModelVertexRendererPtr(new InstancingModelRenderer(true));
}
m->Model.VertexPolygonSort = ModelVertexRendererPtr(new PolygonSortModelRenderer);
for(int vertexType = 0; vertexType < NumVertexTypes; ++vertexType)
{
m->Model.pal_NormalFF[vertexType] = new BatchModelRenderer(m->Model.VertexFF[vertexType]);
m->Model.pal_PlayerFF[vertexType] = new BatchModelRenderer(m->Model.VertexFF[vertexType]);
m->Model.pal_TranspFF[vertexType] = new SortModelRenderer(m->Model.VertexFF[vertexType]);
if (m->Model.VertexHWLit[vertexType])
{
m->Model.pal_NormalHWLit[vertexType] = new BatchModelRenderer(m->Model.VertexHWLit[vertexType]);
m->Model.pal_PlayerHWLit[vertexType] = new BatchModelRenderer(m->Model.VertexHWLit[vertexType]);
m->Model.pal_TranspHWLit[vertexType] = new SortModelRenderer(m->Model.VertexHWLit[vertexType]);
}
if (m->Model.VertexInstancing[vertexType])
{
m->Model.pal_NormalInstancing[vertexType] = new BatchModelRenderer(m->Model.VertexInstancing[vertexType]);
m->Model.pal_PlayerInstancing[vertexType] = new BatchModelRenderer(m->Model.VertexInstancing[vertexType]);
}
}
m->Model.pal_TranspSortAll = new SortModelRenderer(m->Model.VertexPolygonSort);
m->Model.ModWireframe = RenderModifierPtr(new WireframeRenderModifier);
m->Model.ModPlain = RenderModifierPtr(new PlainRenderModifier);
m->Model.ModPlainLit = LitRenderModifierPtr(new PlainLitRenderModifier);
SetFastPlayerColor(true);
m->Model.ModPlayerLit = LitRenderModifierPtr(new LitPlayerColorRender);
m->Model.ModSolidColor = RenderModifierPtr(new SolidColorRenderModifier);
m->Model.ModTransparentUnlit = RenderModifierPtr(new TransparentRenderModifier);
m->Model.ModTransparentLit = LitRenderModifierPtr(new LitTransparentRenderModifier);
m->Model.ModTransparentShadow = RenderModifierPtr(new TransparentShadowRenderModifier);
m->Model.ModTransparentDepthShadow = RenderModifierPtr(new TransparentDepthShadowModifier);
// Particle engine
CParticleEngine::GetInstance()->initParticleSystem();
CEmitter *pEmitter = new CDefaultEmitter(1000, -1);
CParticleEngine::GetInstance()->addEmitter(pEmitter);
// Dimensions
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);
// Validate the currently selected render path
SetRenderPath(m_Options.m_RenderPath);
return true;
}
// resize renderer view
void CRenderer::Resize(int width,int height)
{
// need to recreate the shadow map object to resize the shadow texture
m->shadow->RecreateTexture();
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_NOFRAMEBUFFEROBJECT:
m_Options.m_NoFramebufferObject=value;
break;
case OPT_SHADOWS:
m_Options.m_Shadows=value;
break;
case OPT_FANCYWATER:
m_Options.m_FancyWater=value;
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_NOFRAMEBUFFEROBJECT:
return m_Options.m_NoFramebufferObject;
case OPT_SHADOWS:
return m_Options.m_Shadows;
case OPT_FANCYWATER:
return m_Options.m_FancyWater;
default:
debug_warn("CRenderer::GetOptionBool: unknown option");
break;
}
return false;
}
//////////////////////////////////////////////////////////////////////////////////////////
// SetOptionColor: set color renderer option
void CRenderer::SetOptionColor(Option UNUSED(opt),const RGBAColor& UNUSED(value))
{
// switch (opt) {
// 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(Option UNUSED(opt)) const
{
static const RGBAColor defaultColor(1.0f,1.0f,1.0f,1.0f);
// switch (opt) {
// 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 (!m->IsOpen)
{
// Delay until Open() is called.
m_Options.m_RenderPath = rp;
return;
}
// Renderer has been opened, so validate the selected renderpath
if (rp == RP_DEFAULT)
{
if (m->CanUseRenderPathVertexShader())
rp = RP_VERTEXSHADER;
else
rp = RP_FIXED;
}
if (rp == RP_VERTEXSHADER)
{
if (!m->CanUseRenderPathVertexShader())
{
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(const 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->Model.ModPlayerUnlit = RenderModifierPtr(new FastPlayerColorRender);
else
m->Model.ModPlayerUnlit = RenderModifierPtr(new SlowPlayerColorRender);
}
//////////////////////////////////////////////////////////////////////////////////////////
// BeginFrame: signal frame start
void CRenderer::BeginFrame()
{
// bump frame counter
m_FrameCounter++;
if (m_VertexShader)
m_VertexShader->BeginFrame();
// zero out all the per-frame stats
m_Stats.Reset();
// init per frame stuff
m->shadow->SetupFrame(m_CullCamera, m_LightEnv->GetSunDir());
// choose model renderers for this frame
int vertexType;
if (m_Options.m_Shadows && m->shadow->GetUseDepthTexture())
{
vertexType = OnlyDiffuse;
m->Model.ModNormal = m->Model.ModPlainLit;
m->Model.ModPlainLit->SetShadowMap(m->shadow);
m->Model.ModPlainLit->SetLightEnv(m_LightEnv);
m->Model.ModPlayer = m->Model.ModPlayerLit;
m->Model.ModPlayerLit->SetShadowMap(m->shadow);
m->Model.ModPlayerLit->SetLightEnv(m_LightEnv);
m->Model.ModTransparent = m->Model.ModTransparentLit;
m->Model.ModTransparentLit->SetShadowMap(m->shadow);
m->Model.ModTransparentLit->SetLightEnv(m_LightEnv);
}
else
{
vertexType = AmbientDiffuse;
m->Model.ModNormal = m->Model.ModPlain;
m->Model.ModPlayer = m->Model.ModPlayerUnlit;
m->Model.ModTransparent = m->Model.ModTransparentUnlit;
}
if (m_Options.m_RenderPath == RP_VERTEXSHADER)
{
debug_assert(m->Model.pal_NormalHWLit[vertexType] != 0);
if (m->Model.pal_NormalInstancing)
m->Model.NormalInstancing = m->Model.pal_NormalInstancing[vertexType];
else
m->Model.NormalInstancing = m->Model.pal_NormalHWLit[vertexType];
m->Model.Normal = m->Model.pal_NormalHWLit[vertexType];
if (m->Model.pal_PlayerInstancing)
m->Model.PlayerInstancing = m->Model.pal_PlayerInstancing[vertexType];
else
m->Model.PlayerInstancing = m->Model.pal_PlayerHWLit[vertexType];
m->Model.Player = m->Model.pal_PlayerHWLit[vertexType];
}
else
{
m->Model.NormalInstancing = m->Model.pal_NormalFF[vertexType];
m->Model.Normal = m->Model.pal_NormalFF[vertexType];
m->Model.PlayerInstancing = m->Model.pal_PlayerFF[vertexType];
m->Model.Player = m->Model.pal_PlayerFF[vertexType];
}
if (m_SortAllTransparent)
m->Model.Transp = m->Model.pal_TranspSortAll;
else if (m_Options.m_RenderPath == RP_VERTEXSHADER)
m->Model.Transp = m->Model.pal_TranspHWLit[vertexType];
else
m->Model.Transp = m->Model.pal_TranspFF[vertexType];
}
//////////////////////////////////////////////////////////////////////////////////////////
// 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;
}
void CRenderer::RenderShadowMap()
{
PROFILE( "render shadow map" );
m->shadow->BeginRender();
float shadowTransp = m_LightEnv->GetTerrainShadowTransparency();
glColor3f(shadowTransp, shadowTransp, shadowTransp);
// Figure out transparent rendering strategy
RenderModifierPtr transparentShadows = m->Model.ModTransparentShadow;
if (m->shadow->GetUseDepthTexture())
transparentShadows = m->Model.ModTransparentDepthShadow;
// Render all closed models (i.e. models where rendering back faces will produce
// the correct result)
glCullFace(GL_FRONT);
if (m->shadow->GetUseDepthTexture())
m->terrainRenderer->RenderPatches();
glCullFace(GL_BACK);
// Render models that aren't closed
glDisable(GL_CULL_FACE);
m->Model.Normal->Render(m->Model.ModSolidColor, MODELFLAG_CASTSHADOWS);
if (m->Model.Normal != m->Model.NormalInstancing)
m->Model.NormalInstancing->Render(m->Model.ModSolidColor, MODELFLAG_CASTSHADOWS);
m->Model.Player->Render(m->Model.ModSolidColor, MODELFLAG_CASTSHADOWS);
if (m->Model.Player != m->Model.PlayerInstancing)
m->Model.PlayerInstancing->Render(m->Model.ModSolidColor, MODELFLAG_CASTSHADOWS);
m->Model.Transp->Render(transparentShadows, MODELFLAG_CASTSHADOWS);
glEnable(GL_CULL_FACE);
glColor3f(1.0, 1.0, 1.0);
m->shadow->EndRender();
}
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");
m->terrainRenderer->RenderTerrain(m_Options.m_Shadows ? m->shadow : 0);
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);
// render tiles edges
m->terrainRenderer->RenderPatches();
// set color for outline
glColor3f(0,0,1);
glLineWidth(4.0f);
// render outline of each patch
m->terrainRenderer->RenderOutlines();
// .. and restore the renderstates
glDisable(GL_BLEND);
glDepthMask(1);
// restore fill mode, and we're done
glLineWidth(1.0f);
glPolygonMode(GL_FRONT_AND_BACK,GL_FILL);
}
}
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->Model.Normal->Render(m->Model.ModNormal, 0);
m->Model.Player->Render(m->Model.ModPlayer, 0);
if (m->Model.Normal != m->Model.NormalInstancing)
m->Model.NormalInstancing->Render(m->Model.ModNormal, 0);
if (m->Model.Player != m->Model.PlayerInstancing)
m->Model.PlayerInstancing->Render(m->Model.ModPlayer, 0);
if (m_ModelRenderMode==WIREFRAME) {
// switch wireframe off again
glPolygonMode(GL_FRONT_AND_BACK,GL_FILL);
} else if (m_ModelRenderMode==EDGED_FACES) {
m->Model.Normal->Render(m->Model.ModWireframe, 0);
m->Model.Player->Render(m->Model.ModWireframe, 0);
if (m->Model.Normal != m->Model.NormalInstancing)
m->Model.NormalInstancing->Render(m->Model.ModWireframe, 0);
if (m->Model.Player != m->Model.PlayerInstancing)
m->Model.PlayerInstancing->Render(m->Model.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->Model.Transp->Render(m->Model.ModTransparent, 0);
if (m_ModelRenderMode==WIREFRAME) {
// switch wireframe off again
glPolygonMode(GL_FRONT_AND_BACK,GL_FILL);
} else if (m_ModelRenderMode==EDGED_FACES) {
m->Model.Transp->Render(m->Model.ModWireframe, 0);
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////
// GetModelViewProjectionMatrix: save the current OpenGL model-view-projection matrix
CMatrix3D CRenderer::GetModelViewProjectionMatrix()
{
CMatrix3D proj;
CMatrix3D view;
glGetFloatv( GL_PROJECTION_MATRIX, &proj._11 );
glGetFloatv( GL_MODELVIEW_MATRIX, &view._11 );
return( proj * view );
}
///////////////////////////////////////////////////////////////////////////////////////////////////
// SetObliqueFrustumClipping: change the near plane to the given clip plane (in world space)
// Based on code from Game Programming Gems 5, from http://www.terathon.com/code/oblique.html
// - cp is a clip plane in camera space (cp.dot(v) = 0 for any vector v on the plane)
// - sign is 1 or -1, to specify the side to clip on
void CRenderer::SetObliqueFrustumClipping(const CVector4D& cp, int sign)
{
float matrix[16];
CVector4D q;
// First, we'll convert the given clip plane to camera space, then we'll
// Get the view matrix and normal matrix (top 3x3 part of view matrix)
CMatrix3D viewMatrix;
m_ViewCamera.m_Orientation.GetInverse(viewMatrix);
CMatrix3D normalMatrix = viewMatrix;
normalMatrix._14 = 0;
normalMatrix._24 = 0;
normalMatrix._34 = 0;
normalMatrix._44 = 1;
normalMatrix._41 = 0;
normalMatrix._42 = 0;
normalMatrix._43 = 0;
// Convert the normal to camera space
CVector4D planeNormal(cp.m_X, cp.m_Y, cp.m_Z, 0);
planeNormal = normalMatrix.Transform(planeNormal);
planeNormal.normalize();
// Find a point on the plane: we'll take the normal times -D
float oldD = cp.m_W;
CVector4D pointOnPlane(-oldD * cp.m_X, -oldD * cp.m_Y, -oldD * cp.m_Z, 1);
pointOnPlane = viewMatrix.Transform(pointOnPlane);
float newD = -pointOnPlane.dot(planeNormal);
// Now create a clip plane from the new normal and new D
CVector4D camPlane = planeNormal;
camPlane.m_W = newD;
// Grab the current projection matrix from OpenGL
glGetFloatv(GL_PROJECTION_MATRIX, matrix);
// Calculate the clip-space corner point opposite the clipping plane
// as (sgn(camPlane.x), sgn(camPlane.y), 1, 1) and
// transform it into camera space by multiplying it
// by the inverse of the projection matrix
q.m_X = (sgn(camPlane.m_X) + matrix[8]) / matrix[0];
q.m_Y = (sgn(camPlane.m_Y) + matrix[9]) / matrix[5];
q.m_Z = -1.0f;
q.m_W = (1.0f + matrix[10]) / matrix[14];
// Calculate the scaled plane vector
CVector4D c = camPlane * (sign * 2.0f / camPlane.dot(q));
// Replace the third row of the projection matrix
matrix[2] = c.m_X;
matrix[6] = c.m_Y;
matrix[10] = c.m_Z + 1.0f;
matrix[14] = c.m_W;
// Load it back into OpenGL
glMatrixMode(GL_PROJECTION);
glLoadMatrixf(matrix);
glMatrixMode(GL_MODELVIEW);
}
///////////////////////////////////////////////////////////////////////////////////////////////////
// RenderReflections: render the water reflections to the reflection texture
void CRenderer::RenderReflections()
{
MICROLOG(L"render reflections");
WaterManager& wm = m->waterManager;
// Remember old camera
CCamera normalCamera = m_ViewCamera;
// Temporarily change the camera to one that is reflected.
// Also, for texturing purposes, make it render to a view port the size of the
// water texture, stretch the image according to our aspect ratio so it covers
// the whole screen despite being rendered into a square, and cover slightly more
// of the view so we can see wavy reflections of slightly off-screen objects.
m_ViewCamera.m_Orientation.Translate(0, -wm.m_WaterHeight, 0);
m_ViewCamera.m_Orientation.Scale(1, -1, 1);
m_ViewCamera.m_Orientation.Translate(0, wm.m_WaterHeight, 0);
SViewPort vp;
vp.m_Height = wm.m_ReflectionTextureSize;
vp.m_Width = wm.m_ReflectionTextureSize;
vp.m_X = 0;
vp.m_Y = 0;
m_ViewCamera.SetViewPort(&vp);
m_ViewCamera.SetProjection(1, 5000, DEGTORAD(21)); // Slightly higher than view FOV of 20
CMatrix3D scaleMat;
scaleMat.SetScaling(m_Height/float(std::max(1, m_Width)), 1.0f, 1.0f);
m_ViewCamera.m_ProjMat = scaleMat * m_ViewCamera.m_ProjMat;
SetCamera(m_ViewCamera, m_CullCamera);
CVector4D camPlane(0, 1, 0, -wm.m_WaterHeight);
SetObliqueFrustumClipping(camPlane, -1);
// Save the model-view-projection matrix so the shaders can use it for projective texturing
wm.m_ReflectionMatrix = GetModelViewProjectionMatrix();
// Disable backface culling so trees render properly (it might also be possible to flip
// the culling direction here, but this seems to lead to problems)
glDisable(GL_CULL_FACE);
// Make the depth buffer work backwards; there seems to be some oddness with
// oblique frustum clipping and the "sign" parameter here
glClearDepth(0);
glClear(GL_DEPTH_BUFFER_BIT);
glDepthFunc(GL_GEQUAL);
// Render sky, terrain and models
m->skyManager.RenderSky();
oglCheck();
RenderPatches();
oglCheck();
RenderModels();
oglCheck();
RenderTransparentModels();
oglCheck();
// Copy the image to a texture
pglActiveTextureARB(GL_TEXTURE0_ARB);
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, wm.m_ReflectionTexture);
glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0,
wm.m_ReflectionTextureSize, wm.m_ReflectionTextureSize);
//Reset old camera and re-enable backface culling
SetCamera(normalCamera, m_CullCamera);
glEnable(GL_CULL_FACE);
//glClearDepth(1);
//glClear(GL_DEPTH_BUFFER_BIT);
//glDepthFunc(GL_LEQUAL);
}
///////////////////////////////////////////////////////////////////////////////////////////////////
// RenderRefractions: render the water refractions to the refraction texture
void CRenderer::RenderRefractions()
{
MICROLOG(L"render refractions");
WaterManager& wm = m->waterManager;
// Remember old camera
CCamera normalCamera = m_ViewCamera;
// Temporarily change the camera to make it render to a view port the size of the
// water texture, stretch the image according to our aspect ratio so it covers
// the whole screen despite being rendered into a square, and cover slightly more
// of the view so we can see wavy refractions of slightly off-screen objects.
SViewPort vp;
vp.m_Height = wm.m_RefractionTextureSize;
vp.m_Width = wm.m_RefractionTextureSize;
vp.m_X = 0;
vp.m_Y = 0;
m_ViewCamera.SetViewPort(&vp);
m_ViewCamera.SetProjection(1, 5000, DEGTORAD(21)); // Slightly higher than view FOV of 20
CMatrix3D scaleMat;
scaleMat.SetScaling(m_Height/float(std::max(1, m_Width)), 1.0f, 1.0f);
m_ViewCamera.m_ProjMat = scaleMat * m_ViewCamera.m_ProjMat;
SetCamera(m_ViewCamera, m_CullCamera);
CVector4D camPlane(0, 1, 0, -wm.m_WaterHeight);
SetObliqueFrustumClipping(camPlane, -1);
// Save the model-view-projection matrix so the shaders can use it for projective texturing
wm.m_RefractionMatrix = GetModelViewProjectionMatrix();
// Make the depth buffer work backwards; there seems to be some oddness with
// oblique frustum clipping and the "sign" parameter here
glClearDepth(0);
glClearColor(0.5f, 0.5f, 0.5f, 1.0f); // a neutral gray to blend in with shores
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glDepthFunc(GL_GEQUAL);
// Render terrain and models
RenderPatches();
oglCheck();
RenderModels();
oglCheck();
RenderTransparentModels();
oglCheck();
// Copy the image to a texture
pglActiveTextureARB(GL_TEXTURE0_ARB);
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, wm.m_RefractionTexture);
glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0,
wm.m_RefractionTextureSize, wm.m_RefractionTextureSize);
//Reset old camera and re-enable backface culling
SetCamera(normalCamera, m_CullCamera);
glEnable(GL_CULL_FACE);
glClearDepth(1);
glDepthFunc(GL_LEQUAL);
}
///////////////////////////////////////////////////////////////////////////////////////////////////
// FlushFrame: force rendering of any batched objects
void CRenderer::FlushFrame()
{
oglCheck();
// Prepare model renderers
PROFILE_START("prepare models");
m->Model.Normal->PrepareModels();
m->Model.Player->PrepareModels();
if (m->Model.Normal != m->Model.NormalInstancing)
m->Model.NormalInstancing->PrepareModels();
if (m->Model.Player != m->Model.PlayerInstancing)
m->Model.PlayerInstancing->PrepareModels();
m->Model.Transp->PrepareModels();
PROFILE_END("prepare models");
PROFILE_START("prepare terrain");
m->terrainRenderer->PrepareForRendering();
PROFILE_END("prepare terrain");
# Added tool for viewing models and animations outside the game. Atlas: Added ActorViewer. Moved GL canvas into separate class for shared use. Disabled message-handling callback while blocked on the game, and stopped creating dialog boxes inside the game thread in order to avoid deadlocks (hopefully). Support multiple Views (for independent sets of camera/update/render code). Recalculate territory boundaries when necessary. Changed default list of animations to match those currently used by actors. # Tidied up more code. Moved some more #includes out of .h files, to minimise unnecessary compilation. MathUtil: Deleted unused/unuseful macros (M_PI (use PI instead), M_PI_2 (use PI/2), MAX3, ABS (use abs)). ObjectManager: Removed some ScEd-specific things. Unit: Moved creation out of UnitManager, so units can be created without adding to the manager. Changed CStr8 to the more conventional CStr. app_hooks: Removed warning for setting multiple times. win: Restored SEH catcher. GameSetup, GameView: Removed RenderNoCull, because it doesn't seem to do what it says it does ("force renderer to load everything") since we're loading-on-demand most stuff and it doesn't seem especially useful since we'd prefer to minimise loading times (but feel free to correct me if I'm wrong). (And because it crashes when things need to be initialised in a different order, so it's easier to remove than to understand and fix it.) PatchRData, Renderer: Work sensibly when there's no game (hence no LOS manager, water, etc). LOSManager: Use entity position instead of actor position when possible. TerritoryManager: Allow delayed recalculations (so Atlas can issue lots of move+recalculate commands per frame). Cinematic: Non-pointer wxTimer, so it doesn't leak and doesn't have to be deleted manually. This was SVN commit r4261.
2006-08-28 19:36:42 +02:00
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();
# Added tool for viewing models and animations outside the game. Atlas: Added ActorViewer. Moved GL canvas into separate class for shared use. Disabled message-handling callback while blocked on the game, and stopped creating dialog boxes inside the game thread in order to avoid deadlocks (hopefully). Support multiple Views (for independent sets of camera/update/render code). Recalculate territory boundaries when necessary. Changed default list of animations to match those currently used by actors. # Tidied up more code. Moved some more #includes out of .h files, to minimise unnecessary compilation. MathUtil: Deleted unused/unuseful macros (M_PI (use PI instead), M_PI_2 (use PI/2), MAX3, ABS (use abs)). ObjectManager: Removed some ScEd-specific things. Unit: Moved creation out of UnitManager, so units can be created without adding to the manager. Changed CStr8 to the more conventional CStr. app_hooks: Removed warning for setting multiple times. win: Restored SEH catcher. GameSetup, GameView: Removed RenderNoCull, because it doesn't seem to do what it says it does ("force renderer to load everything") since we're loading-on-demand most stuff and it doesn't seem especially useful since we'd prefer to minimise loading times (but feel free to correct me if I'm wrong). (And because it crashes when things need to be initialised in a different order, so it's easier to remove than to understand and fix it.) PatchRData, Renderer: Work sensibly when there's no game (hence no LOS manager, water, etc). LOSManager: Use entity position instead of actor position when possible. TerritoryManager: Allow delayed recalculations (so Atlas can issue lots of move+recalculate commands per frame). Cinematic: Non-pointer wxTimer, so it doesn't leak and doesn't have to be deleted manually. This was SVN commit r4261.
2006-08-28 19:36:42 +02:00
if (m_WaterManager->m_RenderWater && m_Options.m_FancyWater)
{
// render reflected and refracted scenes, then re-clear the screen
RenderReflections();
RenderRefractions();
glClearColor(m_ClearColor[0],m_ClearColor[1],m_ClearColor[2],m_ClearColor[3]);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}
// render sky; this is done before everything so that
// (a) we can use a box around the camera instead of placing it "infinitely far away"
// (we just disable depth write so that this doesn't affect future rendering)
// (b) transparent objects properly overlap the sky
if (m_SkyManager->m_RenderSky)
{
MICROLOG(L"render sky");
m->skyManager.RenderSky();
oglCheck();
}
// render submitted patches and models
MICROLOG(L"render patches");
RenderPatches();
oglCheck();
g_Game->GetWorld()->GetTerritoryManager()->renderTerritories();
oglCheck();
// render debug-related terrain overlays
TerrainOverlay::RenderOverlays();
oglCheck();
MICROLOG(L"render models");
RenderModels();
oglCheck();
// render transparent stuff, so it can overlap models/terrain
MICROLOG(L"render transparent");
RenderTransparentModels();
oglCheck();
// render water
# Added tool for viewing models and animations outside the game. Atlas: Added ActorViewer. Moved GL canvas into separate class for shared use. Disabled message-handling callback while blocked on the game, and stopped creating dialog boxes inside the game thread in order to avoid deadlocks (hopefully). Support multiple Views (for independent sets of camera/update/render code). Recalculate territory boundaries when necessary. Changed default list of animations to match those currently used by actors. # Tidied up more code. Moved some more #includes out of .h files, to minimise unnecessary compilation. MathUtil: Deleted unused/unuseful macros (M_PI (use PI instead), M_PI_2 (use PI/2), MAX3, ABS (use abs)). ObjectManager: Removed some ScEd-specific things. Unit: Moved creation out of UnitManager, so units can be created without adding to the manager. Changed CStr8 to the more conventional CStr. app_hooks: Removed warning for setting multiple times. win: Restored SEH catcher. GameSetup, GameView: Removed RenderNoCull, because it doesn't seem to do what it says it does ("force renderer to load everything") since we're loading-on-demand most stuff and it doesn't seem especially useful since we'd prefer to minimise loading times (but feel free to correct me if I'm wrong). (And because it crashes when things need to be initialised in a different order, so it's easier to remove than to understand and fix it.) PatchRData, Renderer: Work sensibly when there's no game (hence no LOS manager, water, etc). LOSManager: Use entity position instead of actor position when possible. TerritoryManager: Allow delayed recalculations (so Atlas can issue lots of move+recalculate commands per frame). Cinematic: Non-pointer wxTimer, so it doesn't leak and doesn't have to be deleted manually. This was SVN commit r4261.
2006-08-28 19:36:42 +02:00
if (m_WaterManager->m_RenderWater && g_Game)
{
MICROLOG(L"render water");
m->terrainRenderer->RenderWater();
oglCheck();
// render transparent stuff again, so it can overlap the water
MICROLOG(L"render transparent 2");
RenderTransparentModels();
oglCheck();
// TODO: Maybe think of a better way to deal with transparent objects;
// they can appear both under and above water (seaweed vs. trees), but doing
// 2 renders causes (a) inefficiency and (b) darker over-water objects (e.g.
// trees) than usual because the transparent bits get overwritten twice.
// This doesn't look particularly bad, but it is noticeable if you try
// turning the water off. On the other hand every user will have water
// on all the time, so it might not be worth worrying about.
}
// Clean up texture blend mode so particles and other things render OK
// (really this should be cleaned up by whoever set it)
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
//// Particle Engine Rendering.
MICROLOG(L"render particles");
CParticleEngine::GetInstance()->renderParticles();
oglCheck();
// render debug lines
if (m_DisplayFrustum)
{
MICROLOG(L"display frustum");
DisplayFrustum();
m->shadow->RenderDebugDisplay();
oglCheck();
}
// empty lists
MICROLOG(L"empty lists");
m->terrainRenderer->EndFrame();
// Finish model renderers
m->Model.Normal->EndFrame();
m->Model.Player->EndFrame();
if (m->Model.Normal != m->Model.NormalInstancing)
m->Model.NormalInstancing->EndFrame();
if (m->Model.Player != m->Model.PlayerInstancing)
m->Model.PlayerInstancing->EndFrame();
m->Model.Transp->EndFrame();
}
///////////////////////////////////////////////////////////////////////////////////////////////////
// EndFrame: signal frame end
void CRenderer::EndFrame()
{
g_Renderer.SetTexture(0,0);
static bool once=false;
if (!once && glGetError()) {
LOG(ERROR, LOG_CATEGORY, "CRenderer::EndFrame: GL errors occurred");
once=true;
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////
// DisplayFrustum: debug displays
// - white: cull camera frustum
// - red: bounds of shadow casting objects
void CRenderer::DisplayFrustum()
{
glDepthMask(0);
glDisable(GL_CULL_FACE);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glColor4ub(255,255,255,64);
m_CullCamera.Render(2);
glDisable(GL_BLEND);
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
glColor3ub(255,255,255);
m_CullCamera.Render(2);
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
glEnable(GL_CULL_FACE);
glDepthMask(1);
}
///////////////////////////////////////////////////////////////////////////////////////////////////
// SetCamera: setup projection and transform of camera and adjust viewport to current view
void CRenderer::SetCamera(const CCamera& viewCamera, const CCamera& cullCamera)
{
m_ViewCamera = viewCamera;
m_CullCamera = cullCamera;
CMatrix3D view;
m_ViewCamera.m_Orientation.GetInverse(view);
const CMatrix3D& proj = m_ViewCamera.GetProjection();
glMatrixMode(GL_PROJECTION);
glLoadMatrixf(&proj._11);
glMatrixMode(GL_MODELVIEW);
glLoadMatrixf(&view._11);
SetViewport(m_ViewCamera.GetViewPort());
}
void CRenderer::SetViewport(const SViewPort &vp)
{
glViewport(vp.m_X,vp.m_Y,vp.m_Width,vp.m_Height);
}
void CRenderer::Submit(CPatch* patch)
{
m->terrainRenderer->Submit(patch);
}
void CRenderer::Submit(CModel* model)
{
if (model->GetFlags() & MODELFLAG_CASTSHADOWS) {
PROFILE( "updating shadow bounds" );
m->shadow->AddShadowedBound(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 (canUseInstancing)
m->Model.PlayerInstancing->Submit(model);
else
m->Model.Player->Submit(model);
}
else if (model->GetMaterial().UsesAlpha())
{
m->Model.Transp->Submit(model);
}
else
{
if (canUseInstancing)
m->Model.NormalInstancing->Submit(model);
else
m->Model.Normal->Submit(model);
}
}
void CRenderer::Submit(CSprite* UNUSED(sprite))
{
}
void CRenderer::Submit(CParticleSys* UNUSED(psys))
{
}
void CRenderer::Submit(COverlay* UNUSED(overlay))
{
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 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;
// Errored textures will give a handle of -1
if (h == -1)
h = 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)path_package_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)path_package_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;
}
///////////////////////////////////////////////////////////////////////////////////////////////////
// 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));
}
jsval CRenderer::JSI_GetUseDepthTexture(JSContext*)
{
return ToJSVal(m->shadow->GetUseDepthTexture());
}
void CRenderer::JSI_SetUseDepthTexture(JSContext* ctx, jsval newval)
{
bool depthTexture;
if (!ToPrimitive(ctx, newval, depthTexture))
return;
m->shadow->SetUseDepthTexture(depthTexture);
}
jsval CRenderer::JSI_GetDepthTextureBits(JSContext*)
{
return ToJSVal(m->shadow->GetDepthTextureBits());
}
void CRenderer::JSI_SetDepthTextureBits(JSContext* ctx, jsval newval)
{
int depthTextureBits;
if (!ToPrimitive(ctx, newval, depthTextureBits))
return;
m->shadow->SetDepthTextureBits(depthTextureBits);
}
jsval CRenderer::JSI_GetSky(JSContext*)
{
return ToJSVal(m->skyManager.GetSkySet());
}
void CRenderer::JSI_SetSky(JSContext* ctx, jsval newval)
{
CStrW skySet;
if (!ToPrimitive<CStrW>(ctx, newval, skySet)) return;
m->skyManager.SetSkySet(skySet);
}
jsval CRenderer::JSI_GetHorizonHeight(JSContext*)
{
return ToJSVal(m->skyManager.m_HorizonHeight);
}
void CRenderer::JSI_SetHorizonHeight(JSContext* ctx, jsval newval)
{
float value;
if (!ToPrimitive<float>(ctx, newval, value)) return;
m->skyManager.m_HorizonHeight = value;
}
jsval CRenderer::JSI_GetFancyWater(JSContext*)
{
return ToJSVal(m_Options.m_FancyWater);
}
void CRenderer::JSI_SetFancyWater(JSContext* ctx, jsval newval)
{
bool fast;
if (!ToPrimitive(ctx, newval, fast))
return;
m_Options.m_FancyWater = fast;
}
jsval CRenderer::JSI_GetWaterShininess(JSContext*)
{
return ToJSVal(m->waterManager.m_Shininess);
}
void CRenderer::JSI_SetWaterShininess(JSContext* ctx, jsval newval)
{
float value;
if (!ToPrimitive<float>(ctx, newval, value)) return;
m->waterManager.m_Shininess = value;
}
jsval CRenderer::JSI_GetWaterSpecularStrength(JSContext*)
{
return ToJSVal(m->waterManager.m_SpecularStrength);
}
void CRenderer::JSI_SetWaterSpecularStrength(JSContext* ctx, jsval newval)
{
float value;
if (!ToPrimitive<float>(ctx, newval, value)) return;
m->waterManager.m_SpecularStrength = value;
}
jsval CRenderer::JSI_GetWaterMurkiness(JSContext*)
{
return ToJSVal(m->waterManager.m_Murkiness);
}
void CRenderer::JSI_SetWaterMurkiness(JSContext* ctx, jsval newval)
{
float value;
if (!ToPrimitive<float>(ctx, newval, value)) return;
m->waterManager.m_Murkiness = value;
}
jsval CRenderer::JSI_GetWaterWaviness(JSContext*)
{
return ToJSVal(m->waterManager.m_Waviness);
}
void CRenderer::JSI_SetWaterWaviness(JSContext* ctx, jsval newval)
{
float value;
if (!ToPrimitive<float>(ctx, newval, value)) return;
m->waterManager.m_Waviness = value;
}
jsval CRenderer::JSI_GetWaterRepeatPeriod(JSContext*)
{
return ToJSVal(m->waterManager.m_RepeatPeriod);
}
void CRenderer::JSI_SetWaterRepeatPeriod(JSContext* ctx, jsval newval)
{
float value;
if (!ToPrimitive<float>(ctx, newval, value)) return;
m->waterManager.m_RepeatPeriod = value;
}
void CRenderer::JSI_SetWaterReflectionTintStrength(JSContext* ctx, jsval newval)
{
float value;
if (!ToPrimitive<float>(ctx, newval, value)) return;
m->waterManager.m_ReflectionTintStrength = value;
}
jsval CRenderer::JSI_GetWaterReflectionTintStrength(JSContext*)
{
return ToJSVal(m->waterManager.m_ReflectionTintStrength);
}
void CRenderer::ScriptingInit()
{
AddProperty(L"fastPlayerColor", &CRenderer::JSI_GetFastPlayerColor, &CRenderer::JSI_SetFastPlayerColor);
AddProperty(L"renderpath", &CRenderer::JSI_GetRenderPath, &CRenderer::JSI_SetRenderPath);
AddProperty(L"useDepthTexture", &CRenderer::JSI_GetUseDepthTexture, &CRenderer::JSI_SetUseDepthTexture);
AddProperty(L"sortAllTransparent", &CRenderer::m_SortAllTransparent);
AddProperty(L"fastNormals", &CRenderer::m_FastNormals);
AddProperty(L"displayFrustum", &CRenderer::m_DisplayFrustum);
AddProperty(L"shadowZBias", &CRenderer::m_ShadowZBias);
AddProperty(L"disableCopyShadow", &CRenderer::m_DisableCopyShadow);
AddProperty(L"depthTextureBits", &CRenderer::JSI_GetDepthTextureBits, &CRenderer::JSI_SetDepthTextureBits);
AddProperty(L"skySet", &CRenderer::JSI_GetSky, &CRenderer::JSI_SetSky);
AddProperty(L"horizonHeight", &CRenderer::JSI_GetHorizonHeight, &CRenderer::JSI_SetHorizonHeight);
AddProperty(L"fancyWater", &CRenderer::JSI_GetFancyWater, &CRenderer::JSI_SetFancyWater);
AddProperty(L"waterShininess", &CRenderer::JSI_GetWaterShininess, &CRenderer::JSI_SetWaterShininess);
AddProperty(L"waterSpecularStrength", &CRenderer::JSI_GetWaterSpecularStrength, &CRenderer::JSI_SetWaterSpecularStrength);
AddProperty(L"waterWaviness", &CRenderer::JSI_GetWaterWaviness, &CRenderer::JSI_SetWaterWaviness);
AddProperty(L"waterRepeatPeriod", &CRenderer::JSI_GetWaterRepeatPeriod, &CRenderer::JSI_SetWaterRepeatPeriod);
AddProperty(L"waterMurkiness", &CRenderer::JSI_GetWaterMurkiness, &CRenderer::JSI_SetWaterMurkiness);
AddProperty(L"waterReflTintStrength", &CRenderer::JSI_GetWaterReflectionTintStrength, &CRenderer::JSI_SetWaterReflectionTintStrength);
CJSObject<CRenderer>::ScriptingInit("Renderer");
}