OpenGL 的 texture unit 概念,让我懵逼良久...对照原教程的代码,反复阅读揣摩 ES 2.0 spec 字句,才得以拼凑出完整的逻辑。只想说:太坑爹了!多的不说了。
首先,ES spec 中,texture image unit, texture unit, image unit 说的都是同一个东西。
其次,texture object 与 texture unit,是 2 个并列的存在,两...哦不,二者互不隶属。Texture object 和 texture unit 都有(或可以有)多个,例如:程序可以创建多个 texture object,同时,OpenGL 状态机包含若干固定数量的 texture unit,这个数量取决于具体的 OpenGL 实现。OpenGL 用 GL_TEXTURE0
, GL_TEXTURE1
, GL_TEXTURE2
,... 来指代各个 unit。
再次,可能是嫌概念的迷惑性还不够,ES 2.0 又规定每个 texture unit 内有 2 个 target:GL_TEXTURE_2D
和 GL_TEXTURE_CUBE_MAP
。Texture unit 与 target 之间是隶属关系:target 自古以来就是 unit 的不可分割的一部分。Texture object 须通过 glBindTexture()
绑定到某一 unit 的某一 target 上。
最后,在 fragment shader 中,uniform
texture 变量的值选择其对应的 unit,例如:若值为 0,则会选择 GL_TEXTURE0
unit;若值为 0 则选择 GL_TEXTURE1
。此外,变量的类型则选择 unit 中的 target,例如:sampler2D
类型选择 GL_TEXTURE_2D
。
注意到,fragment shader 中的 uniform
与 texture object 之间没有直接关系,而是通过 texture unit 作为中间纽带建立起关联。一图以蔽之:
要点,
uniform
变量的值选择 texture unituniform
的类型选择 texture unit 的 target,如:sampler2D
选择GL_TEXTURE_2D
target- 调用
glBindTexture()
绑定 texture object 到 texture unit 的指定 target
OpenGL 提供 glActiveTexture()
函数,用来选择"当前 unit"。大多 texture 相关的函数,都隐含地针对当前 unit 进行操作,例如,
glBindTexture()
将 texture object 绑定到当前 unit 的指定 targetglTexImage2D()
将像素数据复制到当前 unit 的指定 target 所绑定的 texture object
在 OpenGL 初始状态下,当前 unit 为 GL_TEXTURE0
,同时,shader 中 uniform
变量的默认值为 0。前一课的示例程序中,没有调用过 glActiveTexture()
设定当前 unit,也没有给 fragment shader 的 uniform
sampler2D
变量进行赋值,其原因在于,OpenGL 初始状态恰巧形成了 uniform
变量、texture unit 与 texture object 之间的正确关系。
这一课的例程加载了 2 个 texture,并叠加应用到 3D 模型上:
C++
...
uniform sampler2D s_tex;
uniform sampler2D s_tex1;
void main()
{
gl_FragColor = mix(texture2D(s_tex, v_tex), texture2D(s_tex1, v_tex), 0.2);
}
这里调用了 GLSL 内建函数 mix()
将 2 个颜色值进行混合。运行效果:
程序要设置两个 uniform
的值,建立与 texture unit 之间的关联:
C
GLint uni_tex = glGetUniformLocation(prog, "s_tex");
GLint uni_tex1 = glGetUniformLocation(prog, "s_tex1");
// Use program before setting uniform/sampler value
glUseProgram(prog);
glUniform1i(uni_tex, 0);
glUniform1i(uni_tex1, 1);
接下来将 texture object 绑定到 texture unit:
C
// Bind the 1st texture object to unit 0
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, tex0);
// Bind the 2nd texture object to unit 1
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, tex1);
特别注意在调用 glBindTexture()
前都须调用 glActiveTexture()
选择当前 unit。
上一课的例子所用的图片(木头)本身是 RGB24 格式,因此没有进行像素类型的转换,直接提交给 OpenGL。这次两张图片格式不同,没法偷懒了,程序中必须进行转换。其实也不难,因为 SDL2 已提供了转换函数:
C
SDL_Surface *surf = ...
int w, h;
void *pixels;
{
w = surf->w;
h = surf->h;
pixels = malloc(4 * w * h);
int err = SDL_ConvertPixels(w, h, surf->format->format, surf->pixels, surf->pitch, SDL_PIXELFORMAT_RGBA32, pixels, w * 4);
if (err)
{
...
}
}
SDL_FreeSurface(surf);
不论原始图片格式为何,这里统一转换为 RGBA32。RGBA32 格式有 alpha 通道,支持透明度。
创建 texture object 并复制数据的过程与之前一样,但这里有一个隐藏的细节,与前述当前 texture unit 的概念有关,需要多啰嗦两句。先看代码:
C
typedef struct
{
GLint format;
int width;
int height;
void *pixels;
} texture_image_t;
static GLuint load_texture(const char *path)
{
...
texture_image_t img = ...
GLuint tex;
glGenTextures(1, &tex);
// Active unit does not matter here. We just need to copy texture data to texture object
glBindTexture(GL_TEXTURE_2D, tex);
glTexImage2D(GL_TEXTURE_2D, 0, img.format, img.width, img.height, 0, img.format, GL_UNSIGNED_BYTE, img.pixels);
...
return tex;
}
注意到这里并没有调用 glActiveTexture()
来选择当前 unit。理由是:在创建 texture object 时,当前 unit 无所谓。不管当前 texture unit 具体是哪个,把 texture object 绑定到它,都可以完成复制 texture object 数据的任务。等到程序流程的后面,在设置 sampler2D
变量值以与 texture unit 建立对应关系时,才需要确保当前 texture unit 的正确。
完整程序代码在这里:gitlab.com/sihokk/lear...