文章目录

  • 一、简介
  • 二、代码实现
  • 2.1单个纹理对象与单元
  • 2.2多个纹理单元与对象


一、简介

如果我们的程序单纯的只是一些颜色什么的,未免不够真实和有趣,但是如果让我们为每一个像素都手动的分配更为真实的颜色值,又过于繁杂而不切实际,因此在OpenGL中也就有了纹理映射这一过程(或者说是为我们的图形进行贴图)。

纹理映射的过程其实非常类似于缓冲区分配的过程,都是在设置OpenGL的状态:
1、创建一个纹理对象。
2、激活纹理单元,并绑定相应的纹理对象。
3、为纹理对象设置相应的参数并将数据传递给纹理对象。
4、在着色器程序中利用纹理采样器(simple2D或者是其他类型),根据纹理坐标以及纹理对象实现对颜色的采样。这一工作主要集中在片元着色器中。
5、绘制出相应的纹理图案。

二、代码实现

2.1单个纹理对象与单元

shader.h

#ifndef SHADER_H
#define SHADER_H
#include <glad/glad.h> // include glad to get all the required OpenGL headers

#include"glm\glm.hpp"		//矩阵部分头文件
#include"glm\gtc\matrix_transform.hpp"
#include"glm\gtc\type_ptr.hpp"

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


class Shader
{
public:
	// the program ID
	unsigned int ID;

	// constructor reads and builds the shader
	Shader(const char* vertexPath, const char* fragmentPath);
	// use/activate the shader
	void use();
	// utility uniform functions
	void setBool(const std::string &name, bool value) const;
	void setInt(const std::string &name, int value) const;
	void setFloat(const std::string &name, float value) const;
	void setVec2(const std::string &name, const glm::vec2 &value) const;
	void setVec2(const std::string &name, float x, float y) const;
	void setVec3(const std::string &name, const glm::vec3 &value) const;
	void setVec3(const std::string &name, float x, float y, float z) const;
	void setVec4(const std::string &name, const glm::vec4 &value) const;
	void setVec4(const std::string &name, float x, float y, float z, float w) const;
	void setMat2(const std::string &name, const glm::mat2 &mat) const;
	void setMat3(const std::string &name, const glm::mat3 &mat) const;
	void setMat4(const std::string &name, const glm::mat4 &mat) const;

private:
	void checkCompileErrors(GLuint shader, std::string type);

};

Shader::Shader(const char * vertexPath, const char * fragmentPath)
{
	// 1. retrieve the vertex/fragment source code from filePath
	std::string vertexCode;
	std::string fragmentCode;
	std::ifstream vShaderFile;
	std::ifstream fShaderFile;
	// ensure ifstream objects can throw exceptions:
	vShaderFile.exceptions(std::ifstream::failbit | std::ifstream::badbit);
	fShaderFile.exceptions(std::ifstream::failbit | std::ifstream::badbit);
	try
	{
		// open files
		vShaderFile.open(vertexPath);
		fShaderFile.open(fragmentPath);
		std::stringstream vShaderStream, fShaderStream;
		// read file's buffer contents into streams
		vShaderStream << vShaderFile.rdbuf();	//重定向方法,读取文件数据
		fShaderStream << fShaderFile.rdbuf();	//将文件内容通过缓冲区传递给stringstream对象
												// close file handlers
		vShaderFile.close();
		fShaderFile.close();
		// convert stream into string,将stringstream对象转换为string对象
		vertexCode = vShaderStream.str();
		fragmentCode = fShaderStream.str();
	}
	catch (std::ifstream::failure e)
	{
		std::cout << "ERROR::SHADER::FILE_NOT_SUCCESFULLY_READ" << std::endl;
	}
	const char* vShaderCode = vertexCode.c_str();
	const char* fShaderCode = fragmentCode.c_str();

	// 2. compile shaders
	unsigned int vertex, fragment;
	int success;
	char infoLog[512];

	// vertex Shader
	vertex = glCreateShader(GL_VERTEX_SHADER);
	glShaderSource(vertex, 1, &vShaderCode, NULL);		//将源码传递给顶点着色器对象
	glCompileShader(vertex);	//运行时动态编译着色器源码,并生成着色器对象

	// print compile errors if any
	glGetShaderiv(vertex, GL_COMPILE_STATUS, &success);
	if (!success)
	{
		glGetShaderInfoLog(vertex, 512, NULL, infoLog);
		std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl;
	};

	// similiar for Fragment Shader
	// fragment Shader
	fragment = glCreateShader(GL_FRAGMENT_SHADER);
	glShaderSource(fragment, 1, &fShaderCode, NULL);
	glCompileShader(fragment);
	//checkCompileErrors(fragment, "FRAGMENT");

	// shader Program
	ID = glCreateProgram();		//创建一个程序对象
	glAttachShader(ID, vertex);		//将着色器对象添加到程序对象之中
	glAttachShader(ID, fragment);
	glLinkProgram(ID);		//链接为真正的着色器程序
	// print linking errors if any
	glGetProgramiv(ID, GL_LINK_STATUS, &success);
	if (!success)
	{
		glGetProgramInfoLog(ID, 512, NULL, infoLog);
		std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl;
	}

	// delete the shaders as they're linked into our program now and no longer necessary
	glDeleteShader(vertex);		//在生成真正的着色器程序之后,则着色器对象将没有用处
	glDeleteShader(fragment);
}

void Shader::use() {
	glUseProgram(ID);
}

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

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

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

void Shader::setVec2(const std::string & name, const glm::vec2 & value) const
{
	glUniform2fv(glGetUniformLocation(ID, name.c_str()), 1, &value[0]);
}

void Shader::setVec2(const std::string & name, float x, float y) const
{
	glUniform2f(glGetUniformLocation(ID, name.c_str()), x, y);
}

void Shader::setVec3(const std::string & name, const glm::vec3 & value) const
{
	glUniform3fv(glGetUniformLocation(ID, name.c_str()), 1, &value[0]);
}

void Shader::setVec3(const std::string & name, float x, float y, float z) const
{
	glUniform3f(glGetUniformLocation(ID, name.c_str()), x, y, z);
}

void Shader::setVec4(const std::string & name, const glm::vec4 & value) const
{
	glUniform4fv(glGetUniformLocation(ID, name.c_str()), 1, &value[0]);
}

void Shader::setVec4(const std::string & name, float x, float y, float z, float w) const
{
	glUniform4f(glGetUniformLocation(ID, name.c_str()), x, y, z, w);
}

void Shader::setMat2(const std::string & name, const glm::mat2 & mat) const
{
	glUniformMatrix2fv(glGetUniformLocation(ID, name.c_str()), 1, GL_FALSE, &mat[0][0]);
}

void Shader::setMat3(const std::string & name, const glm::mat3 & mat) const
{
	glUniformMatrix3fv(glGetUniformLocation(ID, name.c_str()), 1, GL_FALSE, &mat[0][0]);
}

void Shader::setMat4(const std::string & name, const glm::mat4 & mat) const
{
	glUniformMatrix4fv(glGetUniformLocation(ID, name.c_str()), 1, GL_FALSE, &mat[0][0]);
}

void Shader::checkCompileErrors(GLuint shader, std::string type)
{
	GLint success;
	GLchar 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;
		}
	}
}

#endif

main.cpp

#include<glad\glad.h>
#include<GLFW\glfw3.h>
#include"shader.h"

#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"

#include<iostream>

int main() {
	glfwInit();

	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
	glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

	GLFWwindow *window = glfwCreateWindow(800, 600, "Texture", 0, 0);
	if (window == NULL) {
		std::cout << "创建窗口失败" << std::endl;
		glfwTerminate();		//释放glfw相关资源
		return -1;
	}

	glfwMakeContextCurrent(window);		//为特定的窗口创建一个上下文

	if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {		//加载相关的函数地址
		std::cout << "GLAD初始化失败,函数地址无法被加载" << std::endl;
		return -1;
	}

	//设置视口,类似于现实生活中的窗户
	/*int viewWidth, viewHeight;
	glfwGetFramebufferSize(window, &viewWidth, &viewHeight);
	glViewport(0, 0, 800, 600);*/

	//初始化我们的着色器
	Shader ourShader("../Shader/shader_texture.vs", "../Shader/shader_texture.fs");

	//数据部分
	static 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[] = {		//由于OpenGL的基础图元只有三种:点、线以及三角形,所以我们这里使用两个三角形来构建一个矩形
		0, 1, 3, // first triangle
		1, 2, 3  // second triangle
	};

	//GPU部分
	unsigned int VBO, VAO, EBO;		//这些变量有点类似于我们经常使用的文件描述符,也可以说是“句柄”,主要是用来管理GPU分配的内存
	glGenVertexArrays(1, &VAO);		//创建一个顶点数组对象名字,类似于声明
	glGenBuffers(1, &VBO);			//创建一个缓冲区对象名字
	glGenBuffers(1, &EBO);			

	glBindVertexArray(VAO);			//绑定顶点数组对象,并为其分配内存空间
	glBindBuffer(GL_ARRAY_BUFFER, VBO);		//将VBO绑定到一个目标上,不同的目标代表这不同的用途
	glBufferData(GL_ARRAY_BUFFER,sizeof(vertices),vertices,GL_STATIC_DRAW);		//将数据传给GL_ARRAY_BUFFER目标上的VBO缓冲对象

	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 texture;
	glGenTextures(1, &texture);
	//glActiveTexture(GL_TEXTURE0);		//默认是激活的,所以可以不写
	glBindTexture(GL_TEXTURE_2D, texture);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);		//设置我们想要的坐标轴S、T
	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);		//释放加载进来的图像数据内存
	//glBindTexture(GL_TEXTURE_2D, 0);		//解绑纹理对象

	ourShader.use();
	while (!glfwWindowShouldClose(window))
	{
		//渲染
		glClearColor(0.2f, 0.3f, 0.3f, 1.0f);		//指定了清除缓冲区所用的颜色
		glClear(GL_COLOR_BUFFER_BIT);

		glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);	//使用GL_ELEMENT_ARRAY_BUFFER目标上的对象绘制两个三角形以达到四边形的效果
		
		glfwSwapBuffers(window);
		glfwPollEvents();		//检测所有的事件(键盘、鼠标或者一些窗口的状态等)是否被触发
	}

	glDeleteVertexArrays(1, &VAO);
	glDeleteBuffers(1, &VBO);
	glDeleteBuffers(1, &EBO);

	glfwTerminate();

	return 0;
}

着色器部分:

shader_texture.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 = aTexCoord;
}

shader_texture.fs

#version 330 core
out vec4 FragColor;
  
in vec3 ourColor;
in vec2 TexCoord;

uniform sampler2D ourTexture;
//uniform sampler2D texture1;
//uniform sampler2D texture2;

void main()
{
	FragColor = texture(ourTexture, TexCoord);
    //FragColor = texture(ourTexture, TexCoord)*vec4(ourColor, 1.0);
	//FragColor = mix(texture(texture1, TexCoord), texture(texture2, TexCoord), 0.2);
}

实现效果:

android opengl渲染纹理坐标计算 opengl绘制纹理_#include

在这里要特别注意着色器中ourTexture这个uniform变量,它并没有在CPU端被修改,但是它仍然正常工作了,这是因为OpenGL总是会默认激活一个纹理单元GL_TEXTURE0,这个纹理单元已经与texture纹理对象进行了关联,所以变量ourTexture会默认利用纹理坐标来对texture纹理对象进行颜色的采样。

2.2多个纹理单元与对象

OpenGL中存在大约16个纹理单元,因此其可以同时利用多个纹理对象来绘制一些比较有趣图案。

修改上述纹理部分的代码,如下所示:

//设置纹理,类似于上面缓冲区的设置过程
		unsigned int texture1, texture2;
		glGenTextures(1, &texture1);
		//glActiveTexture(GL_TEXTURE0);		//默认是激活的,所以可以不写
		glBindTexture(GL_TEXTURE_2D, texture1);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);		//设置我们想要的坐标轴S、T
		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)
		{
			std::cout << "container:" << nrChannels << std::endl;
			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);		//释放加载进来的图像数据内存
		//glBindTexture(GL_TEXTURE_2D, 0);		//解绑纹理对象

		//设置第二个纹理对象
		glGenTextures(1, &texture2);
		glBindTexture(GL_TEXTURE_2D, texture2);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);		//设置我们想要的坐标轴S、T
		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);

		data = stbi_load("awesomeface.png", &width, &height, &nrChannels, 0);
		if (data)
		{
			std::cout << "awesomeface:" << nrChannels<<std::endl;
			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);		//将图像传递给纹理对象
			glGenerateMipmap(GL_TEXTURE_2D);	//为当前绑定的纹理自动生成所有需要的多级渐远纹理
		}
		else
		{
			std::cout << "Failed to load texture" << std::endl;
		}
		stbi_image_free(data);		//释放加载进来的图像数据内存
		//glBindTexture(GL_TEXTURE_2D, 0);		//解绑纹理对象

		ourShader.use();
		glUniform1i(glGetUniformLocation(ourShader.ID, "texture1"), 0);
		ourShader.setInt("texture2", 1);

		//绑定相应的纹理对象
		glActiveTexture(GL_TEXTURE0);
		glBindTexture(GL_TEXTURE_2D, texture1);
		glActiveTexture(GL_TEXTURE1);
		glBindTexture(GL_TEXTURE_2D, texture2);

运行效果如下所示:

android opengl渲染纹理坐标计算 opengl绘制纹理_着色器_02

不过因为纹理坐标的坐标系统与图片的坐标系统有点不一样(y轴不同),导致图片相对于窗口是倒置的,要想解决这个问题我们可以用书中提到的两个小方法:
1、翻转y值,也就是用1减去y值。
2、可以经着色器中的aTexCoord替换为vec2(aTexCoord.x, 1.0f - aTexCoord.y)。
我这里是使用了第一种方法,将顶点数据中的纹理坐标修改为下面的这种形式就正常了:

static float vertices[] = {
			// positions          // colors           // texture coords
			0.5f,  0.5f, 0.0f,   1.0f, 0.0f, 0.0f,   1.0f, 0.0f,   // top right
			0.5f, -0.5f, 0.0f,   0.0f, 1.0f, 0.0f,   1.0f, 1.0f,   // bottom right
			-0.5f, -0.5f, 0.0f,   0.0f, 0.0f, 1.0f,   0.0f, 1.0f,   // bottom left
			-0.5f,  0.5f, 0.0f,   1.0f, 1.0f, 0.0f,   0.0f, 0.0f    // top left 
		};

android opengl渲染纹理坐标计算 opengl绘制纹理_着色器_03

参考资料:《Learning OpenGL》 《OpenGL编程指南》