啊~~~~~~
终于终于学完了OpenGL的入门篇章。。。个人感觉,对于没有任何渲染和计算机图形学方面基础的同学来说,理解和记忆起来还是有一定难度的。好在诸如初始化环境、创建window、函数指针管理、VAO、VBO、VEO的创建和绑定、纹理的加载和绑定、shader着色器程序的创建和使用、uniform变量的使用方法、摄像机、欧拉角等的使用和创建,都有一定的程式化框架,基本就那么几个步骤,按着步骤来,总可以成功的。
当然,这完全是属于处在“知其然、而不咋地知其所以然”的状态下所采用的方式。当你真的了解并熟悉了整个渲染流程之后,这些所谓现在程式化的步骤,早就不是问题了。
来复习一下之前我们的都学习了些什么(走一遍程式化步骤~~~)
一、专业术语:
1. OpenGL:定义了函数布局和输出的图形API的正式规范,所有的显卡供应商必须按照这个规范来开发API
2. 视口Viewport:我们要渲染的窗口
3. 图形管线:一个顶点在呈现为屏幕像素之前所经历的全部过程的总称
4. 着色器shader:一个运行在显卡GPU上的小型程序。图形管线中的某些部分可以通过自定义着色器程序来替代原有功能,从而实现不同的顶点像素呈现效果。一个最简单的顶点着色器和片段着色器源代码可能是这样的:
// 一个简单顶点着色器,需要配置两个着色器属性,位置0处是顶点属性,位置1处表示顶点颜色属性
// 该顶点着色器有一个输出,即将顶点颜色输出,若在片段着色器中定义了同名的输入属性,则可以拿到该输出值
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aColor;
out vec3 vertexColor;
void main()
{
gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
vertexColor = aColor;
}
// 一个简单的片段着色器,有一个输入:vertexColor,可以接受定点着色器中同名的输入值
// 有一个输出,即为片元最终的颜色
#version 330 core
out vec4 FragColor;
in vec3 vertexColor;
void main()
{
FragColor = vec4(vertexColor, 1.0f);
}
// 创建顶点着色器
unsigned int vertex;
vertex = glCreateShader(GL_VERTEX_SHADER); // 创建顶点着色器
glShaderSource(vertex, 1, &vShaderCode, NULL); // 载入定点着色器源代码字符串
glCompileShader(vertex); // 编译定点着色器
// 创建片段着色器
unsigned int fragment;
fragment = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragment, 1, &fShaderCode, NULL);
glCompileShader(fragment);
// 创建着色器程序
unsigned int shaderProgram;
shaderProgram = glCreateProgram(); // 创建着色器程序
glAttachShader(shaderProgram, vertex); // 载入定在着色器至着色器程序中
glAttachShader(shaderProgram, fragment); // 载入片段着色器至着色器程序中
// 链接着色器程序
glLinkProgram(shaderProgram);
// 激活/启用着色器程序
glUseProgram(shaderProgram);
// 删除着色器,着色器链接给着色器程序之后,着色器本身就可以删除了
glDeleteShader(vertex);
glDeleteShader(fragment);
5. 标准化设备坐标NDC:顶点在通过在裁剪坐标系中的裁剪与透视除法后最终呈现在的坐标系。所有在NDC下,-1.0到1.0的顶点将不会被丢弃并可见
6. 顶点缓冲对象VBO:一个调用显存、并且存储所有顶点数据供显卡使用的缓冲对象
unsigned int VBO;
glGenBuffers(1, &VBO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// 配置shader着色器属性,这一部分与顶点矩阵中包含的数据信息、以及着色器程序的配置紧密相关
// 配置位置属性指针
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);
7. 顶点数组对象VAO:存储缓冲区和顶点的属性状态
unsigned int VAO;
glGenVertexArrays(1, &VAO);
glBindVertexArray(VAO);
8. 索引缓冲对象EBO:一个存储索引、供索引化绘制使用的缓冲对象
unsigned int EBO;
glGenBuffers(1, &EBO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
9. Uniform:特殊类型的GLSL变量,是一种全局类型的变量,在每一个着色器程序中都能够访问Uniform变量
10. 纹理Texture:一种包裹着物体的特殊类型图像
// 一个简单的纹理创建、加载、和绑定的类
#ifndef myTextureLoader_h
#define myTextureLoader_h
#include <stdio.h>
#include <iostream>
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <stb_image.h>
class myTextureLoader {
public:
int width;
int height;
int nrChannels;
unsigned char * data;
unsigned int texture;
myTextureLoader(char const *filename, bool flipVertically = true, bool canOpacity = false) {
data = stbi_load(filename, &width, &height, &nrChannels, 0);
glGenTextures(1, &texture);
bindMyTexture();
stbi_set_flip_vertically_on_load(flipVertically); // 加载纹理图片时,翻转y轴
if(data) {
if (canOpacity == true) {
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
} else {
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);
}
private:
void bindMyTexture() {
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);
}
};
#endif /* myTextureLoader_h */
11. 纹理缠绕:定义了一种当纹理顶点超出范围(0,1)时指定OpenGL如何采样纹理的模式
12. 纹理过滤:定义了一种当有多重温宿选择时,指定OpenGL如何采样纹理的模式,这通常在纹理被放大的情况下发生
13. 多级渐远纹理:被存储的材质的一些缩小版本,根据距观察者的距离会使用材质的合适大小
14. 局部空间:一个物体的初始空间。所有的坐标都是相对于物体的原点来描述的。
15. 世界空间:所有的坐标都相对于全局原点来描述的空间
16. 观察空间:所有的坐标都是从摄像机的视角观察的空间
17. 裁剪空间:所有的坐标都是从摄像机视角观察的,但是该空间应用了投影,这个空间应该是一个顶点坐标的最终空间,作为顶点着色器的输出。
18. 屏幕空间:所有的坐标都是由屏幕视角类观察。坐标范围是从0到屏幕的宽、高
19. 模型矩阵:定义了一个变换矩阵,可以将局部坐标变换到世界坐标。一个模型矩阵可能是这样子的:
// 创建模型矩阵: 将顶点绕x轴旋转-55度
glm::mat4 model = glm::mat4(1.0f);
model = glm::rotate(model, glm::radians(-55.0f), glm::vec3(1.0f, 0.0f, 0.0f));
20. 观察矩阵:定义了一个变换矩阵,用来将坐标从世界空间坐标变换到观察空间坐标。后面的LookAt矩阵和通过欧拉角创建的矩阵,实际上都是为了获得观察矩阵。一个可能的观察矩阵可能是这样的:
// 创建一个最简单的观察矩阵,观察空间距原点3.0
glm::mat4 view = glm::mat4(1.0f);
view = glm::translate(view, glm::vec3(0.0f, 0.0f, -3.0f));
// 使用LookAt矩阵作为观察矩阵: lookAt(观察点位置, 看向的目标点位置, 世界空间上轴)
// 摄像机位置在(0.0f, 0.0f, 3.0f)处,看向世界空间原点(0.0f, 0.0f, 0.0f),世界空间上向量为(0.0f, 1.0f, 0.0f)
glm::mat4 view = glm::mat4(1.0f);
view = glm::lookAt(glm::vec3(0.0f, 0.0f, 3.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 1.0f, 0.0f));
21. 透视矩阵:定义了一个变换矩阵,用来坐标从观察空间变换到裁剪空间坐标。投影矩阵有正射投影矩阵和透视投影矩阵两种类型,一般来说,透视投影矩阵更符合实际场景,具有“近大远小”的效果。一个可能的透视投影矩阵可能是这样的:
// 创建透视投影矩阵
glm::mat4 projection = glm::mat4(1.0f);;
projection = glm::perspective(glm::radians(45.0f), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f);
22. LookAt矩阵:是一种特殊类型的观察矩阵,它创建了一个坐标系,其中所有的坐标都根据从一个位置正在观察目标的用户旋转或平移。
lookAt(观察点位置, 看向的目标点位置, 世界空间上轴)
23. 欧拉角:被定义为偏航角Yaw,俯仰角Pitch,和滚转角Roll,从而允许我们通过这三个值构造任何3D方向
二、第三方工具库
- glad
- glfw
- glew
- stb_image
- glm