今天偶尔碰到一种叫texture array的opengl技术,因为看起来不怎么难掌握,因此就地学了学,做了点小小的东西。恩,就这样。——ZwqXin.com
本文来源于 ZwqXin (http://www.zwqxin.com/), 转载请注明
原文地址:http://www.zwqxin.com/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中。
- boolCMainFrame::SetTextureArray(char**filename,GLuint&texid)
- {
- boolStatus=false;
- AUX_RGBImageRec*pImagex[Texnum];
- memset(pImagex,0,sizeof(void*)*Texnum);
- for(inti=0;i<Texnum;i++)
- {
- pImagex[i]=auxDIBImageLoad(filename[i]);
- if(!pImagex[i])returnfalse;
- }
- 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(intj=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);
- }
- //..................
- returnStatus;
- }
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:
- #extensionGL_EXT_gpu_shader4:enable
- uniformsampler2DArraytexarray;
- voidmain()
- {
- vec4texCoord=vec4(gl_TexCoord[0].xy,floor(gl_TexCoord[0].z),gl_TexCoord[0].w);
- vec4color1=texture2DArray(texarray,texCoord.xyz);
- floatbl=fract(gl_TexCoord[0].z);
- texCoord+=vec4(0.0,0.0,1.0,0.0);
- vec4color2=texture2DArray(texarray,texCoord.xyz);
- floatal=fract(gl_TexCoord[0].z);
- gl_FragColor=mix(color1,color2,al*bl);
- }
#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版本的纹理处理,应用前要#extensionGL_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纹理坐标
原文地址: http://www.zwqxin.com/archives/opengl/learn-texture-array.html