OpenGL ES 2.0 笔记 #6:Texture Unit

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_2DGL_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 unit
  • uniform 的类型选择 texture unit 的 target,如:sampler2D 选择 GL_TEXTURE_2D target
  • 调用 glBindTexture() 绑定 texture object 到 texture unit 的指定 target

OpenGL 提供 glActiveTexture() 函数,用来选择"当前 unit"。大多 texture 相关的函数,都隐含地针对当前 unit 进行操作,例如,

  • glBindTexture() 将 texture object 绑定到当前 unit 的指定 target
  • glTexImage2D() 将像素数据复制到当前 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...

相关推荐
刘好念4 天前
[OpenGL]使用OpenGL实现硬阴影效果
c++·计算机图形学·opengl
闲暇部落5 天前
Android OpenGL ES详解——纹理:纹理过滤GL_NEAREST和GL_LINEAR的区别
opengl·texture·linear·纹理过滤·nearest·邻近过滤·线性过滤
凌云行者6 天前
OpenGL入门005——使用Shader类管理着色器
c++·cmake·opengl
凌云行者6 天前
OpenGL入门006——着色器在纹理混合中的应用
c++·cmake·opengl
凌云行者9 天前
OpenGL入门004——使用EBO绘制矩形
c++·cmake·opengl
闲暇部落9 天前
Android OpenGL ES详解——模板Stencil
android·kotlin·opengl·模板测试·stencil·模板缓冲·物体轮廓
凌云行者11 天前
OpenGL入门003——使用Factory设计模式简化渲染流程
c++·cmake·opengl
凌云行者12 天前
OpenGL入门002——顶点着色器和片段着色器
c++·cmake·opengl
闲暇部落13 天前
Android OpenGL ES详解——裁剪Scissor
android·kotlin·opengl·窗口·裁剪·scissor·视口
彭祥.18 天前
点云标注工具开发记录(四)之点云根据类别展示与加速渲染
pyqt·opengl