OSG-渲染状态、纹理映射(一)

1、渲染状态

        OSG 支持绝大部分的OpenGL固定功能管道(fixed function pipeline)渲染,如Alpha检验、Blending融合剪切平面、颜色蒙板、面选(face culling)深度和模板检验、雾效、点和线的光栅化(rasterization)等。OSG 的渲染状态也允许应用程序指定顶点着色(vertex shader)和片段着色 (fragment shader)。

        OSG 支持绝大部分的OpenGL固定功能管道(fixed function pipeline)渲染,如Alpha检验、Blending融合剪切平面、颜色蒙板、面选(face culling)深度和模板检验、雾效、点和线的光栅化(rasterization)等。OSG 的渲染状态也允许应用程序指定顶点着色(vertex shader)和片段着色 (fragment shader)。

1.1、osg::StateSet 类

        osg::StateSet 类派生自osg::Referenced,以实现更好的数据共享。也就是说共享同一个osg::StateSet的osg::Node 或osg::Drawable 类不需要额外的代码来清理其内存空间。其继承关系图如图5-1所示。

图5-1 osg::StateSet 的继承关系图

        用户的应用程序需要在 osg::StateSet 中设置渲染状态可以将 StateSet 关联到场景图形中的任意-个节点(Node)或关联到Drawable类。正如大部分开发者所知,OpenGL程序的开发需要尽量使状态量的变化实现最小化,并避免冗余的状态设置,StateSet 对象能够自动实现这些优化过程。在OSG 遍历整个场景图形时,StateSet 类会对 OpenGL 的状态属性堆栈进行管理。因此,用户程序可以对不同的场景图形子树作不同的状态设置。在每个子树的遍历过程中,OSG将会高效地执行保存和恢复染状态的操作。用户需要尽量使关联到场景图形的 StateSet 最少化。StateSet 越少,内存的占用也越少,OSG在一次场景图形遍历中所耗费的工作量也越少。

        OSG将渲染状态分成两个部分,分别为渲染属性(Attribute)和渲染模式(Mode)。渲染属性也就是控制渲染特性的状态变量,如雾的颜色或 Blend 融合函数都是 OSG 的状态属性。OSG中的渲染模式和OpenGL的状态特性几乎是一一对应的,这些特性在 OpenGL 中通过函数 glEnable()和glDisable()进行控制。用户程序可以设置模式量以允许或禁止某个功能,如纹理映射、灯光等。简单来说,渲染模式是指渲染的某个功能,而渲染属性是这个功能的控制变量和参数。如果要设置渲染状态的值,用户程序需要执行以下两步操作:

        (1)为将要设置状态的 Node 或 Drawable 对象提供一个 StateSct 实例。

        (2)在 StateSet 实例中设置状态的渲染模式和渲染属性。

        直接从某个Node或Drawable 对象中获得一个StateSet 实例可以使用下面的方法:

        osg::StateSet* state = obj->getOrCreateStateSet();

        在上面的程序段中,obj是一个Node 或 Drawable 实例; getOrCreateStateSet()是这些类定义的方法,这个方法返回一个指向 StateSet 实例的指针,该实例属于obj。如果obj还没有设置过与之关联的StateSet,那么这个方法返回一个新指针并将其关联到obj上。

        StateSet 继承自 Referenced 类。与 StateSet 关联的 Node 或 Drawable 类内部使用ref_ptr来引用StateSet 实例,因此,不是长时间引用 StateSet 的情况下,也可以使用标准 C++指针来定义state。如果state变量是某个函数内的局部变量,且应用程序不会长时间引用这个 StateSet,那么上面代码的使用是完全正确的。上面代码中的state变量指向该obj对象的StateSet 指针,当应用程序获得了一个StateSet的指针时,就可以设置属性和模式了。

1.2、渲染属性和渲染模式

        OSG为每个状态属性定义了不同的类以便应用程序采用。所有的属性类均继承自osg::StateAttribute,osg::StateAttribute类是一个无法直接实例化的虚基类。

        OSG将所有的属性和模式分为两大部分,即纹理(texture)和非纹理(non-texture)。本节将主要探讨非纹理渲染状态的设置。纹理渲染状态的设置将在后面的章节讨论。OSG之所以为纹理属性的设置提供不同的接口,主要是因为纹理属性需要特别为多重纹理设置纹理单元(texture unit)。

        1.设置渲染属性(Attribute)

        如果要设置一项属性,首先需要将修改的属性类实例化,设置该类的数值,然后用osg::StateSet::setAttribute()将其关联到 StateSet。下面的代码段用于实现面剔除(face culling)的属性:

  1. // 获取变量geomStateSet 指针  
  2. osg::StateSet *state = geom->getOrCreateStateSet();//创建并添加CullFace属性类  
  3. osg::CullFace *cf= new osg::CullFace( osg::CullFace::BACK )state->setAttribute(cf);  

在上面的代码段中,geom是一个 Geometry 几何类对象(当然也可以是任何其他派生自 Drawable和Node的对象), 获取geom的StateSet 指针后,代码创建了一个新的osg::CullFace对象,并将其关联到状态量。

        2.设置渲染模式(Mode)

        用户可以使用 osg::StateSet::setMode()设置允许或禁止某种模式。例如,下面的代码将打开雾效模式的许可:

  1. // 获取一个StateSet实例  
  2. osg::StateSet*state = geom->getOrCreateStateSet():  
  3. // 允许这个StateSet的雾效模式  
  4. state->setMode( GL_FOG, osg::StateAttribute::ON);  

        setMode()的第一个输入参数可以是任何一个在glEnable()或glDisable()中合法的 OpenGL 枚举量GLenum;第二个输入参数可以是osg::StateAttribute::ON或osg::StateAttribute::OFF。事实上,这里用到了位屏蔽技术,这个技术将在后面讲解继承状态时说明。

        3.设置渲染属性和模式

        OSG提供了一个简单的、可以同时设置属性和模式的单一函数接口。在很多情况下,属性和模式之间都存在显著的关系。例如,CullFace 属性的对应模式为GL_CULL_FACE。如果要将某个属性关联到一个StateSet,同时要求打开其对应模式的许可,可以使用osg::StateSet::setAttributeAndModes()方法。下面的代码段将关联Blend 融合检验的属性,同时许可颜色融合模式。

  1. // 创建一个BlendFunc 属性  
  2. osg::BlendFunc*bf= new osg::BlendFunc();  
  3. // 关联BlendFun并许可颜色融合模式  
  4. state->setAttributeAndModes(bf);  

        setAttributeAndModes()的第二个输入参数用于允许或禁止第一个参数中渲染属性对应的渲染模式,其默认值为ON。这样,用户的应用程序只需用一个函数就可以方便地指定某个渲染属性,并许可其对应的渲染模式。

1.3、状态继承

        当读者设置节点的渲染状态时,这个状态将被赋予当前的节点及其子节点。如果子节点对同一个渲染状态设置了不同的属性参数,那么新的子节点状态参数将会覆盖原有的。也就是说,默认情况下子节点可以改变自身的某个状态参数或者继承父节点的同一个状态。如图 5-2所示的光照状态继承图体现了这个概念的实现过程。

图5-2 光照状态继承图

        状态继承特性在许多情况下都非常实用,但有时渲染可能需要更多特性。假设场景图形中有一个包含了实体多边形几何体的节点,如果要以线框模式来渲染场景图形,读者的程序就需要覆盖这种多边形渲染模式状态,而不论它出现在什么位置。OSG 允许用户根据场景图形中任意位置的渲染属性和模式需求单独改变原有的状态继承特性。可以选择以下几种枚举形式。

  • osg::StateAttribute::OVERRIDE:如果将一个渲染属性和模式设置为OVERRIDE,那么所有的子节点都将继承这一属性或模式,子节点对它们的更改将会无效。
  • osg::StateAttribute::PROTECTED;这种形式可以视为OVERRIDE 的一个例外。凡是设置为PROTECTED的渲染属性或模式,均不会受到父节点的影响。
  • osg::StateAttribute::INHERIT:这种模式强制子节点继承父节点的渲染状态,其效果是子节点的渲染状态被解除,而使用父节点的状态替代。

        读者可以对这些参数进行位或叠加操作,然后再作为setAttribute()和setMode()建立一个场景图形和setAttributeAndModes()的第二个参数输入。下面的代码段将强制使用线框模式渲染场景图形:

  1. // 获取根节点的染状态StateSet  
  2. osg::StateSet*state = root->getOrCreateStateSet():  
  3. // 创建一个PolygonMode染属性  
  4. osg::PolygonMode*pm = new osg::PolygonMode(osg::PolygonMode::FRONT_AND_BACK,osg::PolygonMode::LINE)  
  5. // 制使用线框渲染  
  6. state->setAttributeAndModes(pm,osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE);  

        使用PROTECTED 参量可以保证父节点的渲染状态不会覆盖子节点的渲染状态。例如,读者可能创建了一个发光的场景,其中包含有使用亮度照明的光源几何体,如果其父节点禁用了光照,那么光源几何体的渲染将会出错。这时,对光源几何体的GL_LIGHTING渲染状态使用PROTECTED 就可以保证它依然可用。

1.4、渲染状态示例

        渲染状态(ClipeNode)示例的代码如程序清单5-1所示。

osg::ref_ptr<osg::Node> createClipNode(osg::ref_ptr<osg::Node> subgraph)
{
	osg::ref_ptr<osg::Group> root = new osg::Group();
	osg::ref_ptr<osg::StateSet> stateset = new osg::StateSet();

	// 多边形线形绘制模式,正面和反面都绘制
	osg::ref_ptr<osg::PolygonMode> polymode = new osg::PolygonMode();
	polymode->setMode(osg::PolygonMode::FRONT_AND_BACK, osg::PolygonMode::LINE);

	// 启用多边形线形绘制模式,并指定状态继承属性为OVERRIDE
	stateset->setAttributeAndModes(polymode, osg::StateAttribute::OVERRIDE | osg::StateAttribute::ON);

	// 多边形线形绘制节点
	osg::ref_ptr<osg::Group> wireframe_subgraph = new osg::Group;
	wireframe_subgraph->setStateSet(stateset.get());// 设置渲染状态
	wireframe_subgraph->addChild(subgraph.get());
	root->addChild(wireframe_subgraph.get());

	osg::ref_ptr<osg::MatrixTransform> transform = new osg::MatrixTransform;
	// 更新回调,实现动态裁剪
	osg::ref_ptr<osg::NodeCallback> nc = new osg::AnimationPathCallback(subgraph->getBound().center(), 
		osg::Vec3(0.0f, 0.0f, 1.0f), osg::inDegrees(45.0f));
	transform->setUpdateCallback(nc.get());

	// 创建裁剪节点
	osg::ref_ptr<osg::ClipNode> clipnode = new osg::ClipNode;
	osg::BoundingSphere bs = subgraph->getBound();
	bs.radius() *= 0.4f;

	// 设置裁剪节点的包围盒
	osg::BoundingBox bb;
	bb.expandBy(bs);

	// 根据前面指定的包围盒创建六个裁剪平面
	clipnode->createClipBox(bb);	
	clipnode->setCullingActive(false);// 禁用拣选
	transform->addChild(clipnode.get());
	root->addChild(transform.get());

	// 创建未被裁剪的节点
	osg::ref_ptr<osg::Group> clippedNode = new osg::Group;
	clippedNode->setStateSet(clipnode->getStateSet());
	clippedNode->addChild(subgraph.get());
	root->addChild(clippedNode.get());

	return root.get();
}

void clipeNode_5_1(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::Node> root = new osg::Node();

	// 加载模型
	string strDataPath = strDataFolder + "cessna.osg";
	osg::ref_ptr<osg::Node> node = osgDB::readNodeFile(strDataPath);
	root = createClipNode(node.get());

	// 优化场景数据
	osgUtil::Optimizer optimzer;
	optimzer.optimize(root.get());

	viewer->setSceneData(root.get());
	viewer->realize();
	viewer->run();
}

运行程序,截图如图 5-3 所示

图5-3渲染状态示例截图

2、纹理映射

        物体的外观不仅包括形状,不同物体表面有着不同的颜色和图案。一个简单而有效地实现这种特性的方法就是使用纹理映射。
        OSG是对底层OpenGL API的封装,OpenGL本身有非常标准而高效的纹理机制。OSG全面支持OpenGL的纹理映射机制,因此,在OSG中使用纹理映射机制非常简单。纹理映射主要包含一维纹理、二维纹理、三维纹理、凹凸纹理、多重纹理、Mipmap纹理、压缩纹理和立方纹理等。
      纹理的基础知识:  

 /* 纹理坐标 */
enum WrapParameter
{
    WRAP_S, // x轴
    WRAP_T, // y轴
    WRAP_R, // z轴
};

/* 纹理的包装模式 */
enum WrapMode
{
    CLAMP = GL_CLAMP, // 截取
    CLAMP_TO_EDGE = GL_CLAMP_TO_EDGE, // 边框始终被忽略
    CLAMP_TO_BORDER = GL_CLAMP_TO_BORDER_ARB, // 它使用的纹理取自图像的边框,没有边框就使用    常量边框颜色
    REPEAT = GL_REPEAT, // 纹理的重复映射
    MIRROR = GL_MIRRORED_REPEAT_IBM, // 纹理镜像从重复映射
};

/* 纹理过滤方法 */
enum FilterParameter
{
    MIN_FILTER, // 用于缩小
    MAG_FILTER  // 用于放大
};

/* 纹理的过滤处理 */
enum FilterMode
{
    LINEAR = GL_LINEAR, // 以周围4个像素的平均值作为纹理
    LINEAR_MIPMAP_LINEAR = GL_LINEAR_MIPMAP_LINEAR, // 使用线性均和计算两个纹理的值
    LINEAR_MIPMAP_NEAREST = GL_LINEAR_MIPMAP_NEAREST, // 线性地改写临近的纹理单元值
    NEAREST = GL_NEAREST, // 取比较接近的像素作为纹理
    NEAREST_MIPMAP_LINEAR = GL_NEAREST_MIPMAP_LINEAR, // 在两个纹理中选择最临近的纹理,并取他们之间的线性均值
    NEAREST_MIPMAP_NEAREST = GL_NEAREST_MIPMAP_NEAREST // 选择最临近的纹理单元值
};

/* 纹理映射模式(处理纹理图像数据与物体本身的融合) */
enum Mode
{
    DECAL = GL_DECAL,       // 贴花
    MODULATE = GL_MODULATE, // 调整
    BLEND = GL_BLEND,       // 混合
    REPLACE = GL_REPLACE,   // 替换,覆盖
    ADD = GL_ADD            // 添加
};

/* 纹理坐标的自动生成模式 */
enum Mode
{
    OBJECT_LINEAR = GL_OBJECT_LINEAR,      // 物体线性,纹理贴图与移动物体保持固定
    EYE_LINEAR = GL_EYE_LINEAR,            // 产生移动物体的动态轮廓线
    SPHERE_MAP = GL_SPHERE_MAP,            // 球体贴图
    NORMAL_MAP = GL_NORMAL_MAP_ARB,        // 法线贴图,用于立方图纹理
    REFLECTION_MAP = GL_REFLECTION_MAP_ARB // 反射贴图
};

/* 贴图坐标 */
enum Coord
{
    S, // x
    T, // y
    R, // z
    Q  // w
};

/* 纹理的内部格式 */
enum InternalFormatMode
{
    USE_IMAGE_DATA_FORMAT,     // 使用贴图本身的格式
    USE_USER_DEFINED_FORMAT,   // 使用用户自定义的格式,如GL_R3G3B3
    USE_ARB_COMPRESSION,       // 使用ARB协会出的贴图压缩格式        
    USE_S3TC_DXT1_COMPRESSION, // 使用S3TC_DXT1压缩格式
    USE_S3TC_DXT3_COMPRESSION, // 使用S3TC_DXT3压缩格式
    USE_S3TC_DXT5_COMPRESSION, // 使用S3TC_DXT5压缩格式
};

2.1 二维纹理映射

         在所有的纹理映射中,二维纹理映射的过程最简单,也非常容易理解。在用户应用程序中创建二维纹理的步骤如下:
        <1> 指定用户几何体的纹理坐标;
        <2> 创建纹理属性对象并保存纹理图形数据;
        <3> 为StateSet设置合适的纹理属性和模式。
        1、纹理坐标
        设置纹理坐标比较简单,纹理坐标是与顶点一一对应的,很像数学中的映射,如图5-4所示。

图5-4 纹理坐标

        下面的代码段创建一个osg::Vec2Array数组,用于保存纹理坐标,同时将其关联到Geometry实例的纹理单元0,如果要对单一的Geometry设置多个纹理,只需要将多个纹理坐标数组关联到Geometry,并针对不同的数组指定不同的纹理单元即可。

  1. // 创建一个Geometry几何体对象  
  2. osg::ref_ptr<osg::Geometry> geom = new osg::Geometry;  
  3. // 创建一个Vec2Array对象以保存纹理单元0的纹理坐标,并将其关联到geom  
  4. osg::ref_ptr<osg::Vec2Array> tc = new osg::Vec2Array;  
  5. geom->setTexCoordArray(0,tc.get());  
  6. tc.push_back(osg::Vec2(0.0f,0.0f));  
  7. tc.push_back(osg::Vec2(1.0f,0.0f));  
  8. tc.push_back(osg::Vec2(1.0f,1.0f));  
  9. tc.push_back(osg::Vec2(0.0f,1.0f));  

         osg::Geometry::setTexCoordArray()的第一个参数是纹理单元号,第二个参数是纹理坐标数组。用户不需要使用类同osg::Geometry::setTexCoordBinding()的函数输入点来绑定纹理数组。纹理坐标总是绑定到每个顶点的。

        在这里有一点要注意,OpenGL的早期版本并不支持多重纹理,而加入多重纹理的特性之后,OpenGL仍然支持非多重纹理的函数接口,以实现向下兼容。从本质上说,此时OpenGL将非多重纹理接口解释为使用纹理单元0对应所有纹理数据。与OpenGL不同,OSG并不支持非多重纹理接口。因此,用户程序必须指定一个纹理单元,以对应纹理坐标数据和纹理状态。如果要使用单一纹理,只需要指定纹理单元0即可。

  1. 纹理数据

        在大多数应用程序中,纹理数据都是从外部导入的图像文件。当没有必要导入图像时,可以生成一幅纹理数据贴图。这里使用从外部导入的纹理数据图形的方法。

        读取图像需要使用一个新类:osg::Image。osg::Image继承自osg::Object类,继承关系图如图5-5所示。

        下面的代码将实现如何读取图像:

  1. osg::ref_ptr<osg::Image> image = new osg::Image;  
  2. image->setFileName("tree.rgb");  

        在读取一个图像后,需要创建一个纹理对象来关联图像。osg::Texture2D属于osg::StateAttribute的派生类,用于管理OpenGL纹理对象,而Image用于管理图像像素数据。如果要使用2D图像文件作为纹理映射的图形,只要将文件名赋给Image对象并将Image关联到Texture2D即可。

        osg::Texture2D继承自osg::Texture。继承关系图如图5-6所示。

         下面的代码将实现图像关联到2D纹理对象上:

  1. // 将图像关联到Texture2D对象  
  2. osg::ref_ptr<osg::Texture2D> tex = new osg::Texture2D;  
  3. tex->setImage(image.get());  

        在关联图像后,可以直接关联到渲染状态。值得注意的是:大量使用纹理贴图的程序往往需要实现更紧凑的内存管理。Image类继承自Referenced类,而Texture2D内部保存了一个指向Image的ref_ptr<>指针。在第一次渲染时,OSG创建了用于保存图像数据的OpenGL纹理对象,其结果是产生了两个纹理图像的副本,一个是Image对象,另一个由OpenGL拥有。简单的单环境(single-context) 场景渲染中,读者可以通过设置 Texture2D 解除对Image 的引用来降低内存损耗。如果当前引用Image对象的只有Texture2D对象那么OSG将释放Image及其内存空间下面的代码演示了设置Texture2D解除对Image引用的方法:

  1. //创建OpenGL纹理对象后,释放内部的ref_ptr<Image>,删除Image图像  
  2. tex->setUnRefImageDataAfterApply( true );  

        默认情况下,Texture2D 不会自动释放对 Image 的引用。在多环境(multi-context)场景渲染中这是一种期望行为,前提是纹理对象并没有在各环境中共享。

        3.纹理状态

        用户程序可以使用纹理状态函数接口为每个纹理单元指定渲染状态。纹理状态函数接口与非纹理状态的接口类似。用户可以使用osg::StateSet::setTextureAttribute()将一个纹理属性关联到 StateSet 对象setTextureAtribute()的第一个参数是纹理单元,第二个参数是继承自StateAttribute类的一种纹理属性,合法的纹理属性类共有6种,其中包括5种纹理类型(osg::TexturelD、osg::Texture2D、osg::Texture3D、osg::TextureCubeMap和osg:TextureRectangle)和一个用于纹理坐标的生成的类(osg::TexGen)。

        下面的代码将根据给定的 Texture2D 属性对象 tex和渲染状态StateSet 将 tex关联到状态并设置使用纹理单元0。

  1. // 创建一个Texture2D属性  
  2. osg::ref_ptr<osg::Texture2D> tex = new osg::Texture2D;  
  3. // 关联材质属性到材质单元0  
  4. state->setTextureAttribute(0, tex.get()):  

        与上面的程序类似,用户可以调用osg::StateSet::setTextureMode()方法来设置材质渲染模式,这个方法与 setMode()方法类似用户可以使用setTextureMode()来设置GL_TEXTURE_1D、GL_TEXTURE2D、GL_TEXTURE_3D、GL_TEXTURE_CUBE_MAP、GL_TEXTURE _RECTANGLE、GL_TEXTURE_GEN_Q、GL_TEXTURE_GEN_R、GL_TEXTURE_GEN_S及 GL_TEXTURE_GEN_T模式。

        与 setTextureAttribute()相似,setTextureMode()的第一个参数表示纹理单元。下面的代码段将禁止纹理单元1的2D映射:

  1. state->setTextureMode(1, GL_TEXTURE_2D, osg::StateAttribute::OFF ):  

        当然,用户也可以使用 osg::StateSet::setTextureAttributesAndModes()来关联纹理渲染属性到StateSet,同时允许相应的纹理模式。如果属性是一个 TexGen 对象,那么 setTextureAttributesAndModes()将设置相应的坐标生成模式GL_TEXTURE_GEN_Q、GL_TEXTURE_GEN_R、GL_TEXTURE_GEN_S和GL_TEXTURE_GEN_T。对于其他纹理属性来说,这一模式是隐含的。例如,下面的代码中,由于第二个参数传入了一个Texture2D 对象作为纹理属性setTextureAttributesAndModes()将允许GL_TEXTURE_2D模式:

  1. // 创建一个Texture2D属性对象  
  2. osg::ref_ptr<osg::Texture2D> tex = new osg::Texture2D:  
  3. //在纹理单元0上,关联2D纹理属性并许可GLTEXTURE2D模式  
  4. state->setTextureAttributeAndModes(0, tex)  

        setTextureAttributeAndModes()的第三个参数的默认值为ON,即允许纹理渲染模式。与setAttributeAndModes()类似,读者可以对这个参数使用位或操作包括OVERRIDE、PROTECTED和INHERIT,以修改纹理属性的继承特性。读者还可以通过修改 setTextureMode()和setTextureAtribute()的第三个参数来指定这个继承标志。

2.2、二维纹理映射示例

        二维纹理映射(osg::Texture2D)示例的代码如序清单5-2所示

osg::ref_ptr<osg::Node> createNode() //创建一个四边形节点
{
	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(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()); // 设置贴图
	stateset->setTextureAttributeAndModes(0, texture.get(), osg::StateAttribute::ON);

	return stateset.get();
}

void texture2D_5_2(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();

	// 创建状态集对象
	osg::ref_ptr<osg::StateSet> stateset = new osg::StateSet();
	stateset = createTexture2DState(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-7 所示。

图5-7 二维纹理映射示例截图

2.3、多重纹理映射

        在进行标准的二维纹理映射处理时,一次把一幅纹理图像应用到一个多边形上。多重纹理允许应用几个纹理,在纹理操作管线中把它们逐个应用到同一个多边形上。多重纹理存在一系列的纹理单元,每个纹理单元执行单独的纹理操作,并把它的结果传递给下一个纹理单元,直到所有纹理单元的操作完成为止,最终显示处理后的效果。

        多重纹理映射非常广泛,它能实现一些高级的渲染技巧,如光照、贴花、合成和细节纹理等。在OSG中实现三维纹理主要有以下几个步骤:

  • 指定用户几何体的纹理坐标。
  • 创建多个纹理属性对象并保存纹理多个图形数据。
  • 为StateSet设置合适的纹理属性和模式。

        看起来和二维纹理映射的区别不大,简单地说就是多个二维纹理映射的叠加。但这里需要注意的是,对于不同的纹理属性对象需要指定不同的纹理单元及纹理坐标,否则就不会启用该纹理单元,或者该纹理单元会被覆盖。

2.4、多重纹理映射示例

        多重纹理映射示例的代码如程序清单5-3示

1.	void multTex_5_3(const string &strDataFolder)  
2.	{  
3.	    osg::ref_ptr<osgViewer::Viewer> viewer = new osgViewer::Viewer();  
4.	    osg::ref_ptr<osg::GraphicsContext::Traits> traits = new osg::GraphicsContext::Traits;  
5.	    traits->x = 40;  
6.	    traits->y = 40;  
7.	    traits->width = 600;  
8.	    traits->height = 480;  
9.	    traits->windowDecoration = true;  
10.	    traits->doubleBuffer = true;  
11.	    traits->sharedContext = 0;  
12.	  
13.	    osg::ref_ptr<osg::GraphicsContext> gc = osg::GraphicsContext::createGraphicsContext(traits.get());  
14.	    osg::ref_ptr<osg::Camera> camera = new osg::Camera;  
15.	    camera->setGraphicsContext(gc.get());  
16.	    camera->setViewport(new osg::Viewport(0, 0, traits->width, traits->height));  
17.	    GLenum buffer = traits->doubleBuffer ? GL_BACK : GL_FRONT;  
18.	    camera->setDrawBuffer(buffer);  
19.	    camera->setReadBuffer(buffer);  
20.	    viewer->addSlave(camera.get());  
21.	  
22.	    osg::ref_ptr<osg::Group> root = new osg::Group();  
23.	  
24.	    string strDataPath = strDataFolder + "cow.osg";  
25.	    osg::ref_ptr<osg::Node> node = osgDB::readNodeFile(strDataPath);  
26.	  
27.	    // 读取贴图  
28.	    strDataPath = strDataFolder + "Images/primitives.jpg";  
29.	    osg::ref_ptr<osg::Image> image = osgDB::readImageFile(strDataPath);  
30.	  
31.	    if (image.get())  
32.	    {  
33.	        //创建二维纹理  
34.	        osg::ref_ptr<osg::Texture2D> texture = new osg::Texture2D;  
35.	        texture->setImage(image.get());  
36.	  
37.	        //设置自动生成坐标  
38.	        osg::ref_ptr<osg::TexGen> texgen = new osg::TexGen;  
39.	        texgen->setMode(osg::TexGen::SPHERE_MAP);  
40.	  
41.	        //设置纹理环境,模式为BLEND,  
42.	        osg::ref_ptr<osg::TexEnv> texenv = new osg::TexEnv;  
43.	        texenv->setMode(osg::TexEnv::BLEND);  
44.	        //设置BLEND操作的颜色  
45.	        texenv->setColor(osg::Vec4(0.6f, 0.6f, 0.6f, 0.0f));  
46.	  
47.	        //启用单元1自动生成纹理坐标,并使用纹理  
48.	        osg::ref_ptr<osg::StateSet> stateset = new osg::StateSet;  
49.	        stateset->setTextureAttributeAndModes(1, texture.get(), osg::StateAttribute::ON);  
50.	        stateset->setTextureAttributeAndModes(1, texgen.get(), osg::StateAttribute::ON);  
51.	  
52.	        //设置纹理环境  
53.	        stateset->setTextureAttribute(1, texenv.get());  
54.	  
55.	        //设置纹理状态  
56.	        node->setStateSet(stateset.get());  
57.	    }  
58.	  
59.	    root->addChild(node.get());  
60.	  
61.	    // 优化场景数据  
62.	    osgUtil::Optimizer optimizer;  
63.	    optimizer.optimize(root.get());  
64.	  
65.	    viewer->setSceneData(root.get());  
66.	    viewer->realize();  
67.	    viewer->run();  
68.	}  

运行程序,截图如图5-8 所示。

图5-8 多重纹理映射示例截图

  • 31
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

听风者868

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值