计算机图形学 | 可编程渲染管线
华中科技大学《计算机图形学》课程
MOOC地址:计算机图形学(HUST)
计算机图形学 | 可编程渲染管线
3.1 从固定到可编程
图形编程的发展
早期的图形编程
那些用来绘制图元及其属性的对应函数库不存在或非常低级;
不具体硬件相关,不具备可移植性。
程序员有很大的控制权,耗费时间。
图形标准的产生
- 图形核心系统(Graphical Kernel System,GKS)
- 程序员层次式交互图形系统(Programmer’s Hierarchical Interactive Graphics System,PHIGS)
- 开放的图形库( Open Graphics LibraryOpenGL )
它们都与设备无关。
固定管线
图形API提供了一个对硬件进行操作的标准接口;从内部实现上来说,API对程序员提出的各种绘制图元或属性的请求都采用固定的方式来处理。
这种内部实现方式通常称为固定功能渲染流水线。
对程序员来说,控制权减少,非常方便。
典型的光栅扫描图形显示子系统:
流水线的概念:
固定到可编程:钩函数hooks的出现突破固定功能流水线的限制,使用可编程着色器修改流水线中某些特定步骤的行为。
GPU渲染管线
渲染管线的功能
决定在给定虚拟相机、三维物体、光源、照明模式,以及纹理等诸多条件的情况下生成或绘制一幅二维图像的过程。
流水线中的三个概念阶段
应用阶段
将需要在屏幕上显示出来绘制的几何体,也就是绘制图元,比如点、线、矩形等输入到绘制管线的下一个阶段。
具体包括图元的顶点数据、摄像机位置、光照纹理等参数。
几何阶段
几何阶段需要将顶点数据最终进行屏幕映射。
这其中需要:
- 将各个图元放入到世界坐标系中,也就是进行模型变换;
- 根据光照纹理等计算顶点处材质的光照着色效果;
- 根据摄像机的位置、取景范围进行观察变换和裁剪;
- 最后进行屏幕映射,也就是把三维模型转换到屏幕坐标系中
光栅化阶段
光栅化部分的输入是经过变换和投影后的顶点、颜色以及纹理坐标,它的工作是给每个像素正确配色,以便绘制整幅图形。
由于输入的是三角形顶点,所以需要根据三角形表面的差异,逐个遍历三角形计算各个像素的颜色值。之后根据其可见性等进行合并得到最后的输出。
实例:
3.2 探秘GPU渲染管线
GPU渲染管线
几何阶段
光栅化阶段
片元操作:
3.3 着色器编程
着色器语言
Phong光照明模型的综合表述:
由物体表面上一点P反射到视点的光强I为环境光的反射光强、理想漫反射光强和镜面反射光强的总和。
I = I a K a + I p K d ( L × N ) + I p K s ( R × V ) n I=I_aK_a+I_pK_d(L×N)+I_pK_s(R×V)^n I=IaKa+IpKd(L×N)+IpKs(R×V)n
GLSL
OpenGL的着色器语言GLSL,也就是OpenGL Shading Language。
- 顶点着色器(Vertex Shader)
- 几何着色器(Geometry Shader)
- 曲面细分着色器(Tessellation Shader)
- 片元着色器(Fragment Shader)
在OpenGL中使用着色器的流程:
- 创建着色器对象
- 源码关联到着色器对象
- 编译着色器
- 创建一个程序对象
- 创建一个程序对象
与OpenGL的通信:
数据类型:
- 标量:整数(int)、无符号整数(uint)、布尔类型(bool)及浮点数(float)
- 矢量:一个矢量可以有2、3、4个分量
- 矩阵
- 结构和数组:结构体的成员或者数组的基类型可以为任意的数据类型
控制结构:
- 循环结构:for、while和do-while
- 选择结构:if-then、if-then-else,Glsl3.0版本引入了switch结构
EBO、VBO和VAO
EBO(Element Buffer Object,也叫IBO:Index Buffer Object):索引缓冲区对象,这个缓冲区主要用来存储顶点的索引信息。
VBO(Vertex Buffer Object):顶点缓冲区对象,主要用来存储顶点的各种信息。
好处:模型的顶点信息放进VBO,这样每次画模型时,数据丌用再从CPU的势力范围内存里取,而是直接从GPU的显存里取,从而提高效率。
VAO(Vertex Arrary Object):顶点数组对象
VAO是一个保存了所有顶点数据属性的状态结合,它存储了顶点数据的格式以及顶点数据所需的VBO对象的引用。VAO本身并没有存储顶点的相关属性数据,这些信息是存储在VBO中的,VAO相当于是对很多个VBO的引用,把一些VBO组合在一起作为一个对象统一管理。
VAO和VBO之间的关系:
例程
方法一:VAO+VBO绘制四边形
// 渲染之前VAO/VBO的绑定生成
// 顶点数据
float vertices[] = {
// 第一个三角形
0.5f, 0.5f, 0.0f, // 右上
0.5f, -0.5f, 0.0f, // 右下
-0.5f, -0.5f, 0.0f, // 左下
// 第二个三角形
-0.5f, -0.5f, 0.0f, // 左下
0.5f, 0.5f, 0.0f, // 右上
-0.5f, 0.5f, 0.0f // 左上
};
// 生成立方体的VAO、VBO
unsigned int VBO, VAO;
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
// 绑定VAO
glBindVertexArray(VAO);
// 绑定VBO并传入顶点数据
glBindBuffer(GL_ARRAY_BUFFER, 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);
// 解绑VBO
glBindBuffer(GL_ARRAY_BUFFER, 0);
// 解绑VAO
glBindVertexArray(0);
// 渲染时
// 使用之前链接好的着色器程序
glUseProgram(shaderProgram);
// 绑定VAO
glBindVertexArray(VAO);
// 用VAO绘制四边形
glDrawArrays(GL_TRIANGLES, 0, 6);
方法二:VAO+VBO+EBO绘制四边形
// 渲染之前VAO/VBO/EBO的绑定生成
// 顶点数据
float vertices[] = {
0.5f, 0.5f, 0.0f, // 右上
0.5f, -0.5f, 0.0f, // 右下
-0.5f, -0.5f, 0.0f, // 左下
-0.5f, 0.5f, 0.0f // 左上
};
// 索引数据(注意这里是从0开始的)
unsigned int indices[] = {
0, 1, 3, // 第一个三角形
1, 2, 3 // 第二个三角形
};
// 生成立方体的VAO、VBO和EBO
unsigned int VBO, VAO, EBO;
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
glGenBuffers(1, &EBO);
// 绑定VAO
glBindVertexArray(VAO);
// 绑定VBO并传入顶点数据
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// 绑定EBO并传入索引数据
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
// 设置顶点属性指针
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
// 解绑VBO
glBindBuffer(GL_ARRAY_BUFFER, 0);
// 注意丌要解绑EBO,因为EBO存储在VAO中
//glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
// 解绑VAO
glBindVertexArray(0);
// 渲染时
// 使用之前链接好的着色器程序
glUseProgram(shaderProgram);
// 绑定VAO(实际上丌需要每次绑定)
glBindVertexArray(VAO);
// 用EBO绘制四边形
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);