OpenGL:纹理

本篇博文讲解OpenGL纹理贴图

一.纹理

纹理:Texture,大部分情况下是一个2D图片(也有1D和3D的纹理)

可以想象纹理是一张绘有砖块的纸,贴到一个3D的房子上,这样房子看起来就像有砖墙外表了

如下512x512的砖墙图就是一个纹理:

接下来就学习怎么把这张纹理贴到之前绘制的三角形上

首先需要定义一组纹理坐标,指定三角形的每个顶点各自对应纹理的哪个部分

二.纹理坐标

2D纹理图像的纹理坐标在x和y轴上,范围(0,1),原点是左下角坐标(0, 0),右上角坐标是(1, 1)

使用纹理坐标获取纹理颜色叫做采样(采集片段颜色)

下图展示纹理坐标与三角形的映射:

我们只要给顶点着色器传递映射到三角形的三个纹理坐标就行了,

接下来它们会被传到片段着色器中,它会为每个片段进行纹理坐标的插值

纹理坐标:

float texCoords[] = {
    0.0f, 0.0f, // 左下角
    1.0f, 0.0f, // 右下角
    0.5f, 1.0f // 上中
};

设置了纹理坐标后,

还要告诉OpenGL进行纹理采样的方式

这就是下面还需要继续配置的纹理环绕模式纹理过滤方式

三.纹理环绕

上一节讲到,纹理坐标的范围是从(0,0)到(1,1)

超过这个范围的,OpenGL会默认重复这个纹理图像

其实OpenGL是由其他更多选择的,只不过需要我们进行配置:

环绕方式

描述

GL_REPEAT

默认:重复纹理图像

GL_MIRRORED_REPEAT

纹理图像镜像重复

GL_CLAMP_TO_EDGE

纹理图像边缘重复

GL_CLAMP_TO_BORDER

用户指定的边缘颜色填充

这些选项可以使用 glTexParameter() 函数对单独的坐标轴进行设置

2D纹理坐标轴:s、t

3D纹理坐标轴:s、t、r

示例:

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT);

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT);

参数一:纹理目标

参数二:纹理坐标轴

参数三:环绕方式

如果是GL_CLAMP_TO_BORDER选项,还需要调用glTexParameterfv()函数指定一个边缘颜色:

float borderColor[] = { 1.0f, 1.0f, 0.0f, 1.0f };

glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor);

四.纹理过滤

纹理过滤可以理解成OpenGL在用纹理进行贴图时,对纹理像素进行采样的一种方式

纹理过滤有很多个选项,最重要的有两种:GL_NEAREST GL_LINEAR

GL_NEAREST:邻近过滤(Nearest Neighbor Filtering),OpenGL默认的纹理过滤方式

OpenGL会选择中心点最接近纹理坐标的那个像素作为采样颜色

下图中有四个像素,加号代表纹理坐标,左上角的纹理像素中心离纹理坐标最近,所以它的像素颜色会被采样:

GL_LINEAR:线性过滤(Linear Filtering)

OpenGL会基于纹理坐标附近的纹理像素,计算出一个近似于这些纹理像素之间的颜色。

一个纹理像素的中心距离纹理坐标越近,那么这个纹理像素的颜色对最终的样本颜色的贡献越大。

下图中你可以看到返回的颜色是邻近像素的混合色:

在一个很大的物体上应用一张低分辨率的纹理,这两种纹理过滤方式有什么不同的效果,

看下面这张图:

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渲染性能,而且小物体上使用大分辨率纹理也会产生不真实感

针对这一点,OpenGL使用了一种叫做多级渐远纹理(Mipmap)的方式

这个概念如果基于官方文档的释义有点难以理解透彻

在解释多级渐元纹理先介绍一个图形图像领域里很常见的术语:图像金字塔

就是一张原始大小的图以一个scalar系数进行阶梯式缩小采样,创建不同scalar层级的图像,就叫图像金字塔

OpenGL多级渐远纹理模式里,这个scalar是1/2

创建一个纹理后,调用glGenerateMipmaps()函数,并传入绑定了纹理ID的OpenGL纹理类型,就可以开启这个纹理的多级渐远模式:

unsigned int texture;

glGenTextures(1, &texture);

glBindTexture(GL_TEXTURE_2D, texture);

...

glGenerateMipmap(GL_TEXTURE_2D);

OpenGL会根据物体的远近,从这个纹理的图像金字塔中选取一张分辨率最适合物体的纹理进行贴图

OpenGL多级渐远纹理示例:

图像金字塔创建和选取的细节我们都不需要关心,OpenGL会自动为我们处理好,我们需要做的仅仅是调用glGenerateMipmaps()

在渲染中切换多级渐远纹理级别(Level)时,OpenGL在两个不同级别的多级渐远纹理层之间会产生不真实的生硬边界。

就像普通的纹理过滤一样,切换多级渐远纹理级别时你也可以在两个不同多级渐远纹理级别之间使用NEAREST和LINEAR过滤。

为了指定不同多级渐远纹理级别之间的过滤方式,可以使用下面四个选项中的一个代替原有的过滤方式: 

过滤方式描述
GL_NEAREST_MIPMAP_NEAREST使用最邻近的多级渐远纹理来匹配像素大小,并使用邻近插值进行纹理采样
GL_LINEAR_MIPMAP_NEAREST使用最邻近的多级渐远纹理级别,并使用线性插值进行采样
GL_LINEAR_MIPMAP_NEAREST使用最邻近的多级渐远纹理级别,并使用线性插值进行采样
GL_LINEAR_MIPMAP_LINEAR在两个邻近的多级渐远纹理之间使用线性插值,并使用线性插值采样

多级渐远纹理只在纹理被缩小的情况下使用

纹理放大时使用多级渐远纹理不会有任何效果,并且会产生GL_INVALID_ENUM错误代码

六.加载和创建纹理

1.stb_image.h

纹理贴图需要将不同格式图片(png,jpg,bmp....)进行字节序列化,可以使用开源的stb_image.h

与其说是库,其实就是一个.h文件,对各种格式图片进行二进制解析的函数声明和定义都在这个.h文件中

我们只需要将其下载放到inc中,然后在代码中define和include就行了

下载地址:stb/stb_image.h at master · nothings/stb · GitHub

代码加载:

(1)头文件加载

#define STB_IMAGE_IMPLEMENTATION

#include "stb_image.h"

#define STB_IMAGE_IMPLEMENTATION:预处理器会修改头文件,让其只包含相关的函数定义源码,相当于将这个头文件变为一个 .cpp 文件了,代码中这句#define 要在 #include "stb_image.h" 前,要不然就会报如下错:

(2)代码中load图片:

int width, height, nrChannels;

unsigned char *data = stbi_load("container.jpg", &width, &height, &nrChannels, 0);

stbi_load()函数:

参数一:图片路径

参数二、三:图片宽高

参数四:颜色通道个数

2.生成纹理

(1) 创建纹理ID:

unsigned int texture;

glGenTextures(1, &texture);

(2) 绑定纹理ID到OpenGL:

glBindTexture(GL_TEXTURE_2D, texture);

(3) glTexImage2D()生成纹理:

glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);

glGenerateMipmap(GL_TEXTURE_2D);

glTexImage2D()函数:

参数一:当前绑定到OpenGL的2D纹理

参数二:多级渐远纹理级别,如果单独手动设置,就填0

参数三:纹理被存储的格式

参数四、五:纹理宽高

参数六:设置为0,别问为什么,OpenGL历史遗留问题

参数七、八:源图格式和数据类型 (我们使用RGB值加载这个图像,并把它们储存为char(byte)数组)

参数九:真正的图像数据

调用glTexImage2D()后,当前绑定到OpenGL的纹理对象就会被附加上纹理图像

如果不设置多级渐远纹理,OpenGL就只会加载原始纹理图像

参数二可以让我们手动设置多级渐远纹理级别(也就是图像金字塔层级),也可以在外部调用glGenerateMipmap()单独设置让OpenGL自行配置层级

(4) 释放图像内存:

stbi_image_free(data);

(5) 生成一个纹理的总体过程:

//创建、绑定纹理
unsigned int texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
// 为当前绑定的纹理对象设置环绕、过滤方式
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);   
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
// 加载并生成纹理
int width, height, nrChannels;
unsigned char *data = stbi_load("container.jpg", &width, &height, &nrChannels, 0);
if (data)
{    
    //加载纹理
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
    //纹理多级渐远
    glGenerateMipmap(GL_TEXTURE_2D);
}
else
{
    std::cout << "Failed to load texture" << std::endl;
}
//回收图像内存
stbi_image_free(data);

3.绘制纹理:

(1).定义添加了纹理坐标的顶点数据:

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 // 左上

};

(2).配置顶点属性

新的顶点格式:

顶点属性配置代码:

glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float)));

glEnableVertexAttribArray(2);

(3)修改着色器代码

修改顶点着色器代码,增加纹理坐标

#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aColor;
layout (location = 2) in vec2 aTexCoord;

out vec3 ourColor;
out vec2 TexCoord;

void main()
{
    gl_Position = vec4(aPos, 1.0);
    ourColor = aColor;
    TexCoord = aTexCoord;
}

修改片段着色器代码,将顶点着色器中纹理坐标的输出变量TexCoord作为输入变量

#version 330 core
out vec4 FragColor;

in vec3 ourColor;
in vec2 TexCoord;

uniform sampler2D ourTexture;

void main()
{
    FragColor = texture(ourTexture, TexCoord);
}

片段着色器里也应当能访问到纹理对象

Sample2D:GLSL的内建数据类型SampleXD,也叫作采样器,可以用于添加纹理对象到着色器

texture():GLSL内建函数,参数一是纹理采样器,参数二是纹理坐标

texture函数会使用之前设置的纹理参数对相应的颜色值进行采样

这个片段着色器的输出就是纹理插值过滤后的颜色

(4)绘制

绑定纹理,glDrawElements()绘制

glBindTexture(GL_TEXTURE_2D, texture);

glBindVertexArray(VAO);

glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);

编译、运行:

修改片段着色器,将纹理颜色与顶点颜色混合:

FragColor = texture(ourTexture, TexCoord) * vec4(ourColor, 1.0);

七.混合纹理

1.纹理单元

木箱纹理贴图效果在上述过程中已经实现

其中有个细节,就是在片段着色器中我们为纹理对象定义了一个sampler2D类型的uniform变量:ourTexture

之前的博文中有讲到过,着色器中的uniform全局变量可以在代码中使用glUniformX()类型函数进行赋值

那么,我们就可以在片段着色器中定义多个纹理

这些纹理在片段着色器中的位置值(也可以理解成索引值)叫作纹理单元

纹理对象在绑定之前需要先调用glActiveTexture()进行激活

glActiveTexture(GL_TEXTURE0); // 在绑定纹理之前先激活纹理单元

glBindTexture(GL_TEXTURE_2D, texture);

OpenGL默认激活GL_TEXTURE0

因为之前的代码中只有一个纹理,所以我们并没有调用glActiveTexture()对纹理专门进行激活操作

OpenGL至少保证有16个纹理单元,也就是说可以激活从GL_TEXTURE0到GL_TEXTRUE15

它们都是按顺序定义的,所以我们也可以通过GL_TEXTURE0 + 8的方式获得GL_TEXTURE8

这在当我们需要循环一些纹理单元的时候会很有用

2.混合渲染两个纹理

两个纹理混合贴图,先对片段着色器代码进行修改:

#version 330 core
...

uniform sampler2D texture1;
uniform sampler2D texture2;

void main()
{
    FragColor = mix(texture(texture1, TexCoord), texture(texture2, TexCoord), 0.2);
}

mix():GLSL内建函数,使用第三个参数加权进行线性插值

            输出:参数1*(1-参数3) + 参数2*参数3

新增一个纹理后,除了片段着色器的代码需要进行修改外,还有渲染流程代码也需要进行相应更改

其中体现在两个地方:

(1).纹理对象绑定到OpenGL之前,先要对其进行激活

glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture1);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, texture2);

glBindVertexArray(VAO);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);

(2).向OpenGL设置纹理采样器的纹理单元

保证每个uniform采样器对应着正确的纹理单元

// 不要忘记在设置uniform变量之前激活着色器程序!
ourShader.use(); 
// 手动设置
glUniform1i(glGetUniformLocation(ourShader.ID, "texture1"), 0); 
// 或者使用我们前文中封装好的着色器类的api设置
ourShader.setInt("texture2", 1); 
while(...)
{
[...]
}

另外还有一点需要注意的是

OpenGL要求y轴0.0坐标是在图片的底部的,但是图片的y轴0.0坐标通常在顶部

所以代码中还需要通过stb_image.h的api来讲图像进行y轴翻转:

stbi_set_flip_vertically_on_load(true);

3.编译、运行 

八.源代码 

需要自己开发的文件有如下几个,接下来就逐一展示源代码:

\opengl-test\assets\shader.vs

#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aColor;
layout (location = 2) in vec2 aTexCoord;

out vec3 ourColor;
out vec2 TexCoord;

void main()
{
	gl_Position = vec4(aPos, 1.0);
	ourColor = aColor;
	TexCoord = vec2(aTexCoord.x, aTexCoord.y);
}

\opengl-test\assets\shader.fs 

#version 330 core
out vec4 FragColor;

//in vec3 ourColor;
in vec2 TexCoord;

// texture samplers
uniform sampler2D texture1;
uniform sampler2D texture2;

void main()
{
	//linearly interpolate between both textures (80% container, 20% awesomeface)
	FragColor = mix(texture(texture1, TexCoord), texture(texture2, TexCoord), 0.2);
}

\opengl-test\inc\Shader.h

#include <glad/glad.h>

#include <string>
#include <fstream>
#include <sstream>
#include <iostream>

using namespace std;

class Shader
{
	public:
		//着色器程序ID
		unsigned int ID;

		// 构造器读取并构建着色器
		Shader(const char* vertexPath, const char* fragmentPath) 
		{
			//1.从filePath中检索顶点 / 片段源代码
			// 顶点和片段着色器代码文件
			ifstream vShaderFile;
			ifstream fShaderFile;

			// 顶点和片段着色器源码字符串
			string vertexCode;
			string fragmentCode;

			// 确保ifstream对象能够抛出异常
			vShaderFile.exceptions(ifstream::failbit | ifstream::badbit);
			fShaderFile.exceptions(ifstream::failbit | ifstream::badbit);

			try 
			{
				// open file
				vShaderFile.open(vertexPath);
				fShaderFile.open(fragmentPath);

				// 读取文件缓冲内容到streams
				std::stringstream vShaderStream, fShaderStream;
				vShaderStream << vShaderFile.rdbuf();
				fShaderStream << fShaderFile.rdbuf();

				// close file hander
				vShaderFile.close();
				fShaderFile.close();

				// stream转换赋值给string字符串,最终string还需要转换成char*指针
				vertexCode = vShaderStream.str();
				fragmentCode = fShaderStream.str();
			}
			catch (std::ifstream::failure& e) 
			{
				std::cout << "ERROR::SHADER::FILE_NOT_SUCCESSFULLY_READ: " << e.what() << std::endl;
			}

		    //string转换成char*
			const char* vShaderCode = vertexCode.c_str();
			const char* fShaderCode = fragmentCode.c_str();

			//2.编译shader
			//创建顶点着色器
			unsigned int vertex;
			vertex = glCreateShader(GL_VERTEX_SHADER);
			glShaderSource(vertex, 1, &vShaderCode, NULL);
			glCompileShader(vertex);
			checkCompileErrors(vertex, "VERTEX");
             
			//创建片段着色器
			unsigned int fragment;
			fragment = glCreateShader(GL_FRAGMENT_SHADER);
			glShaderSource(fragment, 1, &fShaderCode, NULL);
			glCompileShader(fragment);
			checkCompileErrors(fragment, "FRAGMENT");

			//创建着色器程序
			ID = glCreateProgram();
			//附件着色器到着色器程序,然后链接着色器程序到OpenGL
			glAttachShader(ID, vertex);
			glAttachShader(ID, fragment);
			glLinkProgram(ID);
			checkCompileErrors(ID, "PROGRAM");

			//删除着色器
			glDeleteShader(vertex);
			glDeleteShader(fragment);
		}

		// 使用/激活着色器程序
		void use() 
		{
			glUseProgram(ID);
		}

		// uniform工具函数
		void setBool(const std::string& name, bool value) const 
		{
			glUniform1i(glGetUniformLocation(ID, name.c_str()), (int)value);
		}

		void setInt(const std::string& name, int value) const 
		{
			glUniform1i(glGetUniformLocation(ID, name.c_str()), value);
		}

		void setFloat(const std::string& name, float value) const 
		{
			glUniform1f(glGetUniformLocation(ID, name.c_str()), value);
		}

	private:
		// 检测着色器编译错误和着色器程序链接错误
		void checkCompileErrors(unsigned int shader, std::string type)
		{
			int success;
			char infoLog[1024];

			if (type != "PROGRAM")
			{
				glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
				if (!success)
				{
					glGetShaderInfoLog(shader, 1024, NULL, infoLog);
					std::cout << "ERROR::SHADER_COMPILATION_ERROR of type: " << type << "\n" << infoLog << "\n ------------------------------------------------------- " << std::endl;
				}
			}
			else
			{
				glGetProgramiv(shader, GL_LINK_STATUS, &success);
				if (!success)
				{
					glGetProgramInfoLog(shader, 1024, NULL, infoLog);
					std::cout << "ERROR::PROGRAM_LINKING_ERROR of type: " << type << "\n" << infoLog << "\n ------------------------------------------------------- " << std::endl;
				}
			}
		}
};

\opengl-test\src\main.cpp

#include "Shader.h"

#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <iostream>

#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>

#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"

void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void processInput(GLFWwindow* window);

// 窗口大小
const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;

int main()
{
    // glfw: 初始化窗口配置
    glfwInit();
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

    // glfw:创建窗口
    GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL);
    if (window == NULL)
    {
        std::cout << "Failed to create GLFW window" << std::endl;
        glfwTerminate();
        return -1;
    }
    glfwMakeContextCurrent(window);
    glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);

    // glad: load OpenGL所有函数指针
    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
    {
        std::cout << "Failed to initialize GLAD" << std::endl;
        return -1;
    }

    //加载着色器文件
    Shader ourShader("assets/shader.vs", "assets/shader.fs");

    // 配置顶点数据:位置,颜色,纹理坐标
    float vertices[] = {
        // positions          // colors           // texture coords
         0.5f,  0.5f, 0.0f,   1.0f, 0.0f, 0.0f,   1.0f, 1.0f, // top right
         0.5f, -0.5f, 0.0f,   0.0f, 1.0f, 0.0f,   1.0f, 0.0f, // bottom right
        -0.5f, -0.5f, 0.0f,   0.0f, 0.0f, 1.0f,   0.0f, 0.0f, // bottom left
        -0.5f,  0.5f, 0.0f,   1.0f, 1.0f, 0.0f,   0.0f, 1.0f  // top left 
    };

    //顶点索引
    unsigned int indices[] = {
        0, 1, 3, // first triangle
        1, 2, 3  // second triangle
    };

    unsigned int VBO, VAO, EBO;
    glGenVertexArrays(1, &VAO);
    glGenBuffers(1, &VBO);
    glGenBuffers(1, &EBO);

    //绑定VAO
    glBindVertexArray(VAO);
    //绑定VBO
    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
    //绑定EBO
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

    //位置属性
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0);
    //颜色属性
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float)));
    glEnableVertexAttribArray(1);
    //纹理坐标属性
    glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float)));
    glEnableVertexAttribArray(2);

    //创建、绑定两个纹理
    unsigned int texture1, texture2;
    //纹理1:texture1
    glGenTextures(1, &texture1);
    glBindTexture(GL_TEXTURE_2D, texture1); // all upcoming GL_TEXTURE_2D operations now have effect on this texture object
    //设置纹理1环绕方式
    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);
    //设置纹理1过滤方式
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);   

    //纹理1宽、高、色彩通道数
    int width, height, nrChannels;
    //告诉stb_image.h加载纹理1时,翻转y轴,要不然y轴会flip
    stbi_set_flip_vertically_on_load(true);
    //加载图片数据
    unsigned char* data = stbi_load("assets/container.jpg", &width, &height, &nrChannels, 0);
    if (data)
    {
        //绑定数据到纹理1对象
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
        //为纹理1开启多级渐元
        glGenerateMipmap(GL_TEXTURE_2D);
    }
    else
    {
        std::cout << "Failed to load texture" << std::endl;
    }
    //释放图片内存
    stbi_image_free(data);

    //纹理2:texture2
    glGenTextures(1, &texture2);
    glBindTexture(GL_TEXTURE_2D, texture2);
    //设置纹理2环绕方式
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    //设置纹理2过滤方式
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    //加载图片数据
    data = stbi_load("assets/awesomeface.png", &width, &height, &nrChannels, 0);
    if (data)
    {
        //绑定数据到纹理2对象
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
        //为纹理2开启多级渐元
        glGenerateMipmap(GL_TEXTURE_2D);
    }
    else
    {
        std::cout << "Failed to load texture" << std::endl;
    }
    stbi_image_free(data);

    //激活、使用着色器程序
    ourShader.use();
    // 设置纹理1采样器的纹理单元
    glUniform1i(glGetUniformLocation(ourShader.ID, "texture1"), 0);
    // 设置纹理2采样器的纹理单元
    ourShader.setInt("texture2", 1);


    // 渲染循环
    while (!glfwWindowShouldClose(window))
    {
        // 处理外设输入
        processInput(window);

        // render
        glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);

        // 绑定纹理
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, texture1);
        glActiveTexture(GL_TEXTURE1);
        glBindTexture(GL_TEXTURE_2D, texture2);

        //激活着色器程序
        //ourShader.use();

        // 绘制三角形
        glBindVertexArray(VAO);
        glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);

        // glfw: 交换缓冲区和轮询IO事件(按键按下/释放、鼠标移动等)
        glfwSwapBuffers(window);
        glfwPollEvents();
    }

    // 删除分配的所有资源:
    glDeleteVertexArrays(1, &VAO);
    glDeleteBuffers(1, &VBO);
    glDeleteBuffers(1, &EBO);

    // glfw:终止、清除所有先前分配的GLFW资源。
    glfwTerminate();
    return 0;
}

// glfw:处理窗口事件
void processInput(GLFWwindow* window)
{
    if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
        glfwSetWindowShouldClose(window, true);
}

// glfw: 窗口尺寸变化回调
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
    // 确保视口与新窗口尺寸匹配
    glViewport(0, 0, width, height);
}
  • 1
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
OpenGL球面纹理贴图是一种在球体上应用纹理的技术。在OpenGL中,为了使用球面纹理贴图,我们需要首先创建一个球体模型。这可以通过绘制一系列的三角形面片来实现,形成一个球体的表面。接下来,我们需要为球体模型创建一个纹理对象,并加载对应的纹理图像。 在加载纹理图像之后,我们需要设置一些纹理参数,例如缩小过滤和放大过滤的方式。然后,我们可以使用OpenGL纹理映射功能来将纹理图像应用到球体上的各个面片上。 在球面纹理贴图的过程中,需要将纹理坐标映射到球体的表面上。一种常用的方法是使用球坐标系来表示纹理坐标。球坐标系包括两个角度参数:纹理纬度和纹理经度。纹理纬度决定了纹理在球体上的垂直位置,而纹理经度则决定了纹理在球体上的水平位置。 在绘制球体的过程中,我们需要为每个顶点指定纹理坐标。可以根据对应的球面坐标来计算纹理坐标,并将其与顶点一起传递给着色器。在着色器中,可以使用纹理坐标来对纹理进行采样,并将采样到的纹素值与球体的法向量进行组合,从而得到最终的颜色值。 通过这种方式,我们可以在球体表面上实现复杂的纹理图案,例如地球表面的地形、云层等效果。同时,球面纹理贴图还可以应用于球体的各个部分,实现不同部分具有不同纹理的效果。 总而言之,OpenGL的球面纹理贴图技术可以使我们在球体上实现各种有趣的纹理效果,扩展了实时渲染的创作可能性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值