OpenGL入门之纹理Texture
Posted 木大白易
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了OpenGL入门之纹理Texture相关的知识,希望对你有一定的参考价值。
本系列文章为Learn OpenGL个人学习总结!
OpenGL入门(一)之认识OpenGL和创建Window
OpenGL入门(二)之渲染管线pipeline,VAO、VBO和EBO
OpenGL入门(三)之着色器Shader
OpenGL入门(四)之纹理Texture
OpenGL入门(五)之Matrix矩阵操作和坐标系统
OpenGL进阶(一)之帧缓冲FrameBuffer
OpenGL进阶(二)之像素缓冲PixelBuffer
纹理
纹理是一个2D图片(甚至也有1D和3D的纹理),它可以用来添加物体的细节!我们可以在一张图片上插入非常多的细节,这样就可以让物体非常精细而不用指定额外的顶点,来减小开销!
为了能够把纹理映射(Map)到三角形上,我们需要指定三角形的每个顶点各自对应纹理的哪个部分。这样每个顶点就会关联着一个纹理坐标(Texture Coordinate),用来标明该从纹理图像的哪个部分采样(采集片段颜色)。之后在图形的其它片段上进行片段插值(Fragment Interpolation)。
纹理坐标在x和y轴上,范围为0到1之间(注意我们使用的是2D纹理图像)。使用纹理坐标获取纹理颜色叫做采样(Sampling)。纹理坐标起始于(0, 0),也就是纹理图片的左下角,终始于(1, 1),即纹理图片的右上角。
一个纹理坐标:
float texCoords[] =
0.0f, 0.0f, // 左下角
1.0f, 0.0f, // 右下角
0.5f, 1.0f // 上中
;
纹理环绕方式
当纹理坐标超出默认范围时,可以设置环绕方式来展示不同的视觉效果输出!默认为GL_REPEAT
,还有GL_MIRRORED_REPEAT
,GL_CLAMP_TO_EDGE
和GL_CLAMP_TO_BORDER
!
前边提到过纹理坐标的分量可以通过STPQ
访问:
// set the texture wrapping parameters
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); // set texture wrapping to GL_REPEAT (default wrapping method)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
纹理过滤
纹理像素(Texture Pixel):组成一张图片的无数个像素点
纹理坐标:设置的顶点数组
OpenGL以这个顶点的纹理坐标数据去查找纹理图像上的像素,然后进行采样提取纹理像素的颜色。那么就会有一个问题,在对应查找像素的时候,用什么方式去拿像素颜色,OpenGL提供了纹理过滤来帮助我们选择,这里有两个重要的选项!
- GL_NEAREST(也叫邻近过滤,Nearest Neighbor Filtering)是OpenGL默认的纹理过滤方式。当设置为GL_NEAREST的时候,OpenGL会选择中心点最接近纹理坐标的那个像素。
- GL_LINEAR(也叫线性过滤,(Bi)linear Filtering)它会基于纹理坐标附近的纹理像素,计算出一个插值,近似出这些纹理像素之间的颜色。
当进行放大(Magnify)和缩小(Minify)操作的时候可以设置纹理过滤的选项:
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); //缩小
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); //放大
多级渐变纹理
多级渐变纹理主要是用来解决物体远近,纹理大小,分辨率不同的问题!距观察者的距离超过一定的阈值,OpenGL会使用不同的多级渐远纹理,即最适合物体的距离的那个,且节省内存!
注意: 多级渐远纹理主要是使用在纹理被缩小的情况下的:纹理放大不会使用多级渐远纹理。
另外:OpenGL提供glGenerateMipmap()
函数,在创建完一个纹理后调用它OpenGL就会为纹理图像创建一系列多级渐远纹理!
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
...//创建纹理
glGenerateMipmap(GL_TEXTURE_2D);
加载和创建纹理
这里使用stb_image.h
库来加载图片!
https://github.com/nothings/stb
使用非常简单,在我们的cpp文件中添加:
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
创建纹理:
int width, height, nrChannels;
//这里会拿到图像的宽高和颜色通道个数
unsigned char *data = stbi_load("../dependency/stb/container.jpg", &width, &height, &nrChannels, 0);
//生成纹理
unsigned int texture1;
glGenTextures(1, &texture1);
glBindTexture(GL_TEXTURE_2D, texture1);//绑定
... //设置环绕和过滤
/**
* @brief 生成纹理
* 第1个参数:指定纹理target
* 第2个参数:指定多级渐远纹理的级别 0:基本级别
* 第3个参数:纹理存储格式。 这里图像只有RGB
* 第4,5个参数:纹理的宽高
* 第6个参数:总设置为0(历史问题)
* 第7,8个参数:源图像的格式和数据类型
* 第9个参数:图像数据
*/
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
glGenerateMipmap(GL_TEXTURE_2D);
...
stbi_image_free(data);//释放图像内存
应用纹理
这里我们绘制一个矩形,并把图片纹理贴在矩形上
//绘制矩形+纹理
float 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 // 左上
;
const char *vertexShaderSource = "#version 330 core\\n"
"layout (location = 0) in vec3 aPos;\\n"
"layout (location = 1) in vec3 aColor;\\n"
"layout (location = 2) in vec2 aTexCoord;\\n"
"out vec3 vertexColor;\\n"
"out vec2 texCoord;\\n"
"void main()\\n"
"\\n"
" gl_Position = vec4(aPos, 1.0);\\n"
" vertexColor = aColor;\\n"
" texCoord = aTexCoord;\\n"
"\\0";
const char *fragmentShaderSource = "#version 330 core\\n"
"in vec3 vertexColor;\\n" //接收顶点着色器中的输入变量(名称、类型相同)
"in vec2 texCoord;\\n" //接收纹理(名称、类型相同)
"out vec4 color;\\n"
"uniform sampler2D texture1;\\n" //声明一个纹理采样器
"void main()\\n"
"\\n"
" color = texture(texture1, texCoord) * vec4(vertexColor, 1.0);\\n" //将输出颜色设置为纹理 第一个参数纹理采样器,第二个参数纹理坐标
"\\0";
...
GLSL内建的texture()
函数,可以得到一个纹理颜色,第一个参数纹理采样器,第二个参数纹理坐标。
这样我们就能绘制出一个正确的纹理图片了!
需要注意的点:
在前边我们了解了uniform
这个变量,这里在片段着色器中声明了一个采样器,但是在代码中却没有给它赋值,目前看工作正常,能够绘制出图片!这是因为OpenGL中有默认的纹理单元0,且这个纹理单元默认激活!
纹理单元
当我们需要绘制多个纹理时,就要使用纹理单元,把纹理单元赋值给采样器,就能够和纹理一一对应起来!
glActiveTexture(GL_TEXTURE0); // 在绑定纹理之前先激活纹理单元
glBindTexture(GL_TEXTURE_2D, texture);
激活一个纹理单元之后,调用glBindTexture
,就会把纹理绑定到当前已激活的纹理单元!
OpenGL至少保证有16个纹理单元供你使用,也就是说你可以激活从GL_TEXTURE0到GL_TEXTRUE15。它们都是按顺序定义的,所以我们也可以通过GL_TEXTURE0 + 8的方式获得GL_TEXTURE8,这在当我们需要循环一些纹理单元的时候会很有用。
修改一下我们的片段着色器:
const char *fragmentShaderSource = "#version 330 core\\n"
"in vec3 vertexColor;\\n" //接收顶点着色器中的输入变量(名称、类型相同)
"in vec2 texCoord;\\n" //接收纹理(名称、类型相同)
"out vec4 color;\\n"
"uniform sampler2D texture1;\\n" //声明一个纹理采样器
"uniform sampler2D texture2;\\n"
"void main()\\n"
"\\n"
" color = mix(texture(texture1, texCoord), texture(texture2, texCoord), 0.2) * vec4(vertexColor, 1.0);\\n" //将输出颜色设置为纹理 第一个参数纹理采样器,第二个参数纹理坐标
"\\0";
...
//有两个纹理时,就需要设置纹理单元
glUseProgram(shaderProgram);
glUniform1i(glGetUniformLocation(shaderProgram, "texture1"), 0);//指定采样器属于哪个纹理单元
glUniform1i(glGetUniformLocation(shaderProgram, "texture2"), 1);
...
//渲染
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture1);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, texture2);
...
GLSL内建的mix
函数需要接受两个值作为参数,并对它们根据第三个参数进行线性插值。如果第三个值是0.0,它会返回第一个输入;如果是1.0,会返回第二个输入值。0.2会返回80%的第一个输入颜色和20%的第二个输入颜色,即返回两个纹理的混合色。
另外一个需要注意的问题:纹理上下颠倒了!这是因为OpenGL要求y轴0.0坐标是在图片的底部的,但是图片的y轴0.0坐标通常在顶部。
stb_image.h能够在图像加载时帮助我们翻转y轴,在加载前调用:
stbi_set_flip_vertically_on_load(true);
这时我们将得到两个纹理叠加的效果!
以上是关于OpenGL入门之纹理Texture的主要内容,如果未能解决你的问题,请参考以下文章
手把手教会OpenGL之纹理贴图包含纹理载入纹理过滤边界处理纹理参数设置(入门级别案例,棋盘)
手把手教会OpenGL之纹理贴图包含纹理载入纹理过滤边界处理纹理参数设置(入门级别案例,棋盘)