|
首先感谢《让ogre支持中文》前两篇文章的的作者,感谢他给我们提供了宝贵的资料,同时希望各位网友能多提一些宝贵的意见,本人的qq是305024828,我也是ogre的忠实爱好者。希望大家多多批评指正。
回顾:《让ogre支持中文》前两篇文章的作者主要对TextAreaOverlayElement类做了手术,并且能成功的将中文显示到Overlay中。而后随着ogre1.40版本的发布,支持中文已经不在话下。但是如果你希望批量显示中文汉字标注的话,速度问题已经成为瓶颈。即便是你用billboard作为文字的载体。本人曾经亲自测过在AMD Turion (tm) 64 Mobile Technology MT-28 processor(90nm)处理器,512内存,Radeon X700显卡的机器上显示3000多个渲染实体,速度会降到10桢左右。原因是,虽然三角形的数量不多,但是渲染的实体比较多,计算机单独为每个实体设置矩阵以及可视性判断。这样会将大部分的时间用在计算上,而不是显示上。如果将这3000个实体合并成一个实体,其速度将会大大的提高。那么,如何合并这些实体。并且让实体中每个文字元素具有billboard的属性那?这将是这篇文章所要讨论的问题。
为了不改动ogre的源代码,避免引起不必要的麻烦,本人打算做个动态库供外部调用。而不是直接改动ogre的源代码。
主要用到的数据结构:
//记录字符串,字符串的长度和位置
struct _StrPos
{
wchar_t* str;
long strlen;
Vector3 Positon;
};
//记录每个字符的文理坐标(左上角和右下角)
struct _TexCoord
{
float u1;
float v1;
float u2;
float v2;
};
//存放所有字符,字符的长度和位置
typedef std::list<_StrPos*> StringMap;
StringMap mStringList;
//统计有多少无重复字符,以便生成合适大小的图片
typedef std::set<wchar_t> WordTable;
WordTable mTable;
//建立字符和纹理坐标的对应关系
typedef std::map<wchar_t,_TexCoord> TexTable;
TexTable mTexTable;
算法及其主要思想:
一.生成纹理图片
1. 统计你要显示的文字的个数(无重复字符)
在你添加文字的时候,即可统计你要显示多少个无重复的字符,这样做可以确定纹理图片的大小(如果只显示一个字,没有必要生成2048*2048大小的纹理图片,如果这样做会造成很大的浪费)。
主要代码讲解:
void QuickWord::addWord(const String& Word,const Vector3& Pos)
{
_StrPos* str = new _StrPos;
//因为str->str 应该是宽字符,而参数Word不一定是宽字符,
//因此首先将其置为空值。
str->str = NULL;
str->strlen = 0;
str-> ositon = Pos;
mStringList.push_back(str);
//将Word中所有的字符转化为宽字符
String strs = Word;
long bytelen = MultiByteToWideChar(936,0,strs.c_str(),-1,NULL,0);
wchar_t* widechar = new wchar_t[bytelen];
MultiByteToWideChar(936,0,strs.c_str(),-1,widechar,bytelen);
str->str = widechar;
str->strlen = bytelen - 1;
//将字符放到mTable中,因为mTable是std::set<wchar_t>
//类型这样可以避免重复。
long i = 0;
for(i = 0; i < bytelen - 1; i ++)
{
mTable.insert(widechar);
}
}
2. 根据要显示文字的个数确定纹理图片的大小
//确定图片的大小;
//mImgWidth为图片的长和宽,生成的图片是正方形
long chsize = mTable.size();
if(chsize <= 0)//没有字,直接返回失败
return 0;
else if(chsize <= 16)//1--16个字。
mImgWidth = 128;
else if(chsize <= 64)//17--64个字。
mImgWidth = 256;
else if(chsize <= 256)//65--256个字。
mImgWidth = 512;
else if(chsize <= 1024)//257--1024个字。
mImgWidth = 1024;
else if(chsize <= 4096)//1025--4096个字。
mImgWidth = 2048;
else //4097--16384个字。
{
mImgWidth = 2048;
//mChWidth默认的是32*32个像素,如果你的字比较多,
//只能降到16*16个像素,以牺牲字的质量为代价了,哈哈!
mChWidth = 16;
}
3. 生成图片,计算每个字符在该图片的纹理坐标,并将其放在map表中,以便查表
long QuickWord::createTexture()
{
//FreeType里的代码,不用我讲了把
FT_Library library;
FT_Face face;
long error = FT_Init_FreeType( &library );
if ( error )
return 0;
TexturePtr texture;
//判断纹理是否存在
bool exist = TextureManager::getSingleton().resourceExists(mTextureName);
if(!exist)
{
//创建一个正方形的纹理;
texture = TextureManager::getSingleton().createManual(
mTextureName,
ResourceGroupManager: EFAULT_RESOURCE_GROUP_NAME,
TEX_TYPE_2D,
mImgWidth,mImgWidth,8,PF_R8G8B8A8);
}
else
return 0;
//读取ttf文件
DataStreamPtr dataStreamPtr =
ResourceGroupManager::getSingleton().openResource("solo5.ttf",
ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
MemoryDataStream ttfchunk(dataStreamPtr);
error=FT_New_Memory_Face(library,ttfchunk.getPtr(),(FT_Long)ttfchunk.size() ,
0, &face );
if ( error == FT_Err_Unknown_File_Format )
{
return 0;
}
else if ( error )
{
return 0;
}
//设置文字的大小
error = FT_Set_Pixel_Sizes( face, mChWidth, mChWidth );
HardwarePixelBufferSharedPtr pixelBuffer = texture->getBuffer();
pixelBuffer->lock(HardwareBuffer::HBL_NORMAL);
const PixelBox& pixelBox = pixelBuffer->getCurrentLock();
uint8* pDest = static_cast<uint8*>(pixelBox.data);
memset(pDest,0,pixelBox.getConsecutiveSize());
long glyph_index;
WordTable::iterator it;
it = mTable.begin();
long ch_width = 0,ch_hight = 0;
long mChPos = 0;
long xPos,yPos;
long i = 0,j = 0;
//计算纹理图片一行能放几个汉字
long chrowNum = mImgWidth/mChWidth;
byte r,g,b,a;
long offset = 0;
_TexCoord texCoord;
//从mTable中一个一个取字符
for(;it != mTable.end(); it ++)
{
glyph_index = FT_Get_Char_Index( face, *it );
error = FT_Load_Glyph( face, glyph_index, FT_LOAD_DEFAULT);
error=FT_Render_Glyph(face->glyph,
FT_RENDER_MODE_NORMAL);
//取位图图片
FT_GlyphSlot slot = face->glyph;
void* str = (&slot->bitmap)->buffer;
ch_width = (&slot->bitmap)->width;
ch_hight = (&slot->bitmap)->rows;
//纹理坐标
//左上角
texCoord.u1 =
(float)((mChPos%chrowNum)*mChWidth)/(float)(mImgWidth);
texCoord.v1 =
(float)((mChPos/chrowNum)*mChWidth)/(float)(mImgWidth);
//右下角
texCoord.u2 = (float)((mChPos%chrowNum)*mChWidth
+ mChWidth)/(float)(mImgWidth);
texCoord.v2 = (float)((mChPos/chrowNum)*mChWidth
+ mChWidth)/(float)(mImgWidth);
//将字符和纹理坐标对应
mTexTable[*it] = texCoord;
xPos = (mChPos%chrowNum)*mChWidth + (mChWidth - ch_width)/2;
yPos = (mChPos/chrowNum)*mChWidth + (mChWidth - ch_hight)/2;
//将位图图片拷贝到纹理坐标中
for(i = 0; i < ch_hight; i++)
{
for(j = 0; j < ch_width; j++)
{
r = g = b = a = ((&slot->bitmap)->buffer[i*ch_width+j]);
pDest[((yPos + i)*mImgWidth + (xPos + j))*4] = r;
pDest[((yPos + i)*mImgWidth + (xPos + j))*4 + 1] = g;
pDest[((yPos + i)*mImgWidth + (xPos + j))*4 + 2] = b;
pDest[((yPos + i)*mImgWidth + (xPos + j))*4 + 3] = a;
}
}
mChPos++;
}
pixelBuffer->unlock();
mTable.clear();
// Image img;
// img = img.loadDynamicImage (pDest, mImgWidth,mImgWidth,
// PF_R8G8B8A8);
// img.save("F:\\downLoad\\o.jpg");
// 哈哈,不容易啊,写到这里终于可以看到一些东西了,
// 将上述的注释掉,
// 就可以看到你在F:\downLoad\目录下生成的纹理图片了。
// 如下图1所示
FT_Done_Face(face);
FT_Done_FreeType(library);
return 1;
}

图 1 生成的纹理图片
二.生成网格
既然有了纹理图片了,那么下面的问题就是生成一个载有文字的三维实体了,一般我们都用billboard(两个三角形)作为文字的载体,在此我们将利用billboard的改进版,如下图2所示:

图2 一个改进版的billboard文字载体
为了方便的生成和管理网格,我们可以将网格信息(包括顶点信息,法向量信息和纹理坐标信息等)存放在一个类里面统一管理
class SingleMesh
{
public:
//材质信息
String materialName;
//图元的类型,这里为三角形列表
RenderOperation::OperationType operationType;
//网格的坐标信息
CArray<Vector3, Vector3> positionArray;
//法向量信息
CArray<Vector3, Vector3> normalArray;
//纹理坐标信息
CArray<Vector2, Vector2> texture0Array;
//索引信息
CArray<Triangle32, Triangle32> triangleArray;
};
生成网格的代码如下:
long QuickWord::buildMesh(const String& meshName)
{
SingleMesh* singleMesh = new SingleMesh;
//”Ogre/Word”是一个材质的名称,后面会详细讲解
MaterialPtr matFont =
MaterialManager::getSingleton().getByName("Ogre/Word");
//克隆一个与”Ogre/Word”一样的材质
MaterialPtr newFont = matFont->clone(meshName+"_Material");
//mTextureName与前面所生成的纹理对象的名称相同
TextureUnitState* state = newFont->getTechnique(0)->getPass(0)
->createTextureUnitState(mTextureName);
//设置字体的颜色,因为生成的图片是黑白相间的,所以要通过设置的颜色与图片混
//合才能达到设置字体颜色的目的.
state->setColourOperationEx(LBX_MODULATE,LBS_TEXTURE,LBS_MANUAL,
mColor,mColor);
singleMesh->materialName = meshName+"_Material";
StringMap::iterator it;
long indexArray = 0;
//遍历mStringList,mStringList存放的是你要显示的一个一个字符串,字符串的长度,
//以及字符串存放的空间位置.
for(it = mStringList.begin(); it != mStringList.end(); it ++)
{
//生成一个billboard,下面有该函数的实现方法
indexArray += genStringBillboard(singleMesh,*it,indexArray);
delete *it;
}
//至此,我们就将所用的网格信息存放到了singleMesh中,
//注意:该singleMesh中存放的是所有的billboard网格信息,也就是说我们已经把所
//用的billboard信息合并到了singleMesh中.
//生成MeshPtr;
MeshPtr mesh = MeshManager::getSingleton().createManual(meshName,
ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
//根据singleMesh的内容生成MeshPtr ,请参阅ogre官方网站wiki里的内容,
//在此不必累述.
//注意,生成完了mesh,请及时将singleMesh删除,以防止内存泄露
}
//生成一个billboard
long QuickWord::genStringBillboard(SingleMesh* single,_StrPos* str,long index)
{
long strlen = str->strlen;
//计算该billboard的总长度
float tollen = strlen*mHeight;
//计算第一个字符的起始点
float beginLen = str->Positon.y - tollen/2.0;
long i;
Vector3 Point = Vector3::ZERO;
Vector2 texCoord;
Triangle32 TriIndex;
long IndexBegin = index;
Real u1, u2, v1, v2;
//根据改进版的billboard填充网格信息,
//其中法向量信息存放的是该billboard围绕
//视点旋转的点,而不是真正的法向量信息.
//一个字是一个正方形,该正方形是与yoz平面平行.
//并且法向量与x轴正方向一致.
for(i = 0; i < strlen; i ++)
{
unsigned long charI = (unsigned long)(str->str);
//查表得到纹理坐标
TexTable::iterator it = mTexTable.find(charI);
_TexCoord texcoord = (*it).second;
u1 = texcoord.u1;
v1 = texcoord.v1;
u2 = texcoord.u2;
v2 = texcoord.v2;
Point.x = str->Positon.x;
//第一个点
Point.y = beginLen + mWidth*(float)i;
Point.z = str->Positon.z + (mHeight/2);
single->positionArray.Add(Point);
single->normalArray.Add(str->Positon);
texCoord.x = u1;
texCoord.y = v1;
single->texture0Array.Add(texCoord);
//第二个点
Point.y = beginLen + mWidth*(float)(i+1);
Point.z = str->Positon.z + (mHeight/2);
single->positionArray.Add(Point);
single->normalArray.Add(str->Positon);
texCoord.x = u2;
texCoord.y = v1;
single->texture0Array.Add(texCoord);
//第三个点
Point.y = beginLen + mWidth*(float)(i+1);
Point.z = str->Positon.z - (mHeight/2);
single->positionArray.Add(Point);
single->normalArray.Add(str->Positon);
texCoord.x = u2;
texCoord.y = v2;
single->texture0Array.Add(texCoord);
//第四个点
Point.y = beginLen + mWidth*(float)i;
Point.z = str->Positon.z - (mHeight/2);
single->positionArray.Add(Point);
single->normalArray.Add(str->Positon);
texCoord.x = u1;
texCoord.y = v2;
single->texture0Array.Add(texCoord);
//计算索引信息
TriIndex.a = IndexBegin;
TriIndex.b = IndexBegin+3;
TriIndex.c = IndexBegin+1;
single->triangleArray.Add(TriIndex);
TriIndex.a = IndexBegin;
TriIndex.b = IndexBegin+3;
TriIndex.c = IndexBegin+1;
single->triangleArray.Add(TriIndex);
TriIndex.a = IndexBegin+1;
TriIndex.b = IndexBegin+3;
TriIndex.c = IndexBegin+2;
single->triangleArray.Add(TriIndex);
IndexBegin += 4;
}
return strlen*4;
}
三.随视点旋转的Gpu算法
1. 材质脚本
vertex_program BillboardWordVS hlsl
{
source BillboardVS.source
target vs_1_1
entry_point vs_main
}
material Ogre/Word
{
technique
{
pass
{
vertex_program_ref BillboardWordVS
{
param_indexed_auto 0 viewproj_matrix
param_indexed_auto 4 camera_position_object_space
}
alpha_rejection greater_equal 10
}
}
}
2. Gpu程序
//视图投影矩阵
float4x4 matViewProjection;
//相机的位置
float4 CamPos;
struct VS_OUTPUT
{
float4 Pos: POSITION;
float2 tex: TEXCOORD0;
};
VS_OUTPUT vs_main( float4 Pos : POSITION0,
float4 Nor : NORMAL, float2 tex : TEXCOORD0)
{
VS_OUTPUT output;
//归一化
Pos/=Pos.w;
float4 viewPos;
viewPos = CamPos;
viewPos /= viewPos.w;
//Nor记录的是billboard要围绕旋转的点
Nor/= Nor.w;
Pos.xyz -= Nor.xyz;
float2 viewXY = viewPos.xy - Nor.xy;
//正方形正面法向量平行于xoy并始终对着相机
float lenght = sqrt(viewXY.x*viewXY.x + viewXY.y*viewXY.y);
float cosValue = viewXY.x/lenght;
float sinValue = viewXY.y/lenght;
//旋转矩阵
float4x4 RotMat = float4x4(cosValue,-sinValue,0,0,
sinValue,cosValue,0,0,
0,0,1,0,
0,0,0,1);
Pos = mul(RotMat,Pos);
Pos.xyz += Nor.xyz;
output.Pos = mul(matViewProjection,Pos);
output.tex = tex;
return output;
}
哈哈!终于写完了,来看看我们的成果吧.

远看,10000个billboard, 720206个三角形240000个字, 跑了26.85桢

近看,所有的billboard都朝着相机的方向,
注意啊,所有的billboard只是一个渲染实体啊!难以相信吧!

从上向下看
|
|