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::Image
的data()
接口可以访问到第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多实例渲染的例子复现上,我这边用例子代码怎么都从纹理中读不出正确数据,当时请教了杨总也请他看了代码也没看出问题来,没想到最后自己定位到了是这个原因,固特地在此进行记录。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本