prefect
5087732fe3
data model. Profile.cpp implements AbstractProfileTable so the original functionality of the profiling display remains. CRenderer also contains an AbstractProfileTable implementation that displays the rendering stats. Switch through different profile views using F11. This was SVN commit r3154.
1811 lines
49 KiB
C++
Executable File
1811 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 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;
|
|
}
|
|
|
|
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);
|
|
glPolygonMode(GL_FRONT_AND_BACK,GL_FILL);
|
|
glEnable(GL_DEPTH_TEST);
|
|
glEnable(GL_CULL_FACE);
|
|
glDepthMask(false);
|
|
|
|
float time = (float) get_time();
|
|
|
|
float period = 1.6f;
|
|
int curTex = (int)(fmod(time, period)*(60/period));
|
|
ogl_tex_bind(m_WaterTexture[curTex], 0);
|
|
|
|
glMatrixMode(GL_TEXTURE);
|
|
glLoadIdentity();
|
|
float tx = -fmod(time, 20.0f)/20.0f;
|
|
float ty = fmod(time, 35.0f)/35.0f;
|
|
glTranslatef(tx, ty, 0);
|
|
|
|
pglActiveTextureARB(GL_TEXTURE0);
|
|
glEnable(GL_TEXTURE_2D);
|
|
|
|
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);
|
|
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(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);
|
|
}
|
|
|
|
|
|
|
|
|
|
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]);
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// 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");
|
|
}
|
|
|