[基础篇]OpenGL渲染实践

2018/11/24 Map

[基础篇]OpenGL渲染实践

文章代码参考资料:

https://github.com/loyinglin/LearnOpenGLES

通过对代码的讲解,介绍从设置视口-绑定顶点、片元着色器-分配顶点缓存-设置着色器参数-绑定纹理-渲染出图整个基本渲染流程。

​ 那么,开始吧。

​ 在我们开始渲染之前,我们必须告诉OpenGL渲染窗口的尺寸大小,也就是说的视口(ViewPort),调用glViewport函数来进行设置。

glViewport**函数前两个参数控制窗口左下角的位置。第三个和第四个参数控制渲染窗口的宽度和高度(像素)。

​ 为了测试一切都正常工作,我们使用一个自定义的颜色清空屏幕。在每个新的渲染迭代开始的时候我们总是希望清屏,否则我们仍能看见上一次迭代的渲染结果(这可能是你想要的效果,但通常这不是)。具体如下两行

 glClearColor(0,1.0,1.0);
 glClear(GL_COLOR_BUFFER_BIT);

​ 这里,我们做了一些准备工作,包括设置房屋大小和把房屋整体刷成了ClearColor。

​ 在OpenGL中,任何事物都在3D空间中,而屏幕和窗口却是2D像素数组,这导致OpenGL的大部分工作都是关于把3D坐标转变为适应你屏幕的2D像素。3D坐标转为2D坐标的处理过程是由OpenGL的图形渲染管线(Graphics Pipeline,大多译为管线,实际上指的是一堆原始图形数据途经一个输送管道,期间经过各种变化处理最终出现在屏幕的过程)管理的。

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

img

​ 有些着色器允许开发者自己配置,这就允许我们用自己写的着色器来替换默认的。这样我们就可以更细致地控制图形渲染管线中的特定部分了,而且因为它们运行在GPU上,所以它们可以给我们节约宝贵的CPU时间。OpenGL着色器是用OpenGL着色器语言(OpenGL Shading Language, GLSL)写成的。

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

​ 在顶点组织成图元绘制到屏幕之前,我们需要编译其需要的着色器,也就是说的油漆桶。如果我们打算做渲染的话,现代OpenGL需要我们至少设置一个顶点和一个片段着色器。

attribute vec4 position;
 attribute vec2 textCoordinate;
 uniform mat3 rotateMatrix;
 varying lowp vec2 varyTextCoord;
 
 int main()
 {
     varyTextCoord = textCoordinate;
     gl_Position = vPos * rotateMatrix;
 }

​ 这个顶点着色器很简单,包含attribute(外部设置顶点数据源),uniform(外部设置的旋转矩阵),以及varying(输出给片元着色器的纹理向量)和main函数部分,main函数包含varying参数的计算和gl_Position的赋值。

varying lowp vec2 varyTextCoord;
 uniform sampler2D colorMap;

 int main()
 {
     gl_FragColor = texture2D(colorMap , varyTextCoord);
 }

 int main()
 {
     gl_FragColor = texture2D(colorMap , varyTextCoord);
 }

​ 这个片元着色器对顶点着色器的varying纹理索引进行处理,通过外部绑定的纹理要素进行像素着色。

​ 看着GPU上的着色器编码不复杂,编译函数也简单。

GLint program = glCreateProgram();
 {
     GLuint shader1 = glCreateShader(type);//片元还是顶点
     glShaderSource(shader1 , 1 , &source , NULL);
     glCompileShader(shader1);
 }
 glAttachShader(program , shader1);
 ...
 
 glLinkProgram(program);
 glUseProgram(program);

​ 流程是,生成一个着色器程序对象,分别绑定编译好的着色器,然后link,最后use使用。

img

​ 在这里,我们给家里搬进来了油漆桶和刷子。

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

​ 由于我们希望渲染一个三角形,我们一共要指定三个顶点,每个顶点都有一个3D位置。我们会将它们以标准化设备坐标的形式(OpenGL的可见区域)定义为一个float数组。

// 前三个是顶点坐标,后面是纹理坐标,用的VAO
 GLFloat attrArrp[] = 
 {
 0.5,-0.5.-1.0 , 1.0,0.0,
 -0.5,0.5.-1.0 , 0.0,1.0,
 -0.5,-0.5.-1.0 , 0.0,0.0,
 0.5,0.5.1.0 , 1.0,1.0,
 -0.5,0.5.-1.0 , 0.0,1.0,
 0.5,-0.5.1.0 , 1.0,0.0,
 };

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

​ 我们通过顶点缓冲对象(Vertex Buffer Objects, VBO)管理这个内存,它会在GPU内存(通常被称为显存)中储存大量顶点。使用这些缓冲对象的好处是我们可以一次性的发送一大批数据到显卡上,而不是每个顶点发送一次。从CPU把数据发送到显卡相对较慢,所以只要可能我们都要尝试尽量一次性发送尽可能多的数据。当数据发送至显卡的内存中后,顶点着色器几乎能立即访问顶点,这是个非常快的过程。

​ 顶点缓冲对象是我们在OpenGL教程中第一个出现的OpenGL对象。就像OpenGL中的其它对象一样,这个缓冲有一个独一无二的ID,所以我们可以使用glGenBuffers函数和一个缓冲ID生成一个VAO对象:

unsigned int VAO;
glGenBuffers(1,&VAO);

​ OpenGL有很多缓冲对象类型,顶点缓冲对象的缓冲类型是GL_ARRAY_BUFFER。OpenGL允许我们同时绑定多个缓冲,只要它们是不同的缓冲类型。我们可以使用glBindBuffer函数把新创建的缓冲绑定到GL_ARRAY_BUFFER目标上:

glBindBuffer(GL_ARRAY_BUFFER,VAO);

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

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

​ glBufferData是一个专门用来把用户定义的数据复制到当前绑定缓冲的函数。它的第一个参数是目标缓冲的类型:顶点缓冲对象当前绑定到GL_ARRAY_BUFFER目标上。第二个参数指定传输数据的大小(以字节为单位);用一个简单的sizeof计算出顶点数据大小就行。第三个参数是我们希望发送的实际数据。

​ 这里,房子里也有了未经染色的原材料。

​ 由于材料上也可以贴上一些卡通图案以丰富表现内容,因此,绑定纹理可以这样。

// 4绑定纹理到默认的纹理ID(这里只有一张图片,故而相当于默认于片元着色器里面的colorMap,如果有多张图不可以这么做)
  glBindTexture(GL_TEXTURE_2D, 0);
  glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
  glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
  glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
  glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
  float fw = width, fh = height;
  glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, fw, fh, 0, GL_RGBA, GL_UNSIGNED_BYTE, spriteData);//spriteData是数据流
  glBindTexture(GL_TEXTURE_2D, 0);

​ 最后,我们告诉刷子该往哪个方向刷,然后按下快门,一个渲染流程就结束了,剩余的就在相机内部处理了~

glUniformMatrix4fv(rotate, 1, GL_FALSE, (GLfloat *)&zRotation[0]);
 glDrawArrays(GL_TRIANGLES, 0, 6); 

lena老师镇楼

img

Search

    Table of Contents