1:综述
在引擎中常用的汉字文字显示分二维或者三维文字显示,不论二维或者三维文字基本思路都是先找到汉字字体库,然后把字形提取出来进一步处理,方法基本分两大类,一种是直接剖分成网格显示,这种方法生成和显示速度慢一些。另一种是把字形做成纹理,这种相对于网格方法会快一点。
2:OSG中显示文字方法
先用字体库生成相关的字形,然后把字形贴到四边形网格上形成纹理,然后绘制。主要代码利用的osgText库,这个库网上有相关的使用方法,具体了解这个库查阅相关资料和源代码,这个不多说。
这个库提供了边框,补白,阴影等显示效果,在显示文字比较少的情况下是够用了。但是如果显示文字多的话,每一次都要生成字形,每一渲染都要上传纹理,生成速度,速度都比较慢。
3:大规模文字生成和显示
目前市面上的引擎或者专门显示字体的库,一般都着重于显示效果,显示大数据量的文字就很慢了,基本都会卡死。本人针对大数量文字的显示提出了一个基本思路,基本方法就是用空间换时间,具体方法如下:
做一个工具把所有的常用的汉字字形提取出来,然后把这些字形按照行列排成正方形成一个PNG文件,然后保存。等使用的时候把这个PNG文件读入内存,再一次性上传到GPU,显示具体文字的时候再去查找文字在PNG中的位置,然后把文字纹理从PNG图片中抠出来,再贴到四边形上显示出来。
相关的实现类有QFontImplementation, 这个类主要用于生成字形。OSGBigTextImag, 这个类主要实现生成PNG的工具,显示文字。ShareTexture2D,这个类主要实现所有的图片共享纹理。
这种方法有两个优点,第一个就是把所有的字形事先生成一个大PNG文件,所以生成文字速度块,第二个就是共享纹理大图片,省去了每次上传字形纹理的过程,所以显示速度快。
4:源码解析
下面是部分代码注释:
这个静态函数就是生成工具,调用后直接生成所有文字的排列的PNG文件,下面分别
简单注释一下相关函数,更详细的注释可以直接看源代码。
void OSGBigTextImag::createTextImageTool()
{
OSGBigTextImag tool;
//32
tool.getTxtText(32);
tool.gridImage();
tool.writeTextRowCol();
}
//把常用文字读进来,提取字形并保存。
void OSGBigTextImag::getTxtText(float size)
{
std::ifstream ifs(text_path"text.txt");
m_font.setPixelSize(size);
while(!ifs.eof())
{
std::string text;
ifs>>text;
//生成字形。
createTextImage(QString(text.c_str()));
}
ifs.close();
}
//把所有的文字排列成正方形,形成Image
void OSGBigTextImag::gridImage()
{
if(m_images.size() == 0)
{
return;
}
//算出文字排列的正方形的边长
m_gridrc = getTotalGrid();
//写出文字在纹理图中的行列,用TXT文件保存
std::map<QString, QImage*>::iterator it = m_images.begin();
int index = 0;
for(; it != m_images.end(); it++)
{
index++;
m_images_vec.push_back(it->second);
m_text_pos.insert(std::make_pair(it->first.toStdString(), index));
}
QImage *markerImage = new QImage(m_gridrc * m_images[0]->width(), m_gridrc * m_images[0]->height(), m_images[0]- >format());
QPainter *painter = new QPainter(markerImage);
QImage* temp = NULL;
for(size_t i = 0; i < m_gridrc ; i++)
{
for(size_t j = 0; j < m_gridrc; j++)
{
if(i * m_gridrc + j < m_images_vec.size())
{
temp = m_images_vec[i * m_gridrc + j];
}
else
{
std::map<QString, QImage*>::iterator tempIt = m_images.begin();
temp = tempIt->second;
}
//32 48
painter->drawImage(32 * j, 48 * i, *temp);
}
}
std::ofstream ofs(text_path"bigTextTexture.dat", std::ofstream::binary);
ofs.write(((char*)markerImage->bits()), markerImage->byteCount());
ofs.close();
std::ofstream infos(text_path"info.dat");
infos<<markerImage->byteCount()<<" ";
infos<<markerImage->width()<<" " ;
infos<<markerImage->height()<<" " ;
infos<<m_gridrc<<" " ;
infos.close();
//写出文字及行列
writeTextRowCol();
}
显示三维文字,points是文字显示的位置和向量,text就要显示的文字,width宽,height高,color颜色
osg::ref_ptr<osg::MatrixTransform> OSGBigTextImag::create3DtextFast(std::vector<osg::Vec3f> points, QString text, float width, float height, osg::Vec4 color)
{
//生成矩阵,决定文字的位置和方向
osg::ref_ptr<osg::MatrixTransform> transform = new osg::MatrixTransform();
osg::Vec2f vecy = osg::Vec2f(points[1][0], points[1][2]) - osg::Vec2f(points[0][0], points[0][2]);
vecy.normalize();
osg::Matrixf trs;
trs.makeTranslate(points[0]);
osg::Matrixf rot;
rot.makeRotate(osg::Vec3f(0, 0, 1), osg::Vec3f(vecy[0], 0, vecy[1]));
osg::Matrixf mat = rot * trs;
transform->setMatrix(mat);
std::map<QString, osg::ref_ptr<osg::Geode> >::iterator itGeode = m_global_texts.begin();
if(itGeode != m_global_texts.end())
{
transform->addChild(itGeode->second);
return transform;
}
//先查找是否已经生成
std::map<std::string, int>::iterator it = m_text_pos.find(text.toStdString());
float num = 0.0;
if(it != m_text_pos.end())
{
num = it->second;
}
//计算文字在图片中所占的位置
osg::ref_ptr<osg::Vec3Array> pos = new osg::Vec3Array();
pos->push_back(osg::Vec3f(-width, 0, 0)) ;
pos->push_back(osg::Vec3f(width, 0, 0)) ;
pos->push_back(osg::Vec3f(width, 0.0, height)) ;
pos->push_back(osg::Vec3f(-width, 0.0, height));
osg::ref_ptr<osg::Vec2Array> tex = new osg::Vec2Array();
tex->push_back(osg::Vec2(0, 0));
tex->push_back(osg::Vec2(1, 0));
tex->push_back(osg::Vec2(1, 1));
tex->push_back(osg::Vec2(0, 1));
color = osg::Vec4(num, 1.0, 0.0, 1.0);
//生成四边形,把文字贴上去。
osg::ref_ptr<osg::Geode> geode = createGeode(pos, osg::PrimitiveSet::QUADS, color);
( (osg::Geometry*)geode->getDrawable(0) )->setTexCoordArray(0, tex, osg::Array::BIND_PER_VERTEX);
//所有文字共享的stateset
( (osg::Geometry*)geode->getDrawable(0) )->setStateSet( m_global_stateset );
transform->addChild(geode);
return transform;
}
5:性能分析
目前的这种加速的优化方法,只用事先生成的一个大纹理,纹理只上传一次且用shader编程实现,生成和显示文件的速度非常快。
这种方法每个文字只绘制一个四边形,越是绘制文字数量多,越能显示出优势, 预计显示几十万个文字甚至上百万的文字没有问题,已经达到了文字显示的最大速度。