522 lines
14 KiB
C++
522 lines
14 KiB
C++
/**
|
|
* =========================================================================
|
|
* File : ModelRenderer.cpp
|
|
* Project : Pyrogenesis
|
|
* Description : Implementation of ModelRenderer and BatchModelRenderer
|
|
*
|
|
* @author Nicolai Hähnle <nicolai@wildfiregames.com>
|
|
* =========================================================================
|
|
*/
|
|
|
|
#include "precompiled.h"
|
|
|
|
#include "ogl.h"
|
|
#include "Vector3D.h"
|
|
#include "Vector4D.h"
|
|
|
|
#include "ps/CLogger.h"
|
|
#include "ps/Profile.h"
|
|
|
|
#include "graphics/Color.h"
|
|
#include "graphics/LightEnv.h"
|
|
#include "graphics/Model.h"
|
|
#include "graphics/ModelDef.h"
|
|
|
|
#include "renderer/ModelRenderer.h"
|
|
#include "renderer/ModelVertexRenderer.h"
|
|
#include "renderer/Renderer.h"
|
|
#include "renderer/RenderModifiers.h"
|
|
|
|
|
|
#define LOG_CATEGORY "graphics"
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// 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 tmp;
|
|
const SVertexBlend& blend=vertex.m_Blend;
|
|
|
|
// must have at least one valid bone if we're using SkinPoint
|
|
debug_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; i<SVertexBlend::SIZE && blend.m_Bone[i]!=0xff; i++) {
|
|
const CMatrix3D& m=matrices[blend.m_Bone[i]];
|
|
m.Transform(vertex.m_Coords,tmp);
|
|
result+=tmp*blend.m_Weight[i];
|
|
}
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// SkinPoint: skin the vertex normal using it's blend data and given bone matrices
|
|
static void SkinNormal(const SModelVertex& vertex, const CMatrix3D* invtranspmatrices, CVector3D& result)
|
|
{
|
|
CVector3D tmp;
|
|
const SVertexBlend& blend=vertex.m_Blend;
|
|
|
|
// must have at least one valid bone if we're using SkinNormal
|
|
debug_assert(blend.m_Bone[0]!=0xff);
|
|
|
|
const CMatrix3D& m = invtranspmatrices[blend.m_Bone[0]];
|
|
m.Rotate(vertex.m_Norm, result);
|
|
result*=blend.m_Weight[0];
|
|
|
|
for (u32 i=1; i<SVertexBlend::SIZE && vertex.m_Blend.m_Bone[i]!=0xff; i++) {
|
|
const CMatrix3D& m = invtranspmatrices[blend.m_Bone[i]];
|
|
m.Rotate(vertex.m_Norm,tmp);
|
|
result+=tmp*blend.m_Weight[i];
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////
|
|
// ModelRenderer implementation
|
|
|
|
// Helper function to copy object-space position and normal vectors into arrays.
|
|
void ModelRenderer::CopyPositionAndNormals(
|
|
CModelDefPtr mdef,
|
|
VertexArrayIterator<CVector3D> Position,
|
|
VertexArrayIterator<CVector3D> Normal)
|
|
{
|
|
size_t numVertices = mdef->GetNumVertices();
|
|
SModelVertex* vertices = mdef->GetVertices();
|
|
|
|
for(size_t j = 0; j < numVertices; ++j)
|
|
{
|
|
Position[j] = vertices[j].m_Coords;
|
|
Normal[j] = vertices[j].m_Norm;
|
|
}
|
|
}
|
|
|
|
// Helper function to transform position and normal vectors into world-space.
|
|
void ModelRenderer::BuildPositionAndNormals(
|
|
CModel* model,
|
|
VertexArrayIterator<CVector3D> Position,
|
|
VertexArrayIterator<CVector3D> Normal)
|
|
{
|
|
CModelDefPtr mdef = model->GetModelDef();
|
|
size_t numVertices = mdef->GetNumVertices();
|
|
SModelVertex* vertices=mdef->GetVertices();
|
|
|
|
const CMatrix3D* bonematrices = model->GetBoneMatrices();
|
|
if (bonematrices)
|
|
{
|
|
// boned model - calculate skinned vertex positions/normals
|
|
PROFILE( "skinning bones" );
|
|
const CMatrix3D* invtranspbonematrices;
|
|
|
|
// Analytic geometry tells us that normal vectors need to be
|
|
// multiplied by the inverse of the transpose. However, calculating
|
|
// the inverse is slow, and analytic geometry also tells us that
|
|
// for orthogonal matrices, the inverse is equal to the transpose,
|
|
// so the inverse of the transpose is, in fact, the original matrix.
|
|
//
|
|
// The "fast normals" code assumes that bone transformation contain
|
|
// no "weird" transformations like shears or non-uniform scaling
|
|
// (actually, the entire code assumes no scaling) and thus gets
|
|
// around the slow calculation of the inverse.
|
|
if (g_Renderer.m_FastNormals)
|
|
invtranspbonematrices = bonematrices;
|
|
else
|
|
invtranspbonematrices = model->GetInvTranspBoneMatrices();
|
|
|
|
// Avoid the noisy warnings that occur inside SkinPoint/SkinNormal in
|
|
// some broken situations
|
|
if (numVertices && vertices[0].m_Blend.m_Bone[0] == 0xff)
|
|
{
|
|
LOG_ONCE(ERROR, LOG_CATEGORY, "Model %s is boned with unboned animation", mdef->GetName().c_str());
|
|
return;
|
|
}
|
|
|
|
for (size_t j=0; j<numVertices; j++)
|
|
{
|
|
SkinPoint(vertices[j], bonematrices, Position[j]);
|
|
SkinNormal(vertices[j], invtranspbonematrices, Normal[j]);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
PROFILE( "software transform" );
|
|
// just copy regular positions, transform normals to world space
|
|
const CMatrix3D& transform = model->GetTransform();
|
|
const CMatrix3D& invtransform = model->GetInvTransform();
|
|
for (uint j=0; j<numVertices; j++)
|
|
{
|
|
transform.Transform(vertices[j].m_Coords,Position[j]);
|
|
invtransform.RotateTransposed(vertices[j].m_Norm,Normal[j]);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// Helper function for lighting
|
|
void ModelRenderer::BuildColor4ub(
|
|
CModel* model,
|
|
VertexArrayIterator<CVector3D> Normal,
|
|
VertexArrayIterator<SColor4ub> Color,
|
|
bool onlyDiffuse)
|
|
{
|
|
PROFILE( "lighting vertices" );
|
|
|
|
CModelDefPtr mdef = model->GetModelDef();
|
|
size_t numVertices = mdef->GetNumVertices();
|
|
const CLightEnv& lightEnv = g_Renderer.GetLightEnv();
|
|
CColor shadingColor = model->GetShadingColor();
|
|
RGBColor tempcolor;
|
|
|
|
if (onlyDiffuse)
|
|
{
|
|
for (uint j=0; j<numVertices; j++)
|
|
{
|
|
lightEnv.EvaluateDirect(Normal[j], tempcolor);
|
|
tempcolor.X *= shadingColor.r;
|
|
tempcolor.Y *= shadingColor.g;
|
|
tempcolor.Z *= shadingColor.b;
|
|
*(u32*)&Color[j] = ConvertRGBColorTo4ub(tempcolor);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (uint j=0; j<numVertices; j++)
|
|
{
|
|
lightEnv.EvaluateUnit(Normal[j], tempcolor);
|
|
tempcolor.X *= shadingColor.r;
|
|
tempcolor.Y *= shadingColor.g;
|
|
tempcolor.Z *= shadingColor.b;
|
|
*(u32*)&Color[j] = ConvertRGBColorTo4ub(tempcolor);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// Copy UV coordinates
|
|
void ModelRenderer::BuildUV(
|
|
CModelDefPtr mdef,
|
|
VertexArrayIterator<float[2]> UV)
|
|
{
|
|
size_t numVertices = mdef->GetNumVertices();
|
|
SModelVertex* vertices = mdef->GetVertices();
|
|
|
|
for (uint j=0; j < numVertices; ++j, ++UV)
|
|
{
|
|
(*UV)[0] = vertices[j].m_U;
|
|
(*UV)[1] = 1.0-vertices[j].m_V;
|
|
}
|
|
}
|
|
|
|
|
|
// Build default indices array.
|
|
void ModelRenderer::BuildIndices(
|
|
CModelDefPtr mdef,
|
|
u16* Indices)
|
|
{
|
|
u32 idxidx = 0;
|
|
SModelFace* faces = mdef->GetFaces();
|
|
|
|
for (size_t j = 0; j < mdef->GetNumFaces(); ++j) {
|
|
SModelFace& face=faces[j];
|
|
Indices[idxidx++]=face.m_Verts[0];
|
|
Indices[idxidx++]=face.m_Verts[1];
|
|
Indices[idxidx++]=face.m_Verts[2];
|
|
}
|
|
}
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////
|
|
// BatchModelRenderer implementation
|
|
|
|
|
|
/// See BatchModelRendererInternals::phase
|
|
enum BMRPhase {
|
|
/// Currently allow calls to Submit and PrepareModels
|
|
BMRSubmit,
|
|
|
|
/// Allow calls to rendering and EndFrame
|
|
BMRRender
|
|
};
|
|
|
|
|
|
/**
|
|
* Struct BMRModelData: Per-CModel render data used by the BatchModelRenderer.
|
|
*/
|
|
struct BMRModelData : public CModelRData
|
|
{
|
|
BMRModelData(BatchModelRendererInternals* bmri, CModel* model)
|
|
: CModelRData(bmri, model), m_BMRI(bmri), m_Data(0), m_Next(0) { }
|
|
virtual ~BMRModelData();
|
|
|
|
/// Back-link to "our" modelrenderer
|
|
BatchModelRendererInternals* m_BMRI;
|
|
|
|
/// Private data created by derived class' CreateModelData
|
|
void* m_Data;
|
|
|
|
/// Next model in the per-ModelDefTracker-slot linked list.
|
|
BMRModelData* m_Next;
|
|
};
|
|
|
|
|
|
/**
|
|
* Class BMRModelDefTracker: Per-CModelDef data used by the BatchModelRenderer.
|
|
*
|
|
* Note that classes that derive from BatchModelRenderer should use
|
|
* their own per-CModelDef data if necessary.
|
|
*/
|
|
struct BMRModelDefTracker : public CModelDefRPrivate
|
|
{
|
|
BMRModelDefTracker(CModelDefPtr mdef)
|
|
: m_ModelDef(mdef), m_Next(0), m_Slots(0) { }
|
|
|
|
/// Back-link to the CModelDef object
|
|
boost::weak_ptr<CModelDef> m_ModelDef;
|
|
|
|
/// Pointer to the next ModelDefTracker that has submitted models.
|
|
BMRModelDefTracker* m_Next;
|
|
|
|
/// Number of slots used in m_ModelSlots
|
|
uint m_Slots;
|
|
|
|
/// Each slot contains a linked list of model data objects, up to m_Slots-1
|
|
// At the end of the frame, m_Slots is reset to 0, but m_ModelSlots stays
|
|
// the same size (we assume the same number of slots is going to be used
|
|
// next frame)
|
|
std::vector<BMRModelData*> m_ModelSlots;
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
* Struct BatchModelRendererInternals: Internal data of the BatchModelRenderer
|
|
*
|
|
* Separated into the source file to increase implementation hiding (and to
|
|
* avoid some causes of recompiles).
|
|
*/
|
|
struct BatchModelRendererInternals
|
|
{
|
|
BatchModelRendererInternals(BatchModelRenderer* r) : m_Renderer(r) { }
|
|
|
|
/// Back-link to "our" renderer
|
|
BatchModelRenderer* m_Renderer;
|
|
|
|
/// ModelVertexRenderer used for vertex transformations
|
|
ModelVertexRendererPtr vertexRenderer;
|
|
|
|
/// Track the current "phase" of the frame (only for debugging purposes)
|
|
BMRPhase phase;
|
|
|
|
/// Linked list of ModelDefTrackers that have submitted models
|
|
BMRModelDefTracker* submissions;
|
|
|
|
/// Helper functions
|
|
void ThunkDestroyModelData(CModel* model, void* data)
|
|
{
|
|
vertexRenderer->DestroyModelData(model, data);
|
|
}
|
|
|
|
void RenderAllModels(RenderModifierPtr modifier, u32 filterflags, uint pass, uint streamflags);
|
|
};
|
|
|
|
BMRModelData::~BMRModelData()
|
|
{
|
|
m_BMRI->ThunkDestroyModelData(GetModel(), m_Data);
|
|
}
|
|
|
|
|
|
// Construction/Destruction
|
|
BatchModelRenderer::BatchModelRenderer(ModelVertexRendererPtr vertexrenderer)
|
|
{
|
|
m = new BatchModelRendererInternals(this);
|
|
m->vertexRenderer = vertexrenderer;
|
|
m->phase = BMRSubmit;
|
|
m->submissions = 0;
|
|
}
|
|
|
|
BatchModelRenderer::~BatchModelRenderer()
|
|
{
|
|
delete m;
|
|
}
|
|
|
|
// Submit one model.
|
|
void BatchModelRenderer::Submit(CModel* model)
|
|
{
|
|
debug_assert(m->phase == BMRSubmit);
|
|
|
|
CModelDefPtr mdef = model->GetModelDef();
|
|
BMRModelDefTracker* mdeftracker = (BMRModelDefTracker*)mdef->GetRenderData(m);
|
|
CModelRData* rdata = (CModelRData*)model->GetRenderData();
|
|
BMRModelData* bmrdata = 0;
|
|
|
|
// Ensure model def data and model data exist
|
|
if (!mdeftracker)
|
|
{
|
|
mdeftracker = new BMRModelDefTracker(mdef);
|
|
mdef->SetRenderData(m, mdeftracker);
|
|
}
|
|
|
|
if (rdata && rdata->GetKey() == m)
|
|
{
|
|
bmrdata = (BMRModelData*)rdata;
|
|
}
|
|
else
|
|
{
|
|
bmrdata = new BMRModelData(m, model);
|
|
bmrdata->m_Data = m->vertexRenderer->CreateModelData(model);
|
|
rdata = bmrdata;
|
|
model->SetRenderData(bmrdata);
|
|
model->SetDirty(~0u);
|
|
g_Renderer.LoadTexture(model->GetTexture(), GL_CLAMP_TO_EDGE);
|
|
}
|
|
|
|
// Add the model def tracker to the submission list if necessary
|
|
if (!mdeftracker->m_Slots)
|
|
{
|
|
mdeftracker->m_Next = m->submissions;
|
|
m->submissions = mdeftracker;
|
|
}
|
|
|
|
// Add the bmrdata to the modeldef list
|
|
Handle htex = model->GetTexture()->GetHandle();
|
|
uint idx;
|
|
|
|
for(idx = 0; idx < mdeftracker->m_Slots; ++idx)
|
|
{
|
|
BMRModelData* in = mdeftracker->m_ModelSlots[idx];
|
|
|
|
if (in->GetModel()->GetTexture()->GetHandle() == htex)
|
|
break;
|
|
}
|
|
|
|
if (idx >= mdeftracker->m_Slots)
|
|
{
|
|
++mdeftracker->m_Slots;
|
|
if (mdeftracker->m_Slots > mdeftracker->m_ModelSlots.size())
|
|
{
|
|
mdeftracker->m_ModelSlots.push_back(0);
|
|
debug_assert(mdeftracker->m_ModelSlots.size() == mdeftracker->m_Slots);
|
|
}
|
|
mdeftracker->m_ModelSlots[idx] = 0;
|
|
}
|
|
|
|
bmrdata->m_Next = mdeftracker->m_ModelSlots[idx];
|
|
mdeftracker->m_ModelSlots[idx] = bmrdata;
|
|
}
|
|
|
|
|
|
// Call update for all submitted models and enter the rendering phase
|
|
void BatchModelRenderer::PrepareModels()
|
|
{
|
|
debug_assert(m->phase == BMRSubmit);
|
|
|
|
for(BMRModelDefTracker* mdeftracker = m->submissions; mdeftracker; mdeftracker = mdeftracker->m_Next)
|
|
{
|
|
for(uint idx = 0; idx < mdeftracker->m_Slots; ++idx)
|
|
{
|
|
for(BMRModelData* bmrdata = mdeftracker->m_ModelSlots[idx]; bmrdata; bmrdata = bmrdata->m_Next)
|
|
{
|
|
CModel* model = bmrdata->GetModel();
|
|
|
|
debug_assert(model->GetRenderData() == bmrdata);
|
|
|
|
m->vertexRenderer->UpdateModelData(
|
|
model, bmrdata->m_Data,
|
|
bmrdata->m_UpdateFlags);
|
|
bmrdata->m_UpdateFlags = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
m->phase = BMRRender;
|
|
}
|
|
|
|
|
|
// Clear the submissions list
|
|
void BatchModelRenderer::EndFrame()
|
|
{
|
|
static uint mostslots = 1;
|
|
|
|
for(BMRModelDefTracker* mdeftracker = m->submissions; mdeftracker; mdeftracker = mdeftracker->m_Next)
|
|
{
|
|
if (mdeftracker->m_Slots > mostslots)
|
|
{
|
|
mostslots = mdeftracker->m_Slots;
|
|
debug_printf("BatchModelRenderer: SubmissionSlots maximum: %u\n", mostslots);
|
|
}
|
|
mdeftracker->m_Slots = 0;
|
|
}
|
|
m->submissions = 0;
|
|
|
|
m->phase = BMRSubmit;
|
|
}
|
|
|
|
|
|
// Return whether we models have been submitted this frame
|
|
bool BatchModelRenderer::HaveSubmissions()
|
|
{
|
|
return m->submissions != 0;
|
|
}
|
|
|
|
|
|
// Render models, outer loop for multi-passing
|
|
void BatchModelRenderer::Render(RenderModifierPtr modifier, u32 flags)
|
|
{
|
|
debug_assert(m->phase == BMRRender);
|
|
|
|
if (!HaveSubmissions())
|
|
return;
|
|
|
|
uint pass = 0;
|
|
|
|
do
|
|
{
|
|
uint streamflags = modifier->BeginPass(pass);
|
|
const CMatrix3D* texturematrix = 0;
|
|
|
|
if (streamflags & STREAM_TEXGENTOUV1)
|
|
texturematrix = modifier->GetTexGenMatrix(pass);
|
|
|
|
m->vertexRenderer->BeginPass(streamflags, texturematrix);
|
|
|
|
m->RenderAllModels(modifier, flags, pass, streamflags);
|
|
|
|
m->vertexRenderer->EndPass(streamflags);
|
|
} while(!modifier->EndPass(pass++));
|
|
}
|
|
|
|
|
|
// Render one frame worth of models
|
|
void BatchModelRendererInternals::RenderAllModels(
|
|
RenderModifierPtr modifier, u32 filterflags,
|
|
uint pass, uint streamflags)
|
|
{
|
|
for(BMRModelDefTracker* mdeftracker = submissions; mdeftracker; mdeftracker = mdeftracker->m_Next)
|
|
{
|
|
vertexRenderer->PrepareModelDef(streamflags, mdeftracker->m_ModelDef.lock());
|
|
|
|
for(uint idx = 0; idx < mdeftracker->m_Slots; ++idx)
|
|
{
|
|
BMRModelData* bmrdata = mdeftracker->m_ModelSlots[idx];
|
|
|
|
modifier->PrepareTexture(pass, bmrdata->GetModel()->GetTexture());
|
|
|
|
for(; bmrdata; bmrdata = bmrdata->m_Next)
|
|
{
|
|
CModel* model = bmrdata->GetModel();
|
|
|
|
debug_assert(bmrdata->GetKey() == this);
|
|
|
|
if (filterflags && !(model->GetFlags()&filterflags))
|
|
continue;
|
|
|
|
modifier->PrepareModel(pass, model);
|
|
vertexRenderer->RenderModel(streamflags, model, bmrdata->m_Data);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|