3D坐标转2D坐标,由OpenGL图形渲染管线管理。
Graphics Pipeline:管线:实际上指的是一堆原始图形数据途经一个输送管道,期间经过各种变化处理最终出现在屏幕的过程
2D坐标与像素不同:
2D坐标精确表示一个点在2D空间中的位置,而2D像素是这个点的近似值,2D像素受到你的屏幕/窗口分辨率的限制。
OpenGl着色器:OpenGL Shading Language, GLSL
图元(Primitive):OpenGL需要你去指定这些数据所表示的渲染类型,例子:GL_POINTS、GL_TRIANGLES、GL_LINE_STRIP。
图形渲染管线的几个阶段?
一、
顶点着色器(Vertex Shader):顶点着色器主要的目的是把3D坐标转为另一种3D坐标(后面会解释),同时顶点着色器允许我们对顶点属性进行一些基本处理。
二、
图元装配(Primitive Assembly)阶段:把着色器的顶点作为输入。装配成指定的图元形状。
三、
几何着色器(Geometry Shader):把图元形式的顶点集作为输入,并通过构造新顶点产生形状。本例是三角形。
四、
光栅化阶段(Rasterization Stage):把图元映射为屏幕上的像素,生成(Fragment)片段(供片段着色器使用Fragment Shader)
五、
片段之前会裁切(Clipping),裁切会丢弃超出你的视图以外的所有像素,用来提升执行效率。
***OpenGL中的一个片段是OpenGL渲染一个像素所需的所有数据。***
六、
片段着色器的主要目的是计算一个像素的最终颜色。
七、
Alpha测试和混合(Blending)阶段,这个阶段检测片段的对应的深度(和模板(Stencil))值(后面会讲),用它们来判断这个像素是其它物体的前面还是后面,决定是否应该丢弃。
标准化设备坐标(Normalized Device Coordinates, NDC)
:标准化设备坐标是一个x、y和z值在-1.0到1.0的一小段空间
我们通过顶点缓冲对象(Vertex Buffer Objects, VBO)管理这个内存:好处:对象比点发送的速度要快。
绑定缓冲:OpenGL允许同时绑定多个缓冲,只要是不同类型的缓冲,使用glBindBuffer函数吧新创建的缓冲绑定到GL_ARRAY_BUFFER上。
GLuint VBO;
glGenBuffers(1, &VBO);
生成VBO对象。
OpenGl有很多 缓冲对象类型, 但对于《《顶点缓冲对象》》的类型是GL_ARRAY_BUFFER.
然后呢?
glBindBuffer(GL_ARRAY_BUFFER, VBO);
这是吧VBO绑定到GL_ARRAY_BUFFER目标上,
所以,之后的任何缓冲调用都会用来配置当前绑定的缓冲(VBO)。
glBufferDate(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
把之前定义的顶点数据复制到缓冲内存中。glBufferDate是专门把用户定义的数据复制到当前绑定缓冲的函数。
四个参数:1,目标缓冲的类型,绑定到谁身上。
2,传输数据的大小(字节) 3,发送的数据 4,显卡管理形式:
(1) GL_STATIC_DRAW :数据不会或几乎不会改变。
(2) GL_DYNAMIC_DRAW:数据会被改变很多。
(3) GL_STREAM_DRAW :数据每次绘制时都会改变。
片段着色器:控制颜色输出:RGBA:红色、绿色、蓝色和alpha(透明度)分量
OpenGl和GLSL定义的颜色,分量强度设置在0.0到1.0之间。
(Fragment Shader:
下步骤:
将数据与着色器属性链接。
索引缓冲对象(Element Buffer Object,EBO,也叫Index Buffer Object,IBO):
作用:专门储存索引
创建索引缓冲对象:
GLuint EBO;
glGenBuffers(1, &EBO);
然后是绑定:绑定的缓冲类型定义为:GL_ELEMENT_ARRAY_BUFFER
之后把索引复制到缓冲里。
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
最后一件要做的事是用glDrawElements来替换glDrawArrays函数,来指明我们从索引缓冲渲染
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);:

绑定2次:
绘图两次即可:

Graphics Pipeline:管线:实际上指的是一堆原始图形数据途经一个输送管道,期间经过各种变化处理最终出现在屏幕的过程
2D坐标与像素不同:
2D坐标精确表示一个点在2D空间中的位置,而2D像素是这个点的近似值,2D像素受到你的屏幕/窗口分辨率的限制。
OpenGl着色器:OpenGL Shading Language, GLSL
图元(Primitive):OpenGL需要你去指定这些数据所表示的渲染类型,例子:GL_POINTS、GL_TRIANGLES、GL_LINE_STRIP。
图形渲染管线的几个阶段?
一、
顶点着色器(Vertex Shader):顶点着色器主要的目的是把3D坐标转为另一种3D坐标(后面会解释),同时顶点着色器允许我们对顶点属性进行一些基本处理。
二、
图元装配(Primitive Assembly)阶段:把着色器的顶点作为输入。装配成指定的图元形状。
三、
几何着色器(Geometry Shader):把图元形式的顶点集作为输入,并通过构造新顶点产生形状。本例是三角形。
四、
光栅化阶段(Rasterization Stage):把图元映射为屏幕上的像素,生成(Fragment)片段(供片段着色器使用Fragment Shader)
五、
片段之前会裁切(Clipping),裁切会丢弃超出你的视图以外的所有像素,用来提升执行效率。
***OpenGL中的一个片段是OpenGL渲染一个像素所需的所有数据。***
六、
片段着色器的主要目的是计算一个像素的最终颜色。
七、
Alpha测试和混合(Blending)阶段,这个阶段检测片段的对应的深度(和模板(Stencil))值(后面会讲),用它们来判断这个像素是其它物体的前面还是后面,决定是否应该丢弃。
标准化设备坐标(Normalized Device Coordinates, NDC)
:标准化设备坐标是一个x、y和z值在-1.0到1.0的一小段空间
我们通过顶点缓冲对象(Vertex Buffer Objects, VBO)管理这个内存:好处:对象比点发送的速度要快。
绑定缓冲:OpenGL允许同时绑定多个缓冲,只要是不同类型的缓冲,使用glBindBuffer函数吧新创建的缓冲绑定到GL_ARRAY_BUFFER上。
GLuint VBO;
glGenBuffers(1, &VBO);
生成VBO对象。
OpenGl有很多 缓冲对象类型, 但对于《《顶点缓冲对象》》的类型是GL_ARRAY_BUFFER.
然后呢?
glBindBuffer(GL_ARRAY_BUFFER, VBO);
这是吧VBO绑定到GL_ARRAY_BUFFER目标上,
所以,之后的任何缓冲调用都会用来配置当前绑定的缓冲(VBO)。
glBufferDate(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
把之前定义的顶点数据复制到缓冲内存中。glBufferDate是专门把用户定义的数据复制到当前绑定缓冲的函数。
四个参数:1,目标缓冲的类型,绑定到谁身上。
2,传输数据的大小(字节) 3,发送的数据 4,显卡管理形式:
(1) GL_STATIC_DRAW :数据不会或几乎不会改变。
(2) GL_DYNAMIC_DRAW:数据会被改变很多。
(3) GL_STREAM_DRAW :数据每次绘制时都会改变。
片段着色器:控制颜色输出:RGBA:红色、绿色、蓝色和alpha(透明度)分量
OpenGl和GLSL定义的颜色,分量强度设置在0.0到1.0之间。
(Fragment Shader:
下步骤:
将数据与着色器属性链接。
索引缓冲对象(Element Buffer Object,EBO,也叫Index Buffer Object,IBO):
作用:专门储存索引
创建索引缓冲对象:
GLuint EBO;
glGenBuffers(1, &EBO);
然后是绑定:绑定的缓冲类型定义为:GL_ELEMENT_ARRAY_BUFFER
之后把索引复制到缓冲里。
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
最后一件要做的事是用glDrawElements来替换glDrawArrays函数,来指明我们从索引缓冲渲染
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);:
四个参数:绘制模式、顶点数、索引类型、偏移量。
下面是基础图形代码:
// GLEW
#define GLEW_STATIC
#include <GL/glew.h>
// GLFW
#include <GLFW/glfw3.h>
#include <iostream>
void key_callback(GLFWwindow* windows, int key, int scancode, int action, int mode);
void key_callback2(GLFWwindow* windows, int key, int scancode, int action, int mode);
//顶点着色器源码:
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"
"}\0";
int main()
{
std::cout << "Starting GLFW context, OpenGL 3.3" << std::endl;
//这都是初始化GLFW,后面的要看文档
glfwInit();
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);
//创建窗口,
GLFWwindow* window = glfwCreateWindow(800, 600, "LearnOpenGL", nullptr, nullptr);
if (window == nullptr)
{
std::cout << "Failed to create GLFW window" << std::endl;
glfwTerminate();
return -1;
}
glfwMakeContextCurrent(window);//通知GLFW将我们窗口的上下文设置为当前线程的主上下文
//不知道回调函数为什么要加在这里??
//glfwSetKeyCallback(window, key_callback2);回调函数只有最下面那个有作用
glfwSetKeyCallback(window, key_callback);
//初始化GLEW(管理OpenGL的函数指针)
glewExperimental = GL_TRUE;
if (glewInit() != GLEW_OK)
{
std::cout << "Failed to initialize GLEW" << std::endl;
return -1;
}
//视口,OpenGl知道相对于窗口大小显示数据和坐标
int width, height;
glfwGetFramebufferSize(window, &width, &height);
glViewport(0, 0, width, height);//前两个控制左下角的位置,后面是窗口宽度高度(像素)
//动态编译着色器源码:
GLuint vertexShader;
//把创建着色器类型以参数的形式提供给glCreateShader,定点着色器,传递的参数是GL_VERTEX_SHADER.
vertexShader = glCreateShader(GL_VERTEX_SHADER);
//下一步把着色器的源码附加到着色器对象上,然后编译它
glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
glCompileShader(vertexShader);
//下面代码是检查是否编译成功
GLint success;
GLchar infoLog[512];
glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
//用glGetShaderiv检查是否便宜成功,如果失败,获取错误消息,然后打印
if (!success)
{
glGetShaderInfoLog(vertexShader, 512, NULL, infoLog);
std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl;
}
//片段着色器同样的道理
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::FRAGMENT::COMPILATION_FAILED\n" << infoLog << std::endl;
}
//下面是把多个着色器链接为一个着色器程序对象
GLuint shaderProgram;
shaderProgram = glCreateProgram();
//glCreateProgram函数创建一个程序,并返回新创建程序对象的ID引用。
//后面是附加,把之前的着色器附加到程序对象上,然后glLinkProgram链接他们
glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragmentShader);
glLinkProgram(shaderProgram);
//检查编译错误
glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
if (!success) {
glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog);
std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl;
}
//链接完厚删除着色器对象
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
GLfloat 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, // 左上角
};
GLuint indices[] = {
0,1,3,
1,2,3,
0,2,3,
};
GLuint VBO, VAO,EBO;
//使用glGenBuffers函数和一个缓冲ID生成一个VBO对象??
glGenBuffers(1, &VBO);
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &EBO);
//绑定VAO
glBindVertexArray(VAO);
//复制顶点数组到缓冲中,glBufferData是一个专门用来把用户定义的数据复制到当前绑定缓冲的函数
glBindBuffer(GL_ARRAY_BUFFER, VBO);//glBindBuffer函数把新创建的缓冲绑定到GL_ARRAY_BUFFER目标上
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
//设置定点属性指针
//在这里加上索引缓冲,复制我们的索引数组到一个索引缓冲中,供OpenGL使用
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(GLfloat), (GLvoid*)0);
glEnableVertexAttribArray(0);
//安全?
glBindBuffer(GL_ARRAY_BUFFER, 0);
//解绑VAO
glBindVertexArray(0);
//防止退出的循环
while (!glfwWindowShouldClose(window))
{
glfwPollEvents();
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
//glClearColor是一个状态设置函数,glClear是一个状态应用函数
//绘图
glUseProgram(shaderProgram);
glBindVertexArray(VAO);
//glDrawArrays(GL_TRIANGLES, 0, 3);//绘制图形函数,0-3是数组索引
//用glDrawElements来替换glDrawArrays函数,来指明我们从索引缓冲渲染
//glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
//线框模式
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
glDrawElements(GL_TRIANGLES, 9, GL_UNSIGNED_INT, 0);
glBindVertexArray(0);
glfwSwapBuffers(window);
}
//回调函数
//释放:
glDeleteVertexArrays(1, &VAO);
glDeleteBuffers(1, &VBO);
glfwTerminate(); //释放/删除之前的分配的所有资源
return 0;
}
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode)
{
std::cout << key << std::endl;
//用户按下ESC键,我们设置window窗口WindowShouldClose属性为true
//关闭应用程序
if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
glfwSetWindowShouldClose(window, GL_TRUE);
}
void key_callback2(GLFWwindow* window, int key, int scancode, int action, int mode)
{
std::cout << key << std::endl;
//用户按下ESC键,我们设置window窗口WindowShouldClose属性为true
//关闭应用程序
if (key == 65)
std::cout << "成功回调A键" << std::endl;
}
//问题:对ABO和VAO的并不怎么理解?他们的区别在这个例子中也看不出来。
简单的几种图形和线框模式很容易做出来了。
下面是几个练习:
1.Try to draw 2 triangles next to each other using glDrawArrays by adding more vertices to your data:
增加定点后,添加一个绘制三角形函数即可。glDrawArrays:
GLfloat vertices[] = {
0.0f,0.0f,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
};
glDrawArrays(GL_TRIANGLES, 0, 3);//绘制图形函数,0-3是数组索引
glDrawArrays(GL_TRIANGLES, 3, 6);
或者也可以增加到6个点,之后:glDrawArrays(GL_TRIANGLES, 0, 6);
2.Now create the same 2 triangles using two different VAOs and VBOs for their data:
该练习告诉你的是VBO,VAO也可以是数组,可以多个。
建立两个顶点数组:
GLfloat firstTriangle[] = {
-0.9f,-0.5f,0.0f,
-0.0f,-0.5f,0.0f,
-0.45f,0.5f,0.0f
};
GLfloat secondTriangle[] = {
0.9f,-0.5f,0.0f,
0.0f,-0.5f,0.0f,
0.45f,0.5f,0.0f
};
绑定2次:
GLuint VBOs[2], VAOs[2],EBO;
//使用glGenBuffers函数和一个缓冲ID生成一个VBO对象??
glGenBuffers(2, VBOs);
glGenVertexArrays(2, VAOs);
glGenBuffers(1, &EBO);
//绑定VAO
glBindVertexArray(VAOs[0]);
//复制顶点数组到缓冲中,glBufferData是一个专门用来把用户定义的数据复制到当前绑定缓冲的函数
glBindBuffer(GL_ARRAY_BUFFER, VBOs[0]);//glBindBuffer函数把新创建的缓冲绑定到GL_ARRAY_BUFFER目标上
glBufferData(GL_ARRAY_BUFFER, sizeof(firstTriangle), firstTriangle, GL_STATIC_DRAW);
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(GLfloat), (GLvoid*)0);
glEnableVertexAttribArray(0);
glBindVertexArray(0);
//安全?
glBindBuffer(GL_ARRAY_BUFFER, 0);
绘图两次即可:
glUseProgram(shaderProgram);
glBindVertexArray(VAOs[0]);
glDrawArrays(GL_TRIANGLES, 0, 3);//绘制图形函数,0-3是数组索引
glBindVertexArray(VAOs[1]);
glDrawArrays(GL_TRIANGLES, 0, 3);
glBindVertexArray(0);
3.Create two shader programs where the second program uses a different fragment shader that outputs the color yellow; draw both triangles again where one outputs the color yellow:
我自己是写的数组,写两个片段着色器两套源码。也可以写两个名字两个片段着色器。
GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
GLuint fragmentShaderOrange = glCreateShader(GL_FRAGMENT_SHADER);
GLuint fragmentShaderYellow = glCreateShader(GL_FRAGMENT_SHADER);
GLuint shaderProgramOrange = glCreateProgram();
GLuint shaderProgramYellow = glCreateProgram();
glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
glCompileShader(vertexShader);
glShaderSource(fragmentShaderOrange, 1, &fragmentShaderSource, NULL);
glCompileShader(fragmentShaderOrange);
glShaderSource(fragmentShaderYellow, 1, &fragmentShaderSource2, NULL);
glCompileShader(fragmentShaderYellow);
glAttachShader(shaderProgramOrange, vertexShader);
glAttachShader(shaderProgramOrange, fragmentShaderOrange);
glLinkProgram(shaderProgramOrange);
glAttachShader(shaderProgramYellow, vertexShader);
glAttachShader(shaderProgramYellow, fragmentShaderYellow);
glLinkProgram(shaderProgramYellow);