mirror of
https://gitea.wildfiregames.com/0ad/0ad
synced 2026-08-15 14:43:32 -07:00
Reworked model and terrain rendering to use new VertexBuffer classes; many other smaller changes.
This was SVN commit r411.
This commit is contained in:
parent
4f8ce4b1d4
commit
a7d0c194ad
8 changed files with 981 additions and 750 deletions
|
|
@ -8,7 +8,12 @@
|
|||
#include "ModelRData.h"
|
||||
#include "Model.h"
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
// shared list of all submitted models this frame
|
||||
std::vector<CModel*> CModelRData::m_Models;
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
// CModelRData constructor
|
||||
CModelRData::CModelRData(CModel* model)
|
||||
: m_Model(model), m_Vertices(0), m_Normals(0), m_Indices(0), m_VB(0), m_Flags(0)
|
||||
{
|
||||
|
|
@ -17,13 +22,17 @@ CModelRData::CModelRData(CModel* model)
|
|||
Build();
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
// CModelRData destructor
|
||||
CModelRData::~CModelRData()
|
||||
{
|
||||
{
|
||||
// clean up system copies of data
|
||||
delete[] m_Indices;
|
||||
delete[] m_Vertices;
|
||||
delete[] m_Normals;
|
||||
if (m_VB) {
|
||||
glGenBuffersARB(1,(GLuint*) &m_VB);
|
||||
if (m_VB) {
|
||||
// release vertex buffer chunks
|
||||
g_VBMan.Release(m_VB);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -41,22 +50,27 @@ void CModelRData::Build()
|
|||
}
|
||||
|
||||
void CModelRData::BuildIndices()
|
||||
{
|
||||
CModelDef* mdef=m_Model->GetModelDef();
|
||||
|
||||
{
|
||||
CModelDef* mdef=m_Model->GetModelDef();
|
||||
assert(mdef);
|
||||
|
||||
// must have a valid vertex buffer by this point so we know where indices are supposed to start
|
||||
assert(m_VB);
|
||||
|
||||
// allocate indices if we haven't got any already
|
||||
if (!m_Indices) {
|
||||
m_Indices=new u16[mdef->GetNumFaces()*3];
|
||||
}
|
||||
|
||||
// build indices
|
||||
u32 base=m_VB->m_Index;
|
||||
u32 indices=0;
|
||||
SModelFace* faces=mdef->GetFaces();
|
||||
for (int j=0; j<mdef->GetNumFaces(); j++) {
|
||||
SModelFace& face=faces[j];
|
||||
m_Indices[indices++]=face.m_Verts[0];
|
||||
m_Indices[indices++]=face.m_Verts[1];
|
||||
m_Indices[indices++]=face.m_Verts[2];
|
||||
m_Indices[indices++]=face.m_Verts[0]+base;
|
||||
m_Indices[indices++]=face.m_Verts[1]+base;
|
||||
m_Indices[indices++]=face.m_Verts[2]+base;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -77,30 +91,46 @@ static SColor4ub ConvertColor(const RGBColor& src)
|
|||
return result;
|
||||
}
|
||||
|
||||
static CVector3D SkinPoint(const SModelVertex& vertex,const CMatrix3D* matrices)
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// SkinPoint: skin the vertex position using it's blend data and given bone matrices
|
||||
static void SkinPoint(const SModelVertex& vertex,const CMatrix3D* matrices,CVector3D& result)
|
||||
{
|
||||
CVector3D result(0,0,0),tmp;
|
||||
|
||||
for (u32 i=0;vertex.m_Blend.m_Bone[i]!=0xff && i<SVertexBlend::SIZE;i++) {
|
||||
const CMatrix3D& m=matrices[vertex.m_Blend.m_Bone[i]];
|
||||
m.Transform(vertex.m_Coords,tmp);
|
||||
result+=tmp*vertex.m_Blend.m_Weight[i];
|
||||
}
|
||||
|
||||
return result;
|
||||
CVector3D tmp;
|
||||
const SVertexBlend& blend=vertex.m_Blend;
|
||||
|
||||
// must have at least one valid bone if we're using SkinPoint
|
||||
assert(blend.m_Bone[0]!=0xff);
|
||||
|
||||
const CMatrix3D& m=matrices[blend.m_Bone[0]];
|
||||
m.Transform(vertex.m_Coords,result);
|
||||
result*=blend.m_Weight[0];
|
||||
|
||||
for (u32 i=1;blend.m_Bone[i]!=0xff && i<SVertexBlend::SIZE;i++) {
|
||||
const CMatrix3D& m=matrices[blend.m_Bone[i]];
|
||||
m.Transform(vertex.m_Coords,tmp);
|
||||
result+=tmp*blend.m_Weight[i];
|
||||
}
|
||||
}
|
||||
|
||||
static CVector3D SkinNormal(const SModelVertex& vertex,const CMatrix3D* invmatrices)
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// SkinPoint: skin the vertex normal using it's blend data and given bone matrices
|
||||
static void SkinNormal(const SModelVertex& vertex,const CMatrix3D* invmatrices,CVector3D& result)
|
||||
{
|
||||
CVector3D result(0,0,0),tmp;
|
||||
CVector3D tmp;
|
||||
const SVertexBlend& blend=vertex.m_Blend;
|
||||
|
||||
// must have at least one valid bone if we're using SkinNormal
|
||||
assert(blend.m_Bone[0]!=0xff);
|
||||
|
||||
const CMatrix3D& m=invmatrices[blend.m_Bone[0]];
|
||||
m.RotateTransposed(vertex.m_Norm,result);
|
||||
result*=blend.m_Weight[0];
|
||||
|
||||
for (u32 i=0;vertex.m_Blend.m_Bone[i]!=0xff && i<SVertexBlend::SIZE;i++) {
|
||||
const CMatrix3D& m=invmatrices[vertex.m_Blend.m_Bone[i]];
|
||||
for (u32 i=1;vertex.m_Blend.m_Bone[i]!=0xff && i<SVertexBlend::SIZE;i++) {
|
||||
const CMatrix3D& m=invmatrices[blend.m_Bone[i]];
|
||||
m.RotateTransposed(vertex.m_Norm,tmp);
|
||||
result+=tmp*vertex.m_Blend.m_Weight[i];
|
||||
result+=tmp*blend.m_Weight[i];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void CModelRData::BuildVertices()
|
||||
|
|
@ -116,18 +146,21 @@ void CModelRData::BuildVertices()
|
|||
// build vertices
|
||||
u32 numVertices=mdef->GetNumVertices();
|
||||
SModelVertex* vertices=mdef->GetVertices();
|
||||
if (m_Model->GetBoneMatrices()) {
|
||||
// boned model - calculate skinned vertex positions/normals
|
||||
const CMatrix3D* bonematrices=m_Model->GetBoneMatrices();
|
||||
if (bonematrices) {
|
||||
// boned model - calculate skinned vertex positions/normals
|
||||
const CMatrix3D* invbonematrices=m_Model->GetInvBoneMatrices();
|
||||
for (uint j=0; j<numVertices; j++) {
|
||||
m_Vertices[j].m_Position=SkinPoint(vertices[j],m_Model->GetBoneMatrices());
|
||||
m_Normals[j]=SkinNormal(vertices[j],m_Model->GetInvBoneMatrices());
|
||||
SkinPoint(vertices[j],bonematrices,m_Vertices[j].m_Position);
|
||||
SkinNormal(vertices[j],invbonematrices,m_Normals[j]);
|
||||
}
|
||||
} else {
|
||||
// just copy regular positions, transform normals to world space
|
||||
const CMatrix3D& trans=m_Model->GetInvTransform();
|
||||
const CMatrix3D& transform=m_Model->GetTransform();
|
||||
const CMatrix3D& invtransform=m_Model->GetInvTransform();
|
||||
for (uint j=0; j<numVertices; j++) {
|
||||
m_Vertices[j].m_Position=vertices[j].m_Coords;
|
||||
m_Normals[j]=trans.RotateTransposed(vertices[j].m_Norm);
|
||||
transform.Transform(vertices[j].m_Coords,m_Vertices[j].m_Position);
|
||||
invtransform.RotateTransposed(vertices[j].m_Norm,m_Normals[j]);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -137,37 +170,22 @@ void CModelRData::BuildVertices()
|
|||
m_Vertices[j].m_UVs[1]=1-vertices[j].m_V;
|
||||
g_Renderer.m_SHCoeffsUnits.Evaluate(m_Normals[j],m_Vertices[j].m_Color);
|
||||
}
|
||||
|
||||
if (g_Renderer.m_Caps.m_VBO) {
|
||||
if (!m_VB) {
|
||||
glGenBuffersARB(1,(GLuint*) &m_VB);
|
||||
glBindBufferARB(GL_ARRAY_BUFFER_ARB,m_VB);
|
||||
glBufferDataARB(GL_ARRAY_BUFFER_ARB,mdef->GetNumVertices()*sizeof(SVertex),0,mdef->GetNumBones() ? GL_DYNAMIC_DRAW_ARB : GL_STATIC_DRAW_ARB);
|
||||
}
|
||||
glBindBufferARB(GL_ARRAY_BUFFER_ARB,m_VB);
|
||||
glBufferSubDataARB(GL_ARRAY_BUFFER_ARB,0,mdef->GetNumVertices()*sizeof(SVertex),m_Vertices);
|
||||
}
|
||||
|
||||
// upload everything to vertex buffer - create one if necessary
|
||||
if (!m_VB) {
|
||||
m_VB=g_VBMan.Allocate(sizeof(SVertex),mdef->GetNumVertices(),mdef->GetNumBones() ? true : false);
|
||||
}
|
||||
m_VB->m_Owner->UpdateChunkVertices(m_VB,m_Vertices);
|
||||
}
|
||||
|
||||
|
||||
void CModelRData::RenderStreams(u32 streamflags,bool transparentPass)
|
||||
void CModelRData::RenderStreams(u32 streamflags)
|
||||
{
|
||||
// ignore transparent passes if this is a transparent object
|
||||
if (!transparentPass && (m_Flags & MODELRDATA_FLAG_TRANSPARENT)) {
|
||||
return;
|
||||
}
|
||||
|
||||
CModelDef* mdldef=(CModelDef*) m_Model->GetModelDef();
|
||||
|
||||
if (streamflags & STREAM_UV0) g_Renderer.SetTexture(0,m_Model->GetTexture());
|
||||
|
||||
u8* base;
|
||||
if (g_Renderer.m_Caps.m_VBO) {
|
||||
glBindBufferARB(GL_ARRAY_BUFFER_ARB,m_VB);
|
||||
base=0;
|
||||
} else {
|
||||
base=(u8*) &m_Vertices[0];
|
||||
}
|
||||
u8* base=m_VB->m_Owner->Bind();
|
||||
|
||||
// set vertex pointers
|
||||
u32 stride=sizeof(SVertex);
|
||||
|
|
@ -182,11 +200,7 @@ void CModelRData::RenderStreams(u32 streamflags,bool transparentPass)
|
|||
|
||||
// bump stats
|
||||
g_Renderer.m_Stats.m_DrawCalls++;
|
||||
if (transparentPass) {
|
||||
g_Renderer.m_Stats.m_TransparentTris+=numFaces;
|
||||
} else {
|
||||
g_Renderer.m_Stats.m_ModelTris+=numFaces;
|
||||
}
|
||||
g_Renderer.m_Stats.m_ModelTris+=numFaces;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -251,9 +265,9 @@ float CModelRData::BackToFrontIndexSort(CMatrix3D& objToCam)
|
|||
u32 indices=0;
|
||||
for (i=0;i<numFaces;i++) {
|
||||
SModelFace& face=faces[IndexSorter[i].first];
|
||||
m_Indices[indices++]=face.m_Verts[0];
|
||||
m_Indices[indices++]=face.m_Verts[1];
|
||||
m_Indices[indices++]=face.m_Verts[2];
|
||||
m_Indices[indices++]=face.m_Verts[0]+m_VB->m_Index;
|
||||
m_Indices[indices++]=face.m_Verts[1]+m_VB->m_Index;
|
||||
m_Indices[indices++]=face.m_Verts[2]+m_VB->m_Index;
|
||||
}
|
||||
|
||||
// clear list for next call
|
||||
|
|
@ -261,3 +275,88 @@ float CModelRData::BackToFrontIndexSort(CMatrix3D& objToCam)
|
|||
|
||||
return mindist;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// SubmitBatches: submit batches for this model to the vertex buffer
|
||||
void CModelRData::SubmitBatches()
|
||||
{
|
||||
assert(m_VB);
|
||||
m_VB->m_Owner->AppendBatch(m_VB,m_Model->GetTexture()->GetHandle(),m_Model->GetModelDef()->GetNumFaces()*3,m_Indices);
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// RenderModels: render all submitted models; assumes necessary client states already enabled,
|
||||
// and texture environment already setup as required
|
||||
void CModelRData::RenderModels(u32 streamflags)
|
||||
{
|
||||
uint i;
|
||||
#if 1
|
||||
// submit batches for each model to the vertex buffer
|
||||
for (i=0;i<m_Models.size();++i) {
|
||||
CModelRData* modeldata=(CModelRData*) m_Models[i]->GetRenderData();
|
||||
modeldata->SubmitBatches();
|
||||
}
|
||||
|
||||
// step through all accumulated batches
|
||||
const std::list<CVertexBuffer*>& buffers=g_VBMan.GetBufferList();
|
||||
std::list<CVertexBuffer*>::const_iterator iter;
|
||||
for (iter=buffers.begin();iter!=buffers.end();++iter) {
|
||||
CVertexBuffer* buffer=*iter;
|
||||
|
||||
// any batches in this VB?
|
||||
const std::vector<CVertexBuffer::Batch*>& batches=buffer->GetBatches();
|
||||
if (batches.size()>0) {
|
||||
u8* base=buffer->Bind();
|
||||
|
||||
// setup data pointers
|
||||
u32 stride=sizeof(SVertex);
|
||||
glVertexPointer(3,GL_FLOAT,stride,base+offsetof(SVertex,m_Position));
|
||||
if (streamflags & STREAM_COLOR) glColorPointer(3,GL_FLOAT,stride,base+offsetof(SVertex,m_Color));
|
||||
if (streamflags & STREAM_UV0) glTexCoordPointer(2,GL_FLOAT,stride,base+offsetof(SVertex,m_UVs[0]));
|
||||
|
||||
// render each batch
|
||||
for (i=0;i<batches.size();++i) {
|
||||
const CVertexBuffer::Batch* batch=batches[i];
|
||||
if (batch->m_IndexData.size()>0) {
|
||||
if (streamflags & STREAM_UV0) g_Renderer.BindTexture(0,tex_id(batch->m_Texture));
|
||||
for (uint j=0;j<batch->m_IndexData.size();j++) {
|
||||
glDrawElements(GL_TRIANGLES,batch->m_IndexData[j].first,GL_UNSIGNED_SHORT,batch->m_IndexData[j].second);
|
||||
g_Renderer.m_Stats.m_DrawCalls++;
|
||||
g_Renderer.m_Stats.m_ModelTris+=batch->m_IndexData[j].first/2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// everything rendered; empty out batch lists
|
||||
g_VBMan.ClearBatchIndices();
|
||||
#else
|
||||
for (i=0;i<m_Models.size();++i) {
|
||||
CModelRData* modeldata=(CModelRData*) m_Models[i]->GetRenderData();
|
||||
modeldata->RenderStreams(streamflags);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Submit: submit a model to render this frame
|
||||
void CModelRData::Submit(CModel* model)
|
||||
{
|
||||
CModelRData* data=(CModelRData*) model->GetRenderData();
|
||||
if (data==0) {
|
||||
// no renderdata for model, create it now
|
||||
data=new CModelRData(model);
|
||||
model->SetRenderData(data);
|
||||
} else {
|
||||
data->Update();
|
||||
}
|
||||
|
||||
if (data->GetFlags() & MODELRDATA_FLAG_TRANSPARENT) {
|
||||
// add this mode to the transparency renderer for later processing - calculate
|
||||
// transform matrix
|
||||
g_TransparencyRenderer.Add(model);
|
||||
} else {
|
||||
// add to regular model list
|
||||
m_Models.push_back(model);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ public:
|
|||
~CModelRData();
|
||||
|
||||
void Update();
|
||||
void RenderStreams(u32 streamflags,bool transparentPass=false);
|
||||
void RenderStreams(u32 streamflags);
|
||||
|
||||
// return render flags for this model
|
||||
u32 GetFlags() const { return m_Flags; }
|
||||
|
|
@ -26,6 +26,14 @@ public:
|
|||
// sort indices of this object from back to front according to given
|
||||
// object to camera space transform; return sqrd distance to centre of nearest triangle
|
||||
float BackToFrontIndexSort(CMatrix3D& objToCam);
|
||||
|
||||
// submit a model to render this frame
|
||||
static void Submit(CModel* model);
|
||||
// clear per frame patch list
|
||||
static void ClearSubmissions() { m_Models.clear(); }
|
||||
|
||||
// render all submitted models
|
||||
static void RenderModels(u32 streamflags);
|
||||
|
||||
private:
|
||||
// build this renderdata object
|
||||
|
|
@ -34,6 +42,8 @@ private:
|
|||
void BuildVertices();
|
||||
void BuildIndices();
|
||||
|
||||
// submit batches for this model to the vertex buffer
|
||||
void SubmitBatches();
|
||||
|
||||
struct SVertex {
|
||||
// vertex position
|
||||
|
|
@ -46,8 +56,8 @@ private:
|
|||
|
||||
// owner model
|
||||
CModel* m_Model;
|
||||
// handle to models vertex buffer
|
||||
u32 m_VB;
|
||||
// vertex buffer object for this model
|
||||
CVertexBuffer::VBChunk* m_VB;
|
||||
// model render vertices
|
||||
SVertex* m_Vertices;
|
||||
// transformed vertex normals - required for recalculating lighting on skinned models
|
||||
|
|
@ -56,6 +66,8 @@ private:
|
|||
u16* m_Indices;
|
||||
// model render flags
|
||||
u32 m_Flags;
|
||||
// list of all submitted models
|
||||
static std::vector<CModel*> m_Models;
|
||||
};
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,11 @@
|
|||
#include "Renderer.h"
|
||||
#include "PatchRData.h"
|
||||
#include "AlphaMapCalculator.h"
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
// shared list of all submitted patches this frame
|
||||
std::vector<CPatch*> CPatchRData::m_Patches;
|
||||
|
||||
|
||||
const int BlendOffsets[8][2] = {
|
||||
{ 0, -1 },
|
||||
|
|
@ -19,18 +24,40 @@ const int BlendOffsets[8][2] = {
|
|||
{ 1, -1 }
|
||||
};
|
||||
|
||||
inline int clamp(int x,int min,int max)
|
||||
{
|
||||
if (x<min) return min;
|
||||
else if (x>max) return max;
|
||||
else return x;
|
||||
}
|
||||
|
||||
static SColor4ub ConvertColor(const RGBColor& src)
|
||||
{
|
||||
SColor4ub result;
|
||||
result.R=clamp(int(src.X*255),0,255);
|
||||
result.G=clamp(int(src.Y*255),0,255);
|
||||
result.B=clamp(int(src.Z*255),0,255);
|
||||
result.A=0xff;
|
||||
return result;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
// CPatchRData constructor
|
||||
CPatchRData::CPatchRData(CPatch* patch) : m_Patch(patch), m_Vertices(0), m_VBBase(0), m_VBBlends(0)
|
||||
{
|
||||
assert(patch);
|
||||
Build();
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
// CPatchRData destructor
|
||||
CPatchRData::~CPatchRData()
|
||||
{
|
||||
delete[] m_Vertices;
|
||||
if (m_VBBase) glDeleteBuffersARB(1,(GLuint*) &m_VBBase);
|
||||
if (m_VBBlends) glDeleteBuffersARB(1,(GLuint*) &m_VBBlends);
|
||||
// delete copy of vertex data
|
||||
delete[] m_Vertices;
|
||||
// release vertex buffer chunks
|
||||
if (m_VBBase) g_VBMan.Release(m_VBBase);
|
||||
if (m_VBBlends) g_VBMan.Release(m_VBBlends);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -226,48 +253,48 @@ void CPatchRData::BuildBlends()
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// now build outgoing splats
|
||||
m_BlendSplats.resize(splatTextures.size());
|
||||
int splatCount=0;
|
||||
|
||||
std::set<Handle>::iterator iter=splatTextures.begin();
|
||||
for (;iter!=splatTextures.end();++iter) {
|
||||
Handle tex=*iter;
|
||||
|
||||
SSplat& splat=m_BlendSplats[splatCount];
|
||||
splat.m_IndexStart=m_BlendIndices.size();
|
||||
splat.m_Texture=tex;
|
||||
|
||||
for (uint k=0;k<splats.size();k++) {
|
||||
if (splats[k].m_Texture==tex) {
|
||||
m_BlendIndices.push_back(splats[k].m_Indices[0]);
|
||||
m_BlendIndices.push_back(splats[k].m_Indices[1]);
|
||||
m_BlendIndices.push_back(splats[k].m_Indices[2]);
|
||||
m_BlendIndices.push_back(splats[k].m_Indices[3]);
|
||||
splat.m_IndexCount+=4;
|
||||
}
|
||||
}
|
||||
splatCount++;
|
||||
}
|
||||
|
||||
if (g_Renderer.m_Caps.m_VBO) {
|
||||
if (m_VBBlends) {
|
||||
// destroy old buffer
|
||||
glDeleteBuffersARB(1,(GLuint*) &m_VBBlends);
|
||||
} else {
|
||||
// generate buffer index
|
||||
glGenBuffersARB(1,(GLuint*) &m_VBBlends);
|
||||
}
|
||||
|
||||
// create new buffer
|
||||
glBindBufferARB(GL_ARRAY_BUFFER_ARB,m_VBBlends);
|
||||
glBufferDataARB(GL_ARRAY_BUFFER_ARB,m_BlendVertices.size()*sizeof(SBlendVertex),&m_BlendVertices[0],GL_STATIC_DRAW_ARB);
|
||||
}
|
||||
|
||||
// build vertex data
|
||||
if (m_VBBlends) {
|
||||
// release existing vertex buffer chunk
|
||||
g_VBMan.Release(m_VBBlends);
|
||||
}
|
||||
m_VBBlends=g_VBMan.Allocate(sizeof(SBlendVertex),m_BlendVertices.size(),false);
|
||||
m_VBBlends->m_Owner->UpdateChunkVertices(m_VBBlends,&m_BlendVertices[0]);
|
||||
|
||||
|
||||
// now build outgoing splats
|
||||
m_BlendSplats.resize(splatTextures.size());
|
||||
int splatCount=0;
|
||||
|
||||
u32 base=m_VBBlends->m_Index;
|
||||
std::set<Handle>::iterator iter=splatTextures.begin();
|
||||
for (;iter!=splatTextures.end();++iter) {
|
||||
Handle tex=*iter;
|
||||
|
||||
SSplat& splat=m_BlendSplats[splatCount];
|
||||
splat.m_IndexStart=m_BlendIndices.size();
|
||||
splat.m_Texture=tex;
|
||||
|
||||
for (uint k=0;k<splats.size();k++) {
|
||||
if (splats[k].m_Texture==tex) {
|
||||
m_BlendIndices.push_back(splats[k].m_Indices[0]+base);
|
||||
m_BlendIndices.push_back(splats[k].m_Indices[1]+base);
|
||||
m_BlendIndices.push_back(splats[k].m_Indices[2]+base);
|
||||
m_BlendIndices.push_back(splats[k].m_Indices[3]+base);
|
||||
splat.m_IndexCount+=4;
|
||||
}
|
||||
}
|
||||
splatCount++;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void CPatchRData::BuildIndices()
|
||||
{
|
||||
{
|
||||
// must have allocated some vertices before trying to build corresponding indices
|
||||
assert(m_VBBase);
|
||||
|
||||
// number of vertices in each direction in each patch
|
||||
int vsize=PATCH_SIZE+1;
|
||||
|
||||
|
|
@ -290,7 +317,8 @@ void CPatchRData::BuildIndices()
|
|||
|
||||
// now build base splats from interior textures
|
||||
m_Splats.resize(textures.size());
|
||||
|
||||
// build indices for base splats
|
||||
u32 base=m_VBBase->m_Index;
|
||||
for (uint i=0;i<m_Splats.size();i++) {
|
||||
Handle h=textures[i];
|
||||
|
||||
|
|
@ -301,66 +329,33 @@ void CPatchRData::BuildIndices()
|
|||
for (int j=0;j<PATCH_SIZE;j++) {
|
||||
for (int i=0;i<PATCH_SIZE;i++) {
|
||||
if (texgrid[j][i]==h){
|
||||
m_Indices.push_back(((j+0)*vsize+(i+0)));
|
||||
m_Indices.push_back(((j+0)*vsize+(i+1)));
|
||||
m_Indices.push_back(((j+1)*vsize+(i+1)));
|
||||
m_Indices.push_back(((j+1)*vsize+(i+0)));
|
||||
m_Indices.push_back(((j+0)*vsize+(i+0))+base);
|
||||
m_Indices.push_back(((j+0)*vsize+(i+1))+base);
|
||||
m_Indices.push_back(((j+1)*vsize+(i+1))+base);
|
||||
m_Indices.push_back(((j+1)*vsize+(i+0))+base);
|
||||
}
|
||||
}
|
||||
}
|
||||
splat.m_IndexCount=m_Indices.size()-splat.m_IndexStart;
|
||||
}
|
||||
}
|
||||
|
||||
// build indices for the shadow map pass
|
||||
for (int j=0;j<PATCH_SIZE;j++) {
|
||||
for (int i=0;i<PATCH_SIZE;i++) {
|
||||
m_ShadowMapIndices.push_back(((j+0)*vsize+(i+0))+base);
|
||||
m_ShadowMapIndices.push_back(((j+0)*vsize+(i+1))+base);
|
||||
m_ShadowMapIndices.push_back(((j+1)*vsize+(i+1))+base);
|
||||
m_ShadowMapIndices.push_back(((j+1)*vsize+(i+0))+base);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline int clamp(int x,int min,int max)
|
||||
{
|
||||
if (x<min) return min;
|
||||
else if (x>max) return max;
|
||||
else return x;
|
||||
}
|
||||
|
||||
static SColor4ub ConvertColor(const RGBColor& src)
|
||||
{
|
||||
SColor4ub result;
|
||||
result.R=clamp(int(src.X*255),0,255);
|
||||
result.G=clamp(int(src.Y*255),0,255);
|
||||
result.B=clamp(int(src.Z*255),0,255);
|
||||
result.A=0xff;
|
||||
return result;
|
||||
}
|
||||
|
||||
static void BuildHeightmapNormals(int size,u16 *heightmap,CVector3D* normals)
|
||||
{
|
||||
int x, y;
|
||||
int sm=size-1;
|
||||
|
||||
for(y = 0;y < size; y++)
|
||||
for(x = 0; x < size; x++) {
|
||||
|
||||
// Access current normalmap grid point
|
||||
CVector3D* N = &normals[y*size+x];
|
||||
|
||||
// Compute normal by using the height differential
|
||||
u16 h1=(x==sm) ? heightmap[y*size+x] : heightmap[y*size+x+1];
|
||||
u16 h2=(y==sm) ? heightmap[y*size+x] : heightmap[(y+1)*size+x];
|
||||
u16 h3=(x==0) ? heightmap[y*size+x] : heightmap[y*size+x-1];
|
||||
u16 h4=(y==0) ? heightmap[y*size+x] : heightmap[(y-1)*size+x+1];
|
||||
N->X = (h3-h1)*HEIGHT_SCALE;
|
||||
N->Y = CELL_SIZE;
|
||||
N->Z = (h4-h2)*HEIGHT_SCALE;
|
||||
|
||||
// Normalize it
|
||||
float len=N->GetLength();
|
||||
if (len>0) {
|
||||
(*N)*=1.0f/len;
|
||||
} else {
|
||||
*N=CVector3D(0,0,0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CPatchRData::BuildVertices()
|
||||
{
|
||||
CVector3D normal;
|
||||
RGBColor c;
|
||||
|
||||
// number of vertices in each direction in each patch
|
||||
int vsize=PATCH_SIZE+1;
|
||||
|
||||
|
|
@ -377,38 +372,26 @@ void CPatchRData::BuildVertices()
|
|||
CTerrain* terrain=m_Patch->m_Parent;
|
||||
u32 mapSize=terrain->GetVerticesPerSide();
|
||||
|
||||
// build vertices
|
||||
for (int j=0; j<vsize; j++)
|
||||
{
|
||||
for (int i=0; i<vsize; i++)
|
||||
{
|
||||
int ix=px*16+i;
|
||||
int iz=pz*16+j;
|
||||
// build vertices
|
||||
for (int j=0;j<vsize;j++) {
|
||||
for (int i=0;i<vsize;i++) {
|
||||
int ix=px*PATCH_SIZE+i;
|
||||
int iz=pz*PATCH_SIZE+j;
|
||||
int v=(j*vsize)+i;
|
||||
|
||||
CVector3D pos,normal;
|
||||
terrain->CalcPosition(ix,iz,pos);
|
||||
terrain->CalcNormal(ix,iz,normal);
|
||||
|
||||
RGBColor c;
|
||||
g_Renderer.m_SHCoeffsTerrain.Evaluate(normal,c);
|
||||
|
||||
int v=(j*vsize)+i;
|
||||
terrain->CalcPosition(ix,iz,vertices[v].m_Position);
|
||||
terrain->CalcNormal(ix,iz,normal);
|
||||
g_Renderer.m_SHCoeffsTerrain.Evaluate(normal,c);
|
||||
vertices[v].m_Color=ConvertColor(c);
|
||||
vertices[v].m_UVs[0]=i*0.125f;
|
||||
vertices[v].m_UVs[1]=j*0.125f;
|
||||
vertices[v].m_Color=ConvertColor(c);
|
||||
vertices[v].m_Position=pos;
|
||||
}
|
||||
}
|
||||
|
||||
if (g_Renderer.m_Caps.m_VBO) {
|
||||
if (!m_VBBase) {
|
||||
glGenBuffersARB(1,(GLuint*) &m_VBBase);
|
||||
glBindBufferARB(GL_ARRAY_BUFFER_ARB,m_VBBase);
|
||||
glBufferDataARB(GL_ARRAY_BUFFER_ARB,vsize*vsize*sizeof(SBaseVertex),0,GL_STATIC_DRAW_ARB);
|
||||
}
|
||||
glBindBufferARB(GL_ARRAY_BUFFER_ARB,m_VBBase);
|
||||
glBufferSubDataARB(GL_ARRAY_BUFFER_ARB,0,vsize*vsize*sizeof(SBaseVertex),m_Vertices);
|
||||
}
|
||||
if (!m_VBBase) {
|
||||
m_VBBase=g_VBMan.Allocate(sizeof(SBaseVertex),vsize*vsize,false);
|
||||
}
|
||||
m_VBBase->m_Owner->UpdateChunkVertices(m_VBBase,m_Vertices);
|
||||
}
|
||||
|
||||
void CPatchRData::Build()
|
||||
|
|
@ -436,13 +419,7 @@ void CPatchRData::RenderBase()
|
|||
{
|
||||
assert(m_UpdateFlags==0);
|
||||
|
||||
u8* base;
|
||||
if (g_Renderer.m_Caps.m_VBO) {
|
||||
glBindBufferARB(GL_ARRAY_BUFFER_ARB,m_VBBase);
|
||||
base=0;
|
||||
} else {
|
||||
base=(u8*) &m_Vertices[0];
|
||||
}
|
||||
u8* base=m_VBBase->m_Owner->Bind();
|
||||
|
||||
// setup data pointers
|
||||
u32 stride=sizeof(SBaseVertex);
|
||||
|
|
@ -465,13 +442,7 @@ void CPatchRData::RenderStreams(u32 streamflags)
|
|||
{
|
||||
assert(m_UpdateFlags==0);
|
||||
|
||||
u8* base;
|
||||
if (g_Renderer.m_Caps.m_VBO) {
|
||||
glBindBufferARB(GL_ARRAY_BUFFER_ARB,m_VBBase);
|
||||
base=0;
|
||||
} else {
|
||||
base=(u8*) &m_Vertices[0];
|
||||
}
|
||||
u8* base=m_VBBase->m_Owner->Bind();
|
||||
|
||||
// setup data pointers
|
||||
glVertexPointer(3,GL_FLOAT,sizeof(SBaseVertex),base+offsetof(SBaseVertex,m_Position));
|
||||
|
|
@ -493,14 +464,7 @@ void CPatchRData::RenderBlends()
|
|||
|
||||
if (m_BlendVertices.size()==0) return;
|
||||
|
||||
u8* base;
|
||||
if (g_Renderer.m_Caps.m_VBO) {
|
||||
glBindBufferARB(GL_ARRAY_BUFFER_ARB,m_VBBlends);
|
||||
base=0;
|
||||
} else {
|
||||
base=(u8*) &m_BlendVertices[0];
|
||||
}
|
||||
|
||||
u8* base=m_VBBlends->m_Owner->Bind();
|
||||
|
||||
// setup data pointers
|
||||
u32 stride=sizeof(SBlendVertex);
|
||||
|
|
@ -527,6 +491,7 @@ void CPatchRData::RenderBlends()
|
|||
|
||||
void CPatchRData::RenderOutline()
|
||||
{
|
||||
// TODO, RC - fixme, only works for PATCH_SIZE = 16
|
||||
const u16 EdgeIndices[PATCH_SIZE*4] = {
|
||||
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
|
||||
33, 50, 67, 84, 101, 118, 135, 152, 169, 186, 203, 220, 237, 254, 271, 288,
|
||||
|
|
@ -534,13 +499,7 @@ void CPatchRData::RenderOutline()
|
|||
255, 238, 221, 204, 187, 170, 153, 136, 119, 102, 85, 68, 51, 34, 17, 0
|
||||
};
|
||||
|
||||
u8* base;
|
||||
if (g_Renderer.m_Caps.m_VBO) {
|
||||
glBindBufferARB(GL_ARRAY_BUFFER_ARB,m_VBBase);
|
||||
base=0;
|
||||
} else {
|
||||
base=(u8*) &m_Vertices[0];
|
||||
}
|
||||
u8* base=m_VBBase->m_Owner->Bind();
|
||||
|
||||
// setup data pointers
|
||||
glVertexPointer(3,GL_FLOAT,sizeof(SBaseVertex),base+offsetof(SBaseVertex,m_Position));
|
||||
|
|
@ -551,3 +510,298 @@ void CPatchRData::RenderOutline()
|
|||
g_Renderer.m_Stats.m_DrawCalls++;
|
||||
g_Renderer.m_Stats.m_TerrainTris+=numIndices/2;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// SubmitBaseBatches: submit base batches for this patch to the vertex buffer
|
||||
void CPatchRData::SubmitBaseBatches()
|
||||
{
|
||||
assert(m_VBBase);
|
||||
|
||||
for (uint i=0;i<m_Splats.size();i++) {
|
||||
const SSplat& splat=m_Splats[i];
|
||||
m_VBBase->m_Owner->AppendBatch(m_VBBase,splat.m_Texture,splat.m_IndexCount,&m_Indices[splat.m_IndexStart]);
|
||||
}
|
||||
}
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// SubmitBlendBatches: submit next set of blend batches for this patch to the vertex buffer;
|
||||
// return true if all blends on this patch have been submitted, else false
|
||||
bool CPatchRData::SubmitBlendBatches()
|
||||
{
|
||||
if (m_NextBlendSplat<m_BlendSplats.size()) {
|
||||
for (uint i=m_NextBlendSplat;i<m_BlendSplats.size();i++) {
|
||||
const SSplat& splat=m_BlendSplats[i];
|
||||
m_VBBlends->m_Owner->AppendBatch(m_VBBlends,splat.m_Texture,splat.m_IndexCount,&m_BlendIndices[splat.m_IndexStart]);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// RenderBaseSplats: render all base passes of all patches; assumes vertex, texture and color
|
||||
// client states are enabled
|
||||
void CPatchRData::RenderBaseSplats()
|
||||
{
|
||||
uint i;
|
||||
|
||||
// set up texture environment for base pass
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
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);
|
||||
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_ZERO);
|
||||
glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA_ARB, GL_ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
#if 1
|
||||
// submit base batches for each patch to the vertex buffer
|
||||
for (i=0;i<m_Patches.size();++i) {
|
||||
CPatchRData* patchdata=(CPatchRData*) m_Patches[i]->GetRenderData();
|
||||
patchdata->SubmitBaseBatches();
|
||||
}
|
||||
|
||||
// render base passes for each patch
|
||||
const std::list<CVertexBuffer*>& buffers=g_VBMan.GetBufferList();
|
||||
std::list<CVertexBuffer*>::const_iterator iter;
|
||||
for (iter=buffers.begin();iter!=buffers.end();++iter) {
|
||||
CVertexBuffer* buffer=*iter;
|
||||
|
||||
// any batches in this VB?
|
||||
const std::vector<CVertexBuffer::Batch*>& batches=buffer->GetBatches();
|
||||
if (batches.size()>0) {
|
||||
u8* base=buffer->Bind();
|
||||
|
||||
// setup data pointers
|
||||
u32 stride=sizeof(SBaseVertex);
|
||||
glVertexPointer(3,GL_FLOAT,stride,base+offsetof(SBaseVertex,m_Position));
|
||||
glColorPointer(4,GL_UNSIGNED_BYTE,stride,base+offsetof(SBaseVertex,m_Color));
|
||||
glTexCoordPointer(2,GL_FLOAT,stride,base+offsetof(SBaseVertex,m_UVs[0]));
|
||||
|
||||
// render each batch
|
||||
for (i=0;i<batches.size();++i) {
|
||||
const CVertexBuffer::Batch* batch=batches[i];
|
||||
if (batch->m_IndexData.size()>0) {
|
||||
g_Renderer.BindTexture(0,tex_id(batch->m_Texture));
|
||||
for (uint j=0;j<batch->m_IndexData.size();j++) {
|
||||
glDrawElements(GL_QUADS,batch->m_IndexData[j].first,GL_UNSIGNED_SHORT,batch->m_IndexData[j].second);
|
||||
g_Renderer.m_Stats.m_DrawCalls++;
|
||||
g_Renderer.m_Stats.m_TerrainTris+=batch->m_IndexData[j].first/2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// everything rendered; empty out batch lists
|
||||
g_VBMan.ClearBatchIndices();
|
||||
#else
|
||||
for (i=0;i<m_Patches.size();++i) {
|
||||
CPatchRData* patchdata=(CPatchRData*) m_Patches[i]->GetRenderData();
|
||||
patchdata->RenderBase();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// RenderBlendSplats: render all blend passes of all patches; assumes vertex, texture and color
|
||||
// client states are enabled
|
||||
void CPatchRData::RenderBlendSplats()
|
||||
{
|
||||
uint i;
|
||||
|
||||
// switch on second uv set
|
||||
glClientActiveTexture(GL_TEXTURE1);
|
||||
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
|
||||
glClientActiveTexture(GL_TEXTURE0);
|
||||
|
||||
// switch on the composite alpha map texture
|
||||
g_Renderer.BindTexture(1,g_Renderer.m_CompositeAlphaMap);
|
||||
|
||||
// setup additional texenv required by blend pass
|
||||
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE);
|
||||
glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_REPLACE);
|
||||
glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_PREVIOUS);
|
||||
glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB_ARB, GL_SRC_COLOR);
|
||||
glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA_ARB, GL_REPLACE);
|
||||
glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_ARB, GL_TEXTURE);
|
||||
glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA_ARB, GL_ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
// switch on blending
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
// no need to write to the depth buffer a second time
|
||||
glDepthMask(0);
|
||||
|
||||
#if 1
|
||||
// submit blend batches for each patch to the vertex buffer
|
||||
for (i=0;i<m_Patches.size();++i) {
|
||||
CPatchRData* patchdata=(CPatchRData*) m_Patches[i]->GetRenderData();
|
||||
patchdata->SetupBlendBatches();
|
||||
}
|
||||
|
||||
bool finished=true;
|
||||
do
|
||||
{
|
||||
for (i=0;i<m_Patches.size();++i) {
|
||||
CPatchRData* patchdata=(CPatchRData*) m_Patches[i]->GetRenderData();
|
||||
if (!patchdata->SubmitBlendBatches()) finished=false;
|
||||
}
|
||||
} while (!finished);
|
||||
|
||||
// render blend passes for each patch
|
||||
const std::list<CVertexBuffer*>& buffers=g_VBMan.GetBufferList();
|
||||
std::list<CVertexBuffer*>::const_iterator iter;
|
||||
for (iter=buffers.begin();iter!=buffers.end();++iter) {
|
||||
CVertexBuffer* buffer=*iter;
|
||||
|
||||
// any batches in this VB?
|
||||
const std::vector<CVertexBuffer::Batch*>& batches=buffer->GetBatches();
|
||||
if (batches.size()>0) {
|
||||
u8* base=buffer->Bind();
|
||||
|
||||
// setup data pointers
|
||||
u32 stride=sizeof(SBlendVertex);
|
||||
glVertexPointer(3,GL_FLOAT,stride,base+offsetof(SBlendVertex,m_Position));
|
||||
glColorPointer(4,GL_UNSIGNED_BYTE,stride,base+offsetof(SBlendVertex,m_Color));
|
||||
glClientActiveTexture(GL_TEXTURE0);
|
||||
glTexCoordPointer(2,GL_FLOAT,stride,base+offsetof(SBlendVertex,m_UVs[0]));
|
||||
|
||||
glClientActiveTexture(GL_TEXTURE1);
|
||||
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
|
||||
glTexCoordPointer(2,GL_FLOAT,stride,base+offsetof(SBlendVertex,m_AlphaUVs[0]));
|
||||
|
||||
// render each batch
|
||||
for (i=0;i<batches.size();++i) {
|
||||
const CVertexBuffer::Batch* batch=batches[i];
|
||||
if (batch->m_IndexData.size()>0) {
|
||||
g_Renderer.BindTexture(0,tex_id(batch->m_Texture));
|
||||
for (uint j=0;j<batch->m_IndexData.size();j++) {
|
||||
glDrawElements(GL_QUADS,batch->m_IndexData[j].first,GL_UNSIGNED_SHORT,batch->m_IndexData[j].second);
|
||||
g_Renderer.m_Stats.m_DrawCalls++;
|
||||
g_Renderer.m_Stats.m_TerrainTris+=batch->m_IndexData[j].first/2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// everything rendered; empty out batch lists
|
||||
g_VBMan.ClearBatchIndices();
|
||||
#else
|
||||
// render blend passes for each patch
|
||||
for (i=0;i<m_TerrainPatches.size();++i) {
|
||||
CPatchRData* patchdata=(CPatchRData*) m_Patches[i]->GetRenderData();
|
||||
patchdata->RenderBlends();
|
||||
}
|
||||
#endif
|
||||
|
||||
// restore depth writes
|
||||
glDepthMask(1);
|
||||
|
||||
// restore default state: switch off blending
|
||||
glDisable(GL_BLEND);
|
||||
|
||||
// switch off texture unit 1, make unit 0 active texture
|
||||
g_Renderer.BindTexture(1,0);
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
|
||||
// tidy up client states
|
||||
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
|
||||
glClientActiveTexture(GL_TEXTURE0_ARB);
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Submit: submit a patch to render this frame
|
||||
void CPatchRData::Submit(CPatch* patch)
|
||||
{
|
||||
CPatchRData* data=(CPatchRData*) patch->GetRenderData();
|
||||
if (data==0) {
|
||||
// no renderdata for patch, create it now
|
||||
data=new CPatchRData(patch);
|
||||
patch->SetRenderData(data);
|
||||
} else {
|
||||
data->Update();
|
||||
}
|
||||
|
||||
m_Patches.push_back(patch);
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// ApplyShadowMap: apply given shadow map to all terrain patches; assume the texture matrix
|
||||
// has been correctly setup on unit 1 to handle the projection
|
||||
void CPatchRData::ApplyShadowMap(GLuint shadowmaphandle)
|
||||
{
|
||||
uint i;
|
||||
|
||||
// glEnable(GL_ALPHA_TEST);
|
||||
// glAlphaFunc(GL_GREATER,0.0f);
|
||||
|
||||
g_Renderer.BindTexture(0,shadowmaphandle);
|
||||
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE);
|
||||
glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_REPLACE);
|
||||
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_COMBINE_ALPHA_ARB, GL_REPLACE);
|
||||
glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_ARB, GL_TEXTURE);
|
||||
glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA_ARB, GL_SRC_ALPHA);
|
||||
|
||||
glColor3f(1,1,1);
|
||||
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_DST_COLOR,GL_ZERO);
|
||||
|
||||
glEnableClientState(GL_VERTEX_ARRAY);
|
||||
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
|
||||
|
||||
#if 1
|
||||
// submit base batches for each patch to the vertex buffer
|
||||
for (i=0;i<m_Patches.size();++i) {
|
||||
CPatchRData* patchdata=(CPatchRData*) m_Patches[i]->GetRenderData();
|
||||
patchdata->m_VBBase->m_Owner->AppendBatch(patchdata->m_VBBase,0,patchdata->m_ShadowMapIndices.size(),
|
||||
&patchdata->m_ShadowMapIndices[0]);
|
||||
}
|
||||
|
||||
// render base passes for each patch
|
||||
const std::list<CVertexBuffer*>& buffers=g_VBMan.GetBufferList();
|
||||
std::list<CVertexBuffer*>::const_iterator iter;
|
||||
for (iter=buffers.begin();iter!=buffers.end();++iter) {
|
||||
CVertexBuffer* buffer=*iter;
|
||||
|
||||
// any batches in this VB?
|
||||
const std::vector<CVertexBuffer::Batch*>& batches=buffer->GetBatches();
|
||||
if (batches.size()>0) {
|
||||
u8* base=buffer->Bind();
|
||||
|
||||
// setup data pointers
|
||||
u32 stride=sizeof(SBaseVertex);
|
||||
glVertexPointer(3,GL_FLOAT,stride,base+offsetof(SBaseVertex,m_Position));
|
||||
glColorPointer(4,GL_UNSIGNED_BYTE,stride,base+offsetof(SBaseVertex,m_Color));
|
||||
glTexCoordPointer(3,GL_FLOAT,sizeof(SBaseVertex),base+offsetof(SBaseVertex,m_Position));
|
||||
|
||||
// render batch (can only be one per buffer, since all batches are flagged as using a null texture)
|
||||
const CVertexBuffer::Batch* batch=batches[0];
|
||||
for (uint j=0;j<batch->m_IndexData.size();j++) {
|
||||
glDrawElements(GL_QUADS,batch->m_IndexData[j].first,GL_UNSIGNED_SHORT,batch->m_IndexData[j].second);
|
||||
g_Renderer.m_Stats.m_DrawCalls++;
|
||||
g_Renderer.m_Stats.m_TerrainTris+=batch->m_IndexData[j].first/2;
|
||||
}
|
||||
}
|
||||
}
|
||||
// everything rendered; empty out batch lists
|
||||
g_VBMan.ClearBatchIndices();
|
||||
#else
|
||||
for (uint i=0;i<m_Patches.size();++i) {
|
||||
CPatchRData* patchdata=(CPatchRData*) m_Patches[i]->GetRenderData();;
|
||||
patchdata->RenderStreams(STREAM_POS|STREAM_POSTOUV0);
|
||||
}
|
||||
#endif
|
||||
|
||||
glDisable(GL_ALPHA_TEST);
|
||||
|
||||
glDisable(GL_BLEND);
|
||||
glDisableClientState(GL_VERTEX_ARRAY);
|
||||
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
|
||||
}
|
||||
|
|
@ -6,9 +6,13 @@
|
|||
#include "Color.h"
|
||||
#include "Vector3D.h"
|
||||
#include "RenderableObject.h"
|
||||
#include "VertexBufferManager.h"
|
||||
|
||||
class CPatch;
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// CPatchRData: class encapsulating logic for rendering terrain patches; holds per
|
||||
// patch data, plus some supporting static functions for batching, etc
|
||||
class CPatchRData : public CRenderData
|
||||
{
|
||||
public:
|
||||
|
|
@ -20,8 +24,68 @@ public:
|
|||
void RenderBlends();
|
||||
void RenderOutline();
|
||||
void RenderStreams(u32 streamflags);
|
||||
|
||||
// submit a patch to render this frame
|
||||
static void Submit(CPatch* patch);
|
||||
// clear per frame patch list
|
||||
static void ClearSubmissions() { m_Patches.clear(); }
|
||||
|
||||
// render the base pass of all patches
|
||||
static void RenderBaseSplats();
|
||||
// render the blend pass of all patches
|
||||
static void RenderBlendSplats();
|
||||
// apply given shadow map to all terrain patches
|
||||
static void ApplyShadowMap(GLuint handle);
|
||||
|
||||
// submit base batches for this patch to the vertex buffer
|
||||
void SubmitBaseBatches();
|
||||
// submit next set of blend batches for this patch to the vertex buffer;
|
||||
// return true if all blends on this patch have been submitted, else false
|
||||
bool SubmitBlendBatches();
|
||||
|
||||
// perform necessary initialisation prior to rendering blend splats
|
||||
void SetupBlendBatches() { m_NextBlendSplat=0; }
|
||||
|
||||
private:
|
||||
struct SSplat {
|
||||
SSplat() : m_Texture(0), m_IndexCount(0) {}
|
||||
|
||||
// handle of texture to apply during splat
|
||||
Handle m_Texture;
|
||||
// offset into the index array for this patch where splat starts
|
||||
u32 m_IndexStart;
|
||||
// number of indices used by splat
|
||||
u32 m_IndexCount;
|
||||
};
|
||||
|
||||
struct SBaseVertex {
|
||||
// vertex position
|
||||
CVector3D m_Position;
|
||||
// vertex color
|
||||
SColor4ub m_Color;
|
||||
// vertex uvs for base texture
|
||||
float m_UVs[2];
|
||||
};
|
||||
|
||||
struct SBlendVertex {
|
||||
// vertex position
|
||||
CVector3D m_Position;
|
||||
// vertex color
|
||||
SColor4ub m_Color;
|
||||
// vertex uvs for base texture
|
||||
float m_UVs[2];
|
||||
// vertex uvs for alpha texture
|
||||
float m_AlphaUVs[2];
|
||||
};
|
||||
|
||||
struct STex {
|
||||
bool operator==(const STex& rhs) const { return m_Handle==rhs.m_Handle; }
|
||||
bool operator<(const STex& rhs) const { return m_Priority<rhs.m_Priority; }
|
||||
Handle m_Handle;
|
||||
int m_Priority;
|
||||
};
|
||||
|
||||
|
||||
// build this renderdata object
|
||||
void Build();
|
||||
|
||||
|
|
@ -29,54 +93,18 @@ private:
|
|||
void BuildIndices();
|
||||
void BuildVertices();
|
||||
|
||||
struct SSplat {
|
||||
SSplat() : m_Texture(0), m_IndexCount(0) {}
|
||||
|
||||
// handle of texture to apply during splat
|
||||
Handle m_Texture;
|
||||
// offset into the index array for this patch where splat starts
|
||||
u32 m_IndexStart;
|
||||
// number of indices used by splat
|
||||
u32 m_IndexCount;
|
||||
};
|
||||
|
||||
struct SBaseVertex {
|
||||
// vertex position
|
||||
CVector3D m_Position;
|
||||
// vertex color
|
||||
SColor4ub m_Color;
|
||||
// vertex uvs for base texture
|
||||
float m_UVs[2];
|
||||
};
|
||||
|
||||
struct SBlendVertex {
|
||||
// vertex position
|
||||
CVector3D m_Position;
|
||||
// vertex color
|
||||
SColor4ub m_Color;
|
||||
// vertex uvs for base texture
|
||||
float m_UVs[2];
|
||||
// vertex uvs for alpha texture
|
||||
float m_AlphaUVs[2];
|
||||
};
|
||||
|
||||
struct STex {
|
||||
bool operator==(const STex& rhs) const { return m_Handle==rhs.m_Handle; }
|
||||
bool operator<(const STex& rhs) const { return m_Priority<rhs.m_Priority; }
|
||||
Handle m_Handle;
|
||||
int m_Priority;
|
||||
};
|
||||
|
||||
// owner patch
|
||||
CPatch* m_Patch;
|
||||
// vertex buffer handle for base vertices
|
||||
u32 m_VBBase;
|
||||
CVertexBuffer::VBChunk* m_VBBase;
|
||||
// vertex buffer handle for blend vertices
|
||||
u32 m_VBBlends;
|
||||
CVertexBuffer::VBChunk* m_VBBlends;
|
||||
// patch render vertices
|
||||
SBaseVertex* m_Vertices;
|
||||
// patch index list
|
||||
// indices into base vertices for the base splats
|
||||
std::vector<unsigned short> m_Indices;
|
||||
// indices into base vertices for the shadow map pass
|
||||
std::vector<unsigned short> m_ShadowMapIndices;
|
||||
// list of base splats to apply to this patch
|
||||
std::vector<SSplat> m_Splats;
|
||||
// vertices to use for blending transition texture passes
|
||||
|
|
@ -84,7 +112,11 @@ private:
|
|||
// indices into blend vertices for the blend splats
|
||||
std::vector<unsigned short> m_BlendIndices;
|
||||
// splats used in blend pass
|
||||
std::vector<SSplat> m_BlendSplats;
|
||||
std::vector<SSplat> m_BlendSplats;
|
||||
// index of the next blend splat to render
|
||||
u32 m_NextBlendSplat;
|
||||
// list of all submitted patches
|
||||
static std::vector<CPatch*> m_Patches;
|
||||
};
|
||||
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -95,7 +95,6 @@ public:
|
|||
m_DrawCalls+=rhs.m_DrawCalls;
|
||||
m_TerrainTris+=rhs.m_TerrainTris;
|
||||
m_ModelTris+=rhs.m_ModelTris;
|
||||
m_TransparentTris+=rhs.m_TransparentTris;
|
||||
m_BlendSplats+=rhs.m_BlendSplats;
|
||||
return *this;
|
||||
}
|
||||
|
|
@ -107,8 +106,6 @@ public:
|
|||
u32 m_TerrainTris;
|
||||
// number of (non-transparent) model triangles drawn
|
||||
u32 m_ModelTris;
|
||||
// number of transparent model triangles drawn
|
||||
u32 m_TransparentTris;
|
||||
// number of splat passes for alphamapping
|
||||
u32 m_BlendSplats;
|
||||
};
|
||||
|
|
@ -129,9 +126,9 @@ public:
|
|||
// set/get boolean renderer option
|
||||
void SetOptionBool(enum Option opt,bool value);
|
||||
bool GetOptionBool(enum Option opt) const;
|
||||
// set/get color renderer option
|
||||
void SetOptionColor(enum Option opt,const RGBColor& value);
|
||||
const RGBColor& GetOptionColor(enum Option opt) const;
|
||||
// set/get RGBA color renderer option
|
||||
void SetOptionColor(enum Option opt,const RGBAColor& value);
|
||||
const RGBAColor& GetOptionColor(enum Option opt) const;
|
||||
|
||||
// return view width
|
||||
int GetWidth() const { return m_Width; }
|
||||
|
|
@ -197,9 +194,10 @@ public:
|
|||
|
||||
// try and load the given texture
|
||||
bool LoadTexture(CTexture* texture,u32 wrapflags);
|
||||
// set the given unit to reference the given texture; pass a null texture to disable texturing on any unit
|
||||
// set the given unit to reference the given texture; pass a null texture to disable texturing on any unit;
|
||||
// active texture unit always set to given unit on exit
|
||||
void SetTexture(int unit,CTexture* texture);
|
||||
// BindTexture: bind a GL texture object to given unit
|
||||
// bind a GL texture object to active unit
|
||||
void BindTexture(int unit,GLuint tex);
|
||||
// query transparency of given texture
|
||||
bool IsTextureTransparent(CTexture* texture);
|
||||
|
|
@ -211,12 +209,11 @@ public:
|
|||
const Stats& GetStats() { return m_Stats; }
|
||||
|
||||
protected:
|
||||
friend class CVertexBuffer;
|
||||
friend class CPatchRData;
|
||||
friend class CModelRData;
|
||||
friend class CTransparencyRenderer;
|
||||
|
||||
// recurse down given model building renderdata for it and all it's children
|
||||
void UpdateModelDataRecursive(CModel* model);
|
||||
// update renderdata of everything submitted
|
||||
void UpdateSubmittedObjectData();
|
||||
|
||||
|
|
@ -225,17 +222,14 @@ protected:
|
|||
void RenderPatches();
|
||||
|
||||
// model rendering stuff
|
||||
void BuildTransparentPasses(CModel* model);
|
||||
void RenderModelSubmissions();
|
||||
void RenderModelsRecursive(CModel* model,u32 streamflags);
|
||||
void RenderModelsStreams(u32 streamflags);
|
||||
void RenderModels();
|
||||
|
||||
// shadow rendering stuff
|
||||
void CreateShadowMap();
|
||||
void RenderShadowMap();
|
||||
void ApplyShadowMap();
|
||||
void CRenderer::BuildTransformation(const CVector3D& pos,const CVector3D& right,const CVector3D& up,
|
||||
void BuildTransformation(const CVector3D& pos,const CVector3D& right,const CVector3D& up,
|
||||
const CVector3D& dir,CMatrix3D& result);
|
||||
void ConstructLightTransform(const CVector3D& pos,const CVector3D& lightdir,CMatrix3D& result);
|
||||
void CalcShadowMatrices();
|
||||
|
|
@ -259,8 +253,6 @@ protected:
|
|||
// color used to clear screen in BeginFrame
|
||||
float m_ClearColor[4];
|
||||
// submitted object lists for batching
|
||||
std::vector<CPatch*> m_TerrainPatches;
|
||||
std::vector<CModel*> m_Models;
|
||||
std::vector<CSprite*> m_Sprites;
|
||||
std::vector<CParticleSys*> m_ParticleSyses;
|
||||
std::vector<COverlay*> m_Overlays;
|
||||
|
|
@ -274,8 +266,10 @@ protected:
|
|||
u32 m_CompositeAlphaMap;
|
||||
// handle of shadow map
|
||||
u32 m_ShadowMap;
|
||||
// size of each side of shadow map
|
||||
u32 m_ShadowMapSize;
|
||||
// width, height of shadow map
|
||||
u32 m_ShadowMapWidth,m_ShadowMapHeight;
|
||||
// object space bound of shadow casting objects
|
||||
CBound m_ShadowBound;
|
||||
// per-frame flag: has the shadow map been rendered this frame?
|
||||
bool m_ShadowRendered;
|
||||
// projection matrix of shadow casting light
|
||||
|
|
@ -290,20 +284,18 @@ protected:
|
|||
struct Caps {
|
||||
bool m_VBO;
|
||||
bool m_TextureBorderClamp;
|
||||
bool m_PBuffer;
|
||||
bool m_GenerateMipmaps;
|
||||
} m_Caps;
|
||||
// renderer options
|
||||
struct Options {
|
||||
bool m_NoVBO;
|
||||
bool m_NoPBuffer;
|
||||
bool m_Shadows;
|
||||
RGBColor m_ShadowColor;
|
||||
RGBAColor m_ShadowColor;
|
||||
} m_Options;
|
||||
// build card cap bits
|
||||
void EnumCaps();
|
||||
// per-frame renderer stats
|
||||
Stats m_Stats;
|
||||
Stats m_Stats;
|
||||
// active textures on each unit
|
||||
GLuint m_ActiveTextures[MaxTextureUnits];
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,3 +1,11 @@
|
|||
///////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Name: TransparencyRenderer.cpp
|
||||
// Author: Rich Cross
|
||||
// Contact: rich@wildfiregames.com
|
||||
//
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "precompiled.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
|
@ -30,7 +38,9 @@ void CTransparencyRenderer::Sort()
|
|||
// Render: render all deferred passes; call Sort before using to ensure passes
|
||||
// are drawn in correct order
|
||||
void CTransparencyRenderer::Render()
|
||||
{
|
||||
{
|
||||
if (m_Objects.size()==0) return;
|
||||
|
||||
// switch on wireframe if we need it
|
||||
if (g_Renderer.m_ModelRenderMode==WIREFRAME) {
|
||||
glPolygonMode(GL_FRONT_AND_BACK,GL_LINE);
|
||||
|
|
@ -41,13 +51,10 @@ void CTransparencyRenderer::Render()
|
|||
glEnableClientState(GL_COLOR_ARRAY);
|
||||
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
|
||||
|
||||
// setup texture environment to modulate diffuse color with texture color
|
||||
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_COMBINE_RGB_ARB, GL_REPLACE);
|
||||
glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_ONE);
|
||||
glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB_ARB, GL_SRC_COLOR);
|
||||
glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB_ARB, GL_PRIMARY_COLOR);
|
||||
glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB_ARB, GL_SRC_COLOR);
|
||||
|
||||
// just pass through texture's alpha
|
||||
glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA_ARB, GL_REPLACE);
|
||||
|
|
@ -56,14 +63,24 @@ void CTransparencyRenderer::Render()
|
|||
|
||||
glEnable(GL_ALPHA_TEST);
|
||||
glAlphaFunc(GL_GREATER,0.975f);
|
||||
|
||||
RenderObjectsStreams(STREAM_POS|STREAM_COLOR|STREAM_UV0);
|
||||
|
||||
// render everything with color writes off to setup depth buffer correctly
|
||||
glColorMask(0,0,0,0);
|
||||
RenderObjectsStreams(STREAM_POS|STREAM_UV0);
|
||||
glColorMask(1,1,1,1);
|
||||
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
glDepthMask(0);
|
||||
glAlphaFunc(GL_LEQUAL,0.975f);
|
||||
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
|
||||
glAlphaFunc(GL_GREATER,0);
|
||||
|
||||
// setup texture environment to modulate diffuse color with texture color
|
||||
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);
|
||||
glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB_ARB, GL_SRC_COLOR);
|
||||
|
||||
RenderObjectsStreams(STREAM_POS|STREAM_COLOR|STREAM_UV0);
|
||||
|
||||
|
|
@ -138,11 +155,9 @@ void CTransparencyRenderer::Add(CModel* model)
|
|||
|
||||
void CTransparencyRenderer::RenderShadows()
|
||||
{
|
||||
// coarsely sort submitted objects in back to front manner
|
||||
std::sort(m_Objects.begin(),m_Objects.end(),SortObjectsByDist());
|
||||
|
||||
if (m_Objects.size()==0) return;
|
||||
|
||||
// switch on client states
|
||||
glEnableClientState(GL_VERTEX_ARRAY);
|
||||
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
|
||||
|
||||
glDepthMask(0);
|
||||
|
|
@ -152,20 +167,18 @@ void CTransparencyRenderer::RenderShadows()
|
|||
|
||||
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE);
|
||||
glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_REPLACE);
|
||||
glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_PREVIOUS_ARB);
|
||||
glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_PREVIOUS);
|
||||
glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB_ARB, GL_SRC_COLOR);
|
||||
glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA_ARB, GL_REPLACE);
|
||||
glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_ARB, GL_TEXTURE);
|
||||
glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA_ARB, GL_SRC_ALPHA);
|
||||
|
||||
RenderObjectsStreams(STREAM_POS|STREAM_UV0);
|
||||
|
||||
glDisable(GL_ALPHA_TEST);
|
||||
|
||||
glDepthMask(1);
|
||||
glDisable(GL_BLEND);
|
||||
|
||||
// switch off client states
|
||||
glDisableClientState(GL_VERTEX_ARRAY);
|
||||
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
|
||||
}
|
||||
|
||||
|
|
@ -174,14 +187,7 @@ void CTransparencyRenderer::RenderShadows()
|
|||
void CTransparencyRenderer::RenderObjectsStreams(u32 streamflags)
|
||||
{
|
||||
for (uint i=0;i<m_Objects.size();++i) {
|
||||
CModel* model=m_Objects[i].m_Model;
|
||||
|
||||
glPushMatrix();
|
||||
glMultMatrixf(&model->GetTransform()._11);
|
||||
|
||||
CModelRData* modeldata=(CModelRData*) model->GetRenderData();
|
||||
modeldata->RenderStreams(streamflags,true);
|
||||
|
||||
glPopMatrix();
|
||||
CModelRData* modeldata=(CModelRData*) m_Objects[i].m_Model->GetRenderData();
|
||||
modeldata->RenderStreams(streamflags);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +1,11 @@
|
|||
///////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Name: TransparencyRenderer.h
|
||||
// Author: Rich Cross
|
||||
// Contact: rich@wildfiregames.com
|
||||
//
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef __TRANSPARENCYRENDERER_H
|
||||
#define __TRANSPARENCYRENDERER_H
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue