OpenGl-纹理
前言
你将了解
- 纹理的定义
- 使用opengl加载纹理
本节代码在:opengldemo/drawTexture.cpp at main · zhouyueyuedsf/opengldemo (github.com)
纹理的定义
我们从多个角度来看纹理的定义
- 纹理本质是一张图片
- 具象来说,就是一张图片贴到2d/3d物体上,形成映射关系
- 以2d物体为例。我们将纹理归一化到[0,1]的坐标下,然后将2d物体放到坐标系原点. 如下图。 这样会形成p与p'的映射关系,数据关系则为p' = Mp
- 如上面描述的公式,该映射关系并不是一对一的,有可能物体上的两个点都对应纹理的同一个点。opengl中这个M有多种情况即多种纹理的环绕方式
纹理的环绕方式
- GL_REPEAT: 默认行为,重复纹理,如下图
-
GL_MIRRORED_REPEAT:和第一点一样,不过是镜像放置的
-
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方向
另一个角度理解环绕
- 绿色为物体[-0.5, 0.5]的正方体, 黄色映射(M)的纹理坐标[-1.5, 1.5], 蓝色为由多个纹理像素组成的[-2,2]的正方形纹理
- 如果是重复环绕(GL_REPEAT),最后得到的结果就是黄色矩阵透下的蓝色部分
- 此时纹理坐标和纹理像素其实是一一对应的, 在实际运行中,需要做的就是把纹理像素坐标(soil加载出来的)归一化
纹理过滤
即使通过M找到了对应的纹理像素,如下图,找到了蓝色的像素,opengl 会多种策略返回最终的纹理像素。这里看两种,GL_NEAREST和GL_LINEAR
- 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)内部处理,返回了片元颜色