OSG使用纹理存储数据并在shader中读取

在日常的opengl编程中,我们经常把向量、颜色、纹理坐标等逐顶点的属性参数,通过顶点属性的方式传入着色器shader里,每种顶点参数对应唯一的一个序号id,即为vertex attribute index。这个index一般从0开始递增排列,当然也可以不按顺序申明。查询资料可知,通过调用GL_MAX_VERTEX_ATTRIBS能得到当前电脑的显卡支持最多多少个顶点属性,这个值最少为16,一般也就16。那么为了保险起见,我们申请顶点属性也不会超过这个数值。

除了逐顶点的参数外,平常编程中可能还会遇到需要所有顶点都能访问的参数的情况,这个时候一般使用uniform关键字来申明这些变量。常用的单个变量可以用float、int、vec3、sampler2D等类型;也可以用定长数组来指定同种类型的多个统一变量,比如uniform float input[100];。但是如果我们需要用统一变量传输大量的、不同的数据给着色器,那该如何处理呢?

最近学到一个使用纹理储存大量统一变量的方法。它的原理是通过把所有参数写进一张纹理图片里,根据特定id来读取相关数据。下面用一个例子来说明一下该方法的流程。

问题描述

已知有[0, m]个对象,每个对象包含n种数据,现在将这些对象全部储存在纹理里,并且能在着色器里访问每个对象的每个数据。

实现思路

申请纹理

以二维纹理为例,假设申请一个长x像素,宽y像素的纹理对象,如果把每个像素当成一个数据单元,那么该图片可以最多保存x * y个数据单元。根据纹理的像素格式,每个数据单元可以储存的数据量由纹理的像素格式pixelFormat和数据类型type决定。
比如像素格式为GL_RGBA代表其为四通道图片,数据格式为GL_FLOAT代表每个通道储存一个float数据,那么一个数据单元可以储存4个float数据;而像素格式为GL_RED代表为单通道数据,数据类型为GL_UNSIGNED_BYTE代表无符号整型,那么每个数据单元储存一个范围为0-255的整数。本质上每个通道储存的是一个8位二进制数据,所以即使需要储存的是一些特殊数据类型,比如双精度浮点数double或者longlong等类型,也可以通过分段的方法来储存。
例子中对象数量最多为m,每个对象的数据类型为n种,所以申请一个m*n大小的图片用于纹理即可;以采用四通道float类型的数据单元为例。对应的纹理申请和初始化如下:

osg::ref_ptr<osg::Texture2D> rpItemInfoTex = new osg::Texture2D();
//禁止自动缩放尺寸到2的幂,防止采数据时出现以外
rpItemInfoTex->setResizeNonPowerOfTwoHint(false);
rpItemInfoTex->setInternalFormat(GL_RGBA);
rpItemInfoTex->setSourceFormat(GL_RGBA);
rpItemInfoTex->setSourceType(GL_FLOAT);
rpItemInfoTex->setDataVariance(DYNAMIC);
//设置最近采样,防止数据采集时因为插值导致取得错误数据
rpItemInfoTex->setFilter(osg::Texture2D::MIN_FILTER, osg::Texture2D::NEAREST);
rpItemInfoTex->setFilter(osg::Texture2D::Mag_FILTER, osg::Texture2D::NEAREST);

rpItemInfoTex->setImage(new osg::Image());
osg::Image* pInfoImage = rpItemInfoTex->getImage();
pInfoImage->setInternalTextureFormat(GL_RGBA);

int m, int n;
pInfoImage->allocateImage(m, n, 1, GL_RGBA, GL_FLOAT);
memset((void*)pInfoImage->getDataPointer(), 0, pInfoImage->getTotalSizeInBytes());
rpItemInfoTex->dirtyTextureObject();

数据的写入

假设需要对第i个对象的第j个数据的第k个通道里的数据进行写入,那么可以使用如下语句:


*((float*)(rpItemInfoTex->getImage()->data(i, j)) + k) = input;

rpItemInfoTex->dirtyTextureObject();

代码中使用osg::Imagedata()接口可以访问到第i列第j行的像素数据指针,它指向第0个通道的值,所以这个指针+k就能指向第k个通道,再强转成float指针就能输入单精度浮点数数据。同理,如果纹理的数据格式是GL_UNSIGNED_BYTE的话,那么将指针强转成unsigned char就行。(不强转直接用也没问题,因为data接口返回的就是unsigned char指针)。
数据全部写入完成后,记住要调用dirtyTextureObject()让纹理数据能正确生效。这个接口的重要性在如果你的这些数据需要频繁变动时更为重要。

在着色器中读取相关数据


//一些基本变量
uniform sampler2D           u_InfoTex;      //数据储存纹理
uniform vec2                u_InfoTexSize;  //纹理的长宽,也就是(m, n)


//假设需要对第i个对象的第j个数据进行访问
float texCoordS = (i * 1.f + 0.5f) / u_InfoTexSize.x;
float texCoordT = (j * 1.f + 0.5f) / u_InfoTexSize.y;
vec4 value = texture2D(u_InfoTex, vec2(texCoordS, texCoordT));

在数据提取中,有个要点是计算纹理横纵坐标的时候,统统加了个0.5,这是为了在纹理采样的时候采样部位在特定像素的正中心,保证取到的是该像素的数据。经过测试如果不加的话,会出现偶发性的取出错误数据的bug。这个问题最早出现在杨总的osg多实例渲染的例子复现上,我这边用例子代码怎么都从纹理中读不出正确数据,当时请教了杨总也请他看了代码也没看出问题来,没想到最后自己定位到了是这个原因,固特地在此进行记录。

posted @   Joyfulmika  阅读(10)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
点击右上角即可分享
微信分享提示