OpenGL ES图形处理iOS开发记录

OpenGL ES之你好,三角形

2018-08-06  本文已影响4人  测试开发雨辰
OpenGL基础词汇理解

1.单词术语:
(1) 顶点数组对象:Vertex Array Object,VAO
(2) 顶点缓冲对象:Vertex Buffer Object,VBO
(3)索引缓冲对象:Element Buffer Object,EBO或Index Buffer Object,IBO

2.图形渲染管线:指的是一堆原始图形数据途经一个输送管道,期间经过各种变化处理最终出现在屏幕的过程。主要分为2部分:(1)把3D坐标转化为2D坐标,(2)把2D坐标转变为实际的有颜色的像素。

3.着色器: 在GPU上为每一个(渲染管线)阶段运行各自的小程序,从而在图形渲染管线中快速处理你的数据。用OpenGL着色器语言GLSL)写的。

图形渲染管线的各个阶段的抽象展示

下面是一个图形渲染管线的每个阶段的抽象展示。要注意蓝色部分代表的是我们可以注入自定义的着色器的部分。


show.png

当我们谈论一个“位置”的时候,它代表在一个“空间”中所处地点的这个特殊属性;同时“空间”代表着任何一种坐标系,比如x、y、z三维坐标系,x、y二维坐标系

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

顶点输入

开始绘制图形之前,我们必须先给OpenGL输入一些顶点数据。OpenGL是一个3D图形库,所以我们在OpenGL中指定的所有坐标都是3D坐标(x、y和z)。OpenGL不是简单地把所有的3D坐标变换为屏幕上的2D像素;OpenGL仅当3D坐标在3个轴(x、y和z)上都为-1.0到1.0的范围内时才处理它。所有在所谓的标准化设备坐标(Normalized Device Coordinates)范围内的坐标才会最终呈现在屏幕上(在这个范围以外的坐标都不会显示)。

GLfloat vertices[] = {
        -0.5f, -0.5f, 0.0f,
        0.5f, -0.5f, 0.0f,
        0.0f,  0.5f, 0.0f
    };

如下图:


三角形.png
GLuint VBO;
glGenBuffers(1, &VBO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

第四个参数指定了我们希望显卡如何管理给定的数据。它有三种形式:
(1)GL_STATIC_DRAW :数据不会或几乎不会改变。
(2)GL_DYNAMIC_DRAW:数据会被改变很多。
(3)GL_STREAM_DRAW :数据每次绘制时都会改变。

三角形的位置数据不会改变,每次渲染调用时都保持原样,所以它的使用类型最好是GL_STATIC_DRAW。如果,比如说一个缓冲中的数据将频繁被改变,那么使用的类型就是GL_DYNAMIC_DRAW或GL_STREAM_DRAW,这样就能确保显卡把数据放在能够高速写入的内存部分。

顶点着色器

1.用着色器语言GLSL(OpenGL Shading Language)编写顶点着色器,然后编译这个着色器

attribute vec4 vPosition;
void main(void)
{
    gl_Position = vPosition;
}

在顶点着色器中声明所有的输入顶点属性(Input Vertex Attribute)。现在我们只关心位置(Position)数据,所以我们只需要一个顶点属性。GLSL有一个向量数据类型,它包含1到4个float分量,包含的数量可以从它的后缀数字看出来。
向量(Vector)
简明地表达了任意空间中的位置和方向,并且它有非常有用的数学属性。在GLSL中一个向量有最多4个分量,每个分量值都代表空间中的一个坐标,它们可以通过vec.x、vec.y、vec.z和vec.w来获取。注意vec.w分量不是用作表达空间中的位置的(我们处理的是3D不是4D),而是用在所谓透视除法(Perspective Division)上。

为了设置顶点着色器的输出,我们必须把位置数据赋值给预定义的gl_Position变量,它在幕后是vec4类型的

编译着色器

//Create the shader object
GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
//Load the shader source
const char * shaderStringUTF8 = [vertexShaderString UTF8String];
glShaderSource(vertexShader, 1, &shaderStringUTF8, NULL);
//Compile the shader
glCompileShader(vertexShader);

glShaderSource函数把要编译的着色器对象作为第一个参数。第二参数指定了传递的源码字符串数量,这里只有一个。第三个参数是顶点着色器真正的源码,第四个参数我们先设置为NULL。

//Check the compile status
GLint compiled = 0;
glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &compiled);
if (!compiled) {
    GLint infoLen = 0;
    glGetShaderiv(vertexShader, GL_INFO_LOG_LENGTH, &infoLen);
        
    if (infoLen > 1) {
        char *infoLog = malloc(sizeof(char)* infoLen);
        glGetShaderInfoLog(vertexShader, infoLen, NULL, infoLog);
        NSLog(@"Error compiling shader:\n%s\n",infoLog);
        free(infoLog);
    }
        
    glDeleteShader(vertexShader);
    return;
}

片段着色器

void main()
{
    gl_FragColor = vec4(1.0, 0.5, 0.2, 1.0);
}

片段着色器只需要一个输出变量,这个变量是一个4分量向量,它表示的是最终的输出颜色,我们应该自己将其计算出来。这里我们命名为gl_FragColor。

//Create the shader object
GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
//Load the shader source
const char * shaderStringUTF8 = [fragmentShaderString UTF8String];
glShaderSource(fragmentShader, 1, &shaderStringUTF8, NULL);
//Compile the shader
glCompileShader(fragmentShader);

着色器程序

着色器程序对象(Shader Program Object)是多个着色器合并之后并最终链接完成的版本。如果要使用刚才编译的着色器我们必须把它们链接(Link)为一个着色器程序对象,然后在渲染对象的时候激活这个着色器程序。已激活着色器程序的着色器将在我们发送渲染调用的时候被使用。当链接着色器至一个程序的时候,它会把每个着色器的输出链接到下个着色器的输入。当输出和输入不匹配的时候,你会得到一个连接错误。

// Create the program object
GLuint shaderProgram = glCreateProgram();

glCreateProgram函数创建一个程序,并返回新创建程序对象的ID引用。现在我们需要把之前编译的着色器附加到程序对象上,然后用glLinkProgram链接它们:

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

调用glUseProgram函数,用刚创建的程序对象作为它的参数,以激活这个程序对象:

glUseProgram(shaderProgram);

glUseProgram函数调用之后,每个着色器调用和渲染调用都会使用这个程序对象(也就是之前写的着色器)了。
对了,在把着色器对象链接到程序对象以后,记得删除着色器对象,我们不再需要它们了:

glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);

链接顶点属性

顶点缓冲数据.png
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
// 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(float), (void*)0);
glEnableVertexAttribArray(0);
// 2. 当我们渲染一个物体时要使用着色器程序
glUseProgram(shaderProgram);

要想绘制我们想要的物体,OpenGL给我们提供了glDrawArrays函数,它使用当前激活的着色器,之前定义的顶点属性配置,和VBO的顶点数据(通过VAO间接绑定)来绘制图元。

glEnableVertexAttribArray(_positionSlot);
glVertexAttribPointer(_positionSlot, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), NULL);
glDrawArrays(GL_TRIANGLES, 0, 3);

大家一定好奇_positionSlot干嘛的,其实他是顶点着色器里面的vPosition,用来传参数值的

// Get the attribute position slot from program
_positionSlot = glGetAttribLocation(shaderProgram, "vPosition");

glDrawArrays函数第一个参数是我们打算绘制的OpenGL图元的类型。由于我们在一开始时说过,我们希望绘制的是一个三角形,这里传递GL_TRIANGLES给它。第二个参数指定了顶点数组的起始索引,我们这里填0。最后一个参数指定我们打算绘制多少个顶点,这里是3(我们只从我们的数据中渲染一个三角形,它只有3个顶点长)。如图,三角形诞生了!

你好,三角形.png
Demo连接
上一篇下一篇

猜你喜欢

热点阅读