0/openGL环境搭建

在【从0开始手搓游戏引擎】系列里面,图形API是自己手动调用的,刚开始Windows窗体和DX11还好,到了openGL真的就是噩梦,虽然说openGL的跨平台做的非常好,但是作为初学者上来就接触这么多代码还是有点发怵。索性可以通过glfw可以通过几行代码生成整个OpenGL环境,果然学一门东西还是要从简单的开始入手,没有几个人能够撑下来“这是攻击,这是防御,这是大树守卫环节”。

下面是涉及到的代码,我尽量用我的理解写下来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <glad/gl.h>
#define GLFW_INCLUDE_NONE
#include <GLFW/glfw3.h>

int main(void)
{
GLFWwindow* window;

/* 初始化glfw */
if (!glfwInit())
return -1;

/* 创建一个Window 和 OpenGL上下文 */
window = glfwCreateWindow(960, 640, "Hello World", NULL, NULL);
if (!window)
{
//创建失败就退出
glfwTerminate();
return -1;
}

/* 激活上面创建的OpenGL上下文 */
glfwMakeContextCurrent(window);
gladLoadGL(glfwGetProcAddress);

/* 进入游戏引擎主循环 */
while (!glfwWindowShouldClose(window))
{
/* Render here */
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
glClearColor(49.f/255,77.f/255,121.f/255,1.f);

/* Swap front and back buffers */
glfwSwapBuffers(window);

/* 处理鼠标 键盘事件 */
glfwPollEvents();
}

glfwTerminate();
return 0;
}

大体表述的处理流程就是下面这样:

基本流程

1/绘制图形

文件名称 文件描述
main.cpp 主逻辑
VertexData.h 顶点数据(坐标、颜色)
ShaderSource.h Shader代码(顶点Shader和片段Shader)

VertexData.h用来存放需要绘制的三角形相关顶点数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#ifndef UNTITLED_VERTEXDATA_H
#define UNTITLED_VERTEXDATA_H
#include <glm/glm.hpp>
// 顶点的坐标信息
static const glm::vec3 kPositions[3] = {
glm::vec3{ -1.0f, -1.0f,0.0f},
glm::vec3{ 1.0f, -1.0f,0.0f},
glm::vec3{ 0.f, 1.0f,0.0f}
};
// 顶点的颜色信息
static const glm::vec4 kColors[3] = {
glm::vec4{ 1.f, 0.f, 0.f ,1.f},
glm::vec4{ 0.f, 1.f, 0.f ,1.f},
glm::vec4{ 0.f, 0.f, 1.f ,1.f}
};
#endif //UNTITLED_VERTEXDATA_H

然后是着色器的代码,由于本人对着色器仍然处于一知半解的状态,所以就只贴代码.大体来说就是告诉显卡顶点该怎么绘制,顶点间的片段该怎么绘制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//
// Created by captain on 2021/3/25.
//
#ifndef UNTITLED_SHADERSOURCE_H
#define UNTITLED_SHADERSOURCE_H
//顶点着色器代码
static const char* vertex_shader_text =
"#version 110\n"
"uniform mat4 u_mvp;\n"
"attribute vec3 a_pos;\n"
"attribute vec4 a_color;\n"
"varying vec4 v_color;\n"
"void main()\n"
"{\n"
" gl_Position = u_mvp * vec4(a_pos, 1.0);\n"
" v_color = a_color;\n"
"}\n";
//片段着色器代码
static const char* fragment_shader_text =
"#version 110\n"
"varying vec4 v_color;\n"
"void main()\n"
"{\n"
" gl_FragColor = v_color;\n"
"}\n";
#endif //UNTITLED_SHADERSOURCE_H

看完之后,我大致的理解就是下图:

着色器绘制图形的基本过程

Shader本质上来说就是运行在GPU上面的程序

目前市面上的手机,很大一部分仍然是OpenGL ES3.0的版本或更低。

Computer Shader在OpenGL ES 3.1版本才开始支持。

Geometry Shader在OpenGL ES 3.2版本才开始支持。

2/顶点着色器

顶点着色器的功能就是:对输入的顶点坐标进行处理,然后再输出。

版本限定

1
#version 110

每个Shader开头的语句,就是版本限定,也就是说如果你的设备不支持这个版本,那么这个Shader将直接被跳过

统一变量与属性变量以及输出变量

1
2
3
4
uniform mat4 u_MVPMatrix;
attribute vec3 a_pos;
attribute vec4 a_color;
varying vec4 v_color;

由于Shader是运行在GPU上的程序,而GPU是并行运算的.二者的区别就在于一组顶点如果分摊到GPU的逻辑单元,如果每个逻辑单元处理的是相同的数据,那么就称之为统一变量,反之就是属性变量

变量的修饰有点类似于OOP中的public与private

attribute所修饰的属性变量每执行一次Shader就需要被重新赋值

输出变量由varying关键字修饰,用于从顶点着色器,传递数据到片段着色器。

每个Shader都有入口函数 main(),顶点Shader主要工作就是:计算坐标。

得到坐标计算结果后,传给内置变量 gl_Position。

GPU拿到gl_Position,执行裁剪。

3/片段着色器

片段着色器(像素着色器)的功能就是:输出颜色。

片段着色器(像素着色器)也是并行的,不过执行的次数不是顶点个数,而是屏幕像素个数。

举例绘制一个960x540的长方形,每一个像素点的颜色,都是通过执行一次片段着色器来得到,那么GPU需要执行960x540次。

顶点着色器的输出变量,就是片段着色器的输入变量

片段着色器一般都是输出到gl_FragColor这个内置变量。
所有从顶点着色器输出到片段着色器的数据,都会插值!

4/UV

UV坐标指的是顶点对应在图片的哪个位置

UV的坐标范围是[0f, 1f],UV映射就是顶点坐标和贴图对应起来的操作.

贴图文件大致分为三种:

  • 没有压缩的图片格式 bmp
  • CPU压缩的图片格式 无损压缩:png,有损压缩:jpg
  • 显卡支持的图片格式

贴图的显示方式差不多就是下面这样:

贴图的渲染过程

5/杂项笔记

在GPU上创建对象,都是分三步,调用3个API:

  • glGenxxxx 在GPU上进行创建xx对象
  • glBindxxx 将xx对象指定为类型
  • glxxxxxxx 上传数据