OpenGl-纹理

850 阅读3分钟

OpenGl-纹理

前言

你将了解

  • 纹理的定义
  • 使用opengl加载纹理

本节代码在:opengldemo/drawTexture.cpp at main · zhouyueyuedsf/opengldemo (github.com)

纹理的定义

我们从多个角度来看纹理的定义

  • 纹理本质是一张图片
  • 具象来说,就是一张图片贴到2d/3d物体上,形成映射关系
  • 以2d物体为例。我们将纹理归一化到[0,1]的坐标下,然后将2d物体放到坐标系原点. 如下图。 这样会形成p与p'的映射关系,数据关系则为p' = Mp

Untitled.png

  • 如上面描述的公式,该映射关系并不是一对一的,有可能物体上的两个点都对应纹理的同一个点。opengl中这个M有多种情况即多种纹理的环绕方式

纹理的环绕方式

  • GL_REPEAT: 默认行为,重复纹理,如下图

Untitled 1.png

  • GL_MIRRORED_REPEAT:和第一点一样,不过是镜像放置的 Untitled 2.png

  • GL_CLAMP_TO_EDGE: 重复最后一个像素,这里只绘制了x轴

  • GL_CLAMP_TO_BORDER: 超出的坐标指定边缘色

代码设置环绕方式

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT);
  • GL_TEXTURE_WRAP_S:可以理解为x方向
  • GL_TEXTURE_WRAP_T: 理解为Y方向

另一个角度理解环绕

Untitled 3.png

  • 绿色为物体[-0.5, 0.5]的正方体, 黄色映射(M)的纹理坐标[-1.5, 1.5], 蓝色为由多个纹理像素组成的[-2,2]的正方形纹理
  • 如果是重复环绕(GL_REPEAT),最后得到的结果就是黄色矩阵透下的蓝色部分
  • 此时纹理坐标和纹理像素其实是一一对应的, 在实际运行中,需要做的就是把纹理像素坐标(soil加载出来的)归一化

纹理过滤

即使通过M找到了对应的纹理像素,如下图,找到了蓝色的像素,opengl 会多种策略返回最终的纹理像素。这里看两种,GL_NEAREST和GL_LINEAR

Untitled 4.png

  • GL_NEAREST: 就是直接返回蓝色
  • GL_LINEAR:通过附近像素,计算一个最终像素

代码配置如下:

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

纹理加载

基础知识铺垫完了,我们可以用opengl加载一个纹理(图片)分为 几个步骤

  • 读取图片
  • 图片转纹理
  • 绘制纹理

读取图片为纹理

使用soil,soil是一个读取图片纹理的包

int width, height;
unsigned char* image = SOIL_load_image("xxxxx.jpg", &width, &height, 0, SOIL_LOAD_RGB);
  • soil加载一张图片,会自带一个坐标系,可以简单理解为图片坐标系。取像素的时候,就是通过该坐标系取。
  • 该坐标系以左上为原点,和opengl是相反的,所以不做处理的话,默认图片显示是反的

image转纹理

GLuint texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, image);
  • 纹理也是通过id引用的
  • 包含配置,和转换

绘制纹理

opengl绘制必然包含片元着色器,需要理一下写这个的思路

  • 着色器的输入:顶点的映射关系, 纹理坐标,纹理像素,映射关系M矩阵
  • 着色器的输出:色值

顶点映射关系,纹理坐标 由顶点坐标给出

GLfloat vertices[] = {
//     ---- 位置 ----       ---- 颜色 ----     - 纹理坐标 -
     0.5f,  0.5f, 0.0f,   1.0f, 0.0f, 0.0f,   1.0f, 1.0f,   // 右上
     0.5f, -0.5f, 0.0f,   0.0f, 1.0f, 0.0f,   1.0f, 0.0f,   // 右下
    -0.5f, -0.5f, 0.0f,   0.0f, 0.0f, 1.0f,   0.0f, 0.0f,   // 左下
    -0.5f,  0.5f, 0.0f,   1.0f, 1.0f, 0.0f,   0.0f, 1.0f    // 左上
};
  • 这里省略内存描述VAO,VBO,VEO了。辛苦读者去前一节看看
  • 顶点坐标需要交给顶点着色器,如下代码。它将纹理坐标透传给了片元着色器
#version 330 core
layout (location = 0) in vec3 position;
layout (location = 1) in vec3 color;
layout (location = 2) in vec2 texCoord;

out vec2 TexCoord;

void main()
{
    gl_Position = vec4(position, 1.0f);
    TexCoord = texCoord;
}

纹理像素用之前绑定的texture,M矩阵有着色器内部函数texture给到,如下代码

#version 330 core
in vec3 ourColor;
in vec2 TexCoord;

out vec4 color;

uniform sampler2D ourTexture;

void main(){
    color = texture(ourTexture, TexCoord);
}
  • 这里ourTexture需要外部输入

    // 这里只有一个纹理,默认位置为0,对应着第二步的纹理对象
    glUniform1i(glGetUniformLocation(shader.Program, "ourTexture"), 0);
    
  • texture的类似是sampler2D, 如果是3d纹理,可以设置sampler3D

  • 映射关系由函数texture(ourTexture, TexCoord)内部处理,返回了片元颜色