1、纹理映射介绍
物体的外观不仅包括形状,不同物体表面有着不同的颜色和图案。一个简单而有效地实现这种特性的方法就是使用纹理映射。在三维图形中,纹理映射(Texture Mapping)的方法运用广泛,使用该技术可以大大提高物体的真实感。
OSG 是对底层 OpenGL API 的封装,OpenGL 本身有非常标准而高效的纹理机制。OSG 全面支持OpenGL 的纹理映射机制,因此,在 OSG 中使用纹理映射机制非常简单。纹理映射主要包括一维纹理、二维纹理、三维纹理、凸凹纹理、多重纹理、Mipmap 纹理、压缩纹理和立方纹理等。本文主要针对一维纹理、二维纹理、三维纹理、多重纹理、Mipmap 纹理和立方纹理等经常使用的几种纹理加以解释说明。
下面讲解一些纹理的基础知识,这些对于熟悉 OpenGL 的朋友来说,应该都是再基础不过了。
(1)纹理坐标:
cpp
enum WrapParameter
{
WRAP_S, //x 轴
WRAP_T, //y 轴
WRAP_R //z 轴
};
(2)纹理的包装模式:
cpp
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 //纹理镜像的重复映射
};
(3)纹理过滤方法:
cpp
enum FilterParameter
{
MIN_FILTER, //用于缩小
MAG_FILTER //用于放大
};
(4)纹理的过滤处理:
cpp
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 //选择最临近的纹理单元值
};
(5)纹理映射模式(处理纹理图像数据与物体本身的融合):
cpp
enum Mode
{
DECAL= GL_DECAL, //贴花
MODULATE= GL_MODULATE, //调整
BLEND= GL_BLEND, //混合
REPLACE= GL_REPLACE, //替换,覆盖
ADD= GL_ADD //添加
};
(6)纹理坐标的自动生成模式:
cpp
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//反射贴图
};
(7)贴图坐标:
cpp
enum Coord
{
S, //x
T, //y
R, //z
Q //w
};
(8)纹理的内部格式:
cpp
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、二维纹理
2.1 纹理坐标和纹理数据
在所有的纹理映射中,二维纹理映射的过程最简单,也非常容易理解。在用户应用程序中创建二维纹理的步骤如下:
(1)指定用户几何体的纹理坐标。
(2)创建纹理属性对象并保存纹理图形数据。
(3)为 StateSet 设置合适的纹理属性和模式。
1.纹理坐标
在前面几何体的绘制时已经提到用一个二维的向量数据来保存纹理坐标。设置纹理坐标比较简单,纹理坐标是与顶点一一对应的,很像数学中的映射。
下面的代码段创建了一个 osg::Vec2Array 数组,用于保存纹理坐标,同时将其关联到 Geometry 实例的纹理单元 0。如果要对单一的 Geometry 设置多个纹理,只需要将多个纹理坐标数组关联到Geometry,并针对不同的数组指定不同的纹理单元即可。
cpp
//创建一个 Geometry 几何体对象
osg::ref_ptr<osg::Geometry> geom = new osg::Geometry;
//创建一个 Vec2Array 对象以保存纹理单元 0 的纹理坐标,并将其关联到 geom
osg::ref_ptr<osg::Vec2Array> tc = new osg::Vec2Array;
geom->setTexCoordArray( 0, tc.get());
tc->push_back(osg::Vec2( 0.f, 0.f));
tc->push_back(osg::Vec2( 1.f, 0.f));
tc->push_back(osg::Vec2( 1.f, 1.f));
tc->push_back(osg::Vec2( 0.f, 1.f));
osg::Geometry::setTexCoordArray()的第一个参数是纹理单元号,第二个参数是纹理坐标数组。用户不需要使用类同 osg::Geometry::setTexCoordBinding()的函数输入点来绑定纹理数据。纹理坐标总是绑定到每个顶点的。在这里有一点要注意,OpenGL 的早期版本并不支持多重纹理,而加入多重纹理的特性之后,OpenGL 仍然支持非多重纹理的函数接口,以实现向下兼容。从本质上说,此时 OpenGL 将非多重纹理接口解释为使用纹理单元 0 对应所有纹理数据。与 OpenGL 不同,OSG 并不支持非多重纹理接口。因此,用户程序必须指定一个纹理单元,以对应纹理坐标数据和纹理状态。如果要使用单一纹理,只需要指定到纹理单元 0 即可。
2.纹理数据
在大多数应用程序中,纹理数据都是从外部导入的图像文件。当没有必要导入图像时,可以生成一幅纹理数据贴图。这里使用从外部导入的纹理数据图形的方法。读取图像需要使用一个新类------osg::Image。osg::Image 继承自 osg::Object 类。面的代码将实现如何读取图像:
cpp
osg::ref_ptr<osg::Image> image = new osg::Image;
image->setFileName( "tree.rgb" );
在读取一个图像后,需要创建一个纹理对象来关联图像。osg::Texture2D 属于 osg::StateAttribute 的派生类,用于管理 OpenGL 纹理对象,而 Image 用于管理图像像素数据。如果要使用 2D 图像文件作为纹理映射的图形,只要将文件名赋给 Image 对象并将 Image 关联到 Texture2D 即可。osg::Texture2D 继承自 osg::Texture。下面的代码将实现将图像关联到 2D 纹理对象上:
cpp
//将图像关联到 Texture2D 对象
osg::ref_ptr<osg::Texture2D> tex = new osg::Texture2D;
tex->setImage( image.get() );
在关联图像以后,可以直接关联到渲染状态。值得注意的是:大量使用纹理贴图的程序往往需要实现更紧凑的内存管理。Image 类继承自 Referenced 类,而 Texture2D 内部保存了一个指向 Image 的ref_ptr<>指针。在第一次渲染时,OSG 创建了用于保存图像数据的 OpenGL 纹理对象,其结果是产生了两个纹理图像的副本,一个是 Image 对象,另一个由 OpenGL 拥有。简单的单环境(single-context)场景渲染中,读者可以通过设置 Texture2D 解除对 Image 的引用来降低内存损耗。如果当前引用 Image对象的只有 Texture2D 对象,那么 OSG 将释放 Image 及其内存空间。下面的代码演示了设置 Texture2D解除对 Image 引用的方法:
cpp
//创建 OpenGL 纹理对象后,释放内部的 ref_ptr<Image>,删除 Image 图像
tex->setUnRefImageDataAfterApply( true );
默认情况下,Texture2D 不会自动释放对 Image 的引用。在多环境(multi-context)场景渲染中,这是一种期望行为,前提是纹理对象并没有在各环境中共享。
3.纹理状态
用户程序可以使用纹理状态函数接口为每个纹理单元指定渲染状态。纹理状态函数接口与非纹理状态的接口类似。用户可以使用 osg::StateSet::setTextureAttribute()将一个纹理属性关联到 StateSet 对象。setTextureAttribute()的第一个参数是纹理单元,第二个参数是继承自 StateAttribute 类的一种纹理属性。
合法的纹理属性类共有 6 种,其中包括 5 种纹理类型(osg::Texture1D、osg::Texture2D、osg::Texture3D、
osg::TextureCubeMap 和 osg::TextureRectangle)和一个用于纹理坐标的生成的类(osg::TexGen)。
下面的代码将根据给定的 Texture2D 属性对象 tex 和渲染状态 StateSet 将 tex 关联到渲染状态,并设置使用纹理单元 0。
cpp
//创建一个 Texture2D 属性
Osg::ref_ptr<osg::Texture2D> tex = new osg::Texture2D;
//关联材质属性到材质单元 0
state->setTextureAttribute( 0, tex.get() );
与上面的程序类似,用户可以调用 osg::StateSet::setTextureMode()方法来设置材质渲染模式,这个方法与 setMode()方法类似。用户可以使用 setTextureMode()来设置 GL_TEXTURE_1D、GL_TEXTURE_2D、GL_TEXTURE_3DGL_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 纹理映射:
cpp
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 模式:
cpp
//创建一个 Texture2D 属性对象
osg::ref_ptr<osg::Texture2D> tex = new osg::Texture2D;
//在纹理单元 0 上,关联 2D 纹理属性并许可 GL_TEXTURE_2D 模式
state->setTextureAttributeAndModes( 0, tex );
setTextureAttributeAndModes() 的 第 三 个 参 数 的 默 认 值 为 ON , 即 允 许 纹 理 渲 染 模 式 。 与
setAttributeAndModes()类似,读者可以对这个参数使用位或操作包括 OVERRIDE、PROTECTED 和INHERIT,以修改纹理属性的继承特性。读者还可以通过修改 setTextureMode()和 setTextureAttribute()的第三个参数来指定这个继承标志。
2.2 示例效果
2.2.1 普通示例
备注:osg默认会被图片大小非2的n次方的图片进行缩放处理。后续可与矩阵纹理进行对比
cpp
// TestOSGProject.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
#include <windows.h>
#include <osgViewer/Viewer>
#include <osg/Node>
#include <osg/Geode>
#include <osg/Group>
#include <osg/Geometry>
#include <osg/Image>
#include <osg/TexGen>
#include <osg/Texture1D>
#include <osg/TexEnv>
#include <osg/StateSet>
#include <osg/PrimitiveSet>
#include <osgDB/ReadFile>
#include <osgDB/WriteFile>
#include <osgViewer/ViewerEventHandlers> //事件监听
#include <osgGA/StateSetManipulator> //事件响应类,对渲染状态进行控制
#include <osgUtil/Simplifier> //简化几何体
#include <osgUtil/Optimizer>
#pragma comment(lib, "OpenThreadsd.lib")
#pragma comment(lib, "osgd.lib")
#pragma comment(lib, "osgDBd.lib")
#pragma comment(lib, "osgUtild.lib")
#pragma comment(lib, "osgGAd.lib")
#pragma comment(lib, "osgViewerd.lib")
#pragma comment(lib, "osgTextd.lib")
//创建一个四边形节点
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();
}
int main()
{
osg::ref_ptr<osgViewer::Viewer> viewer = new osgViewer::Viewer();
osg::ref_ptr<osg::Group> root = new osg::Group();
//读取贴图文件
osg::ref_ptr<osg::Image> image = osgDB::readImageFile("Images/primitives.gif");
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->addEventHandler(new osgGA::StateSetManipulator(viewer->getCamera()->getOrCreateStateSet()));
viewer->addEventHandler(new osgViewer::StatsHandler());
viewer->addEventHandler(new osgViewer::WindowSizeHandler());
viewer->setSceneData(root.get());
viewer->setUpViewInWindow(600, 600, 1000, 800);
return viewer->run();
}
2.2.2 光照和混合模式示例
cpp
//创建二维纹理状态对象
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());
//关联Texture2D纹理对象,第三个参数默认为ON
stateset->setTextureAttributeAndModes(0, texture.get(), osg::StateAttribute::ON);
//启用混合
stateset->setMode(GL_BLEND, osg::StateAttribute::ON);
//关闭光照
stateset->setMode(GL_LIGHTING, osg::StateAttribute::ON);
return stateset.get();
}
2.2.3 光照关闭示例
cpp
//创建二维纹理状态对象
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());
//关联Texture2D纹理对象,第三个参数默认为ON
stateset->setTextureAttributeAndModes(0, texture.get(), osg::StateAttribute::ON);
//启用混合
stateset->setMode(GL_BLEND, osg::StateAttribute::ON);
//关闭光照
stateset->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
return stateset.get();
}
3、多重纹理
在进行标准的二维纹理映射处理时,一次把一幅纹理图像应用到一个多边形上。多重纹理允许应用几个纹理,在纹理操作管线中把它们逐个应用到同一个多边形上。多重纹理存在一系列的纹理单元,每个纹理单元执行单独的纹理操作,并把它的结果传递给下一个纹理单元,直到所有纹理单元的操作完成为止,最终显示处理后的效果。
多重纹理映射非常广泛,它能实现一些高级的渲染技巧,如光照、贴花、合成和细节纹理等。在OSG 中实现三维纹理主要有以下几个步骤:
(1)指定用户几何体的纹理坐标。
(2)创建多个纹理属性对象并保存纹理多个图形数据。
(3)为 StateSet 设置合适的纹理属性和模式。
看起来和二维纹理映射的区别不大,简单地说就是多个二维纹理映射的叠加。但这里需要注意的是,对于不同的纹理属性对象需要指定不同的纹理单元及纹理坐标,否则就不会启用该纹理单元,或者该纹理单元会被覆盖。
2.2 示例效果
原书上给的示例感觉不太合适不具备说明性,因此自己参照learnopengl实现了多重纹理混合示例。
2.2 源码
cpp
// TestOSGProject.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
#include <windows.h>
#include <osgViewer/Viewer>
#include <osg/Node>
#include <osg/Geode>
#include <osg/Group>
#include <osg/Geometry>
#include <osg/Image>
#include <osg/TexGen>
#include <osg/Texture1D>
#include <osg/TexEnv>
#include <osg/StateSet>
#include <osg/PrimitiveSet>
#include <osgDB/ReadFile>
#include <osgDB/WriteFile>
#include <osgViewer/ViewerEventHandlers> //事件监听
#include <osgGA/StateSetManipulator> //事件响应类,对渲染状态进行控制
#include <osgUtil/Simplifier> //简化几何体
#include <osgUtil/Optimizer>
#pragma comment(lib, "OpenThreadsd.lib")
#pragma comment(lib, "osgd.lib")
#pragma comment(lib, "osgDBd.lib")
#pragma comment(lib, "osgUtild.lib")
#pragma comment(lib, "osgGAd.lib")
#pragma comment(lib, "osgViewerd.lib")
#pragma comment(lib, "osgTextd.lib")
//创建一个四边形节点
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());
geom->setTexCoordArray(1, 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> image2 = osgDB::readImageFile("awesomeface.png");
osg::ref_ptr<osg::Image> image1 = osgDB::readImageFile("container.jpg");
//创建状态集对象
osg::ref_ptr<osg::StateSet> stateset = new osg::StateSet();
//创建二维纹理对象
osg::ref_ptr<osg::Texture2D> texture1 = new osg::Texture2D();
texture1->setDataVariance(osg::Object::DYNAMIC);
//设置贴图
texture1->setImage(image1.get());
//关联Texture2D纹理对象,第三个参数默认为ON
stateset->setTextureAttributeAndModes(0, texture1.get(), osg::StateAttribute::ON);
//创建二维纹理对象
osg::ref_ptr<osg::Texture2D> texture2 = new osg::Texture2D();
texture2->setDataVariance(osg::Object::DYNAMIC);
//设置贴图
texture2->setImage(image2.get());
//关联Texture2D纹理对象,第三个参数默认为ON
stateset->setTextureAttributeAndModes(1, texture2.get(), osg::StateAttribute::ON);
//启用混合
stateset->setMode(GL_BLEND, osg::StateAttribute::ON);
//关闭光照
stateset->setMode(GL_LIGHTING, osg::StateAttribute::ON);
return stateset.get();
}
int main()
{
osg::ref_ptr<osgViewer::Viewer> viewer = new osgViewer::Viewer();
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 = createTexture2DState();
//使用二维纹理
node->setStateSet(stateset.get());
root->addChild(node.get());
//优化场景数据
osgUtil::Optimizer optimizer;
optimizer.optimize(root.get());
//方便查看在多边形之间切换,以查看三角网
viewer->addEventHandler(new osgGA::StateSetManipulator(viewer->getCamera()->getOrCreateStateSet()));
viewer->addEventHandler(new osgViewer::StatsHandler());
viewer->addEventHandler(new osgViewer::WindowSizeHandler());
viewer->setSceneData(root.get());
viewer->setUpViewInWindow(600, 600, 1000, 800);
return viewer->run();
}
4、Mipmap多级渐远纹理
在一个动态的场景中,当一个纹理对象迅速远离视点时,纹理图像必须随着被投影的图像一起缩小。为了实现这种效果,可以通过对纹理图像进行过滤,适当对它进行缩小,以使它映射到物体的表面时不会产生抖动或者闪烁的效果。但这时还存在一个问题,就是当视点距离速度变大时,单个纹理缩小为单个像素之前,在经过一些过渡点时,经过过滤的纹理图像会变化非常明显。同时,也没有必要使用一张那么大的纹理数据了;当使用一个很大的、包含很多贴图的场景时,对渲染效率的影响是相当大的。
为了避免这种突然变化的现象及不必要的渲染负担,可以预先指定一系列分辨率递减的纹理图像。使用 Mipmap 纹理映射必须指定全系列大小为 2 的整数次方的纹理图像,其范围为从最大值到 1×1 的纹理单元。例如,如果最高的纹理分辨率为 32×32,那么必须指定的纹理图像为 32×32、16×16、8×8、4×4、2×2、1×1。通常来说,较小的纹理图像是上一级分辨率的纹理图像的 4 个纹理单元的平均值。当然,这里也没有确定的计算方法,一般是这样计算的。
在 OSG 中使用 Mipmap 纹理映射主要包括下面几个步骤:
(1)将各层的纹理图像数据按照从大到小的顺序(且尺寸必须为 2 的幂次)依次存放到 unsigned char*数组中,将这个数组使用 setImage 送入 Image 对象。
(2)将各层纹理数据在数组中的偏移地址记录到一个 osg::Image::MipmapDataType 列表中,用于选择正确的层次细节纹理图像。
(3)使用 setMipmapLevels()将 MipmapDataType 送入 Image 对象。注意,这一步的次序和 setImage不能颠倒,否则可能无法正确显示各级别的纹理图像。
在示例程序中,可以看到清晰的纹理图像的各个层次级别细节的明显过渡,这里用颜色代替了纹理。
备注:OpenGL描述如下:当调用glTexImage2D时,当前绑定的纹理对象就会被附加上纹理图像。然而,目前只有基本级别(Base-level)的纹理图像被加载了,如果要使用多级渐远纹理,我们必须手动设置所有不同的图像(不断递增第二个参数)。或者,直接在生成纹理之后调用glGenerateMipmap。这会为当前绑定的纹理自动生成所有需要的多级渐远纹理。生成了纹理和相应的多级渐远纹理后,释放图像的内存并解绑纹理对象是一个很好的习惯。
4.1 示例效果
4.2 源码
cpp
// TestOSGProject.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
#include <windows.h>
#include <osgViewer/Viewer>
#include <osg/Node>
#include <osg/Geode>
#include <osg/Group>
#include <osg/Geometry>
#include <osg/Image>
#include <osg/TexGen>
#include <osg/Texture1D>
#include <osg/TexEnv>
#include <osg/StateSet>
#include <osg/PrimitiveSet>
#include <osgDB/ReadFile>
#include <osgDB/WriteFile>
#include <osgViewer/ViewerEventHandlers> //事件监听
#include <osgGA/StateSetManipulator> //事件响应类,对渲染状态进行控制
#include <osgUtil/Simplifier> //简化几何体
#include <osgUtil/Optimizer>
#pragma comment(lib, "OpenThreadsd.lib")
#pragma comment(lib, "osgd.lib")
#pragma comment(lib, "osgDBd.lib")
#pragma comment(lib, "osgUtild.lib")
#pragma comment(lib, "osgGAd.lib")
#pragma comment(lib, "osgViewerd.lib")
#pragma comment(lib, "osgTextd.lib")
//创建一个四边形
osg::ref_ptr<osg::Geode> createQuad()
{
osg::ref_ptr<osg::Geode> geode = new osg::Geode();
osg::ref_ptr<osg::Geometry> geom = new osg::Geometry();
geode->addDrawable(geom.get());
//设置顶点
osg::ref_ptr<osg::Vec3Array> vec = new osg::Vec3Array;
vec->push_back(osg::Vec3(-10.0f, 0.0f, -10.0f));
vec->push_back(osg::Vec3(-10.0f, 0.0f, 10.0f));
vec->push_back(osg::Vec3(10.0f, 0.0f, 10.0f));
vec->push_back(osg::Vec3(10.0f, 0.0f, -10.0f));
geom->setVertexArray(vec.get());
//设置法线
osg::ref_ptr<osg::Vec3Array> nor = new osg::Vec3Array;
nor->push_back(osg::Vec3f(0.0f, -1.0f, 0.0f));
geom->setNormalArray(nor.get());
geom->setNormalBinding(osg::Geometry::BIND_PER_PRIMITIVE_SET);
//设置纹理坐标
osg::ref_ptr<osg::Vec2Array> tex = new osg::Vec2Array;
tex->push_back(osg::Vec2f(0.0f, 0.0f));
tex->push_back(osg::Vec2f(0.0f, 1.0f));
tex->push_back(osg::Vec2f(1.0f, 1.0f));
tex->push_back(osg::Vec2f(1.0f, 0.0f));
geom->setTexCoordArray(0, tex.get());
geom->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::QUADS, 0, 4));
return geode.get();
}
static void fillImage(unsigned char* ptr, unsigned int size)
{
//黑色
if (size == 1)
{
float r = 0.5f;
osg::Vec4 color(0.0f, 0.0f, 0.0f, 1.0f);
*ptr++ = (unsigned char)((color[0]) * 255.0f);
*ptr++ = (unsigned char)((color[1]) * 255.0f);
*ptr++ = (unsigned char)((color[2]) * 255.0f);
*ptr++ = (unsigned char)((color[3]) * 255.0f);
}
//白色
if (size == 2)
{
osg::Vec4 color(1.0f, 1.0f, 1.0f, 1.0f);
for (unsigned int r = 0; r < size; ++r)
{
for (unsigned int c = 0; c < size; ++c)
{
*ptr++ = (unsigned char)((color[0]) * 255.0f);
*ptr++ = (unsigned char)((color[1]) * 255.0f);
*ptr++ = (unsigned char)((color[2]) * 255.0f);
*ptr++ = (unsigned char)((color[3]) * 255.0f);
}
}
}
//黄色
if (size == 4)
{
osg::Vec4 color(0.0f, 1.0f, 0.0f, 1.0f);
for (unsigned int r = 0; r < size; ++r)
{
for (unsigned int c = 0; c < size; ++c)
{
*ptr++ = (unsigned char)((color[0]) * 255.0f);
*ptr++ = (unsigned char)((color[1]) * 255.0f);
*ptr++ = (unsigned char)((color[2]) * 255.0f);
*ptr++ = (unsigned char)((color[3]) * 255.0f);
}
}
}
//红色
if (size == 8)
{
osg::Vec4 color(1.0f, 0.0f, 0.0f, 1.0f);
for (unsigned int r = 0; r < size; ++r)
{
for (unsigned int c = 0; c < size; ++c)
{
*ptr++ = (unsigned char)((color[0]) * 255.0f);
*ptr++ = (unsigned char)((color[1]) * 255.0f);
*ptr++ = (unsigned char)((color[2]) * 255.0f);
*ptr++ = (unsigned char)((color[3]) * 255.0f);
}
}
}
//粉红色
if (size == 16)
{
osg::Vec4 color(1.0f, 0.0f, 1.0f, 1.0f);
for (unsigned int r = 0; r < size; ++r)
{
for (unsigned int c = 0; c < size; ++c)
{
*ptr++ = (unsigned char)((color[0]) * 255.0f);
*ptr++ = (unsigned char)((color[1]) * 255.0f);
*ptr++ = (unsigned char)((color[2]) * 255.0f);
*ptr++ = (unsigned char)((color[3]) * 255.0f);
}
}
}
//黄色
if (size == 32)
{
osg::Vec4 color(1.0f, 1.0f, 0.0f, 1.0f);
for (unsigned int r = 0; r < size; ++r)
{
for (unsigned int c = 0; c < size; ++c)
{
*ptr++ = (unsigned char)((color[0]) * 255.0f);
*ptr++ = (unsigned char)((color[1]) * 255.0f);
*ptr++ = (unsigned char)((color[2]) * 255.0f);
*ptr++ = (unsigned char)((color[3]) * 255.0f);
}
}
}
//蓝绿色
if (size == 64)
{
osg::Vec4 color(0.0f, 1.0f, 1.0f, 1.0f);
for (unsigned int r = 0; r < size; ++r)
{
for (unsigned int c = 0; c < size; ++c)
{
*ptr++ = (unsigned char)((color[0]) * 255.0f);
*ptr++ = (unsigned char)((color[1]) * 255.0f);
*ptr++ = (unsigned char)((color[2]) * 255.0f);
*ptr++ = (unsigned char)((color[3]) * 255.0f);
}
}
}
//灰白色
if (size == 128)
{
osg::Vec4 color(0.5f, 0.5f, 0.5f, 1.0f);
for (unsigned int r = 0; r < size; ++r)
{
for (unsigned int c = 0; c < size; ++c)
{
*ptr++ = (unsigned char)((color[0]) * 255.0f);
*ptr++ = (unsigned char)((color[1]) * 255.0f);
*ptr++ = (unsigned char)((color[2]) * 255.0f);
*ptr++ = (unsigned char)((color[3]) * 255.0f);
}
}
}
//蓝色
if (size == 256)
{
osg::Vec4 color(0.0f, 0.0f, 1.0f, 1.0f);
for (unsigned int r = 0; r < size; ++r)
{
for (unsigned int c = 0; c < size; ++c)
{
*ptr++ = (unsigned char)((color[0]) * 255.0f);
*ptr++ = (unsigned char)((color[1]) * 255.0f);
*ptr++ = (unsigned char)((color[2]) * 255.0f);
*ptr++ = (unsigned char)((color[3]) * 255.0f);
}
}
}
}
int main()
{
osg::ref_ptr<osgViewer::Viewer> viewer = new osgViewer::Viewer();
osg::ref_ptr<osg::Group> root = new osg::Group();
//创建一个平面
osg::ref_ptr<osg::Geode> geode = createQuad();
osg::ref_ptr<osg::StateSet> stateset = new osg::StateSet();
osg::ref_ptr<osg::Image> image = new osg::Image();
//创建一个MipmapDataType列表,用来各层图片数据的偏移地址
osg::Image::MipmapDataType mipmapData;
//纹理的尺寸的最大值,必须为2的幂次
unsigned int s = 256;
//计算所需分配的数组的大小
unsigned int totalSize = 0;
for (unsigned int i = 0; s > 0; s >>= 1, ++i)
{
if (i > 0)
{
mipmapData.push_back(totalSize);
}
totalSize += s * s * 4;
}
//申请一个数据
unsigned char* ptr = new unsigned char[totalSize];
//设置image的尺寸大小,数据及数据格式
image->setImage(256, 256, 256, GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE, ptr, osg::Image::USE_NEW_DELETE, 1);
//将偏移地址传入imge对象
image->setMipmapLevels(mipmapData);
//向image中填充各层数据
s = 256;
for (unsigned int i = 0; s > 0; s >>= 1, ++i)
{
fillImage(ptr, s);
ptr += s * s * 4;
}
//创建一个二维纹理对象
osg::ref_ptr<osg::Texture2D> texture = new osg::Texture2D;
//设置贴图
texture->setImage(0, image.get());
//设置边界处理为REPEATE
texture->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT);
texture->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT);
//设置滤波
texture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::NEAREST_MIPMAP_NEAREST);
texture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::NEAREST);
//启用二维纹理对象
stateset->setTextureAttributeAndModes(0, texture.get(), osg::StateAttribute::ON);
geode->setStateSet(stateset.get());
root->addChild(geode.get());
//优化场景数据
osgUtil::Optimizer optimizer;
optimizer.optimize(root.get());
//方便查看在多边形之间切换,以查看三角网
viewer->addEventHandler(new osgGA::StateSetManipulator(viewer->getCamera()->getOrCreateStateSet()));
viewer->addEventHandler(new osgViewer::StatsHandler());
viewer->addEventHandler(new osgViewer::WindowSizeHandler());
viewer->setSceneData(root.get());
viewer->setUpViewInWindow(600, 600, 1000, 800);
return viewer->run();
}
5、TextureRectangle矩阵纹理
TextureRectangle 纹理映射是在 OpenGL 后来版本中的一个扩展------ARB_texture_rectangle,它也是一种二维纹理映射,但它与前面介绍的二维映射有很大的区别。
使用 TextureRectangle 纹理映射时有以下几点需要注意:
纹理环绕模式。它并不支持所有的纹理包装模式,只能使用 CLAMP、CLAMP_TO_EDGE 或CLAMP_TO_BORDER,并不支持 REPEAT。
纹理滤波。它只支持NEAREST或者LINEAR,不支持Mipmap滤波,使用它时不能实现Mipmap纹理。
不支持纹理边框。
对于没有图形学基础的开发人员来说,TextureRectangle 比较容易理解,也非常容易上手,可以简单理解为一个矩形纹理。
5.1 示例效果
5.2 源码
cpp
// TestOSGProject.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
#include <windows.h>
#include <osgViewer/Viewer>
#include <osg/Node>
#include <osg/Geode>
#include <osg/Group>
#include <osg/Geometry>
#include <osg/Image>
#include <osg/TexGen>
#include <osg/Texture1D>
#include <osg/TexEnv>
#include <osg/TextureRectangle>
#include <osg/TexMat>
#include <osg/StateSet>
#include <osg/PrimitiveSet>
#include <osgDB/ReadFile>
#include <osgDB/WriteFile>
#include <osgViewer/ViewerEventHandlers> //事件监听
#include <osgGA/StateSetManipulator> //事件响应类,对渲染状态进行控制
#include <osgUtil/Simplifier> //简化几何体
#include <osgUtil/Optimizer>
#pragma comment(lib, "OpenThreadsd.lib")
#pragma comment(lib, "osgd.lib")
#pragma comment(lib, "osgDBd.lib")
#pragma comment(lib, "osgUtild.lib")
#pragma comment(lib, "osgGAd.lib")
#pragma comment(lib, "osgViewerd.lib")
#pragma comment(lib, "osgTextd.lib")
//创建一个四边形节点
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::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();
}
int main()
{
osg::ref_ptr<osgViewer::Viewer> viewer = new osgViewer::Viewer();
osg::ref_ptr<osg::Group> root = new osg::Group();
//读取贴图文件
osg::ref_ptr<osg::Image> image = osgDB::readImageFile("Images/primitives.gif");
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->addEventHandler(new osgGA::StateSetManipulator(viewer->getCamera()->getOrCreateStateSet()));
viewer->addEventHandler(new osgViewer::StatsHandler());
viewer->addEventHandler(new osgViewer::WindowSizeHandler());
viewer->setSceneData(root.get());
viewer->setUpViewInWindow(600, 600, 1000, 800);
return viewer->run();
}