前面的教程中,我们用彩虹的所有颜色画了一个正方形。然而,还有另一种成为纹理映射的技术,将光栅二维图像应用于三维几何。在这种情况下,效果不是针对几何体的顶点,而是通过栅格化场景更改获得的所有像素的数据。这种技术可以显着增加最终图像的真实感和细节。 OSG支持多种纹理属性和纹理模式。但是,在讨论纹理之前,让我们先谈谈OSG如何在光栅图像上运行。为了处理光栅图像,OSG提供了一个特殊的类osg::Image,它在其自身中存储图像数据,最终用于对象的纹理处理。1、光栅图像数据的表示 从磁盘加载图像的最佳方法是使用osgDB::readImageFile()调用。它与osg::readNodeFile()调用非常相似。如果我们有一个名为picture。bmp的位图,那么它的加载将如下所示:osg::refptrosg::ImageimageosgDB::readImageFile(picture。bmp); 如果图像加载正确,则指针将有效,否则函数将返回NULL。加载图像后,我们可以使用以下公共方法获取有关图像的信息。t(),s()和r()返回图像的宽度、高度和深度。data()返回指向原始图像数据的unsignedchar指针。通过此指针,开发人员可以直接影响图像数据。你可以使用getPixalFormat()和getDataType()方法了解图像数据的格式。它们返回的值等效于OpenGL函数glTexImage()的格式和类型的参数。例如,如果图片具有像素格式GLRGB并且类型为GLUNSIGNEDBYTE,则使用三个unsignedchar类型的独立元素来表示RGB颜色组件: 你可以创建新的图像对象并为其分配内存。osg::refptrosg::Imageimagenewosg::Image;imageallocateImage(s,t,r,GLRGB,GLUNSIGNEDBYTE);unsignedcharptrimagedata(); 这里s、t、r是图像的尺寸;GLRGB设置像素格式,GLUNSIGNEDBYTE设置数据类型以描述单个颜色分量。所需大小的内部数据缓冲区在内存中分配,如果没有对此映像的引用,则会自动销毁。 OSG插件系统支持下载几乎所有流行的图像格式:。jpg,。bmp,。png,。tif等。通过编写自己的插件,此列表很容易扩展,但这是另一个单独的主题。2、纹理基础 要将纹理应用于三维模型,必须执行多个步骤:将顶点的纹理坐标分配给几何对象。在三维设计师的环境中,这称为UV扫描。为1D、2D、3D或立方体纹理创建纹理属性对象。为纹理属性设置一个或多个图像。将纹理属性和模式附加到应用于正在绘制的对象的状态集。 OSG定义了一个osg::Texture类来封装各种纹理。子类包括osg::Texture1D,osg::Texture2D、osg::Texture3D和osg::TextureCubeMap,它们代表了OpenGL中采用的各种纹理技术。 osg::Texture类最常用的方法是setImage(),它定义了纹理中使用的图像,例如osg::refptrosg::ImageimageosgDB::readImageFile(picture。bmp);osg::refptrosg::Texture2Dtexturenewosg::Texture2D;texturesetImage(image。get()); 或者,可以将图像对象直接传递给纹理类构造函数。osg::refptrosg::ImageimageosgDB::readImageFile(picture。bmp);osg::refptrosg::Texture2Dtexturenewosg::Texture2D(image。get()); 可以通过调用getImage()方法从纹理对象中检索图像。 另一个要点是设置对象osg::Geometry中每个顶点的纹理坐标。这些坐标的传输通过osg::Vec2Array和osg::Vec3Array数组通过调用setTexCoordArray()方法进行。 设置纹理坐标后,我们需要设置纹理槽号(unit),因为OSG支持在同一几何体上施加多个纹理。使用一个纹理时,unit的值始终为0。例如,以下代码演示了单元0几何图形的纹理坐标分配osf::refptrosg::Vec2Arraytexcoordnewosg::Vec2Array;texcoordpushback(osg::Vec2(。。。));。。。geomsetTexCoordArray(0,texcoord。get()); 之后,我们可以向状态集添加一个纹理属性,自动包括相应的纹理模式(在我们的示例中为GLTEXTURE2D),并将该属性应用于包含几何体的几何体或节点geomgetOrCreateStateSet()setTextureAttributeAndModes(texture。get()); 请注意,OpenGL管理显卡图形内存中的图像数据,但osg::Image对象以及相同的数据位于系统内存中。结果,我们将面临这样一个事实,即我们拥有相同数据的两个副本,占用了该过程的内存。如果此图像未由多个纹理属性共享,则可以在OpenGL将其传输到视频适配器内存后立即将其从系统内存中删除。若要启用此功能,osg::Texture类提供了适当的方法。texturesetUnRefImageDataAfterApply(true);3、加载并应用2D纹理 最常用的2D纹理技术是在三维表面的边缘叠加二维图像(或图像)。考虑将单个纹理应用于四边形的最简单示例。 main。h:ifndefMAINHdefineMAINHincludeosgTexture2DincludeosgGeometryincludeosgDBReadFileincludeosgViewerViewerendif main。cppincludemain。hintmain(intargc,charargv〔〕){(void)argc;(void)argv;osg::refptrosg::Vec3Arrayverticesnewosg::Vec3Array;verticespushback(osg::Vec3(0。5f,0。0f,0。5f));verticespushback(osg::Vec3(0。5f,0。0f,0。5f));verticespushback(osg::Vec3(0。5f,0。0f,0。5f));verticespushback(osg::Vec3(0。5f,0。0f,0。5f));osg::refptrosg::Vec3Arraynormalsnewosg::Vec3Array;normalspushback(osg::Vec3(0。0f,1。0f,0。0f));osg::refptrosg::Vec2Arraytexcoordsnewosg::Vec2Array;texcoordspushback(osg::Vec2(0。0f,0。0f));texcoordspushback(osg::Vec2(0。0f,1。0f));texcoordspushback(osg::Vec2(1。0f,1。0f));texcoordspushback(osg::Vec2(1。0f,0。0f));osg::refptrosg::Geometryquadnewosg::Geometry;quadsetVertexArray(vertices。get());quadsetNormalArray(normals。get());quadsetNormalBinding(osg::Geometry::BINDOVERALL);quadsetTexCoordArray(0,texcoords。get());quadaddPrimitiveSet(newosg::DrawArrays(GLQUADS,0,4));osg::refptrosg::Texture2Dtexturenewosg::Texture2D;osg::refptrosg::ImageimageosgDB::readImageFile(。。dataImageslz。rgb);texturesetImage(image。get());osg::refptrosg::Geoderootnewosg::Geode;rootaddDrawable(quad。get());rootgetOrCreateStateSet()setTextureAttributeAndModes(0,texture。get());osgViewer::Viewerviewer;viewer。setSceneData(root。get());returnviewer。run();} 创建边的顶点和法线数组:osg::refptrosg::Vec3Arrayverticesnewosg::Vec3Array;verticespushback(osg::Vec3(0。5f,0。0f,0。5f));verticespushback(osg::Vec3(0。5f,0。0f,0。5f));verticespushback(osg::Vec3(0。5f,0。0f,0。5f));verticespushback(osg::Vec3(0。5f,0。0f,0。5f));osg::refptrosg::Vec3Arraynormalsnewosg::Vec3Array;normalspushback(osg::Vec3(0。0f,1。0f,0。0f)); 创建纹理坐标数组:osg::refptrosg::Vec2Arraytexcoordsnewosg::Vec2Array;texcoordspushback(osg::Vec2(0。0f,0。0f));texcoordspushback(osg::Vec2(0。0f,1。0f));texcoordspushback(osg::Vec2(1。0f,1。0f));texcoordspushback(osg::Vec2(1。0f,0。0f)); 要点在于三维模型的每个顶点对应于二维纹理上的一个点,并且纹理上点的坐标是相对的它们被归一化为图像的实际宽度和高度。我们要将整个加载的图片分别拉伸到正方形上,正方形的角将对应于纹理点(0,0),(0,1),(1,1)和(1,0)。顶点数组中顶点的顺序必须与纹理顶点的顺序相同。 接下来,创建一个正方形,为几何体分配顶点数组和法线数组:osg::refptrosg::Geometryquadnewosg::Geometry;quadsetVertexArray(vertices。get());quadsetNormalArray(normals。get());quadsetNormalBinding(osg::Geometry::BINDOVERALL);quadsetTexCoordArray(0,texcoords。get());quadaddPrimitiveSet(newosg::DrawArrays(GLQUADS,0,4)); 创建纹理对象并加载用于该对象的图像:osg::refptrosg::Texture2Dtexturenewosg::Texture2D;osg::refptrosg::ImageimageosgDB::readImageFile(。。dataImageslz。rgb);texturesetImage(image。get()); 创建场景的根节点,并将我们创建的几何体加入根节点:osg::refptrosg::Geoderootnewosg::Geode;rootaddDrawable(quad。get()); 最后将纹理属性应用于放置几何体的节点:rootgetOrCreateStateSet()setTextureAttributeAndModes(0,texture。get()); osg::Texture2D类确定纹理图像大小是否是2的倍数(例如,64x64或256x512),实际上使用OpenGL函数gluScaleImage()自动缩放不适合大小的图像。有一个setResizeNonPowerOfTwoHint()方法可以确定是否调整图像大小。一些视频卡需要图像大小的倍数为2的幂,而类osg::Texture2D支持使用任意纹理大小。4、关于纹理映射模式 正如我们已经说过的,纹理坐标从0到1规范化。点(0,0)对应于图像的左上角,点(1,1)对应于右下角。如果将纹理坐标设置为大于1,会发生什么情况? 默认情况下,在OpenGL中,就像在OSG中一样,纹理将在轴的方向上重复,纹理坐标的值将超过单位。例如,这种技术通常用于创建长砖墙的模型,使用小纹理,在宽度和高度上多次重复其施加。 此行为可以通过osg::Texture类的setWrap()方法进行控制。第一个参数表示应用混合模式的轴标识符,第二个参数表示映射模式,例如:stexturesetWrap(osg::Texture::WRAPS,osg::Texture::REPEAT);rtexturesetWrap(osg::Texture::WRAPR,osg::Texture::REPEAT); 如果纹理坐标的值超过1,此代码会清楚地指示引擎沿s轴和r轴重复纹理。按纹理映射模式的完整列表:REPEAT重复纹理。MIRROR镜像重复纹理。CLAMPTOEDGE超出0到1限制的坐标将附加到相应的纹理边缘。CLAMPTOBORDER超出0到1限制的坐标将提供用户定义的边框颜色。5、渲染到纹理 纹理渲染技术允许开发人员基于一些三维子舞台或模型创建纹理,并将其应用于主场景的表面。这项技术通常被称为纹理烘焙(texturebaking)。 对于动态烘焙纹理,必须执行三个步骤:创建一个纹理对象以渲染到其中。将场景渲染为纹理。按预期使用生成的纹理。 我们需要创建一个空的纹理对象。OSG允许你创建给定大小的空纹理。setTextureSize()方法允许你设置纹理的宽度和高度,以及深度作为附加参数(对于3D纹理)。 若要将纹理渲染为纹理,可以通过调用attach()方法将其附加到相机对象,该方法将纹理对象作为参数。此外,此方法采用一个参数,指示帧缓冲区的哪个部分应呈现为此纹理。例如,若要将颜色缓冲区传输到纹理,可以运行以下代码:cameraattach(osg::Camera::COLORBUFFER,texture。get()); 可用于呈现的帧缓冲区的其他部分包括深度缓冲区DEPTHBUFFER、模具缓冲区STENCILBUFFER以及从COLORBUFFER0到COLORBUFFER15的其他颜色缓冲区。是否存在其他颜色缓冲区及其数量由视频卡型号决定。 此外,对于渲染纹理的相机,设置投影和视口矩阵的参数,其大小对应于纹理的大小。纹理将在绘制每个帧的过程中更新。请注意,主摄像头不应用于渲染纹理,因为它提供了主场景的渲染,而你只会得到一个黑屏。仅当你执行屏幕外渲染时,可能无法满足此要求。 6、渲染到纹理的实现示例 为了演示渲染到纹理,我们将实现这样一个任务:创建一个正方形,将一个正方形纹理拉伸到上面,当然,我们将用我们最喜欢的纹理渲染一个动画场景。实现该示例的程序非常庞大。但是,我仍然给出完整的源代码。 main。h:ifndefMAINHdefineMAINHincludeosgCameraincludeosgTexture2DincludeosgMatrixTransformincludeosgDBReadFileincludeosgGATrackballManipulatorincludeosgViewerViewerendif main。cpp:includemain。hosg::GeometrycreateQuad(constosg::Vec3pos,floatw,floath){osg::refptrosg::Vec3Arrayverticesnewosg::Vec3Array;verticespushback(pososg::Vec3(w2,0。0f,h2));verticespushback(pososg::Vec3(w2,0。0f,h2));verticespushback(pososg::Vec3(w2,0。0f,h2));verticespushback(pososg::Vec3(w2,0。0f,h2));osg::refptrosg::Vec3Arraynormalsnewosg::Vec3Array;normalspushback(osg::Vec3(0。0f,1。0f,0。0f));osg::refptrosg::Vec2Arraytexcoordsnewosg::Vec2Array;texcoordspushback(osg::Vec2(1。0f,1。0f));texcoordspushback(osg::Vec2(1。0f,0。0f));texcoordspushback(osg::Vec2(0。0f,0。0f));texcoordspushback(osg::Vec2(0。0f,1。0f));osg::refptrosg::Geometryquadnewosg::Geometry;quadsetVertexArray(vertices。get());quadsetNormalArray(normals。get());quadsetNormalBinding(osg::Geometry::BINDOVERALL);quadsetTexCoordArray(0,texcoords。get());quadaddPrimitiveSet(newosg::DrawArrays(GLQUADS,0,4));returnquad。release();}intmain(intargc,charargv〔〕){(void)argc;(void)argv;osg::refptrosg::NodesubmodelosgDB::readNodeFile(。。datacessna。osg);osg::refptrosg::MatrixTransformtransform1newosg::MatrixTransform;transform1setMatrix(osg::Matrix::rotate(0。0,osg::Vec3(0。0f,0。0f,1。0f)));transform1addChild(submodel。get());osg::refptrosg::Geodemodelnewosg::Geode;modeladdChild(createQuad(osg::Vec3(0。0f,0。0f,0。0f),2。0f,2。0f));inttexwidht1024;inttexheight1024;osg::refptrosg::Texture2Dtexturenewosg::Texture2D;texturesetTextureSize(texwidht,texheight);texturesetInternalFormat(GLRGBA);texturesetFilter(osg::Texture2D::MINFILTER,osg::Texture2D::LINEAR);texturesetFilter(osg::Texture2D::MAGFILTER,osg::Texture2D::LINEAR);modelgetOrCreateStateSet()setTextureAttributeAndModes(0,texture。get());osg::refptrosg::Cameracameranewosg::Camera;camerasetViewport(0,0,texwidht,texheight);camerasetClearColor(osg::Vec4(1。0f,1。0f,1。0f,1。0f));camerasetClearMask(GLCOLORBUFFERBITGLDEPTHBUFFERBIT);camerasetRenderOrder(osg::Camera::PRERENDER);camerasetRenderTargetImplementation(osg::Camera::FRAMEBUFFEROBJECT);cameraattach(osg::Camera::COLORBUFFER,texture。get());camerasetReferenceFrame(osg::Camera::ABSOLUTERF);cameraaddChild(transform1。get());osg::refptrosg::Grouprootnewosg::Group;rootaddChild(model。get());rootaddChild(camera。get());osgViewer::Viewerviewer;viewer。setSceneData(root。get());viewer。setCameraManipulator(newosgGA::TrackballManipulator);viewer。setUpViewOnSingleScreen(0);camerasetProjectionMatrixAsPerspective(30。0,staticcastdouble(texwidht)staticcastdouble(texheight),0。1,1000。0);floatdist100。0f;floatalpha10。0f3。14f180。0f;osg::Vec3eye(0。0f,distcosf(alpha),distsinf(alpha));osg::Vec3center(0。0f,0。0f,0。0f);osg::Vec3up(0。0f,0。0f,1。0f);camerasetViewMatrixAsLookAt(eye,center,up);floatphi0。0f;floatdelta0。01f;while(!viewer。done()){transform1setMatrix(osg::Matrix::rotate(staticcastdouble(phi),osg::Vec3(0。0f,0。0f,1。0f)));viewer。frame();phidelta;}return0;} 为了创建一个正方形,我们编写了一个单独的自由函数。osg::GeometrycreateQuad(constosg::Vec3pos,floatw,floath){osg::refptrosg::Vec3Arrayverticesnewosg::Vec3Array;verticespushback(pososg::Vec3(w2,0。0f,h2));verticespushback(pososg::Vec3(w2,0。0f,h2));verticespushback(pososg::Vec3(w2,0。0f,h2));verticespushback(pososg::Vec3(w2,0。0f,h2));osg::refptrosg::Vec3Arraynormalsnewosg::Vec3Array;normalspushback(osg::Vec3(0。0f,1。0f,0。0f));osg::refptrosg::Vec2Arraytexcoordsnewosg::Vec2Array;texcoordspushback(osg::Vec2(1。0f,1。0f));texcoordspushback(osg::Vec2(1。0f,0。0f));texcoordspushback(osg::Vec2(0。0f,0。0f));texcoordspushback(osg::Vec2(0。0f,1。0f));osg::refptrosg::Geometryquadnewosg::Geometry;quadsetVertexArray(vertices。get());quadsetNormalArray(normals。get());quadsetNormalBinding(osg::Geometry::BINDOVERALL);quadsetTexCoordArray(0,texcoords。get());quadaddPrimitiveSet(newosg::DrawArrays(GLQUADS,0,4));returnquad。release();} 该函数将正方形中心的位置及其几何尺寸作为输入。接下来,创建一个顶点数组、一个法线数组和纹理坐标,然后从函数返回创建的几何体。 在主程序的正文中,我们将加载Cessna模型:osg::refptrosg::NodesubmodelosgDB::readNodeFile(。。datacessna。osg);osg::refptrosg::MatrixTransformtransform1newosg::MatrixTransform;transform1setMatrix(osg::Matrix::rotate(0。0,osg::Vec3(0。0f,0。0f,1。0f)));transform1addChild(submodel。get()); 现在我们将为主场景创建一个模型一个我们将在其上渲染的正方形:osg::refptrosg::Geodemodelnewosg::Geode;modeladdChild(createQuad(osg::Vec3(0。0f,0。0f,0。0f),2。0f,2。0f)); 使用RGBA像素格式为1024x1024像素的正方形创建空纹理(带Alpha通道的32位三分量颜色):inttexwidht1024;inttexheight1024;osg::refptrosg::Texture2Dtexturenewosg::Texture2D;texturesetTextureSize(texwidht,texheight);texturesetInternalFormat(GLRGBA);texturesetFilter(osg::Texture2D::MINFILTER,osg::Texture2D::LINEAR);texturesetFilter(osg::Texture2D::MAGFILTER,osg::Texture2D::LINEAR); 将此纹理应用于正方形模型。modelgetOrCreateStateSet()setTextureAttributeAndModes(0,texture。get()); 然后创建一个将烘焙纹理的相机。osg::refptrosg::Cameracameranewosg::Camera;camerasetViewport(0,0,texwidht,texheight);camerasetClearColor(osg::Vec4(1。0f,1。0f,1。0f,1。0f));camerasetClearMask(GLCOLORBUFFERBITGLDEPTHBUFFERBIT); 视口相机的大小与纹理的大小一致。此外,清洁屏幕和清洁蒙版时不要忘记设置背景颜色,指示清除颜色缓冲区和深度缓冲区。接下来,设置相机以渲染为纹理:camerasetRenderOrder(osg::Camera::PRERENDER);camerasetRenderTargetImplementation(osg::Camera::FRAMEBUFFEROBJECT);cameraattach(osg::Camera::COLORBUFFER,texture。get()); 渲染顺序PRERENDER指示此摄像机在渲染到主场景之前进行渲染。我们将FBO指定为渲染的目标,并将纹理附加到摄像机。现在我们将摄像机设置为在绝对坐标系中工作,作为场景,我们设置了要渲染为纹理的子树:使用附加的Cessna模型进行旋转转换:camerasetReferenceFrame(osg::Camera::ABSOLUTERF);cameraaddChild(transform1。get()); 创建根组节点,向其添加主模型(正方形)和相机处理纹理:osg::refptrosg::Grouprootnewosg::Group;rootaddChild(model。get());rootaddChild(camera。get()); 创建场景查看器:osgViewer::Viewerviewer;viewer。setSceneData(root。get());viewer。setCameraManipulator(newosgGA::TrackballManipulator);viewer。setUpViewOnSingleScreen(0); 配置相机的投影矩阵通过裁剪金字塔参数的透视投影:camerasetProjectionMatrixAsPerspective(30。0,staticcastdouble(texwidht)staticcastdouble(texheight),0。1,1000。0); 调整视图矩阵,该矩阵设置相机在空间中的位置:floatdist100。0f;floatalpha10。0f3。14f180。0f;osg::Vec3eye(0。0f,distcosf(alpha),distsinf(alpha));osg::Vec3center(0。0f,0。0f,0。0f);osg::Vec3up(0。0f,0。0f,1。0f);camerasetViewMatrixAsLookAt(eye,center,up); 最后,我们对场景进行动画处理和显示,在每一帧上改变平面绕Z轴旋转的角度。floatphi0。0f;floatdelta0。01f;while(!viewer。done()){transform1setMatrix(osg::Matrix::rotate(staticcastdouble(phi),osg::Vec3(0。0f,0。0f,1。0f)));viewer。frame();phidelta;} 最终我们得到一个有趣的结果: 在此示例中,我们实现了一些场景动画,但请记住,从组织对不同流的数据访问的角度来看,在渲染帧之前或之后扩展run()循环并更改渲染参数并不是一项安全的工作。由于OSG使用多线程渲染,因此有一些常规机制可以在渲染过程中嵌入自己的操作,从而提供对数据的线程安全访问。7、将渲染结果保存到文件 OSG支持将osg::Image对象附加到相机并将帧缓冲区的内容保存到图像数据缓冲区的功能。之后,可以使用osg::writeImageFile()函数将此数据保存到磁盘:osg::refptrosg::Imageimagenewosg::Image;imageallocateImage(width,height,1,GLRGBA,GLUNSIGNEDBYTE);cameraattach(osg::Camera::COLORBUFFER,image。get());。。。osgDB::writeImageFile(image,savedimage。bmp);8、结束语 也许文章中描述的材料看起来微不足道。但是,它概述了在OpenSceneGraph中使用纹理的基础知识,这些基础知识基于使用此引擎的更复杂的技术,我们将来肯定会讨论。 原文链接:http:www。bimant。comblogosgtexturebasics