OSG之Mipmap纹理映射

412 阅读4分钟

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;
}

效果图

2.PNG

动态效果图

1.gif