LearnOpenGL 1.3 你好,三角形

来源:互联网 发布:淘宝中差评多久会消失 编辑:程序博客网 时间:2024/04/20 13:39

你好,三角形

本文整理自LearnOpenGL 及LearnOpenGL CN ,后者为前者的中文版, 完整学习的话建议前往原网站。
三个复杂的单词
顶点数组对象:Vertex Array Object,VAO
顶点缓冲对象:Vertex Buffer Object,VBO
索引缓冲对象:Element Buffer Object,EBO或Index Buffer Object,IBO

OpenGL描述的都是3D事物(2D事物会将其z轴坐标设置为0),但却要显示在2D的屏幕和窗口上,导致OpenGL的大部分工作都是关于把3D坐标转变为适应你屏幕的2D像素。
3D坐标转为2D坐标的处理过程是由OpenGL的图形渲染管线(Graphics Pipeline,大多译为管线,实际上指的是一堆原始图形数据途经一个输送管道,期间经过各种变化处理最终出现在屏幕的过程)管理的。图形渲染管线可以被划分为两个主要部分:第一部分把你的3D坐标转换为2D坐标,第二部分是把2D坐标转变为实际的有颜色的像素。

2D坐标和像素也是不同的,2D坐标精确表示一个点在2D空间中的位置,而2D像素是这个点的近似值,2D像素受到你的屏幕/窗口分辨率的限制。

图形渲染管线接受一组3D坐标,然后把它们转变为你屏幕上的有色2D像素输出。图形渲染管线可以被划分为几个阶段,每个阶段将会把前一个阶段的输出作为输入。所有这些阶段都是高度专门化的(它们都有一个特定的函数),并且很容易并行执行。正是由于它们具有并行执行的特性,当今大多数显卡都有成千上万的小处理核心,它们在GPU上为每一个(渲染管线)阶段运行各自的小程序,从而在图形渲染管线中快速处理你的数据。这些小程序叫做着色器(Shader)。
图形渲染管线的每个阶段的抽象展示,蓝色部分代表的是我们可以注入自定义的着色器的部分。

以数组的形式传递3个3D坐标作为图形渲染管线的输入,用来表示一个三角形,这个数组叫做顶点数据(Vertex Data);顶点数据是一系列顶点的集合。由顶点属性(Vertex Attribute)表示,顶点属性可以包含3d位置,颜色等。

为了让OpenGL知道我们的坐标和颜色值构成的到底是什么,OpenGL需要你去指定这些数据所表示的渲染类型(一系列点、三角形?),这些提示叫做图元(Primitive):GL_POINTS、GL_TRIANGLES、GL_LINE_STRIP

图形渲染管线第部分:顶点着色器(Vertex Shader),输入为一个单独的顶点(3D坐标),输出为另一种3D坐标。

图元装配阶段顶点着色器输出的所有顶点作为输入(如果是GL_POINTS,那么就是一个顶点),并所有的点装配成指定图元的形状(输出顶点)。
几何着色器:输入为一系列顶点,可以通过产生新顶点生成其他形状。
光栅化阶段(Rasterization Stage),把图元映射为最终屏幕上相应的像素
生成供片段着色器(Fragment Shader)使用的片段(Fragment)。在片段着色器运行之前会执行裁切(Clipping)。裁切会丢弃超出你的视图(视口)以外的所有像素,用来提升执行效率。

OpenGL中的一个片段是OpenGL渲染一个像素所需的所有数据。

片段着色器:计算一个像素的最终颜色(光照、阴影、光的颜色).

Alpha测试和混合(Blending)阶段:检测片段的深度和模板(Stencil)值,如果在其他物体后面就会被丢弃。检查alpha(透明度)进行混合(混合(Blend))。

顶点输入

输入数据后,OpenGL将数据进行标准化,OpenGL仅当3D坐标在3个轴(x、y和z)上都为-1.0到1.0的范围内时才处理它。

定义一个三角形,指定三个顶点,以标准化的形式给出,

GLfloat vertices[] = {    -0.5f, -0.5f, 0.0f,     //2d三角形的z轴坐标设置为0     0.5f, -0.5f, 0.0f,     0.0f,  0.5f, 0.0f};

从顶点着色器中出来的坐标就应该是标准设备坐标了([-1,1])。

标准化设备坐标接着会变换为屏幕空间坐标(Screen-space Coordinates),这是使用你通过glViewport函数提供的数据,进行视口变换(Viewport Transform)完成的。所得的屏幕空间坐标又会被变换为片段(标准坐标)输入到片段着色器中。

定义这样的顶点数据以后,我们会把它作为输入发送给图形渲染管线的第一个处理阶段:顶点着色器,顶点着色器会分配内存存储这些顶点数据,还要配置OpenGL如何解释这些内存,并且指定其如何发送给显卡。

顶点缓冲对象(Vertex Buffer Objects, VBO)负责管理这个内存,我们可以使用VBO一次性发送一大批数据到显卡上(对比glBegin(),glEnd()),而不是每个顶点发送一次。从CPU(电脑内存)把数据发送到显卡相对较慢,所以只要可能我们都要尝试尽量一次性发送尽可能多的数据。当数据发送至显卡的内存(显存)中后,顶点着色器几乎能立即访问顶点,速度很快。

VBO有一个独一无二的对象(唯一的ID)。
生成VBO对象

GLuint VBO;glGenBuffers(1, &VBO);

确定或绑定它的类型

glBindBuffer(GL_ARRAY_BUFFER, VBO);

从这一刻起,我们使用的任何(在GL_ARRAY_BUFFER目标上的)缓冲调用都会用来配置当前绑定的缓冲(VBO)。然后我们可以调用glBufferData函数,它会把之前定义的顶点数据复制到缓冲的内存中:

glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

参数依次为目标缓冲类型,传输数据大小,实际数据,显卡管理数据的方式。

GL_STATIC_DRAW :数据不会或几乎不会改变。GL_DYNAMIC_DRAW:数据会被改变很多。GL_STREAM_DRAW :数据每次绘制时都会改变。

指定以便于显卡是否把数据分配在可以高速读写的区域。

顶点着色器

可编程的着色器,

#version 330 core       //版本layout (location = 0) in vec3 position; //输入变量的位置值,输入顶点属性void main(){    gl_Position = vec4(position.x, position.y, position.z, 1.0);    //预定义的变量,用于输出,}

编译着色器

创建着色器

GLuint vertexShader;vertexShader = glCreateShader(GL_VERTEX_SHADER);    //类型,顶点着色器

将源码附加到对象上,编译

glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);glCompileShader(vertexShader);  //1为着色器源码字符串数量

检测是否编译成功

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;}

片段着色器

计算像素最后的颜色输出。
源码

#version 330 coreout vec4 color;     //输出变量void main(){    color = vec4(1.0f, 0.5f, 0.2f, 1.0f);}

编译着色器

GLuint fragmentShader;fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);glShaderSource(fragmentShader, 1, &fragmentShaderSource, null);glCompileShader(fragmentShader);

然后将两个着色器对象链接到着色器程序中。

着色器程序

着色器程序对象:多个着色器合并之后并最终链接完成的版本。
创建程序对象

GLuint shaderProgram;shaderProgram = glCreateProgram();      //返回ID引用

附加着色器到程序对象,并链接。

glAttachShader(shaderProgram, vertexShader);glAttachShader(shaderProgram, fragmentShader);glLinkProgram(shaderProgram);

检测程序是否失败:

glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);if(!success) {    glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog);  ...}

激活该程序对象。

glUseProgram(shaderProgram);

并删除着色器

glDeleteShader(vertexShader);glDeleteShader(fragmentShader);

下面是如何解释内存中的数据。

链接顶点属性

顶点数据内存分配:

glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), (GLvoid*)0);glEnableVertexAttribArray(0);

参数解释
顶点属性,将数据传递到顶点着色器中layout(location=0)的顶点属性中。
大小。
数据类型。
如果标准化,如果标准化,所有数据都会被映射到[0,1]或[-1,1]。
步长,连续的顶点数据之间的步长。
位置数据在缓冲起始位置的偏移量。

顶点属性从VBO获取数据,通过调用glVetexAttribPointer决定使用哪个VBO。

绘制物体的代码例子

// 0. 复制顶点数组到缓冲中供OpenGL使用glBindBuffer(GL_ARRAY_BUFFER, VBO);glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);// 1. 设置顶点属性指针glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), (GLvoid*)0);glEnableVertexAttribArray(0);// 2. 当我们渲染一个物体时要使用着色器程序glUseProgram(shaderProgram);// 3. 绘制物体someOpenGLFunctionThatDrawsOurTriangle();

顶点数组对象

顶点数组对象(Vertex Array Object, VAO),类似于VBO,配置不同的属性只需要切换绑定不同的VAO。
VAO存储的内容:
glEnableVertexAttribArray和glDisableVertexAttribArray的调用。
通过glVertexAttribPointer设置的顶点属性配置。
通过glVertexAttribPointer调用与顶点属性关联的顶点缓冲对象。

VAO创建:

GLuint VAO;glGenVertexArrays(1, &VAO);
// ..:: 初始化代码(只运行一次 (除非你的物体频繁改变)) :: ..// 1. 绑定VAOglBindVertexArray(VAO);    // 2. 把顶点数组复制到缓冲中供OpenGL使用    glBindBuffer(GL_ARRAY_BUFFER, VBO); //绑定类型为2D    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);  //真正的数据内容    // 3. 设置顶点属性指针    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), (GLvoid*)0);    glEnableVertexAttribArray(0);   //默认不启用//4. 解绑VAOglBindVertexArray(0);...// ..:: 绘制代(游戏循环中) :: ..// 5. 绘制物体glUseProgram(shaderProgram);glBindVertexArray(VAO);someOpenGLFunctionThatDrawsOurTriangle();glBindVertexArray(0);

我们一直期待的三角形

绘制图元

glUseProgram(shaderProgram);    //使用激活的着色器glBindVertexArray(VAO);glDrawArrays(GL_TRIANGLES, 0, 3);   //三角形,索引,顶点个数glBindVertexArray(0);   //解绑

索引缓冲对象

索引缓冲对象(Element Buffer Object,EBO,也叫Index Buffer Object,IBO)。
存储索引,OpenGL根据索引决定绘制哪个顶点(顺序)。
使用方法类似于VBO,绑定->复制到缓冲。

glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO); //注意targetglBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

使用glDrawElements代替glDrawArrays指明从索引缓冲渲染,

glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);//绘制模式,顶点数量,索引的类型,EBO的偏移量。

glDrawElements函数从当前绑定到GL_ELEMENT_ARRAY_BUFFER目标的EBO中获取索引。这意味着我们必须在每次要用索引渲染一个物体时绑定相应的EBO,顶点数组对象同样可以保存索引缓冲对象的绑定状态。VAO绑定时正在绑定的索引缓冲对象会被保存为VAO的元素缓冲对象。绑定VAO的同时也会自动绑定EBO。

当目标是GL_ELEMENT_ARRAY_BUFFER的时候,VAO会储存glBindBuffer的函数调用。这也意味着它也会储存解绑调用,所以确保你没有在解绑VAO之前解绑索引数组缓冲,否则它就没有这个EBO配置了。

代码

// ..:: 初始化代码 :: ..// 1. 绑定顶点数组对象glBindVertexArray(VAO);    // 2. 把我们的顶点数组复制到一个顶点缓冲中,供OpenGL使用    glBindBuffer(GL_ARRAY_BUFFER, VBO);    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);    // 3. 复制我们的索引数组到一个索引缓冲中,供OpenGL使用    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);    // 3. 设定顶点属性指针    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), (GLvoid*)0);    glEnableVertexAttribArray(0);// 4. 解绑VAO(不是EBO!)glBindVertexArray(0);[...]你好三角形// ..:: 绘制代码(游戏循环中) :: ..glUseProgram(shaderProgram);glBindVertexArray(VAO);glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0)glBindVertexArray(0);
0 0
原创粉丝点击