一种简单的材质排序方法
简单的统计一下可以看到:纹理有3种,灯火有3种,Alpha有2种。如果按照最小切换次数的思想,应该先优化Alpha的切换,然后是纹理和灯光。但在实际操作中,切换纹理的消耗要远高于Alpha的切换,因为应该先考虑纹理的优化,然后是灯光,最后才是Alpha。
这里就引入以优先级的概念,即在材质中,每种属性都有其优先级。优先级高的属性需要尽可能的减少切换次数。优先级的设定是用户制定的,可能会随开发平台或不同的渲染底层有相应的改变。本文使用 纹理 - 灯光 - alpha 从高到低的顺序。
对材质进行排序必然需要一种能保存排序结果的数据结构。通过以上分析,可以推导出该数据结构至少需要满足以下条件:
1. 无歧义的保存排序结果
2. 动态的添加和删除
3. 能遍历该结构,每次返还的结果与上次返还的结果需要进行的状态切换最优化。
4. 能根据用户指定的优先级进行排序
同时我们再添加一个要求,在常数时间完成遍历和删除功能。
下图是一种满足以上条件的数据结构,由于它是按元素的差异值作为排序的关键操作的,因此可以称它为Diff-Tree。
Diff-Tree中的每个节点都有4个指针。分别为:NextPtr,PrevPtr,FirstChildPtr,ParentPtr。
NextPtr指向其右邻居,PrevPtr指向左邻居,FirstChildPtr指向第一个子节点。ParentPtr指向父节点。其中ParentPtr比较特殊,只有在一个节点本身是另一个节点的第一个子节点时,该指针才有效。即上图中阴影显示的节点。
Node提供Diff操作,该操作返回两个Node的差异值。差异值的计算如下:按材质属性的优先级顺序从高到低比较,如果该属性不相同,返还优先级。
{
SF_PAD = 0,
SF_BASETEXTURE = 1,
SF_LIGHT = 2,
SF_ALPHAFUNC = 3,
SF_MAX_COUNT
};
int SG_Shader::operator - (const SG_Shader& rkShader)
{
for(int i = SF_PAD + 1; i < SF_MAX_COUNT; i++)
{
ShaderFeature eFeature = (ShaderFeature)i;
if(GetShaderFeature(eFeature) != rkShader.GetShaderFeature(eFeature))
return i;
}
return SF_MAX_COUNT; //equal
}
如上面的代码片断,如果两个材质的纹理不同,Diff操作返回1,如果纹理相同,灯光不同,返回2,如果只有Alpha不同,返回3。
用[ x, x, x]的格式来表示一个材质,第1个x是纹理编号,第2个x是灯光编号,第3个是Alpha混合类型编号。在添加的多个节点后,Diff Tree能成为一种深度优先的结构:
DiffTree保证在按深度遍历时,每次返还的结果与上次返还的结果需要进行的状态切换最优化。如上图,首先保证了纹理切换的次数最少(先遍历完所有纹理为1的节点)。
DiffTree的插入排序算法如下:
1.如果树为空,root = insert。
2.check = root,depth = 1
3.遍历check和它所有同级节点,返回与插入节点差异度最大的节点 beinsert.
4.如果insert和beinsert相同,插入结束。
5.如果insert与beinsert差异度小于等于depth,insert被插入为beinsert的邻居节点,返回。
6.如果insert与beinsert差异度大于depth,并且beinsert没有子节点,insert为beinsert的子节点。
7.check = beinsert. FirstChildPtr,跳回第3步。
以下是向树中插入一个[1,2,3]的材质的过程。
附上代码:
created: 2:7:2007 11:35
filename: SG_Shader.h
author: Badkeeper
purpose: Shader is uesd order scenenode render. scene node with same shader
will render in a batch
*********************************************************************/
#ifndef _SG_SHADER_H
#define _SG_SHADER_H
#include "SG_SceneNode.h"
namespace MayEX
{
class SG_Shader
{
public:
SG_Shader();
~SG_Shader();
enum ShaderFeature
{
SF_PAD = 0,
SF_BASETEXTURE = 1,
SF_LIGHT = 2,
SF_ALPHAFUNC = 3,
SF_MAX_COUNT
};
bool operator == (const SG_Shader& rkShader);
//this operator is used in shader order tree sorting
int operator - (const SG_Shader& rkShader);
int operator - (const SG_Shader& rkShader) const;
void SetShaderFeature(ShaderFeature eFeature,int ID);
int GetShaderFeature(ShaderFeature eFeature);
int GetShaderFeature(const ShaderFeature eFeature) const;
SG_Shader* GetNext();
SG_Shader* GetFirstChild();
SG_Shader* GetPrev();
SG_Shader* GetParent();
const SG_Shader* GetNext() const;
const SG_Shader* GetFirstChild() const;
const SG_Shader* GetPrev() const;
const SG_Shader* GetParent() const;
void SetNext(SG_Shader* pkShader);
void SetFirstChild(SG_Shader* pkShader);
protected:
void SetPrev(SG_Shader* pkShader);
void SetParent(SG_Shader* pkShader);
int m_aiShaderFeatures[SF_MAX_COUNT];
SG_Shader* m_pkNextShader;
SG_Shader* m_pkChildShader;
SG_Shader* m_pkPrevShader;
SG_Shader* m_pkParentShader;
};
}
#endif
using namespace MayEX;
//---------------------------------------------------------
SG_Shader::SG_Shader()
: m_pkChildShader(NULL)
, m_pkNextShader(NULL)
, m_pkPrevShader(NULL)
, m_pkParentShader(NULL)
{
for(int i = SF_PAD; i < SF_MAX_COUNT; i++)
m_aiShaderFeatures[i] = -1;
}
//---------------------------------------------------------
SG_Shader::~SG_Shader()
{
if(m_pkNextShader)
{
delete m_pkNextShader;
m_pkNextShader = NULL;
}
if(m_pkChildShader)
{
delete m_pkChildShader;
m_pkChildShader = NULL;
}
//destory this
}
//---------------------------------------------------------
int SG_Shader::operator - (const SG_Shader& rkShader)
{
for(int i = SF_PAD + 1; i < SF_MAX_COUNT; i++)
{
ShaderFeature eFeature = (ShaderFeature)i;
if(GetShaderFeature(eFeature) != rkShader.GetShaderFeature(eFeature))
return i;
}
return SF_MAX_COUNT; //equal
}
//---------------------------------------------------------
int SG_Shader::operator - (const SG_Shader& rkShader) const
{
for(int i = SF_PAD + 1; i < SF_MAX_COUNT; i++)
{
ShaderFeature eFeature = (ShaderFeature)i;
if(GetShaderFeature(eFeature) != rkShader.GetShaderFeature(eFeature))
return i;
}
return SF_MAX_COUNT; //equal
}
//---------------------------------------------------------
void SG_Shader::SetShaderFeature(ShaderFeature eFeature,int ID)
{
assert(eFeature != SF_PAD && "Set error shader feature");
m_aiShaderFeatures[eFeature] = ID;
}
//---------------------------------------------------------
int SG_Shader::GetShaderFeature(ShaderFeature eFeature)
{
assert(eFeature != SF_PAD && "Get error shader feature");
return m_aiShaderFeatures[eFeature];
}
//---------------------------------------------------------
int SG_Shader::GetShaderFeature(const ShaderFeature eFeature) const
{
assert(eFeature != SF_PAD && "Get error shader feature");
return m_aiShaderFeatures[eFeature];
}
//---------------------------------------------------------
SG_Shader* SG_Shader::GetNext()
{
return m_pkNextShader;
}
//---------------------------------------------------------
SG_Shader* SG_Shader::GetFirstChild()
{
return m_pkChildShader;
}
//---------------------------------------------------------
SG_Shader* SG_Shader::GetPrev()
{
return m_pkPrevShader;
}
//---------------------------------------------------------
SG_Shader* SG_Shader::GetParent()
{
return m_pkParentShader;
}
//---------------------------------------------------------
const SG_Shader* SG_Shader::GetNext() const
{
return m_pkNextShader;
}
//---------------------------------------------------------
const SG_Shader* SG_Shader::GetFirstChild() const
{
return m_pkChildShader;
}
//---------------------------------------------------------
const SG_Shader* SG_Shader::GetPrev() const
{
return m_pkPrevShader;
}
//---------------------------------------------------------
const SG_Shader* SG_Shader::GetParent() const
{
return m_pkParentShader;
}
//---------------------------------------------------------
void SG_Shader::SetNext(SG_Shader* pkShader)
{
m_pkNextShader = pkShader;
if(pkShader)
pkShader->SetPrev(this);
}
//---------------------------------------------------------
void SG_Shader::SetFirstChild(SG_Shader* pkShader)
{
m_pkChildShader = pkShader;
if(pkShader)
pkShader->SetParent(this);
}
//---------------------------------------------------------
void SG_Shader::SetPrev(SG_Shader* pkShader)
{
m_pkPrevShader = pkShader;
}
//---------------------------------------------------------
void SG_Shader::SetParent(SG_Shader* pkShader)
{
m_pkParentShader = pkShader;
}
created: 3:7:2007 11:35
filename: SG_ShaderTree.h
author: Badkeeper
purpose: Shader Tree is used sort geometry render.
*********************************************************************/
#ifndef _SG_SHADER_TREE_H
#define _SG_SHADER_TREE_H
#include "c_string_utility.h"
#include "SG_Shader.h"
namespace MayEX
{
class SG_ShaderTree
{
public:
SG_ShaderTree();
~SG_ShaderTree();
bool InsertShader(SG_Shader* pkShader);
bool RemoveShader(SG_Shader* pkShader);
int Size();
void OutputShaderTreeStruct();
SG_Shader* GetFirstNode();
SG_Shader* GetNext(SG_Shader* pkNode);
SG_Shader* GetParent(SG_Shader* pkNode);
protected:
bool _insertShader(SG_Shader* pkFirstNode,
SG_Shader* pkInsertShader,
int iTreeLevel);
SG_Shader* _findMaxDiffNodeOnThisLevel(const SG_Shader* pkFirstNode,
const SG_Shader* pkCompareShader);
void _outputShaderTreeStruct(const SG_Shader* pkTreeRoot,int iTreeLevel);
void _outputShaderStruct(const SG_Shader* pkNode,int iTreeLevel);
SG_Shader* m_pkRootShader;
int m_iSize;
};
}
#endif
#include "c_logger_manager.h"
using namespace MayEX;
using namespace LogSystem;
//-----------------------------------------------------
SG_ShaderTree::SG_ShaderTree()
: m_pkRootShader(NULL)
, m_iSize(0)
{
}
//---------------------------------------------------------
SG_ShaderTree::~SG_ShaderTree()
{
if(m_pkRootShader)
{
delete m_pkRootShader;
m_pkRootShader = NULL;
}
}
//---------------------------------------------------------
bool SG_ShaderTree::RemoveShader(SG_Shader* pkShader)
{
if(!pkShader)
return false;
SG_Shader* pkReplaceShader = NULL;
if(pkShader->GetFirstChild())
{
pkReplaceShader = pkShader->GetFirstChild();
pkReplaceShader->SetFirstChild(pkReplaceShader->GetNext());
pkReplaceShader->SetNext(pkShader->GetNext());
}
else if(pkShader->GetNext())
{
pkReplaceShader = pkShader->GetNext();
}
SG_Shader* pkPrevShader = pkShader->GetPrev();
if(pkPrevShader)
{
assert(pkPrevShader->GetNext() == pkShader && "ShaderTree Critical Error");
pkPrevShader->SetNext(pkReplaceShader);
}
SG_Shader* pkParentShader = pkShader->GetParent();
if(pkParentShader)
{
assert(pkParentShader->GetFirstChild() == pkShader && "ShaderTree Critical Error");
assert(!pkPrevShader && "ShaderTree Critical Error");
pkParentShader->SetFirstChild(pkReplaceShader);
}
return true;
}
//---------------------------------------------------------
bool SG_ShaderTree::InsertShader(SG_Shader* pkShader)
{
if(!pkShader)
return false;
if(!m_pkRootShader)
{
m_pkRootShader = pkShader;
m_iSize++;
return true;
}
SG_Shader* pkRootShader = _findMaxDiffNodeOnThisLevel(m_pkRootShader,pkShader);
return _insertShader(pkRootShader,pkShader,1);
}
//---------------------------------------------------------
bool SG_ShaderTree::_insertShader(SG_Shader* pkFirstNode,
SG_Shader* pkInsertShader,int iTreeLevel)
{
int iDiff = *pkFirstNode - *pkInsertShader;
if(iDiff == SF_MAX_COUNT)
{
//Equal TODO
return true;
}
if(iDiff <= iTreeLevel)
{
SG_Shader* pkFirstNeigborNode = pkFirstNode->GetNext();
pkFirstNode->SetNext(pkInsertShader);
pkInsertShader->SetNext(pkFirstNeigborNode);
m_iSize++;
return true;
}
if(pkFirstNode->GetFirstChild() == NULL)
{
pkFirstNode->SetFirstChild(pkInsertShader);
m_iSize++;
return true;
}
else
{
SG_Shader* pkFirstChildNode = pkFirstNode->GetFirstChild();
SG_Shader* pkParent = _findMaxDiffNodeOnThisLevel(pkFirstChildNode,pkInsertShader);
assert(pkParent && "ShaderTree insert failure");
return _insertShader(pkParent,pkInsertShader,iTreeLevel+1);
}
}
//---------------------------------------------------------
SG_Shader* SG_ShaderTree::_findMaxDiffNodeOnThisLevel(const SG_Shader* pkFirstNode,
const SG_Shader* pkCompareShader)
{
int iDiff = *pkFirstNode - *pkCompareShader;
SG_Shader* pkResult = const_cast<SG_Shader*>(pkFirstNode);
SG_Shader* pkDummy = pkResult;
while(pkDummy->GetNext())
{
pkDummy = pkDummy->GetNext();
int iDiff2 = *pkDummy - *pkCompareShader;
if(iDiff2 >= iDiff)
{
pkResult = pkDummy;
iDiff = iDiff2;
}
}
return pkResult;
}
//---------------------------------------------------------
void SG_ShaderTree::OutputShaderTreeStruct()
{
LOGLISTBEGIN(LOGTYPE_PROFILE,"");
_outputShaderTreeStruct(m_pkRootShader,1);
LOGLISTEND(LOGTYPE_PROFILE);
}
//---------------------------------------------------------
void SG_ShaderTree::_outputShaderTreeStruct(const SG_Shader* pkTreeRoot,
int iTreeLevel)
{
SG_Shader* pkOutputShader = const_cast<SG_Shader*>(pkTreeRoot);
if(!pkOutputShader)
return;
_outputShaderStruct(pkOutputShader,iTreeLevel);
//output child
const SG_Shader* pkChildShader = pkTreeRoot->GetFirstChild();
if(pkChildShader)
{
LOGLISTBEGIN(LOGTYPE_PROFILE,"");
_outputShaderTreeStruct(pkChildShader,iTreeLevel + 1);
LOGLISTEND(LOGTYPE_PROFILE);
}
//output neighbor
const SG_Shader* pkNeighborShader = pkTreeRoot->GetNext();
while(pkNeighborShader)
{
_outputShaderTreeStruct(pkNeighborShader,iTreeLevel);
pkNeighborShader = pkNeighborShader->GetNext();
}
return ;
}
//---------------------------------------------------------
void SG_ShaderTree::_outputShaderStruct(const SG_Shader* pkNode,int iTreeLevel)
{
if(!pkNode)
return;
Utility::StringType kResult = "[";
for(int i = SG_Shader::SF_PAD + 1; i < SG_Shader::SF_MAX_COUNT; i++)
{
char cFeature[32] = {0};
int iID = pkNode->GetShaderFeature(SG_Shader::ShaderFeature(i));
sprintf(cFeature," %i," ,iID);
kResult += cFeature;
}
kResult += "]";
LOGLISTITEM(LOGTYPE_PROFILE,kResult.c_str());
return;
}
//---------------------------------------------------------
SG_Shader* SG_ShaderTree::GetFirstNode()
{
return m_pkRootShader;
}
//---------------------------------------------------------
SG_Shader* SG_ShaderTree::GetParent(SG_Shader* pkNode)
{
SG_Shader* pkParent = NULL;
if(!pkNode->GetPrev())
pkParent = pkNode->GetParent();
else
{
SG_Shader* pkPrev = pkNode->GetPrev();
while(pkPrev->GetPrev())
pkPrev = pkPrev->GetPrev();
assert(pkPrev);
pkParent = pkPrev->GetParent();
}
return pkParent;
}
//---------------------------------------------------------
SG_Shader* SG_ShaderTree::GetNext(SG_Shader* pkNode)
{
if(!pkNode)
return NULL;
if(pkNode->GetFirstChild())
return pkNode->GetFirstChild();
else if(pkNode->GetNext())
return pkNode->GetNext();
SG_Shader* pkParent = GetParent(pkNode);
while(pkParent && !pkParent->GetNext())
pkParent = GetParent(pkParent);
if(pkParent)
return pkParent->GetNext();
return NULL;
}