OSG-纹理映射(二)

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一样直接指定属性使用。例如:

  1. //设置自动纹理坐标,并指定相关的平面  
  2. osg::TexGen* texgen = new osg::TexGen;  
  3. texgen->setMode(osg::TexGen::OBJECT_LINEAR):  
  4. texgen->setPlane(osg::TexGen::S,osg::Plane(0.0f,0.0f,1.0f,0.0f)):  
  5. //启用纹理坐标生成器  
  6. 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。因此,可以使用设置渲染属性的方式来启用立方图纹理,代码如下:

  1. // 设置立方图纹理  
  2. osg::TextureCubeMap *skymap = osg::TextureCubeMap();  
  3. stateset->setTextureAttributeAndModes(0,skymap,osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE):  

        创建天空盒的方法有很多,可以直接绘制一个,当然效果是非常不好的。实际中,经常使用的天空盒包括两种,即方体天空盒球体天空盒。更多的是球体天空盒的应用,看起来更真实,更能描述真实的天空效果。

        创建球体天空盒的主要步骤如下:

        (1)创建立方图纹理对象,读取立方图纹理贴图。

        (2)设置自动生成纹理坐标。

        (3)设置纹理矩阵。

        (4)设置立方图纹理。

        (5)设置矩阵变换节点,以实现合适的矩阵变换。

        (6)关闭光照并关闭背面剔除,设置渲染顺序,加入到叶节点中绘制。

        在上面的步骤中有几点需要注意的地方:

  • 读取立方图纹理贴图时,需要特别注意的是,纹理贴图要与立方体的各个面(+X,-X,+Y, -Y,+Z,-Z)一一对应,否则,生成的天空盒会非常难看。
  1. POSITIVE_X=0// Left X正方向  
  2. NEGATIVE_X=1// Right X负方向  
  3. POSITIVE_Y=2// Front Y正方向  
  4. NEGATIVE_Y=3// Back Y负方向  
  5. POSITIVE_Z=4// Up Z正方向  
  6. 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 三维纹理映射示例截图

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/287958.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Android 串口协议

前言 本协议是 Android 应用端与主控板之间的通信协议&#xff0c;是串行通信协议。 协议要求同一时间只能有两个通讯端点在相互通讯&#xff0c;采用小端传输数据。 硬件层基于RS485协议&#xff0c;采取半双工&#xff0c;一主多从的通讯模式。Android定义为主机&#xff0c…

DataGear 4.7.0 发布,数据可视化分析平台

DataGear 4.7.0 发布&#xff0c;严重漏洞和BUG修复&#xff0c;具体更新内容如下&#xff1a; 新增&#xff1a;HTTP数据集新增【编码请求地址】支持&#xff0c;可用于解决请求地址中文乱码问题&#xff1b;新增&#xff1a;新增数据源密码加密存储支持&#xff08;开启需设…

怎么有效利用HTTPS协议

HTTPS的发展史可以追溯到早期的互联网时代&#xff0c;当时HTTP协议被广泛使用&#xff0c;但由于通信过程是明文的&#xff0c;导致用户的敏感信息容易被截取和窃取。为了解决这个问题&#xff0c;HTTPS协议应运而生。 HTTPS是在HTTP协议的基础上加入了传输层安全协议&#x…

深挖小白必会指针笔试题<一>

目录 引言 关键解决办法&#xff1a; 学会画图确定指向关系 例题一&#xff1a; 画图分析&#xff1a; 例题二&#xff1a; 画图分析&#xff1a; 例题三&#xff1a; 注&#xff1a;%x是按十六进制打印 画图分析&#xff1a; 例题四&#xff1a; 画图分析&…

基于Java+SpringMvc+Vue求职招聘系统详细设计实现

基于JavaSpringMvcVue求职招聘系统详细设计实现 &#x1f345; 作者主页 专业程序开发 &#x1f345; 欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; &#x1f345; 文末获取源码联系方式 &#x1f4dd; 文章目录 基于JavaSpringMvcVue求职招聘系统详细设计实现一、前言介…

众和策略:今日,有“天地板”,也有“地天板”

今日早盘&#xff0c;A股持续坚持弱势震动&#xff0c;两市成交有进一步萎缩的趋势。 盘面上&#xff0c;煤炭、传媒娱乐、旅行、房地产等板块相对活泼&#xff0c;混合实际、PEEK材料、苹果概念、华为汽车等板块跌幅居前。 个股方面&#xff0c;神马电力连续5日涨停&#xf…

react useEffect 内存泄漏

componentWillUnmount() {this.setState (state, callback) > {return;};// 清除reactionthis.reaction();}useEffect 使用AbortController useEffect(() > { let abortController new AbortController(); // your async action is here return () > { abortCo…

TCP/IP的网络层(即IP层)之IP地址和网络掩码,在视频监控系统中的配置和应用

在给客户讲解我们的AS-V1000视频监控平台的时候&#xff0c;有的客户经常会配置错误IP地址的掩码和网关&#xff0c;导致出现一些网路问题。而在视频监控系统中&#xff0c;IP地址和子网掩码是用于标识网络中设备的重要标识符。IP地址被用来唯一地标识一个网络设备&#xff0c;…

express+mongoDB开发入门教程之mongoDB安装

系列文章 node.js express框架开发入门教程 expressmongoDB开发入门教程之mongoDB安装expressmongoDB开发入门教程之mongoose使用讲解 文章目录 系列文章前言一、mongoDB安装1.下载2.安装3. 设置全局环境变量4.启动mongoDB服务 二、可视化管理工具 前言 MongoDB是一个基于分布…

【盛况回顾】聚焦流程创新,共话科技共赢:企业“流程三驾马车”闭环主题沙龙圆满落幕

12月7日&#xff0c;由上海斯歌主办&#xff0c;博阳精讯、凡得科技协办的“流程创新科技共赢——企业流程三驾马车闭环主题沙龙”在上海召开并圆满落幕。本次沙龙&#xff0c;上海斯歌携手来自不同行业的客户与伙伴的资深业务、解决方案专家&#xff0c;围绕流程体系化建模、流…

uniCloud 云数据库(新建表、增、删、改、查)

新建表结构描述文件 todo 为自定义的表名 表结构描述文件的默认后缀为 .schema.json 设置表的操作权限 uniCloud-aliyun/database/todo.schema.json 默认的操作权限都是 false "permission": {"read": false,"create": false,"update&quo…

Spring上下文之support模块DefaultLifecycleProcessor

博主介绍:✌全网粉丝5W+,全栈开发工程师,从事多年软件开发,在大厂呆过。持有软件中级、六级等证书。可提供微服务项目搭建与毕业项目实战,博主也曾写过优秀论文,查重率极低,在这方面有丰富的经验✌ 博主作品:《Java项目案例》主要基于SpringBoot+MyBatis/MyBatis-plus+…

Numpy基础

目录&#xff1a; 一、简介:二、array数组ndarray&#xff1a;1.array( )创建数组&#xff1a;2.数组赋值和引用的区别&#xff1a;3.arange( )创建区间数组&#xff1a;4.linspace( )创建等差数列&#xff1a;5.logspace( )创建等比数列&#xff1a;6.zeros( )创建全0数组&…

Spring源码之依赖注入(二)

书接上文 文章目录 一. Autowire底层注入逻辑1. 属性注入逻辑 一. Autowire底层注入逻辑 前面我们分析了Spring时如何找到某个目标类的所有注入点这一个核心逻辑&#xff0c;但还没又对核心注入方法inject进行详细分析&#xff0c;下面我们就来详细分析Spring拿到所有的注入点…

每日一题——LeetCode1005.K次取反后最大化的数组和

方法一 个人方法&#xff1a; 将数组从小到大排序后&#xff0c;假设数组共有n个负数&#xff0c;要使数组的和尽可能大就要尽可能将较大的负数变为正数&#xff0c;有以下几种情况&#xff1a; 1、k<n&#xff0c;那就把数组前k个负数都转为正数即可。 2、k>n&#xf…

立体匹配算法(Stereo correspondence)

SGM(Semi-Global Matching)原理&#xff1a; SGM的原理在wiki百科和matlab官网上有比较详细的解释&#xff1a; wiki matlab 如果想完全了解原理还是建议看原论文 paper&#xff08;我就不看了&#xff0c;懒癌犯了。&#xff09; 优质论文解读和代码实现 一位大神自己用c实现…

15、Kubernetes核心技术 - 探针

目录 一、概述 二、探针类型 2.1、就绪探针&#xff08;Readiness Probe&#xff09; 2.2、存活探针&#xff08;Liveness Probe&#xff09; 三、探针探测方法 3.1、exec 3.2、httpGet 3.3、tcpSocket 四、探针配置项 五、探针使用 5.1、就绪探针&#xff08;Readin…

智慧工厂UWB技术定位系统源码,工厂人员轨迹定位系统源码

智慧工厂人员定位系统通过在作业现场部署UWB高精度定位设备及网络&#xff0c;实现人、车、物的实时位置监控。搭建二维或三维业务功能展现平台&#xff0c;集成现场视频监控、门禁系统&#xff0c;实现工厂人员定位与视频监控和门禁联动&#xff0c;实时掌握全厂人员、车辆、作…

C#_var

文章目录 一、前言二、隐式类型的局部变量2.1 var和匿名类型2.2 批注 三、总结 一、前言 C#中有一个 var 类型&#xff0c;不管什么类型的变量&#xff0c;都可以用它接收&#xff0c;实属懒人最爱了。 我没有了解过它的底层&#xff0c;甚至没看过它的说明文档&#xff0c;也…

2023年安全狗答卷:十年命题 安全以赴

你好 2024 2023年&#xff0c;既是网络安全攻防变化的一年 也是安全狗2013年到2023年十年发展里 浓墨重彩的一笔 在新故相推之间 安全狗怀揣着不变的守护初心 迎来了十年安全命题的新起点 2024年&#xff0c;坚守安全&#xff0c;拥抱新命题