绘制三角形的本质是光栅化的过程,那什么是光栅化呢? 简单来说,光栅化就是将三维图形转变成二维图形,在屏幕上显示出来的过程。(小时候看西游记的时候,一直在幻想自己可以钻进电视里哈哈哈),刚开始做3D相关软件开发的时候,心里就一直有个疑问,图形是怎么在电脑里的软件上显示出来的,后来百度的时候了解到是光栅化,当时对这个概念也是似懂非懂。言归正传,下面我们看一下这个张图,整个图形渲染管线包括以下几个阶段,而光栅化就是其中的一个阶段。总体而言,图形渲染管线包括应用程序阶段、几何阶段、光栅化阶段。
应用程序阶段所做的工作是由程序员决定的,是可控的,程序员可以使用加速算法,几何处理,这一阶段只有cpu参与。这一阶段的主要任务是将需要显示在屏幕上的图元(点,线,三角形)等发送给gpu的显存中。
几何阶段:主要包括mvp矩阵变换,顶点的着色,以及裁剪,视口变换。
这里重点说一下顶点着色,我们说 确定材质上的光照效果 的操作被称为着色。着色通常需要考虑三角形的每个顶点的数据,三角形的顶点可以存储材质,颜色或者深度等值,而三角形内部的这些值就通过插值来实现。
光栅化阶段:给定经过mvp变换之后的顶点,颜色以及纹理,给每个像素配正确的颜色,从而挥着整个图像的过程,叫做光栅化。首先我们可以用包围盒来确定三角形的位置,然后通过采样包围盒里的像素,通过三角形检测判断哪些像素是在三角形内,哪些像素是在三角形外,然后确定需要被着色的像素,通过z-buffer的方法,来判断深度值,确保屏幕上能显示的像素的值是最小的。
对于一整个图形而言,我们需要对一个大的图形进行三角剖分,对每一个三角形都进行着色。下一届会讲到矩形的绘制。整个光栅化过程可以分为:
1.三角形的设定
2.三角形遍历
3.像素着色
4.融合阶段(深度测试)
有了图形学图形渲染管线的一些知识,我们下面看一下opengl是怎样绘制三角形的。
//GLSL opengl shading language
//vertex triangles(shape assembly,geometry shader) rasterize fragment test blending
//图元类型 GL_POINTS GL_TRIANGLES GL_LINE_STRIP
#include <iostream>
#define GLEW_STATIC
#include<GL/glew.h>
#include<GLFW/glfw3.h>
void key_callback(GLFWwindow* window, int key, int sancode, int action, int mode);
void key_callback(GLFWwindow* window, int key, int sancode, int action, int mode)
{
if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
{
glfwSetWindowShouldClose(window, GL_TRUE);
}
}
// Window dimensions
const GLuint WIDTH = 800, HEIGHT = 600;
// Shaders
//顶点着色器源码
const GLchar* vertexShaderSource = "#version 330 core\n"
"layout (location = 0) in vec3 position;\n"
"void main()\n"
"{\n"
"gl_Position = vec4(position.x, position.y, position.z, 1.0);\n"
"}\0";
//片段颜色着色器源码
const GLchar* fragmentShaderSource = "#version 330 core\n"
"out vec4 color;\n"
"void main()\n"
"{\n"
"color = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n"
"}\n";
// The MAIN function, from here we start the application and run the game loop
int main()
{
std::cout << "Starting GLFW context, OpenGL 3.3" << std::endl;
// Init GLFW
//实例化glfw窗口并配置
glfwInit();
// Set all the required options for GLFW
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);
//创建窗口对象
// Create a GLFWwindow object that we can use for GLFW's functions
GLFWwindow* window = glfwCreateWindow(WIDTH, HEIGHT, "ILoveLearnOpenGL", nullptr, nullptr);
if (window == nullptr)
{
std::cout << "Failed to create GLFW window" << std::endl;
glfwTerminate();
return -1;
}
//将窗口上下文设置为当前主线程的上下文
glfwMakeContextCurrent(window);
//设置键盘输入的回调函数
glfwSetKeyCallback(window, key_callback);
// Set this to true so GLEW knows to use a modern approach to retrieving function pointers and extensions
//使用glew库来管理OpenGL的指针,GL_TRUE表示使用现代化的技术
glewExperimental = GL_TRUE;
// Initialize GLEW to setup the OpenGL Function pointers
if (glewInit() != GLEW_OK)
{
std::cout << "Failed to initialize GLEW" << std::endl;
return -1;
}
// Define the viewport dimensions
//根据屏幕来获取屏幕的w,h
int width, height;
glfwGetFramebufferSize(window, &width, &height);
glViewport(0, 0, width, height);
//glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
#pragma region 设置顶点属性
//创建shader
GLuint vertexShader;
vertexShader = glCreateShader(GL_VERTEX_SHADER);
//将着色器的源码附着在着色器上 然后编译
glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
glCompileShader(vertexShader);
//判断是否编译成功
GLint success;
GLchar infoLog[512];
glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
//不成功输出日志
if (!success)
{
glGetShaderInfoLog(vertexShader, 512, NULL, infoLog);
std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl;
}
#pragma endregion
//片段着色器
GLuint fragmentShader;
fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
glCompileShader(fragmentShader);
glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
if (!success)
{
glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog);
std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl;
}
//Shader Program Object
GLuint shaderProgram;
shaderProgram = glCreateProgram();
//将着色器附加到着色器程序上,然后链接
glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragmentShader);
glLinkProgram(shaderProgram);
//检测链接是否成功
glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
if (!success) {
glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog);
}
//-------至此,我们已经将cpu中存储的顶点数据发送给了gpu显存,并指示gpu
// 如何在渲染管线中的顶点shader以及fragment shader中去如何处理它
//glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), (GLvoid*)0);
#if 1
//定义三角形三个顶点
GLfloat vertices[] = {
-0.5f, -0.5f, 0.0f,
0.5f, -0.5f, 0.0f,
0.0f, 0.5f, 0.0f
};
#endif
//VBO vertex buffer object,分配一个ID 1
GLuint VBO;
glGenBuffers(1, &VBO);
//VAO vertex Array Object 顶点数组对象
GLuint VAO;
glGenVertexArrays(1, &VAO);
//绑定
glBindBuffer(GL_ARRAY_BUFFER, VBO);
//复制定义数据到缓冲内存,将cpu中数据发送到gpu 显存中
// 1. 绑定VAO
glBindVertexArray(VAO);
// 2. 把顶点数组复制到缓冲中供OpenGL使用
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// 3. 设置顶点属性指针
//接下来,我们告诉OpenGL该如何解释这些顶点
/* para1 顶点位置
* para2 顶点属性大小vec3 (3个值)
* para3 指定数据的类型
* para4 是否会被标准化 到[-1,1]
* para5 步长 stride ,连续顶点之间的间隔 设置为0 自动设置
* para6 偏移量
*/
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), (GLvoid*)0);
glEnableVertexAttribArray(0);
//4. 解绑VAO
glBindVertexArray(0);
// Game loop 游戏循环
while (!glfwWindowShouldClose(window))
{
// Check if any events have been activiated (key pressed, mouse moved etc.) and call corresponding response functions
//键盘鼠标响应事件
glfwPollEvents();
//填充成橙色
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
//激活 着色器程序对象
glUseProgram(shaderProgram);
glBindVertexArray(VAO);
glDrawArrays(GL_TRIANGLES, 0, 3);
//解绑
glBindVertexArray(0);
// Swap the screen buffers 交换颜色缓冲
//前缓冲保存着最终输出的图像,它会在屏幕上显示;
// 而所有的的渲染指令都会在后缓冲上绘制。当所有的渲染指令执行完毕后
// 我们交换(Swap)前缓冲和后缓冲
//这样图像就立即呈显出来,之前提到的不真实感就消除了。
glfwSwapBuffers(window);
}
//删除顶点着色器对象和片段着色器对象
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
// Terminate GLFW, clearing any resources allocated by GLFW.
glfwTerminate();
return 0;
}
具体的代码的含义可以参考:OpenGL - LearnOpenGL-CN
运行结果: