引言
在学习了一些图形学的知识后,现在使用这些做一下渲染器,使用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)
关于这几个空间如下的公式:
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的立方体中:
透视矩阵:
图形在经过正交矩阵的变化之后,为了实现“近大远小”的效果,需要进行一个压缩,称之为压缩矩阵,关于这个压缩矩阵的推导,在games101上或者其他地方有比较多的资料,用相似三角形和齐次坐标得来,不做推导,下面给出透视矩阵:
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 宽高比 近平面 远平面