2.6 Mipmap纹理映射
在一个动态的场景中,当一个纹理对象迅速远离视点时,纹理图像必须随着被投影的图像一起缩小。为了实现这种效果,可以通过对纹理图像进行过滤,适当对它进行缩小,以使它映射到物体的表面时不会产生抖动或者闪烁的效果。但这时还存在一个问题,就是当视点距离速度变大时,单个纹理缩小为单个像素之前,在经过一些过渡点时,经过过滤的纹理图像会变化非常明显。同时,也没有必要使用一张那么大的纹理数据了:当使用一个很大的、包含很多贴图的场景时,对渲染效率的影响是相当大的。
为了避免这种突然变化的现象及不必要的渲染负担,可以预先指定一系列分辨率递减的纹理图像。使用Mipmap纹须指定全系列大小为2的整数次方的纹理图像,其范围为从最大值到1*1的纹理单元。例如,如果最高的纹理分辨率为 32x32,那么必须指定的纹理图像为 32x32、16x16、8x8、4x4、2x2、1x1。通常来说,较小的纹理图像是上一级分辨率的纹理图像的4个纹理单元的平均值(类似于图像降采样)。当然,这里也没有确定的计算方法,一般是这样计算的。
在OSG中使用Mipmap 纹理映射主要包括下面几个步骤:
(1)将各层的纹理图像数据按照从大到小的顺序(且尺寸必须为2的幂次)依次放到unsiged char*数组中,将这个数组使用setImage送入Image对象。
(2)将各层纹理数据在数组中的偏移地址记录到一个osg::Image::MipmapDataType列表中,用于选择正确的层次细节纹理图像。
(3)使用setMipmapLevels()将MipmapDataType送入Image对象。注意,这一步的次序和setImage不能颠倒,否则可能无法正确显示各级别的纹理图像。
在第5.2.6节的示例程序中,读者可以看到清晰的纹理图像的各个层次级别细节的明显过渡,这是为了向读者演示Mipmap 纹理的过渡变换。在实际项目中就需要用真实的纹理数据了。
2.7、Mipmap纹理映射示例
Mipmap纹理映射(TextureMipmap)示例的代码如序清单5-4所示。
1. osg::ref_ptr<osg::Geode> createQuad_5_4()//创建一个四边形
2. {
3. osg::ref_ptr<osg::Geode> geode = new osg::Geode();
4.
5. osg::ref_ptr<osg::Geometry> geom = new osg::Geometry();
6. geode->addDrawable(geom.get());
7.
8. // 设置顶点
9. osg::ref_ptr<osg::Vec3Array> vec = new osg::Vec3Array;
10. vec->push_back(osg::Vec3(-10.0f, 0.0f, -10.0f));
11. vec->push_back(osg::Vec3(-10.0f, 0.0f, 10.0f));
12. vec->push_back(osg::Vec3(10.0f, 0.0f, 10.0f));
13. vec->push_back(osg::Vec3(10.0f, 0.0f, -10.0f));
14.
15. geom->setVertexArray(vec.get());
16.
17. //设置法线
18. osg::ref_ptr<osg::Vec3Array> nor = new osg::Vec3Array;
19. nor->push_back(osg::Vec3f(0.0f, -1.0f, 0.0f));
20.
21. geom->setNormalArray(nor.get());
22. geom->setNormalBinding(osg::Geometry::BIND_PER_PRIMITIVE_SET);
23.
24.
25. //设置纹理坐标
26. osg::ref_ptr<osg::Vec2Array> tex = new osg::Vec2Array;
27. tex->push_back(osg::Vec2f(0.0f, 0.0f));
28. tex->push_back(osg::Vec2f(0.0f, 1.0f));
29. tex->push_back(osg::Vec2f(1.0f, 1.0f));
30. tex->push_back(osg::Vec2f(1.0f, 0.0f));
31.
32. geom->setTexCoordArray(0, tex.get());
33.
34. geom->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::QUADS, 0, 4));
35.
36. return geode.get();
37. }
38.
39. static void fillImage(unsigned char* ptr, unsigned int size)
40. {
41. // 白色
42. if (size == 1)
43. {
44. float r = 0.5f;
45. osg::Vec4 color(1.0f, 1.0f, 1.0f, 1.0f);
46. *ptr++ = (unsigned char)((color[0])*255.0f);
47. *ptr++ = (unsigned char)((color[1])*255.0f);
48. *ptr++ = (unsigned char)((color[2])*255.0f);
49. *ptr++ = (unsigned char)((color[3])*255.0f);
50. }
51.
52. // 蓝色
53. if (size == 2)
54. {
55. osg::Vec4 color(0.0f, 0.0f, 1.0f, 1.0f);
56. for (unsigned int r = 0; r < size; ++r)
57. {
58. for (unsigned int c = 0; c < size; ++c)
59. {
60. *ptr++ = (unsigned char)((color[0])*255.0f);
61. *ptr++ = (unsigned char)((color[1])*255.0f);
62. *ptr++ = (unsigned char)((color[2])*255.0f);
63. *ptr++ = (unsigned char)((color[3])*255.0f);
64. }
65. }
66. }
67.
68. // 黄色
69. if (size == 4)
70. {
71. osg::Vec4 color(0.0f, 1.0f, 0.0f, 1.0f);
72. for (unsigned int r = 0; r < size; ++r)
73. {
74. for (unsigned int c = 0; c < size; ++c)
75. {
76. *ptr++ = (unsigned char)((color[0])*255.0f);
77. *ptr++ = (unsigned char)((color[1])*255.0f);
78. *ptr++ = (unsigned char)((color[2])*255.0f);
79. *ptr++ = (unsigned char)((color[3])*255.0f);
80. }
81. }
82. }
83.
84. // 红色
85. if (size == 8)
86. {
87. osg::Vec4 color(1.0f, 0.0f, 0.0f, 1.0f);
88. for (unsigned int r = 0; r < size; ++r)
89. {
90. for (unsigned int c = 0; c < size; ++c)
91. {
92. *ptr++ = (unsigned char)((color[0])*255.0f);
93. *ptr++ = (unsigned char)((color[1])*255.0f);
94. *ptr++ = (unsigned char)((color[2])*255.0f);
95. *ptr++ = (unsigned char)((color[3])*255.0f);
96. }
97. }
98. }
99.
100. // 粉红色
101. if (size == 16)
102. {
103. osg::Vec4 color(1.0f, 0.0f, 1.0f, 1.0f);
104. for (unsigned int r = 0; r < size; ++r)
105. {
106. for (unsigned int c = 0; c < size; ++c)
107. {
108. *ptr++ = (unsigned char)((color[0])*255.0f);
109. *ptr++ = (unsigned char)((color[1])*255.0f);
110. *ptr++ = (unsigned char)((color[2])*255.0f);
111. *ptr++ = (unsigned char)((color[3])*255.0f);
112. }
113. }
114. }
115.
116. // 黄色
117. if (size == 32)
118. {
119. osg::Vec4 color(1.0f, 1.0f, 0.0f, 1.0f);
120. for (unsigned int r = 0; r < size; ++r)
121. {
122. for (unsigned int c = 0; c < size; ++c)
123. {
124. *ptr++ = (unsigned char)((color[0])*255.0f);
125. *ptr++ = (unsigned char)((color[1])*255.0f);
126. *ptr++ = (unsigned char)((color[2])*255.0f);
127. *ptr++ = (unsigned char)((color[3])*255.0f);
128. }
129. }
130. }
131.
132. // 蓝绿色
133. if (size == 64)
134. {
135. osg::Vec4 color(0.0f, 1.0f, 1.0f, 1.0f);
136. for (unsigned int r = 0; r < size; ++r)
137. {
138. for (unsigned int c = 0; c < size; ++c)
139. {
140. *ptr++ = (unsigned char)((color[0])*255.0f);
141. *ptr++ = (unsigned char)((color[1])*255.0f);
142. *ptr++ = (unsigned char)((color[2])*255.0f);
143. *ptr++ = (unsigned char)((color[3])*255.0f);
144. }
145. }
146. }
147.
148. // 灰白色
149. if (size == 128)
150. {
151. osg::Vec4 color(0.5f, 0.5f, 0.5f, 1.0f);
152. for (unsigned int r = 0; r < size; ++r)
153. {
154. for (unsigned int c = 0; c < size; ++c)
155. {
156. *ptr++ = (unsigned char)((color[0])*255.0f);
157. *ptr++ = (unsigned char)((color[1])*255.0f);
158. *ptr++ = (unsigned char)((color[2])*255.0f);
159. *ptr++ = (unsigned char)((color[3])*255.0f);
160. }
161. }
162. }
163.
164. // 黑色
165. if (size == 256)
166. {
167. osg::Vec4 color(0.0f, 0.0f, 0.0f, 1.0f);
168. for (unsigned int r = 0; r < size; ++r)
169. {
170. for (unsigned int c = 0; c < size; ++c)
171. {
172. *ptr++ = (unsigned char)((color[0])*255.0f);
173. *ptr++ = (unsigned char)((color[1])*255.0f);
174. *ptr++ = (unsigned char)((color[2])*255.0f);
175. *ptr++ = (unsigned char)((color[3])*255.0f);
176. }
177. }
178. }
179. }
180.
181. void mipmap_5_4()
182. {
183. osg::ref_ptr<osgViewer::Viewer> viewer = new osgViewer::Viewer();
184. osg::ref_ptr<osg::GraphicsContext::Traits> traits = new osg::GraphicsContext::Traits;
185. traits->x = 40;
186. traits->y = 40;
187. traits->width = 600;
188. traits->height = 480;
189. traits->windowDecoration = true;
190. traits->doubleBuffer = true;
191. traits->sharedContext = 0;
192.
193. osg::ref_ptr<osg::GraphicsContext> gc = osg::GraphicsContext::createGraphicsContext(traits.get());
194. osg::ref_ptr<osg::Camera> camera = new osg::Camera;
195. camera->setGraphicsContext(gc.get());
196. camera->setViewport(new osg::Viewport(0, 0, traits->width, traits->height));
197. GLenum buffer = traits->doubleBuffer ? GL_BACK : GL_FRONT;
198. camera->setDrawBuffer(buffer);
199. camera->setReadBuffer(buffer);
200. viewer->addSlave(camera.get());
201.
202. osg::ref_ptr<osg::Group> root = new osg::Group();
203.
204. // 创建一个平面
205. osg::ref_ptr<osg::Geode> geode = createQuad_5_4();
206.
207. osg::ref_ptr<osg::StateSet> stateset = new osg::StateSet();
208.
209. osg::ref_ptr<osg::Image> image = new osg::Image();
210.
211. // 创建一个MipmapDataType列表,用来各层图片数据的偏移地址
212. osg::Image::MipmapDataType mipmapData;
213.
214. // 纹理的尺寸的最大值,必须为2的幂次
215. unsigned int s = 256;
216.
217. // 计算所需分配的数组的大小
218. unsigned int totalSize = 0;
219.
220. for (unsigned int i = 0; s > 0; s >>= 1, ++i)
221. {
222. if (i > 0)
223. {
224. mipmapData.push_back(totalSize);
225. }
226.
227. totalSize += s*s * 4;
228. }
229.
230. //申请一个数据
231. unsigned char* ptr = new unsigned char[totalSize];
232.
233. //设置image的尺寸大小,数据及数据格式
234. image->setImage(256, 256, 256, GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE, ptr, osg::Image::USE_NEW_DELETE, 1);
235.
236. //将偏移地址传入imge对象
237. image->setMipmapLevels(mipmapData);
238.
239. //向image中填充各层数据
240. s = 256;
241. for (unsigned int i = 0; s > 0; s >>= 1, ++i)
242. {
243. fillImage(ptr, s);
244.
245. ptr += s*s * 4;
246. }
247.
248. // 创建一个二维纹理对象
249. osg::ref_ptr<osg::Texture2D> texture = new osg::Texture2D;
250. texture->setImage(0, image.get()); //设置贴图
251. texture->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT);// 设置边界处理为REPEATE
252. texture->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT);
253. texture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::NEAREST_MIPMAP_NEAREST);// 设置滤波
254. texture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::NEAREST);
255.
256. stateset->setTextureAttributeAndModes(0, texture.get(), osg::StateAttribute::ON); // 启用二维纹理对象
257. geode->setStateSet(stateset.get());
258. root->addChild(geode.get());
259.
260. // 优化场景数据
261. osgUtil::Optimizer optimizer;
262. optimizer.optimize(root.get());
263.
264. viewer->setSceneData(root.get());
265. viewer->realize();
266. viewer->run();
267. }
运行程序,截图如图5-9所示。
图5-9 Mipmap纹理映射示例截图
2.8、TextureRectangle纹理映射
TextureRectangle 纹理映射是在OpenGL后来版本中的一个扩展-ARB_texture_rectangle,它也是一种二维纹理映射,但它与前面介绍的二维映射有很大的区别,如表5-1所示。
表5-1 Texture2D与TextureRectangle 比较
Texture2D | TextureRectangle | |
纹理坐标 | 坐标必须被单位化,范围被限定在0-1之间,其他范围不在0-1之间的纹理坐标不会被支持 | 纹理坐标不要求单位化 |
纹理大小 | 纹理大小必须是2的n次方,如1024、512等。当然,如果读者的显卡驱动支持ARB_non_power_of_two或OpenGL2.0,则不会受到此限制 | 纹理尺寸的大小是任意的,如513x1025 |
使用TextureRectangle纹理映射时有以下几点需要注意:
- 纹理环绕模式。它并不支持所有的纹理包装模式,只能使用CLAMP、CLAMP_TO_EDGE或CLAMP_TO_BORDER,并不支持 REPEAT。
- 纹理滤波。它只支持NEAREST或者LINEAR,不支持Mipmap滤波,使用它时不能实现Mipmap纹理。
- 不支持纹理边框。
对于没有图形学基础的开发人员来说,TextureRectangle 比较容易理解,也非常容易上手,可以简单理解为一个矩形纹理。
2.9、TextureRectangle 纹理映射示例
TextureRectangle纹理映射示例的代码如程序清单5-5所示。
osg::ref_ptr<osg::Node> createNode_5_5()// 创建一个四边形节点
{
osg::ref_ptr<osg::Geode> geode = new osg::Geode();
osg::ref_ptr<osg::Geometry> geom = new osg::Geometry();
//设置顶点
osg::ref_ptr<osg::Vec3Array> vc = new osg::Vec3Array();
vc->push_back(osg::Vec3(0.0f, 0.0f, 0.0f));
vc->push_back(osg::Vec3(1.0f, 0.0f, 0.0f));
vc->push_back(osg::Vec3(1.0f, 0.0f, 1.0f));
vc->push_back(osg::Vec3(0.0f, 0.0f, 1.0f));
geom->setVertexArray(vc.get());
// 设置纹理坐标
osg::ref_ptr<osg::Vec2Array> vt = new osg::Vec2Array();
vt->push_back(osg::Vec2(0.0f, 0.0f));
vt->push_back(osg::Vec2(1.0f, 0.0f));
vt->push_back(osg::Vec2(1.0f, 1.0f));
vt->push_back(osg::Vec2(0.0f, 1.0f));
geom->setTexCoordArray(0, vt.get());
// 设置法线
osg::ref_ptr<osg::Vec3Array> nc = new osg::Vec3Array();
nc->push_back(osg::Vec3(0.0f, -1.0f, 0.0f));
geom->setNormalArray(nc.get());
geom->setNormalBinding(osg::Geometry::BIND_OVERALL);
// 添加图元
geom->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::QUADS, 0, 4));
// 绘制
geode->addDrawable(geom.get());
return geode.get();
}
osg::ref_ptr<osg::StateSet> createTexture2DState_5_5(osg::ref_ptr<osg::Image> image)// 创建二维纹理状态对象
{
// 创建状态集对象
osg::ref_ptr<osg::StateSet> stateset = new osg::StateSet();
// 创建二维纹理对象
osg::ref_ptr<osg::TextureRectangle> texture = new osg::TextureRectangle();
texture->setDataVariance(osg::Object::DYNAMIC);
texture->setImage(image.get()); // 设置贴图
// 设置纹理矩阵,并设置为根据矩阵纹理(TextureRectangle)的大小自动缩放
// 从而允许应用一个矩形纹理到一个纹理坐标不在0-1上
osg::ref_ptr<osg::TexMat> texmat = new osg::TexMat;
texmat->setScaleByTextureRectangleSize(true);
// 启用纹理及纹理矩阵
stateset->setTextureAttributeAndModes(0, texmat.get(), osg::StateAttribute::ON);
stateset->setTextureAttributeAndModes(0, texture.get(), osg::StateAttribute::ON);
// 关闭光照
stateset->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
return stateset.get();
}
void textureRectangle_5_5(const string &strDataFolder)
{
osg::ref_ptr<osgViewer::Viewer> viewer = new osgViewer::Viewer();
osg::ref_ptr<osg::GraphicsContext::Traits> traits = new osg::GraphicsContext::Traits;
traits->x = 40;
traits->y = 40;
traits->width = 600;
traits->height = 480;
traits->windowDecoration = true;
traits->doubleBuffer = true;
traits->sharedContext = 0;
osg::ref_ptr<osg::GraphicsContext> gc = osg::GraphicsContext::createGraphicsContext(traits.get());
osg::ref_ptr<osg::Camera> camera = new osg::Camera;
camera->setGraphicsContext(gc.get());
camera->setViewport(new osg::Viewport(0, 0, traits->width, traits->height));
GLenum buffer = traits->doubleBuffer ? GL_BACK : GL_FRONT;
camera->setDrawBuffer(buffer);
camera->setReadBuffer(buffer);
viewer->addSlave(camera.get());
osg::ref_ptr<osg::Group> root = new osg::Group();
// 读取贴图文件
string strDataPath = strDataFolder + "Images/primitives.jpg";
osg::ref_ptr<osg::Image> image = osgDB::readImageFile(strDataPath);
osg::ref_ptr<osg::Node> node = createNode_5_5();
//创建状态集对象
osg::ref_ptr<osg::StateSet> stateset = new osg::StateSet();
stateset = createTexture2DState_5_5(image.get());
//使用二维纹理
node->setStateSet(stateset.get());
root->addChild(node.get());
//优化场景数据
osgUtil::Optimizer optimizer;
optimizer.optimize(root.get());
viewer->setSceneData(root.get());
viewer->realize();
viewer->run();
}
运行程序,截图如图5-10 所示
图5-10 TextureRectangle 纹理映射示例截图
2.10、自动生成纹理坐标
为了实现使用纹理贴图生成模型的轮廓线或者有光泽的模型对任意环境的反射,并不需要显示的指定纹理坐标,可以利用自动生成纹理坐标的机制处理。通常情况下,有多种自动生成纹理坐标的方法,不同的生成纹理坐标的方法有不同的用途。下面列举几种自动生成纹理坐标的模式。
OBJECT_LINEAR=GL_OBJECT_LINEAR。当纹理图像与移动的物体保持固定时,在物体坐标中指定参考平面是最为合适的。此时,可以使用OBJECT_LINEAR 来实现纹理图像映射。
EYE_LINEAR=GL_EYE_LINEAR。为了实现移动物体的动态轮廓线,可以在视觉坐标中指定使用EYE_LINEAR,在石油或煤矿的演示中经常用到。
SPHERE_MAP=GL_SPHERE_MAP。用于球体环境贴图。
NORMAL_MAP=GL_NORMAL_MAP_ARB。用于立方图纹理。
REFLECTION_MAP=GL_REFLECTION_MAPARB。用于球体环境纹理。
osg::TexGen直接继承自状态属性类,因此可以像osg::Texture2D一样直接指定属性使用。例如:
- //设置自动纹理坐标,并指定相关的平面
- osg::TexGen* texgen = new osg::TexGen;
- texgen->setMode(osg::TexGen::OBJECT_LINEAR):
- texgen->setPlane(osg::TexGen::S,osg::Plane(0.0f,0.0f,1.0f,0.0f)):
- //启用纹理坐标生成器
- stateset->setTextureAttribute(0,texgen,osg::StateAttribute::OVERRIDE);
osg::TexGen的继承关系图如图5-11所示。
图5-11 osg::TexGen 的继承关系图
2.11、自动生成纹理坐标示例
自动生成纹理坐标(osg::TexGen)示例的代码如程序清单5-6所示。
// 5-6 自动生成纹理坐标(osg::TexGen)示例的代码
osg::ref_ptr<osg::StateSet> createTexture1DState(const string &strDataFolder)// 创建二维纹理属性
{
string strDataPath = strDataFolder + "Images/primitives.jpg";
osg::ref_ptr<osg::Image> image = osgDB::readImageFile(strDataPath);
// 创建二维纹理
osg::ref_ptr<osg::Texture2D> texture = new osg::Texture2D;
texture->setWrap(osg::Texture2D::WRAP_S, osg::Texture2D::REPEAT);//设置边界处理
texture->setWrap(osg::Texture2D::WRAP_T, osg::Texture2D::REPEAT);
texture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR);//设置滤波
texture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::NEAREST);
texture->setImage(image.get());//设置贴图
// 设置自动纹理坐标,并指定相关的平面
osg::ref_ptr<osg::TexGen> texgen = new osg::TexGen;
texgen->setMode(osg::TexGen::OBJECT_LINEAR);
texgen->setPlane(osg::TexGen::S, osg::Plane(0.0f, 0.0f, 1.0f, 0.0f));
// 创建属性集
osg::ref_ptr<osg::StateSet> stateset = new osg::StateSet;
//启用二维纹理
stateset->setTextureAttribute(0, texture.get(), osg::StateAttribute::OVERRIDE);
stateset->setTextureMode(0, GL_TEXTURE_2D, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE);
//启用纹理坐标生成器
stateset->setTextureAttribute(0, texgen.get(), osg::StateAttribute::OVERRIDE);
stateset->setTextureMode(0, GL_TEXTURE_GEN_S, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE);
stateset->setTextureMode(0, GL_TEXTURE_GEN_T, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE);
return stateset.get();
}
void TexGen_5_6(const string &strDataFolder)
{
osg::ref_ptr<osgViewer::Viewer> viewer = new osgViewer::Viewer();
osg::ref_ptr<osg::GraphicsContext::Traits> traits = new osg::GraphicsContext::Traits;
traits->x = 40;
traits->y = 40;
traits->width = 600;
traits->height = 480;
traits->windowDecoration = true;
traits->doubleBuffer = true;
traits->sharedContext = 0;
osg::ref_ptr<osg::GraphicsContext> gc = osg::GraphicsContext::createGraphicsContext(traits.get());
osg::ref_ptr<osg::Camera> camera = new osg::Camera;
camera->setGraphicsContext(gc.get());
camera->setViewport(new osg::Viewport(0, 0, traits->width, traits->height));
GLenum buffer = traits->doubleBuffer ? GL_BACK : GL_FRONT;
camera->setDrawBuffer(buffer);
camera->setReadBuffer(buffer);
viewer->addSlave(camera.get());
osg::ref_ptr<osg::Group> root = new osg::Group();
string strDataPath = strDataFolder + "cessna.osg";
osg::ref_ptr<osg::Node> node = osgDB::readNodeFile(strDataPath);
// 自动生成纹理坐标属性
osg::ref_ptr<osg::StateSet> stateset = new osg::StateSet();
stateset = createTexture1DState(strDataFolder);
node->setStateSet(stateset.get());
root->addChild(node.get());
// 优化场景数据
osgUtil::Optimizer optimizer;
optimizer.optimize(root.get());
viewer->setSceneData(root.get());
viewer->realize();
viewer->run();
}
运行程序,截图如图5-12所示
图5-12自动生成纹理坐示例截图
2.12、计算纹理坐标
在很多时候,直接指定纹理坐标是非常不方便的,如曲面纹理坐标,只有少数的曲面(如圆锥、圆柱等)可以在不产生扭曲的情况下映射到平面上,其他的曲面在映射到表面时都会产生一定程度的扭曲。一般而言,曲面表面的曲率越大,纹理所需要的扭曲度就越大。这时,直接指定纹理坐标可能是一件非常困难的事情了
第5.2.12节的示例序通过一个纹理坐标生成器(继承自osg:NodeVisitor访问器)遍历模型的所有顶点及法线,然后根据顶点、法线及一定的比例来确定纹理坐标。
2.13、计算纹理坐标示例
计算纹理坐标示例的代码如程序清单5-7所示。
class TexCoordGenerator : public osg::NodeVisitor// 纹理坐标生成器,继承自NodeVisitor
{
public:
//遍历所有的子节点
TexCoordGenerator() : NodeVisitor(NodeVisitor::TRAVERSE_ALL_CHILDREN)
{
//
}
void apply(osg::Geode& geode)
{
//通过包围盒来确定合适的比例
const osg::BoundingSphere &bsphere = geode.getBound();
float scale = 10;
if (bsphere.radius() != 0)
{
scale = 5 / bsphere.radius();
}
//遍历所有几何体,并设置纹理坐标
for (unsigned i = 0; i < geode.getNumDrawables(); ++i)
{
osg::Geometry* geo = dynamic_cast<osg::Geometry*>(geode.getDrawable(i));
if (geo)
{
osg::Vec2Array* tc = generate_coords(geo->getVertexArray(), geo->getNormalArray(), scale);
geo->setTexCoordArray(0, tc);
}
}
NodeVisitor::apply(geode);
}
protected:
// 计算纹理坐标
osg::Vec2Array* generate_coords(osg::Array* vx, osg::Array* nx, float scale)
{
osg::Vec2Array* v2a = dynamic_cast<osg::Vec2Array*>(vx);
osg::Vec3Array* v3a = dynamic_cast<osg::Vec3Array*>(vx);
osg::Vec4Array* v4a = dynamic_cast<osg::Vec4Array*>(vx);
osg::Vec2Array* n2a = dynamic_cast<osg::Vec2Array*>(nx);
osg::Vec3Array* n3a = dynamic_cast<osg::Vec3Array*>(nx);
osg::Vec4Array* n4a = dynamic_cast<osg::Vec4Array*>(nx);
osg::ref_ptr<osg::Vec2Array> tc = new osg::Vec2Array;
for (unsigned i = 0; i < vx->getNumElements(); ++i) {
osg::Vec3 P;
if (v2a) P.set((*v2a)[i].x(), (*v2a)[i].y(), 0);
if (v3a) P.set((*v3a)[i].x(), (*v3a)[i].y(), (*v3a)[i].z());
if (v4a) P.set((*v4a)[i].x(), (*v4a)[i].y(), (*v4a)[i].z());
osg::Vec3 N(0, 0, 1);
if (n2a) N.set((*n2a)[i].x(), (*n2a)[i].y(), 0);
if (n3a) N.set((*n3a)[i].x(), (*n3a)[i].y(), (*n3a)[i].z());
if (n4a) N.set((*n4a)[i].x(), (*n4a)[i].y(), (*n4a)[i].z());
int axis = 0;
if (N.y() > N.x() && N.y() > N.z()) axis = 1;
if (-N.y() > N.x() && -N.y() > N.z()) axis = 1;
if (N.z() > N.x() && N.z() > N.y()) axis = 2;
if (-N.z() > N.x() && -N.z() > N.y()) axis = 2;
osg::Vec2 uv;
switch (axis) {
case 0: uv.set(P.y(), P.z()); break;
case 1: uv.set(P.x(), P.z()); break;
case 2: uv.set(P.x(), P.y()); break;
default:;
}
tc->push_back(uv * scale);
}
return tc.release();
}
};
osg::ref_ptr<osg::StateSet> createTexture2DState_5_7(osg::ref_ptr<osg::Image> image)// 创建二维纹理状态对象
{
// 创建状态集对象
osg::ref_ptr<osg::StateSet> stateset = new osg::StateSet();
// 创建二维纹理对象
osg::ref_ptr<osg::Texture2D> texture = new osg::Texture2D();
texture->setDataVariance(osg::Object::DYNAMIC);
texture->setImage(image.get());// 设置贴图
texture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR_MIPMAP_LINEAR);
texture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR);
texture->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT);
texture->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT);
stateset->setTextureAttributeAndModes(0, texture.get(), osg::StateAttribute::ON);
return stateset.get();
}
void calTextureCoordinate_5_7(const string &strDataFolder)
{
osg::ref_ptr<osgViewer::Viewer> viewer = new osgViewer::Viewer();
osg::ref_ptr<osg::GraphicsContext::Traits> traits = new osg::GraphicsContext::Traits;
traits->x = 40;
traits->y = 40;
traits->width = 600;
traits->height = 480;
traits->windowDecoration = true;
traits->doubleBuffer = true;
traits->sharedContext = 0;
osg::ref_ptr<osg::GraphicsContext> gc = osg::GraphicsContext::createGraphicsContext(traits.get());
osg::ref_ptr<osg::Camera> camera = new osg::Camera;
camera->setGraphicsContext(gc.get());
camera->setViewport(new osg::Viewport(0, 0, traits->width, traits->height));
GLenum buffer = traits->doubleBuffer ? GL_BACK : GL_FRONT;
camera->setDrawBuffer(buffer);
camera->setReadBuffer(buffer);
viewer->addSlave(camera.get());
osg::ref_ptr<osg::Group> root = new osg::Group();
// 读取贴图文件
string strDataPath = strDataFolder + "Images/primitives.jpg";
osg::ref_ptr<osg::Image> image = osgDB::readImageFile(strDataPath);
strDataPath = strDataFolder + "dumptruck.osg";
osg::ref_ptr<osg::Node> node = osgDB::readNodeFile(strDataPath);
// 计算纹理坐标
TexCoordGenerator tcg;
node->accept(tcg);
// 创建状态集对象
osg::ref_ptr<osg::StateSet> stateset = new osg::StateSet();
stateset = createTexture2DState_5_7(image.get());
// 使用二维纹理
node->setStateSet(stateset.get());
root->addChild(node.get());
// 优化场景数据
osgUtil::Optimizer optimizer;
optimizer.optimize(root.get());
viewer->setSceneData(root.get());
viewer->realize();
viewer->run();
}
运行程序,截图如图5-13所示。
图5-13计算纹理坐标示例截图
2.14、立方图纹理
立方图纹理是一种特殊的技术,它是一个由 6 幅二维图像构成的、以原点为中心的纹理立方体。对于每个片段而言,纹理坐标(S,T,R)都被当作一个方向向量来看待,每个纹理单元表示从原点所看到的纹理立方体的东西。立方图纹理应用非常广泛,可以利用它来实现环境贴图、反射和光照等效果。
立方图纹理的功能与其他的纹理操作是相互独立的,不要认为它是一种特殊的纹理。因此,立方图纹理可以使用其他标准的纹理特性,如多重纹理、纹理边框等。但需要注意的是,立方图纹理渲染时需要很大的内存,是通常二维纹理的 6倍左右。在为其指定纹理数据和创建纹理对象时,应该把它作为一个整体来处理,而不是作为单个的面来指定。
立方图纹理技术实现需要的主要类是osg::TextureCubeMap,它继承自osg::Texture类封装了OpenGL中的立方图纹理函数的功能。其继承关系图如图5-14 所示。
图5-14 osg::TextureCubeMap 的继承关系图
从继承关系中可以看出osg::TextureCubeMap 同样继承自osg::StateAttribute。因此,可以使用设置渲染属性的方式来启用立方图纹理,代码如下:
- // 设置立方图纹理
- osg::TextureCubeMap *skymap = osg::TextureCubeMap();
- stateset->setTextureAttributeAndModes(0,skymap,osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE):
创建天空盒的方法有很多,可以直接绘制一个,当然效果是非常不好的。实际中,经常使用的天空盒包括两种,即方体天空盒和球体天空盒。更多的是球体天空盒的应用,看起来更真实,更能描述真实的天空效果。
创建球体天空盒的主要步骤如下:
(1)创建立方图纹理对象,读取立方图纹理贴图。
(2)设置自动生成纹理坐标。
(3)设置纹理矩阵。
(4)设置立方图纹理。
(5)设置矩阵变换节点,以实现合适的矩阵变换。
(6)关闭光照并关闭背面剔除,设置渲染顺序,加入到叶节点中绘制。
在上面的步骤中有几点需要注意的地方:
- 读取立方图纹理贴图时,需要特别注意的是,纹理贴图要与立方体的各个面(+X,-X,+Y, -Y,+Z,-Z)一一对应,否则,生成的天空盒会非常难看。
- POSITIVE_X=0// Left X正方向
- NEGATIVE_X=1// Right X负方向
- POSITIVE_Y=2// Front Y正方向
- NEGATIVE_Y=3// Back Y负方向
- POSITIVE_Z=4// Up Z正方向
- NEGATIVE_Z=5// Down Z负方向
- 一定要设置关闭背面剔除及光照,并设置正确的渲染顺序。为了实现更真实的效果,通常天空盒只会根据视点做适当的变换,这就需要继承变换类实现一个新的变换类,第 5.2.14 节的示例中会有详细说明,可仔细体会。
2.15、立方图纹理示例
立方图纹理(osg::TextureCubeMap)示例的代码如程序清单5-8所示。
void TextureCubeMap_5_8(const string &strDataFolder)
{
osg::ref_ptr<osgViewer::Viewer> viewer = new osgViewer::Viewer();
osg::ref_ptr<osg::GraphicsContext::Traits> traits = new osg::GraphicsContext::Traits;
traits->x = 40;
traits->y = 40;
traits->width = 600;
traits->height = 480;
traits->windowDecoration = true;
traits->doubleBuffer = true;
traits->sharedContext = 0;
osg::ref_ptr<osg::GraphicsContext> gc = osg::GraphicsContext::createGraphicsContext(traits.get());
osg::ref_ptr<osg::Camera> camera = new osg::Camera;
camera->setGraphicsContext(gc.get());
camera->setViewport(new osg::Viewport(0, 0, traits->width, traits->height));
GLenum buffer = traits->doubleBuffer ? GL_BACK : GL_FRONT;
camera->setDrawBuffer(buffer);
camera->setReadBuffer(buffer);
viewer->addSlave(camera.get());
osg::ref_ptr<osg::Group> root = new osg::Group();
string strDataPath = strDataFolder + "cow.osg";
osg::ref_ptr<osg::Node> node = osgDB::readNodeFile(strDataPath);
// 读取贴图
strDataPath = strDataFolder + "Images/primitives.jpg";
osg::ref_ptr<osg::Image> image = osgDB::readImageFile(strDataPath);
if (image.get())
{
//创建二维纹理
osg::ref_ptr<osg::Texture2D> texture = new osg::Texture2D;
texture->setImage(image.get());
//设置自动生成坐标
osg::ref_ptr<osg::TexGen> texgen = new osg::TexGen;
texgen->setMode(osg::TexGen::SPHERE_MAP);
//设置纹理环境,模式为BLEND,
osg::ref_ptr<osg::TexEnv> texenv = new osg::TexEnv;
texenv->setMode(osg::TexEnv::BLEND);
// 设置BLEND操作的颜色
texenv->setColor(osg::Vec4(0.6f, 0.6f, 0.6f, 0.0f));
// 启用单元1自动生成纹理坐标,并使用纹理
osg::ref_ptr<osg::StateSet> stateset = new osg::StateSet;
stateset->setTextureAttributeAndModes(1, texture.get(), osg::StateAttribute::ON);
stateset->setTextureAttributeAndModes(1, texgen.get(), osg::StateAttribute::ON);
//设置纹理环境
stateset->setTextureAttribute(1, texenv.get());
//设置纹理状态
node->setStateSet(stateset.get());
}
root->addChild(node.get());
// 优化场景数据
osgUtil::Optimizer optimizer;
optimizer.optimize(root.get());
viewer->setSceneData(root.get());
viewer->realize();
viewer->run();
}
运行程序,截图如图5-15所示。
图5-15 立方图纹理示例截图
2.16、渲染到纹理
在OSG中主要有以下4种buffer:
-
- FrameBuffer。相当于一个本地缓存集合,包括颜色缓存、深度缓存、模板缓存和累积缓存用于存放每帧的渲染数据。
- Frame Buffer Object。是OpenGL的一个高级扩展,提供了一种渲染到目标对象的新的机制。
- Pixel Buffer Objects。是窗口系统的一个扩展,实现离屏渲染,与一个不可见的窗口图形环境相似。
- Vertex Buffer Object。是OpenGL的一个高级扩展用于将顶点数据提交给显卡的高速缓存区使显卡能够快速存取,以提高渲染速度。
渲染到纹理就是将当前的渲染结果(framebuffer)通过纹理的方式直接读取,这样可以在很大程度上提高渲染的性能,避免从framebuffer 里面拷贝纹理对象,从而节省很大的内存。
渲染到纹理主要用于生成动态纹理、反射效果和图像模糊等。下面通过示例程序向读者演示如何渲染到纹理。
2.17、渲染到纹理示例
渲染到纹理(Render To Texture)示例的代码如程序清单5-9所示。
struct MyCameraPostDrawCallback : public osg::Camera::DrawCallback // 定义相机绘制回调
{
public:
MyCameraPostDrawCallback(osg::ref_ptr<osg::Image> image) :
_image(image)
{
}
virtual void operator () (const osg::Camera& /*camera*/) const
{
if (_image && _image->getPixelFormat() == GL_RGBA && _image->getDataType() == GL_UNSIGNED_BYTE)
{
//获得Image的中心
int column_start = _image->s() / 4;
int column_end = 3 * column_start;
int row_start = _image->t() / 4;
int row_end = 3 * row_start;
//将像素数据进行反向
for (int r = row_start; r < row_end; ++r)
{
unsigned char* data = _image->data(column_start, r);
for (int c = column_start; c < column_end; ++c)
{
(*data) = 255 - (*data); ++data;
(*data) = 255 - (*data); ++data;
(*data) = 255 - (*data); ++data;
(*data) = 255; ++data;
}
}
_image->dirty();
}
else if (_image && _image->getPixelFormat() == GL_RGBA && _image->getDataType() == GL_FLOAT)
{
//获得Image的中心
int column_start = _image->s() / 4;
int column_end = 3 * column_start;
int row_start = _image->t() / 4;
int row_end = 3 * row_start;
//将像素数据进行反向
for (int r = row_start; r < row_end; ++r)
{
float* data = (float*)_image->data(column_start, r);
for (int c = column_start; c < column_end; ++c)
{
(*data) = 1.0f - (*data); ++data;
(*data) = 1.0f - (*data); ++data;
(*data) = 1.0f - (*data); ++data;
(*data) = 1.0f; ++data;
}
}
_image->dirty();
}
}
public:
osg::ref_ptr<osg::Image> _image;
};
// 创建预渲染场景
osg::ref_ptr<osg::Node> createPreRenderSubGraph(osg::ref_ptr<osg::Node> subgraph,unsigned tex_width,
unsigned tex_height, osg::Camera::RenderTargetImplementation renderImplementation, bool useImage)
{
if (!subgraph)
{
return 0;
}
// 创建一个包含预渲camera的 Group 节点
osg::ref_ptr<osg::Group> parent = new osg::Group;
// 创建纹理,用来绑定相机渲染的结果
osg::ref_ptr<osg::Texture> texture = 0;
{
osg::ref_ptr<osg::Texture2D> texture2D = new osg::Texture2D;
texture2D->setTextureSize(tex_width, tex_height);
texture2D->setInternalFormat(GL_RGBA);
texture2D->setFilter(osg::Texture2D::MIN_FILTER, osg::Texture2D::LINEAR);
texture2D->setFilter(osg::Texture2D::MAG_FILTER, osg::Texture2D::LINEAR);
texture = texture2D;
}
// 创建一个用来浏览的四边形几何体
{
osg::ref_ptr<osg::Geometry> polyGeom = new osg::Geometry();
//设置该几何体不使用显示列表
polyGeom->setSupportsDisplayList(false);
float height = 100.0f;
float width = 200.0f;
//创建顶点数组,并添加数据
osg::ref_ptr<osg::Vec3Array> vertices = new osg::Vec3Array;
vertices->push_back(osg::Vec3(0.0f, 0.0f, 0.0f));
vertices->push_back(osg::Vec3(width, 0.0f, 0.0f));
vertices->push_back(osg::Vec3(width, 0.0f, height));
vertices->push_back(osg::Vec3(0.0f, 0.0f, height));
//创建纹理数组,并添加数据
osg::ref_ptr<osg::Vec2Array> texcoords = new osg::Vec2Array();
texcoords->push_back(osg::Vec2(0.0f, 0.0f));
texcoords->push_back(osg::Vec2(1.0f, 0.0f));
texcoords->push_back(osg::Vec2(1.0f, 1.0f));
texcoords->push_back(osg::Vec2(0.0f, 1.0f));
polyGeom->setVertexArray(vertices.get());
//使用vbo扩展
{
osg::ref_ptr<osg::VertexBufferObject> vbObject = new osg::VertexBufferObject;
vertices->setVertexBufferObject(vbObject);
polyGeom->setUseVertexBufferObjects(true);
}
polyGeom->setTexCoordArray(0, texcoords.get());
//创建颜色数组,并设置绑定方式及添加数据
osg::ref_ptr<osg::Vec4Array> colors = new osg::Vec4Array;
colors->push_back(osg::Vec4(1.0f, 1.0f, 1.0f, 1.0f));
polyGeom->setColorArray(colors.get());
polyGeom->setColorBinding(osg::Geometry::BIND_OVERALL);
polyGeom->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::QUADS, 0, vertices->size()));
//现在我们需要将纹理附加到该几何体上,我们创建一个包含该纹理的StateSet
osg::ref_ptr<osg::StateSet> stateset = new osg::StateSet;
stateset->setTextureAttributeAndModes(0, texture, osg::StateAttribute::ON);
polyGeom->setStateSet(stateset);
osg::ref_ptr<osg::Geode> geode = new osg::Geode();
geode->addDrawable(polyGeom.get());
parent->addChild(geode.get());
}
// 需要创建一个相机节点,用来渲染到该纹理(RTT)
{
osg::ref_ptr<osg::Camera> camera = new osg::Camera;
//设置背景色及清除颜色和深度缓存
camera->setClearColor(osg::Vec4(0.1f, 0.1f, 0.3f, 1.0f));
camera->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
//获得该节点的范围盒
const osg::BoundingSphere& bs = subgraph->getBound();
if (!bs.valid())
{
return subgraph.get();
}
float znear = 1.0f*bs.radius();
float zfar = 3.0f*bs.radius();
float proj_top = 0.25f*znear;
float proj_right = 0.5f*znear;
znear *= 0.9f;
zfar *= 1.1f;
//设置投影矩阵.
camera->setProjectionMatrixAsFrustum(-proj_right, proj_right, -proj_top, proj_top, znear, zfar);
//将相机对准该子场景
camera->setReferenceFrame(osg::Transform::ABSOLUTE_RF);
camera->setViewMatrixAsLookAt(bs.center() - osg::Vec3(0.0f, 2.0f, 0.0f)*bs.radius(), bs.center(), osg::Vec3(0.0f, 0.0f, 1.0f));
//设置视口
camera->setViewport(0, 0, tex_width, tex_height);
//设置相机的渲染序列
camera->setRenderOrder(osg::Camera::PRE_RENDER);
//设置相机渲染通过 OpenGL frame buffer object 实现
camera->setRenderTargetImplementation(renderImplementation);
if (useImage)
{
osg::ref_ptr<osg::Image> image = new osg::Image;
//image->allocateImage(tex_width, tex_height, 1, GL_RGBA, GL_UNSIGNED_BYTE);
image->allocateImage(tex_width, tex_height, 1, GL_RGBA, GL_FLOAT);
//将Image附加到相机的COLOR_BUFFER
camera->attach(osg::Camera::COLOR_BUFFER, image.get());
//添加相机的绘制后回调,修改images数据
camera->setPostDrawCallback(new MyCameraPostDrawCallback(image.get()));
//这里我们不直接将相机的COLOR_BUFFER附加到该纹理上,是为了修改渲染后的图像数据
texture->setImage(0, image.get());
}
else
{
//直接将该纹理附加到相机的颜色缓存.
camera->attach(osg::Camera::COLOR_BUFFER, texture.get());
}
//添加要绘制的子场景
camera->addChild(subgraph.get());
parent->addChild(camera.get());
}
return parent.get();
}
void renderToTexture_5_9(const string &strDataFolder)
{
osg::ref_ptr<osgViewer::Viewer> viewer = new osgViewer::Viewer();
osg::ref_ptr<osg::GraphicsContext::Traits> traits = new osg::GraphicsContext::Traits;
traits->x = 40;
traits->y = 40;
traits->width = 600;
traits->height = 480;
traits->windowDecoration = true;
traits->doubleBuffer = true;
traits->sharedContext = 0;
osg::ref_ptr<osg::GraphicsContext> gc = osg::GraphicsContext::createGraphicsContext(traits.get());
osg::ref_ptr<osg::Camera> camera = new osg::Camera;
camera->setGraphicsContext(gc.get());
camera->setViewport(new osg::Viewport(0, 0, traits->width, traits->height));
GLenum buffer = traits->doubleBuffer ? GL_BACK : GL_FRONT;
camera->setDrawBuffer(buffer);
camera->setReadBuffer(buffer);
viewer->addSlave(camera.get());
unsigned tex_width = 1024;
unsigned tex_height = 512;
osg::Camera::RenderTargetImplementation renderImplementation = osg::Camera::FRAME_BUFFER_OBJECT;
bool useImage = false;
// 读取模型
string strDataPath = strDataFolder + "cessna.osg";
osg::ref_ptr<osg::Node> loadedModel = osgDB::readNodeFile(strDataPath);
// 创建一个transform节点,用来选装该子场景
osg::ref_ptr<osg::MatrixTransform> loadedModelTransform = new osg::MatrixTransform;
loadedModelTransform->addChild(loadedModel.get());
// 设置更新回调
osg::ref_ptr<osg::NodeCallback> nc = new osg::AnimationPathCallback(loadedModelTransform->getBound().center(), osg::Vec3(0.0f, 0.0f, 1.0f), osg::inDegrees(45.0f));
loadedModelTransform->setUpdateCallback(nc);
osg::ref_ptr<osg::Group> rootNode = new osg::Group();
rootNode->addChild(createPreRenderSubGraph(loadedModelTransform.get(), tex_width, tex_height, renderImplementation, useImage));
// 优化场景数据
osgUtil::Optimizer optimizer;
optimizer.optimize(rootNode.get());
viewer->setSceneData(rootNode.get());
viewer->realize();
viewer->run();
}
运行程序,截图如图5-16所示。
图5-16渲染到纹理示例截图
2.18、一维纹理
一维纹理(osg::TexturelD)在实际的应用中比较少见。只有当绘制带纹理的物体的所有变化都发生在同一个方向上时,用一维纹理比较合适。
osg::TexturelD类继承自osg::Texture类封装了OpenGL一维纹理函数的功能。其继承关系图如图5-17所示。
图5-17 osg::TexturelD的继承关系图
从继承关系图可以看出,osg::TexturelD和前面介绍的二维纹理类似,都是继承自osg::Texture。其实,一维纹理就是一幅高度为 1 的二维纹理图像,但它的底部和顶部没有边框。当然,一维纹理的使用方法与二维纹理也相同,这里不再重复讲解。
2.19、一维纹理示例
一维纹理(osg::TexturelD)示例的代码如程序清单5-10所示。
osg::ref_ptr<osg::StateSet> createTexture1DState()// 创建一维纹理属性
{
// 创建贴图对象,实践上是一个高度为1的二维图像
osg::ref_ptr<osg::Image> image = new osg::Image;
image->allocateImage(1024, 1, 1, GL_RGBA, GL_FLOAT); // 为image分配一个空间
image->setInternalTextureFormat(GL_RGBA); // 设置纹理图像数据格式RGBA
// 为image填充数据
osg::Vec4* dataPtr = (osg::Vec4*)image->data();
for (int i = 0; i < 1024; ++i)
{
*dataPtr++ = osg::Vec4(1.0f, 0.5f, 0.8f, 0.5f);
}
// 创建一维纹理
osg::ref_ptr<osg::Texture1D> texture = new osg::Texture1D();
texture->setWrap(osg::Texture1D::WRAP_S, osg::Texture1D::MIRROR); // 设置环绕模式
texture->setFilter(osg::Texture1D::MIN_FILTER, osg::Texture1D::LINEAR);// 设置滤波
texture->setImage(image.get()); //设置贴图
// 设置自动纹理坐标,并指定相关的平面
osg::ref_ptr<osg::TexGen> texgen = new osg::TexGen;
texgen->setMode(osg::TexGen::OBJECT_LINEAR);
texgen->setPlane(osg::TexGen::S, osg::Plane(0.0f, 0.0f, 1.0f, -10000));
// 创建属性集
osg::ref_ptr<osg::StateSet> stateset = new osg::StateSet;
stateset->setTextureAttribute(0, texture.get(), osg::StateAttribute::OVERRIDE);// 启用一维纹理
stateset->setTextureMode(0, GL_TEXTURE_1D, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE);
stateset->setTextureAttribute(0, texgen.get(), osg::StateAttribute::OVERRIDE);// 启用纹理生成器
stateset->setTextureMode(0, GL_TEXTURE_GEN_S, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE);
return stateset.get();
}
void create1DTexture(const string &strDataFolder)
{
osg::ref_ptr<osgViewer::Viewer> viewer = new osgViewer::Viewer();
osg::ref_ptr<osg::GraphicsContext::Traits> traits = new osg::GraphicsContext::Traits;
traits->x = 40;
traits->y = 40;
traits->width = 600;
traits->height = 480;
traits->windowDecoration = true;
traits->doubleBuffer = true;
traits->sharedContext = 0;
osg::ref_ptr<osg::GraphicsContext> gc = osg::GraphicsContext::createGraphicsContext(traits.get());
osg::ref_ptr<osg::Camera> camera = new osg::Camera;
camera->setGraphicsContext(gc.get());
camera->setViewport(new osg::Viewport(0, 0, traits->width, traits->height));
GLenum buffer = traits->doubleBuffer ? GL_BACK : GL_FRONT;
camera->setDrawBuffer(buffer);
camera->setReadBuffer(buffer);
viewer->addSlave(camera.get());
osg::ref_ptr<osg::Group> root = new osg::Group();
// 读取模型
string strDataPath = strDataFolder + "cessna.osg";
osg::ref_ptr<osg::Node> node = osgDB::readNodeFile(strDataPath);
// 使用一维纹理属性
osg::ref_ptr<osg::StateSet> stateset = new osg::StateSet();
stateset = createTexture1DState();
node->setStateSet(stateset.get());
root->addChild(node.get());
// 优化场景数据
osgUtil::Optimizer optimizer;
optimizer.optimize(root.get());
viewer->setSceneData(root.get());
viewer->realize();
viewer->run();
}
运行程序,截图如图5-18 所示
图5-18一维纹理示例截图
2.20、三维纹理映射
三维纹理映射(osg::Texture3D)是一大类应用范畴的一部分,称为体渲染。三维纹理主要应用于医学领域和科学领域,笔者也未对其做深入研究,只限于简单的了解,这里也只简单介绍一下。在OsgChina 中国官方成员中,hesicong 目前主要研究这个方向。在医学领域应用程序中,三维纹理主要应用于断层计算成像(CT)和核磁共振(MRT)图像,在网上有很多相关的演示视频。当然,这里也不会向读者演示一个精妙的虚拟手术,只是作一些简单的介绍。
在实际的虚拟现实项目中,三维纹理的应用不多,它虽然能达到很好的效果,但当面对一个很大的场景时,渲染的负担是非常大的,三维纹理可能也会非常之大,它占用的内存资源也会非常多,有时即使是一个非常粗糙的三维纹理,它占用的内存也可能是普通二维纹理的 16 倍或32倍。如果渲染一个大的场景,就需要一个高配置的机器。
osg::Texture3D类继承自 osg::Texture 类封装了OpenGL的二维纹理函数的一些功能,但它不支持立方图纹理。osg::Texture3D 的继承关系图如图5-19 所示。在它的父类中同样有osg::StateAttribute。因此,它同样可以通过设置渲染属性来启用三维纹理属性。
到目前为止,我们已经讲解了基本的几种纹理映射,从它们的类的继承关系可以看出,它们都继承自osg::Texture。下面来看一下这些纹理的共同之处,osg::Texture 关联各种类的关系图如图5-20所示。从图中可以看出,前面所讲的几种基本纹理都可以通过设置渲染属性来启用并设置状态继承。
三维纹理也不是很神秘,可以把它看成是一层层的二维纹理图像矩阵所构成的一个有多层深度的纹理图像。三维纹理图像结构如图5-21所示。
图 5-21 三维纹理图像结构
使用三维纹理的方法同使用二维纹理基本类似,需要注意的是:三维纹理图像的二维纹理子图的大小和像素格式需要一致;使用三维纹理时需要初始化一个图形环境。在第 5.2.20节的示例中并没有使用专业的三维纹理图像数据,专业的三维纹理图像是非常有价值的,也是非常难得的,示例只是将多张二维纹理数据压入一个渲染体。
2.21、三维纹理映射示例
三维纹理映射(osg::Texture3D)示例的代码如程序清单5-11所示。
osg::ref_ptr<osg::Node> createNode_5_11() // 创建一个四边形节点
{
osg::ref_ptr<osg::Geode> geode = new osg::Geode();
osg::ref_ptr<osg::Geometry> geom = new osg::Geometry();
// 设置顶点
osg::ref_ptr<osg::Vec3Array> vc = new osg::Vec3Array();
vc->push_back(osg::Vec3(0.0f, 0.0f, 0.0f));
vc->push_back(osg::Vec3(1.0f, 0.0f, 0.0f));
vc->push_back(osg::Vec3(1.0f, 0.0f, 1.0f));
vc->push_back(osg::Vec3(0.0f, 0.0f, 1.0f));
geom->setVertexArray(vc.get());
// 设置纹理坐标
osg::ref_ptr<osg::Vec2Array> vt = new osg::Vec2Array();
vt->push_back(osg::Vec2(0.0f, 0.0f));
vt->push_back(osg::Vec2(1.0f, 0.0f));
vt->push_back(osg::Vec2(1.0f, 1.0f));
vt->push_back(osg::Vec2(0.0f, 1.0f));
geom->setTexCoordArray(0, vt.get());
// 设置法线
osg::ref_ptr<osg::Vec3Array> nc = new osg::Vec3Array();
nc->push_back(osg::Vec3(0.0f, -1.0f, 0.0f));
geom->setNormalArray(nc.get());
geom->setNormalBinding(osg::Geometry::BIND_OVERALL);
// 添加图元
geom->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::QUADS, 0, 4));
// 绘制
geode->addDrawable(geom.get());
return geode.get();
}
// 初始化一个图形环境
class MyGraphicsContext
{
public:
MyGraphicsContext()
{
// 设置图形环境特性
osg::ref_ptr<osg::GraphicsContext::Traits> traits = new osg::GraphicsContext::Traits;
traits->x = 0;//设置左上角坐标
traits->y = 0;
traits->width = 1;//设置宽度和高度
traits->height = 1;
traits->windowDecoration = false;// 设置窗口扩展
traits->doubleBuffer = false;// 设置双缓冲
traits->sharedContext = 0;
traits->pbuffer = true;// 设置pbuffer
// 创建图形环境
_gc = osg::GraphicsContext::createGraphicsContext(traits.get());
// 如果创建失败
if (!_gc)
{
//设置pbuffer为false
traits->pbuffer = false;
//重新创建图形环境
_gc = osg::GraphicsContext::createGraphicsContext(traits.get());
}
// 是否初始化
if (_gc.valid())
{
// 初始化
_gc->realize();
_gc->makeCurrent();
}
}
bool valid() const { return _gc.valid() && _gc->isRealized(); }
private:
osg::ref_ptr<osg::GraphicsContext> _gc;
};
osg::ref_ptr<osg::StateSet> createState(const string &strDataFolder)// 创建三维纹理属性
{
// 创建图形环境
MyGraphicsContext gc;
if (!gc.valid())
{
// 如果创建失败,则返回
osg::notify(osg::NOTICE) << "Unable to create the graphics context required to build 3d image." << std::endl;
return 0;
}
// 读取四张二维纹理图像
string strDataPath = strDataFolder + "Images/lz.rgb";
osg::ref_ptr<osg::Image> image_0 = osgDB::readImageFile(strDataPath);
strDataPath = strDataFolder + "Images/reflect.rgb";
osg::ref_ptr<osg::Image> image_1 = osgDB::readImageFile(strDataPath);
strDataPath = strDataFolder + "Images/tank.rgb";
osg::ref_ptr<osg::Image> image_2 = osgDB::readImageFile(strDataPath);
strDataPath = strDataFolder + "Images/skymap.jpg";
osg::ref_ptr<osg::Image> image_3 = osgDB::readImageFile(strDataPath);
// 判断是否正确读取
if (!image_0 || !image_1 || !image_2 || !image_3)
{
std::cout << "Warning: could not open files." << std::endl;
return new osg::StateSet();
}
//判断纹理格式是否一致
if (image_0->getPixelFormat() != image_1->getPixelFormat() || image_0->getPixelFormat() != image_2->getPixelFormat() || image_0->getPixelFormat() != image_3->getPixelFormat())
{
std::cout << "Warning: image pixel formats not compatible." << std::endl;
return new osg::StateSet();
}
// 得到支持的最大的三维纹理单元的大小
//GLint textureSize = osg::Texture3D::getExtensions(0, true)->maxTexture3DSize();
//if (textureSize > 256)
//{
// textureSize = 256;
//}
GLint textureSize = 256;
// 对四张二维纹理图像缩放,以达到相同的大小
image_0->scaleImage(textureSize, textureSize, 1);
image_1->scaleImage(textureSize, textureSize, 1);
image_2->scaleImage(textureSize, textureSize, 1);
image_3->scaleImage(textureSize, textureSize, 1);
// 创建一个三维纹理数据图像,注意深度为4
osg::ref_ptr<osg::Image> image_3d = new osg::Image;
// 第一个和第二个参数是纹理的大小,第三个参数指的是三维纹理数据图像的深度
image_3d->allocateImage(textureSize, textureSize, 4, image_0->getPixelFormat(), image_0->getDataType());
//把四张二维纹理图像压入三维纹理数据图像
//第1-3个参数分别是s,t,r上的偏移,当然这里只是r上的偏移
//第四个参数是子二维纹理图像数据
image_3d->copySubImage(0, 0, 0, image_0.get());
image_3d->copySubImage(0, 0, 1, image_1.get());
image_3d->copySubImage(0, 0, 2, image_2.get());
image_3d->copySubImage(0, 0, 3, image_3.get());
//设置纹理格式
image_3d->setInternalTextureFormat(image_0->getInternalTextureFormat());
//创建三维纹理对象
osg::ref_ptr<osg::Texture3D> texture3D = new osg::Texture3D;
//设置滤波,不支持mip map滤波
texture3D->setFilter(osg::Texture3D::MIN_FILTER, osg::Texture3D::LINEAR);
texture3D->setFilter(osg::Texture3D::MAG_FILTER, osg::Texture3D::LINEAR);
//设置环绕模式
texture3D->setWrap(osg::Texture3D::WRAP_R, osg::Texture3D::REPEAT);
//关联三维纹理图像数据
texture3D->setImage(image_3d.get());
//设置自动生成纹理坐标
osg::ref_ptr<osg::TexGen> texgen = new osg::TexGen;
//设置自动生成纹理坐标为视觉线性
texgen->setMode(osg::TexGen::OBJECT_LINEAR);
//指定参考平面
texgen->setPlane(osg::TexGen::R, osg::Plane(0.0f, 0.0f, 0.0f, 0.2f));
//创建状态属性对象
osg::ref_ptr<osg::StateSet> stateset = new osg::StateSet;
//设置在R上自动生成纹理坐标
stateset->setTextureMode(0, GL_TEXTURE_GEN_R, osg::StateAttribute::ON);
//启用自动生成纹理坐标
stateset->setTextureAttribute(0, texgen.get());
//启用三维纹理对象
stateset->setTextureAttributeAndModes(0, texture3D.get(), osg::StateAttribute::ON);
return stateset.get();
}
void create3DTexture(const string &strDataFolder)
{
osg::ref_ptr<osgViewer::Viewer> viewer = new osgViewer::Viewer();
osg::ref_ptr<osg::GraphicsContext::Traits> traits = new osg::GraphicsContext::Traits;
traits->x = 40;
traits->y = 40;
traits->width = 600;
traits->height = 480;
traits->windowDecoration = true;
traits->doubleBuffer = true;
traits->sharedContext = 0;
osg::ref_ptr<osg::GraphicsContext> gc = osg::GraphicsContext::createGraphicsContext(traits.get());
osg::ref_ptr<osg::Camera> camera = new osg::Camera;
camera->setGraphicsContext(gc.get());
camera->setViewport(new osg::Viewport(0, 0, traits->width, traits->height));
GLenum buffer = traits->doubleBuffer ? GL_BACK : GL_FRONT;
camera->setDrawBuffer(buffer);
camera->setReadBuffer(buffer);
viewer->addSlave(camera.get());
osg::ref_ptr<osg::Group> root = new osg::Group();
osg::ref_ptr<osg::Node> node = createNode();
// 创建状态属性对象
osg::ref_ptr<osg::StateSet> stateset = new osg::StateSet();
stateset = createState(strDataFolder);
// 使用三维纹理
node->setStateSet(stateset.get());
root->addChild(node.get());
// 优化场景数据
osgUtil::Optimizer optimizer;
optimizer.optimize(root.get());
viewer->setSceneData(root.get());
viewer->realize();
viewer->run();
}
运行程序,截图如图 5-22 所示。
图5-22 三维纹理映射示例截图