opengl渲染器入门01管线纹理变化

117 阅读6分钟

引言

在学习了一些图形学的知识后,现在使用这些做一下渲染器,使用opengl学习,主要参考learnOpengl(LearnOpenGL CN (learnopengl-cn.github.io)的思路和代码。 预先装相关的库,不做过多的解释。 opengl是一个状态机,理解这个很重要。

Part1:窗口

#include <glad/glad.h>

#include <GLFW/glfw3.h>

#include <iostream>

/*重构窗口大小*/

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

{

glViewport(0, 0, width, height);

}

/*键盘输入响应函数*/

void processInput(GLFWwindow* window)

{

if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)

glfwSetWindowShouldClose(window, true);

}

int main()

{

glfwInit();

glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); //主版本:3

glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); //次版本:3

glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); //设置为核心模式,并且兼容前面的一些的版本

GLFWwindow* window = glfwCreateWindow(800, 600, "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); //绑定窗口大小改变时调用的函数

if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) //初始化glad

{

std::cout << "Failed to initialize GLAD" << std::endl;

return -1;

}

while (!glfwWindowShouldClose(window)) //开始渲染循环

{

processInput(window); //自定义的检测键盘输入函数

glClearColor(0.2f, 0.3f, 0.3f, 1.0f);     //清空颜色缓冲区的颜色值,RGBA

glClear(GL_COLOR_BUFFER_BIT);   //清空颜色的缓冲

glfwSwapBuffers(window); //双缓冲,交换前后buffer

glfwPollEvents(); //检查事件队列中是否有事件传达

}

glfwTerminate(); //结束线程,释放资源

return 0;

}

很简单的东西,初始化,加载,渲染循环,结束,没有过多的东西。

Part2:管线

首先是着色器代码

const char *vertexShaderSource = "#version 330 core\n"   //着色器的版本号
"layout (location = 0) in vec3 aPos;\n"                 //通道数和输入的量
"void main()\n" "{\n" " 
gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n"      //gl_Position内置的变量
"}\0"; 
const char *fragmentShaderSource = "#version 330 core\n" 
"out vec4 FragColor;\n"                                //颜色的输出
"void main()\n" 
"{\n" 
" FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n" 
"}\n\0";

构建着色器

	//设置顶点着色器
	unsigned int vertexShader = glCreateShader(GL_VERTEX_SHADER);
	glShaderSource(vertexShader, 1, &vertexShaderSource, nullptr);
	glCompileShader(vertexShader);
	//检查
	int flag;
	char infolog[512];
	glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &flag);
	if (!flag)
	{
		glGetShaderInfoLog(vertexShader, 512, nullptr, infolog);
		std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infolog << std::endl;
	}
	//片元着色器
	unsigned int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
	glShaderSource(fragmentShader, 1, &fragmentShaderSource, nullptr);
	glCompileShader(fragmentShader);
	glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &flag);
	if (!flag)
	{
		glGetShaderInfoLog(fragmentShader, 512, nullptr, infolog);
		std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infolog << std::endl;
	}
	//连接着色器
	unsigned int shaderProgram = glCreateProgram();
	glAttachShader(shaderProgram, vertexShader);
	glAttachShader(shaderProgram, fragmentShader);
	glLinkProgram(shaderProgram);
	//检查
	glGetProgramiv(shaderProgram, GL_LINK_STATUS, &flag);
	if (!flag)
	{
		glGetProgramInfoLog(shaderProgram, 512, nullptr, infolog);
		std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infolog << std::endl;
	}
	glDeleteShader(vertexShader);
	glDeleteShader(fragmentShader);

//设置VAO,VBO

float vertices[] = {
		-0.5f, -0.5f, 0.0f, // left  
		 0.5f, -0.5f, 0.0f, // right 
		 0.0f,  0.5f, 0.0f  // top   
};
	//VAO,VBO
	unsigned int VAO, VBO;
	glGenVertexArrays(1, &VAO);  //生成一个VAO
	glGenBuffers(1, &VBO);      //生成一个VBO
	glBindVertexArray(VAO);        //绑定VAO
	glBindBuffer(GL_ARRAY_BUFFER, VBO);      //绑定VBO
	glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);     //索引,数量,类型,归一化,相邻的偏移量,起始位置     //一个点的所有的信息的长度
	glEnableVertexAttribArray(0);         //开启通道(上面的索引)
	glBindBuffer(GL_ARRAY_BUFFER, 0);     //解绑VBO
	glBindVertexArray(0);                 //解绑VAO,这操作是合法安全的 

//执行渲染

glUseProgram(shaderProgram); 
glBindVertexArray(VAO); // seeing as we only have a single VAO there's no need to bind it every time, but we'll do so to keep things a bit more organized 
glDrawArrays(GL_TRIANGLES, 0, 6); 
//glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);  //绑定了EBO使用这个渲染,可以减少空间的使用

Part3:纹理

在使用库的时候需要添加 #define STB_IMAGE_IMPLEMENTATION",但是好像不能多次的添加。 //创建纹理

    unsigned int texture1;
    glGenTextures(1, &texture1);    //生成纹理
    glBindTexture(GL_TEXTURE_2D, texture1);    //绑定,并且设置为2D的纹理
    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;
    stbi_set_flip_vertically_on_load(true); //翻转Y轴,Y是从上到下的
    unsigned char* data = stbi_load("container.jpg", &width, &height, &nrChannels, 0);
    //参数表为 图片的路径 宽度 高度 图片的通道数(rgba=4,rgb=3) 期望输出的通道数(0表示有多少输出多少)
    if (data)
    {
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
        //参数表为 纹理的格式(2D纹理) 纹理的细节级别(0为基本级别) 纹理的内部格式(rgba/rgb) 宽度 高度 边框大小(0没有边框) 输入的数据的格式 输入的数据的类型 输入数据的指针 
        glGenerateMipmap(GL_TEXTURE_2D);   //生成mipmap纹理
    }
    else
    {
        std::cout << "Failed to load texture" << std::endl;
    }
    stbi_image_free(data);   //销毁数据

//纹理的使用

    ourShader.use();    //使用之前,需要先指明着色器
    glUniform1i(glGetUniformLocation(ourShader.ID, "texture1"), 0);   //指明纹理的通道和名字第一张
    ourShader.setInt("texture2", 1);  //第二章纹理
    //渲染中
        glActiveTexture(GL_TEXTURE0);    //激活0号纹理
        glBindTexture(GL_TEXTURE_2D, texture1);   //送入纹理实例,指定为0号端口
        glActiveTexture(GL_TEXTURE1);   //激活1号纹理
        glBindTexture(GL_TEXTURE_2D, texture2); //送入纹理实例,指定为1号端口
   //片元着色器中
#version 330 core
out vec4 FragColor;
in vec3 ourColor;
in vec2 TexCoord;
// texture samplers
uniform sampler2D texture1;   //接受传入的纹理1号
uniform sampler2D texture2;   //2号

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

Part3:变换

前面的数学方法我就不进行多余的讲解,只说明一下glm的三个变化,另外在进行移动,旋转和放缩的时候的顺序不能够随意更改。

//trans矩阵的变化
glm::mat4 trans = glm::mat4(1.0f)
trans = glm::translate(trans, glm::vec3(1.0f, 1.0f, 0.0f));
//rotate
trans = glm::rotate(trans, glm::radians(90.0f), glm::vec3(0.0, 0.0, 1.0));
//scale
trans = glm::scale(trans, glm::vec3(0.5, 0.5, 0.5));

Part4:坐标系统

在opengl中,主要有5个重要的坐标系统分别问:

  • 局部空间(Local Space,或者称为物体空间(Object Space))
  • 世界空间(World Space)
  • 观察空间(View Space,或者称为视觉空间(Eye Space))
  • 裁剪空间(Clip Space)
  • 屏幕空间(Screen Space)

关于这几个空间如下的公式:

图片22.png

同时这里对于常用的正式投影和透视投影做一个简单的解释,顶点在经过转化为齐次坐标,并且经过M变化和V变化后有如下结果:

l<x<r b<y<t f<z<n 分别是x=[left right],y=[botton,top],z=[far,near].

正交矩阵:

在经过变化之后,我们需要把“物体”移动到中心,并且是一个边长为2的正方体,也就是x=[-1,1],y=[-1,1],z=[-1,1].第一步首先把把它移动到中心的位置,然后在压缩到边长为2的立方体中:

图片23.png

透视矩阵:

图形在经过正交矩阵的变化之后,为了实现“近大远小”的效果,需要进行一个压缩,称之为压缩矩阵,关于这个压缩矩阵的推导,在games101上或者其他地方有比较多的资料,用相似三角形和齐次坐标得来,不做推导,下面给出透视矩阵:

图片24.png

在glm中,我们我可以直接使用的相关的函数,正交投影为

glm::ortho(0.0f, 800.0f, 0.0f, 600.0f, 0.1f, 100.0f);

参数表为:left,right,bottom,top,near,far。

透视投影为:

glm::mat4 proj = glm::perspective(glm::radians(45.0f), (float)width/(float)height, 0.1f, 100.0f); 参数表为:视角参数FOV 宽高比 近平面 远平面