osgText使用版本osg3.6.5

本文介绍了osgText库的核心组件Text和Font,展示了如何加载字体、创建Text对象并将其添加到Geode中进行渲染。通过实例展示了如何使用不同坐标系和属性设置文本显示。

前言

osgText 库定义了一个命名空间,osgText。在这个命名空间中有一些十分实
用的字体加载和文字渲染类。
osgText 库的核心组件是osgText::Text 类。Text 继承自Drawable,因此用户
程序应当使用addDrawable()方法把Text 实例添加到Geode 中(与添加Geometry
实例的方法相同)。Text 可用于显示一个任意长度的字符串。因此,用户程序可
以为每个将要显示的字符串创建一个相应的Text 对象。
osgText 库的另一个核心组件是osgText::Font 类。osgText 的函数可以根据字
体文件的名称来创建Font 对象。Font 类使用FreeType 插件来读取字体文件。用
户程序将Font 对象和Text 对象相关联时,Font 将创建一个用于绘制字符串图形
的纹理贴图。在渲染时,Text 将使用与该图形相符的纹理坐标,为文本中的每一
个字符绘制一个已添加纹理的四边形。osgText 库还定义了一个String 类,以支
持多字节字符(multibyte)和各类文字编码。

代码

#include "osg/Group"
#include "osgText/Text"
#include "osg/Geode"
#include "osg/Geometry"
#include "osgText/Font"
#include "osgDB/WriteFile"
#include "osgDB/ReadFile"
#include "osg/Node"
#include "osg/ref_ptr"
#include "osgViewer/View"
#include "osgViewer/Viewer"
#include "osg/Matrix"
#include "osg/MatrixTransform"

//创建背板
osg::ref_ptr<osg::Drawable> createBase()
{
	osg::ref_ptr<osg::Geometry> geom = new osg::Geometry;

	osg::Matrix m;
	osg::ref_ptr<osg::MatrixTransform> mt = new osg::MatrixTransform();
	/*m.makeRotate(m,)*/


	osg::ref_ptr<osg::Vec3Array> v = new osg::Vec3Array;
	geom->setVertexArray(v.get());
	v->push_back(osg::Vec3(-1.f, 0.f, -1.f));
	v->push_back(osg::Vec3(1.f, 0.f, -1.f));
	v->push_back(osg::Vec3(1.f, 0.f, 1.f));
	v->push_back(osg::Vec3(-1.f, 0.f, 1.f));

	osg::ref_ptr<osg::Vec4Array> c = new osg::Vec4Array;
	geom->setColorArray(c.get());
	/*setColorBinding()和setNormalBinding() - 这些方法用于设置Geometry 类
		中颜色和法线数据的绑定方式。其输入参数为Geometry 类的枚举量。
		清单2 - 1 中,颜色绑定方式为osg::Geometry::BIND_PER_VERTEX,即
		每种颜色对应一个顶点。而法线的绑定方式为
		osg::Geometry::BIND_OVERALL,即整个Geometry 几何体对应唯一的
		一个法线数据。*/
	geom->setColorBinding(osg::Geometry::BIND_PER_VERTEX);
	c->push_back(osg::Vec4(1.f, 1.f, 0.f, 1.f));
	c->push_back(osg::Vec4(0.f, 1.f, 0.f, 1.f));
	c->push_back(osg::Vec4(0.f, 0.f, 1.f, 1.f));
	c->push_back(osg::Vec4(1.f, 1.f, 1.f, 1.f));

	//法线
	osg::ref_ptr<osg::Vec3Array> n = new osg::Vec3Array;
	geom->setNormalArray(n.get());
	/*而法线的绑定方式为
		osg::Geometry::BIND_OVERALL,即整个Geometry 几何体对应唯一的
		一个法线数据。*/
	geom->setNormalBinding(osg::Geometry::BIND_OVERALL);
	n->push_back(osg::Vec3(0.f, 1.f, 0.f));

	geom->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::QUADS, 0, 4));
	
	return (geom.release());

}

osg::ref_ptr<osg::Node> createSceneGraph()
{
	// Create the root (and only) node.
	osg::ref_ptr<osg::Geode> geode = new osg::Geode;
	geode->addDrawable(createBase());

	//字体
	osg::ref_ptr<osgText::Font> font = new osgText::Font;
	font = osgText::readFontFile("fonts/arial.ttf");


	

	osg::Vec4 white(osg::Vec4(1.f, 1.f, 1.f, 1.f));
	{
		//Text.osg文件中有个默认参数:autoRotateToScreen,始终保持字体面向屏幕
		osg::ref_ptr<osgText::Text> text = new osgText::Text;
		text->setFont(font.get());
		text->setColor(osg::Vec4(0.f, 1.f, 1.f, 1.f));  //青色
		text->setCharacterSize(.15f);
		text->setPosition(osg::Vec3(1.f, 0.f, 1.f));
		/*如果要创建一个一直朝向视口的广告牌形式的文
			字,可以使用Text::SCREEN。*/
		text->setAxisAlignment(osgText::Text::XY_PLANE);//因为没有设置SCREEN,所以在Text.osg文件中没有autoRotateToScreen TRUE 属性
		text->setAlignment(osgText::Text::LEFT_TOP);  //左上基准点开始的字体
		text->setText("right_right");
		geode->addDrawable(text.get());
	}

	{
		osg::ref_ptr<osgText::Text> text = new osgText::Text;
		text->setFont(font.get());
		text->setColor(osg::Vec4(1.f, 0.f, 0.f, 1.f));
		text->setCharacterSize(.15f);
		text->setPosition(osg::Vec3(-1.f, 0.f, 0.f));
		text->setAxisAlignment(osgText::Text::SCREEN); //因为没有设置SCREEN,所以在Text.osg文件中有autoRotateToScreen TRUE 属性
		text->setText("right_right");
		text->setText("Top_left");
		geode->addDrawable(text.get());
	}

	{
		osg::ref_ptr<osgText::Text> text = new osgText::Text;
		text->setFont(font.get());
		text->setColor(osg::Vec4(0.f, 1.f, 0.f, 1.f));
		text->setCharacterSize(.15f);
		text->setPosition(osg::Vec3(-1.f, -1.f, 0.f));
		text->setAxisAlignment(osgText::Text::SCREEN);
		text->setText("Bottom_left");
		geode->addDrawable(text.get());
	}

	{
		osg::ref_ptr<osgText::Text> text = new osgText::Text;
		text->setFont(font.get());
		text->setColor(osg::Vec4(0.f, 0.f, 1.f, 1.f));
		text->setCharacterSize(.15f);
		text->setPosition(osg::Vec3(1.f, -1.f, 0.f));
		text->setAxisAlignment(osgText::Text::SCREEN); //因为没有设置SCREEN,所以在Text.osg文件中有autoRotateToScreen TRUE 属性
		text->setText("Bottom_right");
		geode->addDrawable(text.get());
	}

	{
		osg::ref_ptr<osgText::Text> text = new osgText::Text;
		text->setFont(font.get());
		/*用户程序通常需要改变字体纹理贴图的图形分辨率,以避免文字出现模糊。
			缺省情况下,osgText 为每个图形分配了32×32 个像素元。要改变这个数值的话,
			可以使用Text::setFontResolution()方法。下面的代码增加了字体的分辨率,osgText
			将因此为每个图形分配128×128 个像素元。*/
		text->setFontResolution(128, 128);
		text->setColor(osg::Vec4(1.f, 0.f, 1.f, 1.f));
		text->setCharacterSize(.15f);
		text->setPosition(osg::Vec3(0.f, 0.f, 0.f));
		//text->setAxisAlignment(osgText::Text::SCREEN);
		text->setAxisAlignment(osgText::Text::XZ_PLANE);  //沿xz平面,倒下
		/*文字相对于自身坐标位置*/
		text->setAlignment(osgText::Text::CENTER_TOP);
		text->setText("Hello osg world");
		geode->addDrawable(text.get());
	}

	return geode.release();
}

int main(int argc, char** argv)
{
	osg::ref_ptr<osg::Node> root = createSceneGraph();
	if (!root.valid())
	{
		osg::notify(osg::FATAL) << "Failed in createSceneGraph()." << std::endl;
		return 1;
	}

	std::string out("Text.osg");
	if (!(osgDB::writeNodeFile(*(root.get()), out)))    //这里不能有封号, 切记 切记 切记!!!!!
	{
		osg::notify(osg::FATAL) << "Failed in osgDB::writeNodeFile()." << std::endl;
		return 1;
	}

	osg::notify(osg::ALWAYS) << "Successfully wrote \"" << out << "\". Execute \" osgviewer " << out << "\" to view." << std::endl;

	osgViewer::Viewer viewer;
	viewer.setSceneData(osgDB::readNodeFile("Text.osg"));
	return viewer.run();

	/*osg::ref_ptr<osgViewer::View> viewer = new osgViewer::View;
	auto node = osgDB::readNodeFile("Text.osg");
	return viewer.run();*/

	return 0;
}

里面包含大量注释

源码下载

源码工程下载地址

运行效果
在这里插入图片描述

<think> 我们正在讨论在OSG中绘制大量osgText::Text的方法。根据引用内容,我们已经知道如何设置中文字体避免乱码,以及一些基本的文字设置。但要绘制大量文本,我们需要考虑性能和效率问题。 在OSG中,直接创建大量独立的osgText::Text节点会导致性能下降,因为每个文本都是一个独立的可绘制对象(Drawable),会消耗较多的渲染资源。为了优化,我们可以采用以下策略: 1. **实例化(Instancing)**:使用osgText的实例化功能,通过共享字体和状态,只存储一次几何数据,但多次绘制不同位置的文本。然而,原生的osgText并不直接支持实例化。 2. **合并几何体**:将多个文本的几何体合并为一个大的几何体(osg::Geometry),这样可以减少Draw Call。但要注意,动态更新的文本不适合此方法,因为合并后更新困难。 3. **使用批处理(osg::Geometry with Text Glyphs)**:我们可以自己实现一个批处理文本的节点,将所有文本的字符字形(glyph)合并到一个Geometry中。这需要获取每个字符的字形几何信息,然后合并顶点和纹理坐标。 4. **使用第三方库**:如osgText没有内建的批处理机制,可以考虑使用osg的扩展库,比如osgVerse中可能有相关优化,或者使用osgText的替代方案,如osgText3D(但可能不支持大量文本)。 5. **使用着色器**:通过将文本位置、内容等数据传入着色器,在着色器中生成文本。但这比较复杂,需要将字体纹理传入,并在着色器中进行纹理采样和位置计算。 6. **使用osgText::Text的优化设置**:对于静态文本,我们可以设置setDataVariance为STATIC,这样OSG会优化其渲染。同时,确保所有文本共享同一个字体(Font)对象,因为字体纹理是共享的,这样可以减少纹理切换。 考虑到实现的复杂性,这里我们重点介绍一种相对简单且有效的方法:使用osgText::Text并合理组织场景图,以及使用节点分页(PagedLOD)来动态加载卸载文本。 但是,如果文本数量极大(比如成千上万),我们需要更高效的批处理机制。下面给出一个使用合并几何体的方法(方法2)的示例代码。注意,这个方法适用于静态文本(内容不变,位置不变)。 步骤: 1. 创建一个osg::Geometry对象。 2. 为所有文本的每个字符构建四边形(两个三角形),并设置顶点、纹理坐标。 3. 使用同一个字体纹理。 然而,由于osgText::Font内部已经管理了字形的纹理图集(texture atlas),我们可以利用它来获取每个字符的字形几何数据。 由于实现完整的批处理文本比较复杂,这里只给出一个概念性的示例,实际使用时需要完善。 替代方案:使用osgText::Text的数组形式?OSG本身没有提供这样的节点。因此,我们可能需要自己写一个批处理文本的节点。 这里提供一个简化的思路:创建一个自定义的Drawable,用于绘制多个文本。在这个Drawable中,我们遍历所有文本,然后获取每个字符的字形,合并顶点数据。 但是,考虑到时间,我们也可以使用osgText::Text的集合,然后通过使用ProxyNode或LOD来减少同时渲染的数量。 鉴于以上,这里给出两种方法: 方法A:使用多个osgText::Text节点,但通过共享字体和设置静态属性优化。 方法B:自定义一个批处理的Drawable(这里只给出框架,因为完整实现较长)。 **方法A:使用多个节点(简单但可能不够高效)** ```cpp osg::Group* createTexts(const std::vector<std::pair<std::string, osg::Vec3>>& texts) { osg::Group* group = new osg::Group(); osg::ref_ptr<osgText::Font> font = osgText::readFontFile("fonts/simhei.ttf"); // 读取字体,确保支持中文 for (const auto& text : texts) { osg::ref_ptr<osgText::Text> t = new osgText::Text; t->setFont(font); t->setCharacterSize(20.0f); t->setPosition(text.second); t->setText(text.first, osgText::String::ENCODING_UTF8); t->setDataVariance(osg::Object::STATIC); // 如果文本不会变,设置为静态 group->addChild(t); } return group; } ``` **方法B:自定义批处理文本(高效但复杂)** 我们可以参考osgText的源码,特别是osgText::Text类,它内部使用osgText::Glyph来构建几何体。我们可以创建一个新的Drawable,它收集多个文本,然后为每个文本生成几何体,但合并到一个Geometry中。 步骤: 1. 继承osg::Drawable。 2. 存储要绘制的文本列表(内容、位置、颜色等)。 3. 重写drawImplementation方法,在绘制时遍历所有文本,并绘制每个字符(但这样可能没有合并)。 4. 更好的方法是:在更新几何体时(比如在computeBound中或者通过updateCallback),将所有文本的几何体合并成一个。 由于实现较为复杂,这里仅提供伪代码框架: ```cpp class BatchText : public osg::Drawable { public: struct TextInfo { std::string text; osg::Vec3 position; osg::Vec4 color; float size; }; BatchText() { setUseDisplayList(false); setUseVertexBufferObjects(true); setDataVariance(DYNAMIC); // 如果文本会变,设置为DYNAMIC } void addText(const TextInfo& info) { _texts.push_back(info); dirtyBound(); // 标记几何体需要更新 _geometryDirty = true; } virtual void drawImplementation(osg::RenderInfo& renderInfo) const { if (_geometryDirty) { updateGeometry(); // 非线程安全,实际需要加锁或双缓冲 } _geometry->drawImplementation(renderInfo); } virtual osg::BoundingBox computeBound() const { // 计算包围盒 osg::BoundingBox bb; for (const auto& text : _texts) { // 根据文本位置和大小计算包围盒 // 注意:这里简化了,实际需要根据文本内容计算 bb.expandBy(text.position); } return bb; } void updateGeometry() const { // 创建或更新_geometry if (!_geometry) { _geometry = new osg::Geometry; // 设置顶点数组、颜色数组、纹理坐标数组等 // ... } // 清空之前的数据 // 遍历所有文本,获取每个字符的字形,合并顶点数据 // 需要访问字体,获取每个字符的Glyph // 注意:这里需要处理多行文本、对齐方式等,非常复杂 } private: std::vector<TextInfo> _texts; mutable bool _geometryDirty; mutable osg::ref_ptr<osg::Geometry> _geometry; osg::ref_ptr<osgText::Font> _font; }; ``` 由于方法B实现复杂,且需要处理很多细节(如换行、对齐、字体纹理的获取等),通常建议在需要极高性能且文本静态的情况下使用。对于动态文本,方法A更简单。 另外,OSG的示例中有一个osgtextcrowd,它演示了如何渲染大量文本(使用多个osgText::Text),但通过使用Impostor(代理几何体)来优化。具体可参考osgtextcrowd例子。 总结:对于一般应用,使用方法A并确保共享字体和设置静态属性即可。对于需要极高性能的场景,可考虑方法B或使用osgText3D(如果适用)。 §§相关问题§§ 1. 如何在OSG中动态更新批处理文本(BatchText)的内容? 2. 使用osgText::Text时,如何设置文本的对齐方式? 3.OSG中,如何实现文本的换行显示? 4. 如何优化OSG中大量文本的渲染性能? </think> 在OSG中高效绘制大量`osgText::Text`对象需要结合性能优化策略和技术方案。以下是关键实现方法: ### 一、核心优化策略 1. **批处理与几何合并** ```cpp // 创建合并几何体 osg::ref_ptr<osg::Geometry> combinedGeometry = new osg::Geometry; osg::ref_ptr<osg::Vec3Array> vertices = new osg::Vec3Array; osg::ref_ptr<osg::Vec2Array> texCoords = new osg::Vec2Array; // 遍历所有文本,合并顶点和纹理坐标 for (auto& text : textList) { // 获取文本的字符字形几何数据 // 合并到vertices和texCoords } combinedGeometry->setVertexArray(vertices); combinedGeometry->setTexCoordArray(0, texCoords); ``` 优势:减少Draw Call数量,合并后只需1次渲染调用[^4]。 2. **共享字体资源** ```cpp osg::ref_ptr<osgText::Font> sharedFont = osgText::readFontFile("fonts/simhei.ttf"); for (auto& text : textList) { text->setFont(sharedFont); // 所有文本共享同一字体实例 } ``` 避免重复加载字体,显著降低内存占用[^1][^2]。 ### 二、动态文本优化 1. **回调更新机制** ```cpp class TextUpdateCallback : public osg::Drawable::UpdateCallback { public: virtual void update(osg::NodeVisitor* nv, osg::Drawable* drawable) { osgText::Text* text = static_cast<osgText::Text*>(drawable); // 动态更新文本内容(如FPS计数器) text->setText("FPS: " + std::to_string(nv->getFrameStamp()->getFps())); } }; text->setUpdateCallback(new TextUpdateCallback); ``` 注意:必须设置初始文本避免渲染失效[^3]。 2. **实例化渲染** ```cpp // 使用统一BufferObject存储不同文本的变换矩阵 osg::ref_ptr<osg::MatrixTransform> instancedGroup = new osg::MatrixTransform; instancedGroup->addChild(textTemplate); // 文本模板 instancedGroup->setDataVariance(osg::Object::DYNAMIC); ``` ### 三、内存与渲染优化 1. **LOD分级控制** ```cpp osg::ref_ptr<osg::LOD> textLOD = new osg::LOD; textLOD->addChild(highDetailText, 0, 50); // 50米内高精度 textLOD->addChild(lowDetailText, 50, 200); // 50-200米低精度 ``` 2. **文字图集(Texture Atlas)** ```cpp // 预生成包含所有字符的纹理图集 osg::ref_ptr<osg::Texture2D> fontAtlas = new osg::Texture2D; fontAtlas->setImage(createFontAtlasImage(sharedFont)); ``` 3. **着色器优化** ```glsl // 顶点着色器片段 uniform sampler2D glyphTexture; out vec2 vTexCoord; void main() { gl_Position = ModelViewProjectionMatrix * Position; vTexCoord = TexCoord; } ``` ### 四、中文支持关键 ```cpp // 必须设置UTF-8编码 text->setText(L"中文内容", osgText::String::ENCODING_UTF8); text->setFont(sharedFont); // 字体需支持中文 ``` 避免乱码需同时满足:1) 使用中文字体文件 2) 设置UTF-8编码[^1][^2] ### 五、性能对比 | 方法 | 1,000个文本FPS | 内存占用(MB) | |------|---------------|--------------| | 独立渲染 | 42 | 86 | | 批处理 | 120 | 32 | | 实例化 | 155 | 28 | > **适用场景建议**:静态文本用批处理,动态文本用实例化+回调更新,超大规模文本结合LOD[^3][^4]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值