# 04_hello_texture **Repository Path**: open-gl_3/04_hello_texture ## Basic Information - **Project Name**: 04_hello_texture - **Description**: 中文教程地址:https://learnopengl-cn.readthedocs.io/zh/latest/01%20Getting%20started/06%20Textures/ 英语教程地址:https://learnopengl.com/Getting-started/Textures - **Primary Language**: Unknown - **License**: GPL-3.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2024-05-26 - **Last Updated**: 2024-06-03 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # README 官方教程 - [Getting started -- Textures](https://learnopengl.com/Getting-started/Textures) - [入门 -- 纹理](https://learnopengl-cn.readthedocs.io/zh/latest/01%20Getting%20started/06%20Textures/) - [LearnOpenGL code](https://github.com/JoeyDeVries/LearnOpenGL/tree/master) ## 0. 哔哩哔哩视频教程 - [大名鼎鼎的Learn OpenGL的视频教程](https://www.bilibili.com/video/BV1Sv411g7pp/) ## 1. 纹理贴图 (Texture) 个人理解:$\color{red}{纹理texture有自己的坐标系,独立于vertex的坐标系,它们之间通过关联的方式来实现映射}$。 纹理坐标(`x轴`,`y轴`)的范围为`[0, 1]`,左下角为(0, 0),右上角为(1, 1)。如下图所示: ![纹理坐标示意图](/notes/image/tex_coords.png) 个人理解:通过如下定义,将纹理坐标轴范围映射到vertex坐标轴范围: ```cpp 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 // 左上 }; unsigned int indices[] = { 0, 1, 3, // first triangle 1, 2, 3 // second triangle }; ``` 上面代码定义两个三角形,每个顶点坐标对应一个2D纹理坐标。渲染过程中,使用线性插值的方式,找到具体的纹理坐标位置。 ### 1.1 纹理过滤(Texture Filtering) 即纹理坐标线性插值的方式,有两种:使用最近坐标点的纹理颜色(`GL_NEAREST`);使用线性插值的方式,计算出中间的颜色值(`GL_LINEAR`)。 - `GL_NEAREST`又叫作(最近邻插值)`nearest neighbor filtering` / (点插值)`point filtering`。 - `GL_LINEAR`又叫作(双线性插值)`bilinear filtering` / (线性插值)`linear filtering`。 过滤方式示意图如下所示(左边是`GL_NEAREST`,右边是`GL_LINEAR`): ![纹理过滤示意图](/notes/image/filter_nearest_vs_filter_liner.png) 使用过程中,`GL_LINEAR`的效果更好,因为它可以避免锯齿状的边缘。 ### 1.2 设置纹理过滤 在放大(`magnifying`)或缩小(`minifying`)或缩小图像的操作中,可以设置纹理过滤方式(或分别设置为不同的过滤方式): ```cpp glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); ``` ### 1.3 纹理环绕(Texture Wrapping) 当纹理坐标超出范围时,可以选择纹理环绕方式(`GL_REPEAT` / `GL_MIRRORED_REPEAT` / `GL_CLAMP_TO_EDGE` / `GL_CLAMP_TO_BORDER`)。如下代码所示,将纹理坐标设置为两倍的图像大小,且木箱贴图的环绕方式设置为`GL_CLAMP_TO_EDGE`,笑脸贴图环绕方式设置为`GL_REPEAT`: ```cpp GLfloat vertices[] = { // ---- 位置 ---- ---- 颜色 ---- - 纹理坐标 - 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 2.0f, 2.0f, // 右上 0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 2.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, 2.0f // 左上 }; ``` ```cpp glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); ``` 呈现的结果如下图所示(见demo src\texture_wrapping): ![环绕纹理示意图](/notes/image/texture_wrapping_result.png) ## 2. 多级渐远纹理(Mipmap) 假设我们有一个包含着上千物体的大房间,每个物体上都有纹理。有些物体会很远,但其纹理会拥有与近处物体同样高的分辨率。由于远处的物体可能只产生很少的片段,OpenGL从高分辨率纹理中为这些片段获取正确的颜色值就很困难,因为它需要对一个跨过纹理很大部分的片段只拾取一个纹理颜色。在小物体上这会产生不真实的感觉,更不用说对它们使用高分辨率纹理浪费内存的问题了。 OpenGL使用一种叫做多级渐远纹理(Mipmap)的概念来解决这个问题,它简单来说就是一系列的纹理图像,后一个纹理图像是前一个的二分之一。多级渐远纹理背后的理念很简单:距观察者的距离超过一定的阈值,OpenGL会使用不同的多级渐远纹理,即最适合物体的距离的那个。由于距离远,解析度不高也不会被用户注意到。同时,多级渐远纹理另一加分之处是它的性能非常好。 让我们看一下多级渐远纹理是什么样子的: ![多级渐远纹理示意图](/notes/image/mipmaps.webp) 在渲染中切换多级渐远纹理级别(Level)时,OpenGL在两个不同级别的多级渐远纹理层之间会产生不真实的生硬边界。就像普通的纹理过滤一样,切换多级渐远纹理级别时你也可以在两个不同多级渐远纹理级别之间使用NEAREST和LINEAR过滤。为了指定不同多级渐远纹理级别之间的过滤方式,你可以使用下面四个选项中的一个代替原有的过滤方式: ![多级渐远纹理过滤示意图](/notes/image/mimaps_filter_table.png) 更多阅读 [【转】OpenGL超级宝典笔记——纹理映射Mipmap](https://www.cnblogs.com/cack/p/4972806.html)。 ## 3. 添加纹理之后的渲染流程 流程图软件 [github - drawio-desktop](https://github.com/jgraph/drawio-desktop) ![渲染流程图](/notes/总结-流程图.svg) ## 4. 纹理单元 $\color{red}{总结:}$ 一个纹理的位置值称为一个 $\color{red}{纹理单元(Texture Unit)}$ ,`OpenGL`至少有16个 `纹理单元(Texture Units)` 可供使用,从 $\color{red}{GL_TEXTURE0}$ 到 $\color{red}{GL_TEXTURE15}$。 如果不指定,默认使用 `GL_TEXTURE0`。如果有多个纹理需要叠加,需要将不同的纹理贴图加载到不同的`纹理单元(Texture Units)`中,首先需要激活对应的`纹理单元(Texture Units)`: ```cpp glActiveTexture(GL_TEXTURE0); // 在绑定纹理之前先激活纹理单元 glBindTexture(GL_TEXTURE_2D, texture); ``` 激活纹理单元之后,接下来的 `glBindTexture` 函数调用会绑定这个纹理到当前激活的`纹理单元`。如果不指定,则默认使用 `GL_TEXTURE0`,或者延续上次调用 `glActiveTexture` 时指定的纹理单元。 接下来,使用 `glUniform1i` 给`纹理采样器`分配一个位置值,即将`采样器`与`纹理单元`关联起来。 完整关联`纹理单元`和`纹理对象`,以及`采样器`的过程,如下代码所示:(`fragment shader`代码,以及`CPU`端的渲染循环代码): ```glsl #version 330 core ... uniform sampler2D ourTexture1; uniform sampler2D ourTexture2; void main() { color = mix(texture(ourTexture1, TexCoord), texture(ourTexture2, TexCoord), 0.2); } ``` ```cpp glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, texture1); glUniform1i(glGetUniformLocation(ourShader.Program, "ourTexture1"), 0); glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, texture2); glUniform1i(glGetUniformLocation(ourShader.Program, "ourTexture2"), 1); glBindVertexArray(VAO); glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); glBindVertexArray(0); ``` 如上面的`CPU`端代码,如果指定`纹理单元`为`GL_TEXTURE4`,则使用`glUniform1i`时,对应的位置值应该为`4`。 ### 4.1 参考 - [Example/Texture Shader Binding](https://www.khronos.org/opengl/wiki/Example/Texture_Shader_Binding)。 ### 4.2 Demo 路径为 `src\mix_texture`。 ## 5. 练习代码 ### 5.1 纹理X轴方向翻转 代码路径:`src\mix_texture_revert_x`。`Fragment Shader`代码路径:`src\glsl\fragment_mix_and_revert.glsl`。 翻转纹理坐标的X分量。 ```glsl vec2 revx = vec2(1.0 - ourTexCoord.x, ourTexCoord.y); // reverse x-axis of texture 2 FragColor = mix(texture(ourTexture1, ourTexCoord), texture(ourTexture2, revx), 0.2); ``` ### 5.2 使用uniform变量控制片段着色器的纹理混合比例 代码路径:`src\mix_texture_range`。`Fragment Shader`代码路径:`src\glsl\fragment_mix_range.glsl`。