今天偶尔碰到一种叫texture array的opengl技术,因为看起来不怎么难掌握,因此就地学了学,做了点小小的东西。恩,就这样。——ZwqXin.com
本文来源于 ZwqXin (http://www.zwqxin.cn/), 转载请注明
原文地址:http://www.zwqxin.cn/archives/opengl/learn-texture-array.html
Texture Array,一种opengl拓展,从名字就可以看出它是什么:存储纹理的数组。当然,数组存什么不可以?但是我真的第一次见它用来存储数组的 - - 纹理是什么?一块内存区域。(OpenGL Shading Language里作者称它为一种“抽象的复杂的数据类型”,好了,与其深究,还是把它看作存储区域吧...)我们通过什么操纵这种内存?纹理单元+纹理ID。前者就是一种object,类似FBO的容器,用来装纹理——但是一般一个纹理单元只装载一张纹理。然而,你可以随意更改一个纹理单元里面那张纹理图:通过纹理ID。可以说,一张纹理图对应一个纹理ID,这个ID在生成纹理(glGenTextures)的时候就唯一指定了,OpenGL靠它标识你载入的纹理。纹理单元(或者你干脆叫它纹理对象[texture object]好了)默认只开启一个,叫“0号纹理单元”,当然你可以用glActive(GL_TEXTUREi)来开启其他的,不多说。一个纹理单元每个瞬间只能绑定一张纹理图(glBindTexture),你不开启其他纹理单元情况下,看到你的程序里面花花绿绿的纹理,其实只不过是该纹理单元不停绑定一个纹理ID,然后解开再绑定下一个——它们不是同时发生的,只不过因为太快(一帧内完成),你的“幻觉”。
好了,小小基础讲到这里。Texture Array纹理数组的最显著特性,就是它不同于以上传统的经验:同样一个纹理单元,却可以在它身上同时绑定多张纹理图。当然不变的仍是纹理单元与纹理ID一一对应,因为这多张纹理图共享一个纹理ID。这可怎么用呢?且听我说。
首先,如何生成一个纹理数组?这跟我们平时载入纹理的过程是很相似的,更确切地说,是跟生成三维纹理的过程相似。(注意,我这里谈论二维纹理数组,Texture Array支持一维与二维纹理。)但是关键的“目标Target”要设成GL_TEXTURE_2D_ARRAY_EXT。注意这里filename是一个指向文件名数组的指针,我载入了Texnum张纹理,把其信息暂存入pImagex数组内,再用载入三维纹理的方法一一载入纹理ID,texid中。
- bool CMainFrame::SetTextureArray(char* *filename, GLuint& texid)
- {
- bool Status=false;
- AUX_RGBImageRec *pImagex[Texnum] ;
- memset(pImagex, 0,sizeof(void *) *Texnum);
- for(int i = 0; i < Texnum; i++)
- {
- pImagex[i] =auxDIBImageLoad(filename[i]);
- if(!pImagex[i]) return false;
- }
- Status=true;
- glGenTextures(1, &texid);
- glBindTexture(GL_TEXTURE_2D_ARRAY_EXT, texid);
- glTexParameteri(GL_TEXTURE_2D_ARRAY_EXT, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
- glTexParameteri(GL_TEXTURE_2D_ARRAY_EXT, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
- glTexParameteri(GL_TEXTURE_2D_ARRAY_EXT, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
- glTexParameteri(GL_TEXTURE_2D_ARRAY_EXT, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
- glTexParameteri( GL_TEXTURE_2D_ARRAY_EXT, GL_GENERATE_MIPMAP_SGIS, GL_TRUE);
- glTexImage3D(GL_TEXTURE_2D_ARRAY_EXT, 0, GL_RGBA8, pImagex[0]->sizeX, pImagex[0]->sizeY, Texnum, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL);
- for(int j = 0; j < Texnum; j++)
- {
- glTexSubImage3D(GL_TEXTURE_2D_ARRAY_EXT, 0, 0,0,j, pImagex[j]->sizeX, pImagex[j]->sizeY,1, GL_RGB, GL_UNSIGNED_BYTE, pImagex[j]->data);
- }
- //..................
- return Status;
- }
用法也很简单,不过得fragment shader配合。注意设定纹理坐标得时候要指定r纹理坐标,它表明该顶点选择的是纹理数组中第几张纹理图的相应st纹理坐标。纹理图的排列顺序是上面载入时候的顺序(因此这里也就是filename数组中相应文件名的顺序),载入纹理数组后,用“Layer”来标识(相当于数组下标)。Layer 0就是第一张纹理,依此类推。glTexCoord3f(1.0, 0.0, 1.0)表示对当前顶点应用第二张纹理(Layer2)中(1.0, 0.0)的st坐标。虽然r纹理坐标是浮点数,但因为这里它只起指涉作用,因此只有整数部分有作用(别以为可以偏移,至少我试过,不可以。小数部分依然对选择有影响,但这种影响是混乱的),在shader中,我们要用floor取整。
应用纹理,不能通过以往“绑定纹理ID”的做法,而要通过shader,在需要应用纹理数组做纹理的图元应用shader:
- #extension GL_EXT_gpu_shader4 : enable
- uniform sampler2DArray texarray;
- void main()
- {
- vec4 texCoord = vec4(gl_TexCoord[0].xy, floor(gl_TexCoord[0].z),gl_TexCoord[0].w);
- vec4 color1 = texture2DArray(texarray, texCoord.xyz);
- float bl = fract(gl_TexCoord[0].z);
- texCoord += vec4(0.0, 0.0, 1.0,0.0);
- vec4 color2 = texture2DArray(texarray, texCoord.xyz);
- float al = fract(gl_TexCoord[0].z);
- gl_FragColor = mix( color1, color2, al*bl);
- }
其中vertex shader没什么用,直接去掉(disattach)也可 - -不过我还是习惯让顶点shader和像素shader在一起。注意的是在vertex shader传递过来的(或者从应用直接过来的)gl_TexCoord[0]的r坐标(shader中叫p坐标,这里用z表示)要取整(floor),因为在像素处理前各像素的纹理坐标是经过插值的(栅格化Rasterization,这里有提到),而对每个像素真正有用的r坐标只为了指出该像素位置用的是第几张纹理的纹理坐标。采样变量sampler2DArray和纹理颜色获取函数texture2DArray用于texture array版本的纹理处理,应用前要#extension GL_EXT_gpu_shader4 : enable 启用拓展。shader这里如果直接输出color1会是如下结果(举例):
而用我的shader做pingpong后可以有这样的边界朦胧效果(另举例)
这里mix混合了两次具有偏移的纹理坐标采样所得的颜色。注意我偏移的是r纹理坐标(偏移1单位),这样就可在隔壁纹理图上采样了。用小数部分做混合因子,得到的是相邻纹理的边界混合。美妙!
试试把上面的效果应用到一座山怎么样?呵呵,我试了,还做了demo呵呵。就附在下面的演示demo里。看看下篇文章:Terrain Texture-Array Demo看看我是怎么做的吧~
本日志demo放出:TextureArrayDemobyzwqxin.rar
DEMO使用了shader,需要下载glew库到指定目录,下载见此:OpenGL常用的库
按键:
↑ ↓ 在自动放映完毕后可手动调节矩形角点r纹理坐标
本文来源于 ZwqXin (http://www.zwqxin.cn/), 转载请注明
原文地址:http://www.zwqxin.cn/archives/opengl/learn-texture-array.html