1 概述
在一个动态的场景中,当一个纹理对象迅速远离视点时,纹理图像必须随着被投影的图像一起缩小。为了实现这种效果,可以通过对纹理图像进行过滤,适当对它进行缩小,以使它映射到物体的表面时不会产生抖动或者闪烁的效果。
但这时还存在一个问题,就是当视点距离速度变大时,单个纹理缩小为单个像素之前,在经过一些过渡点时,经过过滤的纹理图像会变化非常明显。同时,也没有必要使用一张那么大的纹理数据了;当使用一个很大的、包含很多贴图的场景时,对渲染效率的影响是相当大的。
为了避免这种突然变化的现象及不必要的渲染负担,可以预先指定一系列分辨率递减的纹理图像。使用Mipmap纹理映射必须指定全系列大小为2的整数次方的纹理图像,其范围为从最大值到1x1的纹理单元。例如,如果最高的纹理分辨率为32x32,那么必须指定的纹理图像为32x32、16x16、8x8、4x4、2x2、1x1。通常来说,较小的纹理图像是上一级分辨率的纹理图像的4个纹理单元的平均值。当然,这里也没有确定的计算方法,一般是这样计算的。
在OSG中使用Mipmap纹理映射主要包括下面几个步骤:
(1)将各层的纹理图像数据按照从大到小的顺序(且尺寸必须为2的幂次)依次存放到unsignedchar*数组中,将这个数组使用 setlmage送入Image对象。
(2)将各层纹理数据在数组中的偏移地址记录到一个osg::Image::MipmapDataType列表中,用于选择正确的层次细节纹理图像。
(3)使用setMipmapLevelsO将MipmapDataType 送入Image对象。注意,这一步的次序和 setImage 不能颠倒,否则可能无法正确显示各级别的纹理图像。
2 Mipmap 纹理映射示例
在示例程序中,读者可以看到清晰的纹理图像的各个层次级别细节的明显过渡,这是为了向读者演示Mipmap纹理的过渡变换。在实际项目中就需要用真实的纹理数据了。
Mipmap 纹理映射(Texture Mipmap)示例的代码如程序清单所示。
#include <osgViewer/Viewer>
#include <osg/Node>
#include <osg/Geode>
#include <osg/Group>
#include <osg/Image>
#include <osg/TexGen>
#include <osg/Texture1D>
#include <osg/TexEnv>
#include <osg/StateSet>
#include <osgDB/ReadFile>
#include <osgDB/WriteFile>
#include <osgUtil/Optimizer>
#include <Windows.h>
#include <iostream>
#include <string>
using namespace std;
//创建一个四边形
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(1.0f, 1.0f, 1.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(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);
}
}
}
//黄色
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, 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);
}
}
}
}
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->setSceneData(root.get());
setWindowSize(viewer, 640, 480);
viewer->realize();
viewer->run();
return 0;
}
效果图
动态效果图