OpenGL (二): 纹理绘制

朴灿烈づ我的快乐病毒、 2022-04-10 04:29 532阅读 0赞

纹理绘制

  • 前言
  • 部分代码
    • texture.vs
    • texture.fs
    • main.cpp ( 部分 )
    • 结果
    • 逐行解释
  • 其他API
    • Texture Wrap
    • Texture Mix
      • texture.fs
      • main.cpp
      • 结果
    • Texture Filter
  • 总结

前言

我个人不喜欢”纹理”这个翻译, 所以之后使用 Texture 来替代.
一个几何图案的 Texture 即为与它相绑定的图片, 从上一篇博客可以知道, Fragment Shader 负责计算一个图形的颜色, 而一个图片实际上就是一个颜色复杂的方形, 所以 OpenGL 中 Texture 是由 Fragment Shader 来完成的. 具体的流程是:

  1. 将图片信息加载到 Shader 中
  2. Vertex Shader 将 Texture 图片与几何图形的各个顶点相映射, 并将坐标传递给 Fragment Shader
  3. Fragment Shader 根据该坐标从图片中取样, 从而得到图片对应位置的像素值 (即颜色)

部分代码

如果读过上一篇博客应该能够对 OpenGL 的使用进行自己的简单封装, 所以从本篇博客开始, 只贴部分代码.
.vs 后缀表示这是一个 Vertex Shader
.fs 后缀表示这是一个 Fragment Shader

texture.vs

  1. #version 330 core
  2. layout (location = 0) in vec3 position;
  3. layout (location = 1) in vec2 texCoord;
  4. out vec2 _texCoord;
  5. void main() {
  6. _texCoord = texCoord;
  7. gl_Position = vec4(position, 1.0);
  8. }

其中 texCoord 表示该顶点对应的 Texture 坐标.
Texture 坐标定义为: 左下角为 ( 0, 0 ), 右上角为 ( 1, 1 )

texture.fs

  1. #version 330 core
  2. out vec4 outColor;
  3. in vec2 _texCoord;
  4. uniform sampler2D tex;
  5. void main() {
  6. outColor = texture(tex, _texCoord);
  7. }

其中 tex 变量表示的是该 Texture 的图片数据, texture(tex, _texCoord) 表示取该图片的该坐标的像素值.

main.cpp ( 部分 )

  1. float vertices[20] = {
  2. -0.5f, -0.5f, 0.f, 0.f, 0.f,
  3. -0.5f, 0.5f, 0.f, 0.f, 1.f,
  4. 0.5f, -0.5f, 0.f, 1.f, 0.f,
  5. 0.5f, 0.5f, 0.f, 1.f, 1.f
  6. };
  7. Shader *shader = new Shader("texture.vs", "texture.fs");
  8. int width, height, nChannels;
  9. unsigned char* data = stbi_load("aragaki_yui.jpg", &width, &height, &nChannels, 0);
  10. GLuint texture;
  11. glGenTextures(1, &texture);
  12. glBindTexture(GL_TEXTURE_2D, texture);
  13. glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
  14. glGenerateMipmap(GL_TEXTURE_2D);
  15. // 加载 Vertex Array...
  16. shader->use();
  17. glBindTexture(GL_TEXTURE_2D, texture);
  18. glBindVertexArray(VAO);
  19. glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);

结果

新垣结衣

逐行解释

  1. vertices 数组分为4组, 每行5个元素, 分别是 x, y, z 坐标和其 Texture 的 x, y 坐标. 我们可以看到, 方形的左下角对应 Texture 的左下角 (第一行数据), 方形的右上角对应 Texture 的右上角 (第四行数据). 向 Vertex Shader 送数据的时候应该是这样送:

    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(float) 5, (void)0);

    1. glEnableVertexAttribArray(0);
    2. glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(float) * 5, (void*)(sizeof(float) * 3)); // 最后一个参数是偏移量, 即跳过前面3个float
    3. glEnableVertexAttribArray(1);
  2. Shader 类是对 glProgram 的封装

  3. stbi_load() 函数来自于 stb_image 库, 在这下载. 功能是读取一个图片并返回一个 unsigned char* 指针.
  4. 接下来是生成并绑定一个 Texture 缓冲, 并将图片的数据 Copy 过去, 类似于传递顶点数据, 其中

    1. glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
    2. glGenerateMipmap(GL_TEXTURE_2D);
    • 第一个参数是设置 Mipmap 的等级, 0 表示这是基本等级.
    • 第二个参数表示存到缓冲区的图片格式.
    • 第三, 第四个参数表示图片的宽度和高度.
    • 第五个参数是历史遗留问题, 必须为0.
    • 第六个参数是原始图片的格式. 最后两个参数表示输入图片的数据类型和指针.
  5. glGenerateMipmap() 函数用于生成图片的 Mipmap, Mipmap 简单来说就是为一个图片生成多个尺寸大小成 2 : 1的等比数列的图片序列, 这样的好处是当图片在绘制时如果太小了, 可以使用相应的小图片来进行计算, 从而提高时空效率.

为什么如果不调用 glGenerateMipmap() 则不会绘制图像? 这一点我还没搞懂

其他API

Texture Wrap

当图片的面积小于其多边形的面积时, 多余部分该如何填充?
API 为:

  1. glTexParameteri(GL_TEXTURE_2D, [WRAP_DIMENSION], [WRAP_MODE]);

其中 WRAP_DIMENSION 取值为

  1. GL_TEXTURE_WRAP_S : 表示 x 轴方向
  2. GL_TEXTURE_WRAP_T : 表示 y 轴方向
  3. GL_TEXTURE_WRAP_R : 表示 z 轴方向

其中 WRAP_MODE 取值为下面这 4 个宏:

  1. GL_REPEAT (默认)
    GL\_REPEAT
  2. GL_MIRRORED_REPEAT
    GL\_MIRRORED\_REPEAT
  3. GL_CLAMP_TO_EDGE
    GL\_CLAMP\_TO\_EDGE
  4. GL_CLAMP_TO_BORDER
    需要指定 Border 的颜色, 默认为黑色.

    float borderColor[] = { 0.8f, 0.2f, 0.5f, 1.f};

    1. glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor);

GL\_CLAMP\_TO\_BORDER

Texture Mix

Fragment Shader 可以混合多种 Texture 和颜色:

texture.fs

  1. #version 330 core
  2. out vec4 outColor;
  3. in vec2 _texCoord;
  4. uniform sampler2D tex1;
  5. uniform sampler2D tex2;
  6. void main() {
  7. outColor = mix(texture(tex1, _texCoord), texture(tex2, _texCoord), 0.2) * vec4(1.0, 0.8, 0.95, 1.0);
  8. }

其中 mix 函数表示将两个 Texture 的像素混合, 它的第三个参数表示前者取 0.8, 后者取 0.2.
OpenGL 里颜色混合一般用乘法

main.cpp

  1. // 绑定两次 GL_TEXTURE_2D
  2. glActiveTexture(GL_TEXTURE0);
  3. glBindTexture(GL_TEXTURE_2D, texture);
  4. glActiveTexture(GL_TEXTURE1);
  5. glBindTexture(GL_TEXTURE_2D, bg);
  6. int tex1Loc = shader->getUniformLocation("tex1");
  7. int tex2Loc = shader->getUniformLocation("tex2");
  8. glUniform1i(tex1Loc, 0);
  9. glUniform1i(tex2Loc, 1);
  10. // glBindVertexArray(VAO)...

结果

MIX

Texture Filter

Texture Filter 适用于当一个图片的分辨率太低时, 应如何填充”空隙”, API为

  1. glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, [FILTER_MODE]);

其中 FILTER_MODE有两个取值:

  1. GL_NEAREST (默认)
    GL\_NEAREST
  2. GL_LINEAR
    GL\_LINEAR

总结

Texture 绘制是计算机图形学中十分常用的功能, OpenGL巧妙地将图片数据和 Shader 相联系, 尤其是用 Fragment Shader 来计算图片的像素, 让我对图片的绘制有了新的理解!

发表评论

表情:
评论列表 (有 0 条评论,532人围观)

还没有评论,来说两句吧...

相关阅读

    相关 OpenGL纹理置换编程

    在OpenGL中,纹理贴图是一种常用的技术,用于将图像或图案应用到3D模型表面上。然而,有时仅仅使用静态纹理贴图可能会显得单调,无法达到想要的效果。在这种情况下,使用纹理置换技

    相关 openGL纹理

    纹理压缩技术已经广泛应用在各种3D游戏之中,它们包括:DXTC(Direct X Texture Compress,DirectX纹理压缩,以S3TC为基础)、S3TC(S3

    相关 OpenGL纹理贴图

    1.纹理贴图的步骤 1)创建纹理对象,并为它指定一个纹理。 glGenTextures() glGenerateMipmap() 2)确定纹理如何应用到每个像素上 g