#include <iostream>
#define GLEW_STATIC
#include <GL/glew.h>
#include <GLFW/glfw3.h>
using namespace std;
//int main()
//{
//
// printf("hihihi");
//
// cin.get();
//
//
// return 0;
//}
//齐次坐标系 【-1,1】Normalized Device Coordinates(NDC)
//Vertex input
float vertices[] = {
-0.5f, -0.5f, 0.0f,
0.5f, -0.5f, 0.0f,
0.0f, 0.5f, 0.0f,
0.5f, -0.5f, 0.0f,
0.0f, 0.5f, 0.0f,
0.8f, 0.8f, 0.0f
};
//Vertex shader
const char* vertexShaderSource = "#version 330 core\n"
"layout (location = 6) in vec3 aPos;\n"
"void main()\n"
"{\n"
" gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n"
"}\0";
//Fragment shader
const char* fragmentShaderSource ="#version 330 core\n"
"out vec4 FragColor; \n"
"void main()\n"
"{\n"
" FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n"
"}\0";
void processInput(GLFWwindow* window)
{
if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
glfwSetWindowShouldClose(window, true);
}
int main(int argc, char* argv[])
{
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 EXIT_FAILURE;
}
glfwMakeContextCurrent(window);
//==========================
glewExperimental = GL_TRUE;
if (glewInit() != GLEW_OK)
{
std::cout << "Failed to initialize GLEW" << std::endl;
return EXIT_FAILURE;
}
int width, height;
glfwGetFramebufferSize(window, &width, &height);
glViewport(0, 0, width, height);
//可视化窗口之后,绑定该数据,产生Vertex input
// vertex buffer objects (VBO) can store a large number of vertices in the GPU's memory.
unsigned int VBO;
//1.产生一个bufferID
glGenBuffers(1, &VBO);
//2.将改bufferID绑定到GL_ARRAY_BUFFER copy our vertices array in a buffer for OpenGL to use
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
//Vertex shader
unsigned int vertexShader;
vertexShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
glCompileShader(vertexShader);
//FragmentShader
unsigned int fragmentShader;
fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
glCompileShader(fragmentShader);
//Shader Program
unsigned int shaderProgram;
shaderProgram = glCreateProgram();
glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragmentShader);
glLinkProgram(shaderProgram);
//VAO Vertex Array Object
unsigned int VAO;
glGenVertexArrays(1, &VAO);
// 1. bind Vertex Array Object
glBindVertexArray(VAO);
2. copy our vertices array in a buffer for OpenGL to use
//glBindBuffer(GL_ARRAY_BUFFER, VBO);
//glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// 3. then set our vertex attributes pointers
glVertexAttribPointer(6, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(6);
while (!glfwWindowShouldClose(window))
{
processInput(window);
glClearColor(0.2, 0.3, 0.3f, 1.0);
glClear(GL_COLOR_BUFFER_BIT);
// ..:: Drawing code (in render loop) :: ..
// 4. draw the object
glUseProgram(shaderProgram);
glBindVertexArray(VAO);
glDrawArrays(GL_TRIANGLES, 0, 6);
glfwPollEvents();
glfwSwapBuffers(window);
}
glfwTerminate();
//std::cin.get();
return 0;
}
转自opengpu论坛:
3D图形的概念和渲染管线(Render Pipeline)
前面介绍了3D图形历史,接下来要解说的是3D图形的处理流程。
3D图形管线的流程图
另:
CPU负责的3D图形处理部分 = 游戏引擎?
图1的[1]和[2]的部分,主要是在CPU里进行的处理。
配置3D物体或是移动后再设置,因为这两种很类似,所以把这些在系统中处理的部分全部称作[游戏引擎]。
在游戏引擎中,遵从键盘输入、鼠标输入、游戏控制输入让3D角色移动,或是进行开枪击中敌人的命中时的碰撞检测,根据碰撞的结果,虽然要进行把3D角色们击飞的物理模拟,但这些是游戏逻辑的部分,某种意味上,是和[1][2]相同的部分。
另外,在[2]中如果有对应DirectX 10/SM4.0的GPU,要是能利用Geometry Shader,那么也可以在GPU上进行了,例如关于particle或billboard这样的point sprite,把创建和销毁用Geometry Shader来处理,就可以实现让GPU介入的处理。虽然如此,一般的3D游戏处理中,这部分还是由CPU来处理的部分。
Vertex Pipeline和Vertex Shader的坐标系是什么?
图中红线的[3][4][5][6]的部分,是进行顶点相关处理的顶点管线。
通常从这里开始是在GPU内部进行处理的部分。但是,为了简化内部逻辑以降低成本,所以要让图形机能整合,就是所谓的[统一芯片组],把这个顶点管线转移到CPU的(仿真)系统也是存在的。
那么,直到稍早之前,很多时候把这个顶点管线称为[几何体处理]。所谓几何体(Geometry)就是是[几何学]。在高中,虽然就会在数学或是[代数•几何]的课程中,学到[向量(Vector)运算]和[线性映射(Linear Map)或线性变换(Linear Transformation)],不过这就是那个世界的事了。说句闲话,NVIDIA的GPU,GeForce系列的名字是由「Geometric Force(几何学的力量)」的缩写所造的名词,可以说有着[G-Force重力]的双关语。
把话题转回来,说回3D图形上的[3次元向量]的概念,简单而言想成是[三次元空间上的”方向”]就可以了。这些”方向”被x,y,z的3个轴的坐标值来表示,以这些”方向”为基准的被称之为[坐标系]。
「局部坐标系」,如果具体的描述就是对于某个3D角色来说,设置为基准的坐标系,3D角色的方向就是这个3D角色的基准坐标系,通过处理[朝向是哪里],控制的人会很轻松,所以利用了局部坐标系这个概念。
对了,一般的3D角色上虽然有很多是带有手臂和脚,考虑到那些手脚在关节处弯曲的情况,如果用关节为基准的局部坐标系进行控制会更容易理解,但是这样考虑的话,就要把局部坐标系做成多层的结构,最终的处理会变得不容易理解。
接下来,支配3D全局空间的整体坐标系就很有必要了,这就是[世界坐标系]。在处理3D图形的顶点管线中的顶点单位时,从局部坐标系向世界坐标系的变换会多次发生。
这样生成的顶点要根据shader程序进行坐标系的变换处理,就是[3][顶点着色语言][Vertex Shader]。通过着色器编程,就可以进行独特而特殊的坐标系变换。
图2 坐标系的概念图
Vertex Shader的另一项工作:顶点空间的阴影处理。
图1到3的Vertex Shader的工作不光是坐标变换,另一个重要的工作就是顶点空间的阴影处理和光照处理(lighting)。
「坐标变换」是「数学上的」的感觉,所谓“计算”的印象容易想象并理解。可是,在计算机中做光照,也就是说“光的照射”,这样的印象就很难想象是怎么回事。当然GPU因为不是照相机而是计算机,不可能直接拍出光照射后的效果照片,所以需要通过计算来获得。
光一旦照射到物体上,就会在那里反射/扩展和被吸收。如果这个物体有颜色或图案,那么也许就可以看到这个颜色,如果照射的光有颜色,那么就可以看到和物体颜色或图案合成后的颜色。寻求这样的计算处理,就是计算机图形的基本想法。
怎样才能把这个处理放入计算机所擅长的计算中呢?实际也是使用向量运算。
显示光的方向的[光源向量]和显示视线方向的[视线向量],还有显示构成光照射到的多边形的顶点方向的[法线向量],一共用到了这3个向量。再把这些向量对应到通过光和视线方向来表示反射的反射方程里来计算。
这个反射方程,可以表现出想要的各种类型的材质,这个反射方程在程序上的表现就是Vertex Shader程序。并且,实际执行这个顶点单位反射方程的程序,就是Vertex Shader.
在Vertex Shader中,不仅可以进行顶点空间的阴影处理,还可以进行多边形的纹理(Texture)坐标计算。纹理坐标的计算,就是计算出在怎样的多边形上,把怎样的材质纹理通过什么样的方式贴上去。还有,实际做纹理映射(Texture Mapping)是在[8][9]的Piexl Shader的时候,这里只是纹理映射的准备工作。
Vertex Shader工作的例子, 利用Vertex Shader来做折射表现。
Geometry Shader可以增减顶点的厉害东西
在DirectX 9/SM3.0之前,GPU并不包含Geometry Shader,3D模型的顶点信息是通过CPU这边预先准备好之后,再输入到GPU中,在GPU这里不能随意的进行增减。
为了打碎这个“原则性限制”,拥有可以自由增减顶点功能的着色器就是[Geometry Shader]。
通过Shader程序可以指定Geometry Shader对顶点信息进行增减。还有,因为实际增减的是复数的顶点,所以对各种的线段、多边形、粒子等图元也可以进行增减。
利用Geometry Shader的各种方法被创造出来,因为可以自由的生成多边形,那么就可以在地面上生长出草的多边形,或者让3D角色生长出毛发等是最基本的使用方法。在游戏中,还可以把不需要做逻辑交互处理的例如火花等特效的表现,使用Geometry Shader来生成。
【千里马肝注:Geometry Shader并不如想像中,或者说是宣传中那么好。可能是由于成本或是其他的什么原因,Geometry Shader通常是在display driver中实现的,也就是说其实是由CPU负责计算,当重新返回GPU的VS时,对流水线的影响很大,所以Geometry Shader的实际效能并不高,甚至是非常低。】
大致上就是可以做到这些吧。
用Geometry Shader生成的顶点,因为还可以再返回到Vertex Shader,所以可以对返回的顶点重新进行处理。例如普通方法不能实现的,把低多边形的3D模型,在Geometry Shader里通过插值生成更平滑的高多边形,理论上也是可行。
顶点管线处理的最后
[5][6]是顶点管线最后的处理部分,负责在描绘之前做最后的准备工作。
将世界坐标系中的坐标,进一步在[5]上进行换到照相机视点坐标系的处理。这个相当于照相机在拍摄照片时构图和镜头的部分,这一连串的处理总的来说是[透视变换处理]。
因为3D图形只需要描画出视野内捕捉到的影像就可以了,一旦[5]的处理结束后,就转移到以视野空间为主体来考虑。
[6]是通过判断,将不需要描绘的多边形,在进入实际描绘处理的像素管线之前,进行剔除的处理(也称为Culling处理)。
[Clipping处理],是把完全在视野范围以外的3D模型的多边形进行剔除,如果3D模型的多边形只有一部分在视野内,那么会对视野范围内的多边形进行分割处理。
【千里马肝注:为了避免昂贵的View Frustum Clipping,一旦出现下图的这种情况,其代价为:
Extra vertices produced,costing more bandwidth
CPU cost for interpolation of x,y,z, u,v,color,specular,alpha and fog
Breaking up of strips and fans
Poor vertex locality of new vertices,which hurts CPU and vertex cache coherency
请参考:Guard Band。】
「背面剔除」,即没有朝向视点方向的顶点,理论上从视点应该把看不到的多边型进行剔除。但如果涉及到透明物体的情况,进行这样的处理有时会出现不协调的问题。
【千里马肝:即Independent Transparency,从视点观察时模型时,可能出现由于顶点传入顺序的原因,背面的顶点在前面的顶点之前就绘制的问题,于是Alpha Blend就不正确了。】
负责分解和发送像素单位工作的光栅器(rasterrizer)
当变换到视野空间,无用多边形都剔除后,在[7]上进行的是,把到没有实际形态的多边形,绘制为对应在画面上的像素的处理。另外,在最新的3D图形上,并不只是绘制显示帧,也有把场景渲染到纹理的情况,这个时候[7]会进行多边形和纹理像素的处理。
在[7]上的处理,实质上是将从顶点管线上把成为顶点单位(多边形单位)输出的计算结果,作为像素单位进行分解,持续的向像素管线发送任务,可以说是起到中介的作用。
这个[7]的处理被称作[triangle setup],或者是[rasterise处理],因为是个规范化的处理,所以从1990年代初期的GPU里就作为固定功能实现在GPU里,到现在也没有太大的进化。
通常,一个多边形因为要用多个像素来绘制,所以多边形通过光栅化被分解成大量的Pixel Task。在GPU上Pixel Shader处理单元的数量如此之多,是由于需要处理的像素是如此繁多的原故。
图5:Rasterise也可以说成是生成piexl shader的任务书,还有,一个多边型会生成多个pixel task.
|
Pixel Shader的输出,简单的说就是[场景中构成多边形的像素,会在这里确定最终的颜色],虽然可以想象就是按原样向显存里写入[Pixel的绘制处理完成了],但还有一些工作要做。
那个就是[10]的Render BackEnd,另外NVIDIA称呼这个部分为ROP Unit。ROP Unit到底是Rendering Output Pipeline还是Raster Operation的简称并不是太清楚,在本连载中将前者作为正确的解释。
尽管如此,这里是做Pixel Shader输出时[是不是可以写入的验证],以及写入时[决定如何写入]等,这些是向显存写入的控制部分。Pixel Shader自己可以从纹理中读取,但不能直接向显存写入,所
以这个处理也成为极为重要的部分。
可是,因为DirectX 9/SM2.0以前的GPU上Pixel Shader数量和ROP Unit数量通常是一致的,于是有着Pixel Shader与ROP Unit是一一对应关系的印象。但是从DirectX 9/SM3.0的GPU开始,伴随着Pixel
Shader程序的高度复杂化,Pixel Shader的数量被作为重点进行增加,结果,ROP Unit的数量一般都比Pixel Shader的数量要少。
最新的GPU一般都是Pixel Shader > ROP Unit的结构。上图是GeForce 7800 GTX的结构图。中间是4X6 = 24个Pixel Shader,下面是16个ROP Unit。
[是不是可以写入的验证]的部分为[Alpha Test]、[Stencil Test]和[Depth Test]。
Alpha Test是对输出的Pixel颜色是否完全透明的测试。α元素为0时为透明不需要绘制,意味着将放弃对应Pixel的绘制。
Alpha Test只绘制不透明部分的Pixel。
Stencil Test能够以多种方式进行检测,以Stencil Buffer的内容为参照来计算Frame Buffer,对于设定好的条件如果不能通过,就会把那个Pixel放弃绘制。应用之一,作为Stencil Shadow Volume的影子生成技术。