再谈纹理

前篇文章《可视化编程中的几个基础概念及个人理解》非常简单的记录了一下自己对纹理等相关概念的理解。这里,对其在做进一步的挖掘,从osg的源码说起。

:所谓纹理,也即图片,说到底不过一堆数字的组合。纹理、图片等从本质上讲都可以看做数据的一种存储格式而已。既然纹理是数据,那么它就有大小,正负等数据特征。
所以,见到纹理的时候,并不能单纯的认为其只是存储图像特征(图像数据特征是其元素格式一般为rgb、rg、g等通道模式,颜色一般取值为0-1或者0-255),而应当把它当做数据看待,也就说纹理采样值(纹素)可以有负值!!

再谈glTexImage2D()

该函数在osg或者opengl编程中有着非常重要的作用--从cpu向gpu传送数据,函数原型:

void glTexImage2D(    GLenum target,
     GLint level,
     GLint internalFormat,
     GLsizei width,
     GLsizei height,
     GLint border,
     GLenum format,
     GLenum type,
     const GLvoid * data);

其中参数:target、internalFormat、format和type对纹理的读取采样等操作有着非常重要的影响。
target:指定所绑定的纹理单元。
internalFormat:根据数据本身的特点,应用程序指定读取该纹理数据的分辨率及格式。
format:也可以表示为pixelFormat,指定纹理在内存中的存贮格式。
type:纹素的数据类型。

应用程序主要通过这几个参数决定了如何向GPU传送数据以及传送什么样的数据。
例如:某纹理数据为4通道,且每个纹素的单个通道所占内存为32字节,数据类型为float型,则该纹理数据的internalFormat的值应该为:GL_RGBA32F,该纹理数据在内存中的基本存储格式应该为GL_RGBA,type对应的值应该为GL_FLOAT.

初学opengl或者osg时,可能并没意识到该函数的重要作用,一直采样默认方式,进而导致项目迟滞!

OSG纹理

这里并不打算记录OSG如何读取纹理、绑定纹理、设置采样方式、添加状态等设置,只是单单从源码看纹理,以及OSG默认纹理设置给自己带来的坑,并最后修改OSG设置纹理的源码,以实现自己的目的。

利用osgDB::readImageFIle();将纹理数据从硬盘读入内存之后,可直接将其添加到osg::texture2D对象,就基本完成了将图片转换为纹理的过程。这一过程看似简单,OSG却默默的做了很多很多不为人知的事情,其中最重要的就是设置glTexImage2D()函数。

osg类似opengl,也是一个大的状态机,虽然在主程序(每一帧绘制前)中做了各种各样状态的设定,但是真正起作用的时候却是在每一帧的绘制过程中完成的。

读入纹理之后,直接定位函数glTexIamge2D()的源码,在osg源码的Texture.cpp文件中:

glTexImage2D( target, 0, _internalFormat,
                inwidth, inheight, _borderWidth,
                (GLenum)image->getPixelFormat(),
                (GLenum)image->getDataType(),
                dataPtr);

可以看到,该函数的format和type参数是由读入纹理image决定的。再看该源码中另外两个比较重要的参数inwidth,inheight,这两个参数并没有用image->s()image->r(),也就是说,该函数**向GPU传送数据的时候并不一定是传送的原始图像(纹理)的大小!!**,事实也是确实如此!!

例如:读入一份四通道的tiff数据,数据类型是float类型,该纹理像素为3600*1720,使用在不修改源码的情况下对该数据采样如图;

在终端输出如下提示:Scaling image 11.tif from (3600,1720) to (4096,2048)
当用ps软件将该tiff图像的分辨率调节为4096*2048时,输出入下:

得出结论:OSG加载纹理,并将纹理传送到GPU的过程中,默认对纹理进行了缩放。其实不只是缩放,还对纹理进行了归一化处理--将纹素转换为 0-1 之间的数据!!
上述的缩放通常并不是我们想要的!

实际追踪源码并添加监视可知,参数inwidth, inheight,的值并不是原始图像数据的分辨率,而是与该分辨率数据( 3600 * 1720)最接近的2的幂次方数据(4096 * 2048)。

那么到底为什么会转化为(4096*2048)呢,原因是:使用osg接口读入数据并加载纹理的时候,osg自动默认对纹理的分辨率进行了计算,并将非标准纹理(分辨率不是2的幂次方)线性变换为标准纹理格式,以提高纹理采样效率。

到底是那个函数如此"自作聪明"的执行了“缩放”处理呢?!追溯OSG源码发现,如果读入的纹理数据分辨率不是标准格式,那么osg会自动调用函数:

     gluScaleImage(&psm, image->getPixelFormat(),
                           image->s(),image->t(),image->getDataType(),image->data(),
                           inwidth,inheight,image->getDataType(),
                           dataPtr)

这“该死”的gluScaleImage函数不仅对纹理进行了标准化的缩放,还进行了归一化处理---将纹素数据转换为0-1之间!!

至此,对OSG如何处理纹理有了一个最基本认识。

再谈 texture()

首先,texture()是glsl内置函数,主要用来采样纹理。

上篇文章说道:从绑定到sampler2D当前纹理中作纹理查找(不考虑阴影及比较操作,bias参数暂时不考虑),texture函数的返回值是一个颜色值,也就说返回的vec4对象的各个元素值应该在0~1之间,其实并不算准确。它只是适用于单纯的颜色图片采样,若采样的纹理是数据时(如tiff保存的地形数据),用osg纹理贴图就会出现上图1所示的情况。
如上图1所示,用osg采样时程序没有任何错误,当然也不会报错,但采样结果肯定不是正确的,所以也极难排查出可视化效果不理想的原因。

现在来看texture()函数,可以确认的是,该函数的返回值不一定是0-1之间,它可以返回纹理数据本身最原始的数据值(包括负数)。在OSG或者opengl中,glsl的texture()函数返回值可由函数glTexImage()指定;在OSG中,可通过osg::Texture2D的对象和osg::Image的对象共同指定。

在osg中:

image->setPixelFormat(GL_RGBA);  //设置像素的格式(内存中图像数据的物理布局)
image->setInternalTextureFormat(GL_RGBA32F_ARB);
image->setDataType(GL_FLOAT);

即可。
原因:osg中,glTexImage2D()函数调用了image的内部参数,参考上文osg中对glTexImage2D()函数的调用。

上文知道,如果利用osg读取非标准化的纹理数据,osg会自动调用函数gluScaleImage函数对其进行标准归一化处理,当texture采样时其实采样的已经不是最原始的纹理数据!

到此为止,一个严重问题:如果想对 非标准纹理非归一化的纹理数据采样,那应该如何操作?
要解决此问题,目前好像只有修改源码(如果有其他方法,请一定留言联系,谢谢)。
在osg源码中,追踪并搜索gluScaleImage函数,发现这两个函数都是在一个if(needRescalImage):
图片描述

将该函数整个注释掉,然后修改glTexImage函数的参数为image的宽和高即可,最后重新生成dll库。这样,osg便不会自作聪明的对读入的纹理数据执行gluScaleImage操作。

总结:
1、纹理也是数据,具有数据的一般属性,如值的大小、正负、存储方式等,一副普通图片可以当做纹理进行采样,但纹理并不单纯的指代图片。
2、在osg中,如果读入的是非标准纹理,则osg会默认调用gluScaleImage函数对纹理进行归一化处理,所谓归一化是指将数据大小限制在0-1之间,将分辨率调节为最接近原始图像分辨率的2的幂次方项。进行归一化操作时,osg虽然不会报错,但是会给出提示,类似:Scaling image 纹理路径 from (3600,1720) to (4096,2048)。此时glsl语言中texture函数采样的结果已经不是最原始纹理的纹素数据了。
3、要想利用osg读取非标准格式的纹理数据,目前来看好像必须修改源码,重新生成dll文件。
4、修改某开源项目数据源码时,一定确认自己的程序所调用的dll库,并最好明确依赖关系,如果库调用不对,即使修改源码也不会起作用。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值