OSG全面支持 OpenGL 的光照特性,包括材质属性(material property)、光照属性(light property)和光照模型 (lighting model)。与 OpenGL 相似,OSG中的光源也是不可见的,而非渲染一个灯泡或其他自然形状。同样,光源会创建着色效果,但并不创建阴影,osgShadow 可以用来创建阴影。
1.osg::Light类
OSG将OpenGL中的 glLight()作了一个 light状态的类封装,用于保存灯光的模式与属性参数信息。osg::Light 类派生自osg::StateAttribute类,继承了对模式与属性参数信息的操作接口。在osg::light类中通过 apply(State&state)函数将灯光的状态参数信息应用到OpenGL 的状态机中。osg::Light的继承关系图如图5-23 所示。
图5-23 osg::Light的继承关系图
osg::Light类包括的属性参数如下:
- int _lightnum; // 灯光数量
- Vec4 _ambient; // 环境光颜色
- Vec4 _diffuse; // 漫光颜色
- Vec4 _specular; // 镜面光颜色
- Vec3 _position; // 光源的位置信息
- Vec3 _direction;// 光源的方向
- float _constant_attenuation; // 常最衰减
- float _linear_attenuation; // 线性衰减
- float _quadratic_attenuation;// 二次方衰减
- float _spot_exponent; // 指数衰减
- float_spot_cutoff; // 关闭衰减(spread)
上面的参数应该都比较容易理解。OSG 支持最多8个光源,即GL_LIGHTO~GL_LIGHT7,这与读者的OpenGL版本也有关系。
2.osg::LightSource类
osg::LightSource 类直接继承自 osg::Group。作为一个灯光管理类,继承了 osg::Group 类的管理节点的接口;将灯光作为一个节点可以加入到场景图中进行渲染。继承关系图如图 5-24 所示。
图5-24 osg::LightSource 的继承关系图
osg::LightSource类中的成员函数为:
- void setReferenceFrame(ReferenceFrame rf);// 设置赖引用
帧引用包括如下两个枚举变量:
- enum ReferenceFrame
- {
- RELATIVE_RF, // 相对顿引用
- ABSOLUTE_RF // 绝对引用
- }
设置光源的引用帧时,不是相对于父节点的帧引用,就是相对于绝对坐标的帧,默认的设置为RELATIVE_RF,设置引用为RELATIVE_RF 同样会设置CullingActive 的标志量为开(ON)状态,并且对它的父节点也起作用;否则,对它与它所有的父节点都禁用拣选(Culling),对防止不合适的拣选是必需的,如果绝对光源在场景图的深处将会对拣选的时间有影响,因此,推荐在场景的顶部使用绝对的光源。
3.场景中使用光源
在一个场景中添加光源主要包括以下步骤
(1)指定场景模型的法线。
(2)允许光照并设置光照状态。
(3指定光源属性并关联到场景图形
对于场景中的模型,只有当其中设有单位法线时才会正确地显示光照。当场景中的模型没有指定法线时,可以用前面讲到的osgUtl::SmoothingVisitor自动生成法线。需要注意的是,法向量必须单位化。有时场景中的模型虽然指定了单位法向量,但是光照的计算结果过于明亮或过于暗淡(可能是缩放变换造成的),这时最有效的解决方案是在 StateSet 中允许法线的重放缩模式,代码如下:
- osg::StateSet *state = geode->setOrCreateStateSet():
- state->setMode(GL_RESCALE_NORMAL,osg::StateAttribute::ON);
与在OpenGL中相同,这一特性可以保证法线在均匀放缩变换时仍然保持单位长度。如果场景中的放缩变换是非均匀的,那么读者可以允许法线归一化模式,以保证法线为单位长度。由于要进行法线的重新放缩,归一化模式往往会耗费大量的时间,编程时要尽量避免。归一化模式的代码如下:
- osg::StateSet *state = geode->setOrCreateStateSet():
- state->setMode(GL_NORMALIZE,osg::StateAttribute::ON);
要在OSG中获得光照效果,需要允许光照并至少允许一个光源。程序osgviewer 在默认情况下就是这样做的,它在根节点的 StateSet 中已经设置了相应的模式。读者可以在自己的程序中进行相同的设置。下面的代码段用于允许光照并为根节点的 StateSet 允许两个光源(GL_LIGHTO和GL_LIGHT1)
- osg::StateSet *state = geode->setOrCreateStateSet():
- state->setMode(GL_LIGHTING,osg::StateAttribute::ON);
- state->setMode(GL_LIGHT0,osg::StateAttribute::ON);
- state->setMode(GL_LIGHT1,osg::StateAttribute::ON);
在场景中添加一个光源,可以创建一个osg::Light对象以定义光源参数,然后将osg::Light添加到一个osg::LightSource节点中,并将 LightSource 节点添加到场景图形。osg::LightSource 是一个包含了唯一的Light定义的高效的组节点,而由osg::Light 定义的光源将对整个场景产生影响。下面的代码实现将osg::Light添加到osg::LightSource对象中;
- osg::ref_ptr<osg::LightSource> ls = new osg::LightSource;
- ls->setLight(light.get());
在实际生活中,当光照照射到物体上时都会反射等现象,所以,在对光源的设置完成以后需要设置模型的表面材质,第5.4节会讲到,下面先看看关于光照的两个示例。
4.简单光源示例
简单光源 (osg::LightSource)示例的代码如程序清单5-12 所示:
1. osg::ref_ptr<osg::Group> createLight(osg::ref_ptr<osg::Node> node) //向场景中添加光源
2. {
3. osg::ref_ptr<osg::Group> lightRoot = new osg::Group();
4. lightRoot->addChild(node);
5.
6. // 开启光照
7. osg::ref_ptr<osg::StateSet> stateset = new osg::StateSet();
8. stateset = lightRoot->getOrCreateStateSet();
9. stateset->setMode(GL_LIGHTING, osg::StateAttribute::ON);
10. stateset->setMode(GL_LIGHT0, osg::StateAttribute::ON);
11.
12. // 计算包围盒
13. osg::BoundingSphere bs;
14. node->computeBound();
15. bs = node->getBound();
16.
17. // 创建一个Light对象
18. osg::ref_ptr<osg::Light> light = new osg::Light();
19. light->setLightNum(0);
20. light->setDirection(osg::Vec3(0.0f, 0.0f, -1.0f));// 设置方向
21. light->setPosition(osg::Vec4(bs.center().x(), bs.center().y(), bs.center().z() + bs.radius(), 1.0f));// 设置位置
22. light->setAmbient(osg::Vec4(1.0f, 1.0f, 1.0f, 1.0f));// 设置环境光的颜色
23. light->setDiffuse(osg::Vec4(1.0f, 1.0f, 1.0f, 1.0f)); // 设置散射光的颜色
24. light->setConstantAttenuation(1.0f);// 设置恒衰减指数
25. light->setLinearAttenuation(0.0f); // 设置线形衰减指数
26. light->setQuadraticAttenuation(0.0f);// 设置二次方衰减指数
27.
28. // 创建光源
29. osg::ref_ptr<osg::LightSource> lightSource = new osg::LightSource();
30. lightSource->setLight(light.get());
31. lightRoot->addChild(lightSource.get());
32.
33. return lightRoot.get();
34. }
35.
36. void lightSource_5_12(const string &strDataFolder)
37. {
38. osg::ref_ptr<osgViewer::Viewer> viewer = new osgViewer::Viewer();
39. osg::ref_ptr<osg::GraphicsContext::Traits> traits = new osg::GraphicsContext::Traits;
40. traits->x = 40;
41. traits->y = 40;
42. traits->width = 600;
43. traits->height = 480;
44. traits->windowDecoration = true;
45. traits->doubleBuffer = true;
46. traits->sharedContext = 0;
47.
48. osg::ref_ptr<osg::GraphicsContext> gc = osg::GraphicsContext::createGraphicsContext(traits.get());
49. osg::ref_ptr<osg::Camera> camera = new osg::Camera;
50. camera->setGraphicsContext(gc.get());
51. camera->setViewport(new osg::Viewport(0, 0, traits->width, traits->height));
52. GLenum buffer = traits->doubleBuffer ? GL_BACK : GL_FRONT;
53. camera->setDrawBuffer(buffer);
54. camera->setReadBuffer(buffer);
55. viewer->addSlave(camera.get());
56.
57. osg::ref_ptr<osg::Group> root = new osg::Group();
58.
59. // 读取模型
60. string strDataPath = strDataFolder + "cow.osg";
61. osg::ref_ptr<osg::Node> node = osgDB::readNodeFile(strDataPath);
62.
63. // 向场景中添加光源
64. root->addChild(createLight(node.get()));
65.
66. // 优化场景数据
67. osgUtil::Optimizer optimizer;
68. optimizer.optimize(root.get());
69.
70. viewer->setSceneData(root.get());
71. viewer->realize();
72. viewer->run();
73. }
运行程序,截图如图5-25 所示
图5-25 简单光源示例裁图
4.聚光灯示例
聚光灯(SpotLight)示例的代码如程序清单5-13所示:
1. osg::ref_ptr<osg::Image> createSpotLightImage(const osg::Vec4& centerColour,
2. const osg::Vec4& backgroudColour, unsigned int size, float power) // 创建聚光灯纹理的mipmap贴图
3. {
4. // 创建Image对象
5. osg::ref_ptr<osg::Image> image = new osg::Image;
6. image->allocateImage(size, size, 1, GL_RGBA, GL_UNSIGNED_BYTE); // 动态分配一个size*size大小的image
7.
8. // 填充image
9. // 以中心为原点,颜色逐渐向四周衰减
10. float mid = (float(size) - 1)*0.5f;
11. float div = 2.0f / float(size);
12. for (unsigned int r = 0; r < size; ++r)
13. {
14. unsigned char* ptr = image->data(0, r, 0);
15. for (unsigned int c = 0; c < size; ++c)
16. {
17. float dx = (float(c) - mid)*div;
18. float dy = (float(r) - mid)*div;
19. float r = powf(1.0f - sqrtf(dx*dx + dy*dy), power);
20. if (r < 0.0f) r = 0.0f;
21. osg::Vec4 color = centerColour*r + backgroudColour*(1.0f - r);
22. *ptr++ = (unsigned char)((color[0])*255.0f);
23. *ptr++ = (unsigned char)((color[1])*255.0f);
24. *ptr++ = (unsigned char)((color[2])*255.0f);
25. *ptr++ = (unsigned char)((color[3])*255.0f);
26. }
27. }
28. return image.get();
29. }
30.
31. osg::ref_ptr<osg::StateSet> createSpotLightDecoratorState(unsigned int lightNum,
32. unsigned int textureUnit) // 创建聚光灯状态属性
33. {
34. osg::ref_ptr<osg::StateSet> stateset = new osg::StateSet;
35.
36. // 开启ID为lightNum的光照
37. stateset->setMode(GL_LIGHT0 + lightNum, osg::StateAttribute::ON);
38.
39. // 设置中心的颜色和环境光的颜色
40. osg::Vec4 centerColour(1.0f, 1.0f, 1.0f, 1.0f);
41. osg::Vec4 ambientColour(0.05f, 0.05f, 0.05f, 1.0f);
42.
43. // 创建聚光灯纹理
44. osg::ref_ptr<osg::Texture2D> texture = new osg::Texture2D();
45. texture->setImage(createSpotLightImage(centerColour, ambientColour, 64, 1.0));
46. texture->setBorderColor(osg::Vec4(ambientColour));
47. texture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_BORDER);
48. texture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_BORDER);
49. texture->setWrap(osg::Texture::WRAP_R, osg::Texture::CLAMP_TO_BORDER);
50.
51. // 打开纹理单元
52. stateset->setTextureAttributeAndModes(textureUnit, texture.get(), osg::StateAttribute::ON);
53.
54. // 设置自动生成纹理坐标
55. stateset->setTextureMode(textureUnit, GL_TEXTURE_GEN_S, osg::StateAttribute::ON);
56. stateset->setTextureMode(textureUnit, GL_TEXTURE_GEN_T, osg::StateAttribute::ON);
57. stateset->setTextureMode(textureUnit, GL_TEXTURE_GEN_R, osg::StateAttribute::ON);
58. stateset->setTextureMode(textureUnit, GL_TEXTURE_GEN_Q, osg::StateAttribute::ON);
59.
60. return stateset.get();
61. }
62.
63. osg::ref_ptr<osg::Node> createSpotLightNode(const osg::Vec3& position,
64. const osg::Vec3& direction, float angle, unsigned int lightNum,
65. unsigned int textureUnit) // 创建聚光灯节点
66. {
67. osg::ref_ptr<osg::Group> group = new osg::Group;
68.
69. // 创建光源
70. osg::ref_ptr<osg::LightSource> lightsource = new osg::LightSource;
71. osg::ref_ptr<osg::Light> light = lightsource->getLight();
72. light->setLightNum(lightNum);
73. light->setPosition(osg::Vec4(position, 1.0f));
74. light->setAmbient(osg::Vec4(0.00f, 0.00f, 0.05f, 1.0f));
75. light->setDiffuse(osg::Vec4(1.0f, 1.0f, 1.0f, 1.0f));
76. group->addChild(lightsource);
77.
78. // 计算法向量
79. osg::Vec3 up(0.0f, 0.0f, 1.0f);
80. up = (direction ^ up) ^ direction;
81. up.normalize();
82.
83. // 创建自动生成纹理坐标节点
84. osg::ref_ptr<osg::TexGenNode> texgenNode = new osg::TexGenNode;
85. texgenNode->setTextureUnit(textureUnit); // 关联纹理单元
86.
87. osg::ref_ptr<osg::TexGen> texgen = texgenNode->getTexGen(); // 设置纹理坐标生成器
88. texgen->setMode(osg::TexGen::EYE_LINEAR); // 设置模式为视觉线性
89. texgen->setPlanesFromMatrix(osg::Matrixd::lookAt(position, position + direction, up)*
90. osg::Matrixd::perspective(angle, 1.0, 0.1, 100)); // 从视图中指定参考平面
91. group->addChild(texgenNode.get());
92.
93. return group.get();
94. }
95.
96. osg::ref_ptr<osg::AnimationPath> createAnimationPath(const osg::Vec3& center,
97. float radius, double looptime) // 创建动画路径(请参看后面章节的OSG动画)
98. {
99. osg::ref_ptr<osg::AnimationPath> animationPath = new osg::AnimationPath;
100. animationPath->setLoopMode(osg::AnimationPath::LOOP);
101.
102. int numSamples = 40;
103. float yaw = 0.0f;
104. float yaw_delta = 2.0f*osg::PI / ((float)numSamples - 1.0f);
105. float roll = osg::inDegrees(30.0f);
106.
107. double time = 0.0f;
108. double time_delta = looptime / (double)numSamples;
109. for (int i = 0; i < numSamples; ++i)
110. {
111. osg::Vec3 position(center + osg::Vec3(sinf(yaw)*radius, cosf(yaw)*radius, 0.0f));
112. osg::Quat rotation(osg::Quat(roll, osg::Vec3(0.0, 1.0, 0.0))*osg::Quat(-(yaw + osg::inDegrees(90.0f)), osg::Vec3(0.0, 0.0, 1.0)));
113.
114. animationPath->insert(time, osg::AnimationPath::ControlPoint(position, rotation));
115.
116. yaw += yaw_delta;
117. time += time_delta;
118.
119. }
120. return animationPath.get();
121. }
122.
123. osg::ref_ptr<osg::Node> createBase(const string &strDataFolder, const osg::Vec3& center, float radius)// 创建地形平面
124. {
125. osg::ref_ptr<osg::Geode> geode = new osg::Geode;
126.
127. osg::ref_ptr<osg::StateSet> stateset = new osg::StateSet();
128. string strDataPath = strDataFolder + "Images/lz.rgb";
129. osg::ref_ptr<osg::Image> image = osgDB::readImageFile(strDataPath);
130. if (image.get())
131. {
132. osg::ref_ptr<osg::Texture2D> texture = new osg::Texture2D;
133. texture->setImage(image.get());
134. stateset->setTextureAttributeAndModes(0, texture.get(), osg::StateAttribute::ON);
135. }
136.
137. geode->setStateSet(stateset.get());
138.
139. osg::ref_ptr<osg::HeightField> grid = new osg::HeightField;
140. grid->allocate(38, 39);
141. grid->setOrigin(center + osg::Vec3(-radius, -radius, 0.0f));
142. grid->setXInterval(radius*2.0f / (float)(38 - 1));
143. grid->setYInterval(radius*2.0f / (float)(39 - 1));
144.
145. float minHeight = FLT_MAX;
146. float maxHeight = -FLT_MAX;
147.
148.
149. unsigned int r;
150. for (r = 0; r < 39; ++r)
151. {
152. for (unsigned int c = 0; c < 38; ++c)
153. {
154. float h = vertex[r + c * 39][2];
155. if (h > maxHeight) maxHeight = h;
156. if (h < minHeight) minHeight = h;
157. }
158. }
159.
160. float hieghtScale = radius*0.5f / (maxHeight - minHeight);
161. float hieghtOffset = -(minHeight + maxHeight)*0.5f;
162.
163. for (r = 0; r < 39; ++r)
164. {
165. for (unsigned int c = 0; c < 38; ++c)
166. {
167. float h = vertex[r + c * 39][2];
168. grid->setHeight(c, r, (h + hieghtOffset)*hieghtScale);
169. }
170. }
171.
172. geode->addDrawable(new osg::ShapeDrawable(grid.get()));
173.
174. osg::ref_ptr<osg::Group> group = new osg::Group;
175. group->addChild(geode.get());
176.
177. return group.get();
178.
179. }
180.
181. osg::ref_ptr<osg::Node> createMovingModel(const string &strDataFolder, const osg::Vec3& center,
182. float radius) // 创建动画模型
183. {
184. float animationLength = 10.0f;
185. osg::ref_ptr<osg::AnimationPath> animationPath = createAnimationPath(center, radius, animationLength);
186.
187. osg::ref_ptr<osg::Group> model = new osg::Group;
188. string strDataPath = strDataFolder + "cessna.osg";
189. osg::ref_ptr<osg::Node> cessna = osgDB::readNodeFile(strDataPath);
190. if (cessna.get())
191. {
192. const osg::BoundingSphere& bs = cessna->getBound();
193.
194. float size = radius / bs.radius()*0.3f;
195. osg::ref_ptr<osg::MatrixTransform> positioned = new osg::MatrixTransform;
196. positioned->setDataVariance(osg::Object::STATIC);
197. positioned->setMatrix(osg::Matrix::translate(-bs.center())*
198. osg::Matrix::scale(size, size, size)*
199. osg::Matrix::rotate(osg::inDegrees(180.0f), 0.0f, 0.0f, 2.0f));
200.
201. positioned->addChild(cessna.get());
202.
203. osg::ref_ptr<osg::MatrixTransform> xform = new osg::MatrixTransform;
204. xform->setUpdateCallback(new osg::AnimationPathCallback(animationPath, 0.0f, 2.0));
205. xform->addChild(positioned);
206.
207. // 添加聚光灯节点
208. xform->addChild(createSpotLightNode(osg::Vec3(0.0f, 0.0f, 0.0f), osg::Vec3(0.0f, 1.0f, -1.0f), 60.0f, 0, 1));
209.
210. model->addChild(xform.get());
211. }
212.
213. return model.get();
214. }
215.
216. osg::ref_ptr<osg::Node> createModel(const string &strDatafolder) // 创建场景
217. {
218. osg::Vec3 center(0.0f, 0.0f, 0.0f);
219. float radius = 100.0f;
220.
221. // 创建动画模型
222. osg::ref_ptr<osg::Node> shadower = createMovingModel(strDatafolder, center, radius*0.5f);
223.
224. // 创建地形平面
225. osg::ref_ptr<osg::Node> shadowed = createBase(strDatafolder, center - osg::Vec3(0.0f, 0.0f, radius*0.1), radius);
226.
227. // 创建场景组节点
228. osg::ref_ptr<osg::Group> root = new osg::Group;
229. root->setStateSet(createSpotLightDecoratorState(0, 1));// 设置状态属性
230. root->addChild(shadower.get()); // 添加子节点
231. root->addChild(shadowed.get());
232.
233. return root.get();
234. }
235.
236. void spotLight_5_13(const string &strDatafolder)
237. {
238. osg::ref_ptr<osgViewer::Viewer> viewer = new osgViewer::Viewer();
239. osg::ref_ptr<osg::GraphicsContext::Traits> traits = new osg::GraphicsContext::Traits;
240. traits->x = 40;
241. traits->y = 40;
242. traits->width = 600;
243. traits->height = 480;
244. traits->windowDecoration = true;
245. traits->doubleBuffer = true;
246. traits->sharedContext = 0;
247.
248. osg::ref_ptr<osg::GraphicsContext> gc = osg::GraphicsContext::createGraphicsContext(traits.get());
249. osg::ref_ptr<osg::Camera> camera = new osg::Camera;
250. camera->setGraphicsContext(gc.get());
251. camera->setViewport(new osg::Viewport(0, 0, traits->width, traits->height));
252. GLenum buffer = traits->doubleBuffer ? GL_BACK : GL_FRONT;
253. camera->setDrawBuffer(buffer);
254. camera->setReadBuffer(buffer);
255. viewer->addSlave(camera.get());
256.
257. osg::ref_ptr<osg::Group> root = new osg::Group();
258.
259. // 添加场景
260. root->addChild(createModel(strDatafolder));
261.
262. // 优化场景数据
263. osgUtil::Optimizer optimizer;
264. optimizer.optimize(root.get());
265.
266. viewer->setSceneData(root.get());
267. viewer->realize();
268. viewer->run();
269. }
运行程序,截图如图5-26所示
图5-26 聚光示例截图