游戏开发论坛

 找回密码
 立即注册
搜索
查看: 11013|回复: 10

---------基于Gpu技术的批量汉字的显示

[复制链接]

4

主题

15

帖子

26

积分

注册会员

Rank: 2

积分
26
QQ
发表于 2008-7-23 17:08:00 | 显示全部楼层 |阅读模式
首先感谢《让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-&gtositon = 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只是一个渲染实体啊!难以相信吧!



从上向下看

4

主题

15

帖子

26

积分

注册会员

Rank: 2

积分
26
QQ
 楼主| 发表于 2008-7-23 17:17:00 | 显示全部楼层

Re: ---------基于Gpu技术的批量汉字的显示

沙发先抢了!
第一次发帖,希望大家多多支持

2

主题

52

帖子

58

积分

注册会员

Rank: 2

积分
58
发表于 2008-7-23 17:27:00 | 显示全部楼层

Re:---------基于Gpu技术的批量汉字的显示

cool,楼主精神可贵。

3

主题

4

帖子

0

积分

新手上路

Rank: 1

积分
0
发表于 2008-7-25 01:11:00 | 显示全部楼层

Re:基于GPU技术的批量汉字的显示

图片来自qq,不可引用,没法看

4

主题

15

帖子

26

积分

注册会员

Rank: 2

积分
26
QQ
 楼主| 发表于 2008-7-25 17:16:00 | 显示全部楼层

Re:基于GPU技术的批量汉字的显示

Sorry, I don't konw how to upload image

13

主题

31

帖子

46

积分

注册会员

Rank: 2

积分
46
发表于 2008-8-2 17:42:00 | 显示全部楼层

Re: 基于GPU技术的批量汉字的显示

楼主很用心,我做的时候偷懒直接用drawtext生成texture,不过用数组索引纹理结构,效率还是蛮高的。唯一的问题是遇到不支持32位贴图的显卡就完了

3

主题

16

帖子

20

积分

注册会员

Rank: 2

积分
20
发表于 2009-3-13 09:42:00 | 显示全部楼层

Re:基于GPU技术的批量汉字的显示

虽然来的晚了点,但好贴永远值得看。
支持。
还有呀!
//根据singleMesh的内容生成MeshPtr ,请参阅ogre官方网站wiki里的内容,
这说的文章在那里呀?
我找得好辛苦呀!
麻烦有心人回复一下。

3

主题

16

帖子

20

积分

注册会员

Rank: 2

积分
20
发表于 2009-3-13 14:05:00 | 显示全部楼层

Re: 基于GPU技术的批量汉字的显示

这是我重构的文件,但似乎只能生成一个字体纹理,却显示不出公告板,应该还要在笔者说的“//根据singleMesh的内容生成MeshPtr ,请参阅ogre官方网站wiki里的内容”的地方添加代码,希望有心人补全。

3

主题

16

帖子

20

积分

注册会员

Rank: 2

积分
20
发表于 2009-3-13 14:09:00 | 显示全部楼层

Re: 基于GPU技术的批量汉字的显示

代码文件在解压包里,只给出了有关重构的类,要演示的,需要自己添加。

4

主题

15

帖子

26

积分

注册会员

Rank: 2

积分
26
QQ
 楼主| 发表于 2009-3-14 10:15:00 | 显示全部楼层

Re:基于GPU技术的批量汉字的显示

回楼上的
SingleMesh是我自己定义的一个数据结构。里面存放的是顶点数据,索引数据。纹理数据。
如果想将这些数据转换成Ogre可以直接用的数据,就需要你自己去写了
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

作品发布|文章投稿|广告合作|关于本站|游戏开发论坛 ( 闽ICP备17032699号-3 )

GMT+8, 2025-6-8 14:00

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表