前言

在后面咱们介绍了 [OpenglEs之EGL环境搭建]() ,在前面的例子中,咱们将无可避免地须要应用到着色器。而着色器才是Opengl的灵魂所在,有了着色器才有了Opengl天马行空的世界。

图形渲染管线

要想了解什么是着色器以及着色器的作用就必须先理解下图形渲染管线。

在OpenGL中,任何事物都在3D空间中,而屏幕和窗口却是2D像素数组,这导致OpenGL的大部分工作都是对于把3D坐标转变为适应你屏幕的2D像素。将OpenGL中的3D世界转换到屏幕窗口中的2D世界中展现进去的过程称为图形渲染管线。
也能够说将一堆原始图形数据途经一个输送管道,期间通过各种变动解决最终呈现在屏幕的过程就是图形渲染管线。图形渲染管线能够被划分为两个次要局部:第一局部把你的3D坐标转换为2D坐标,第二局部是把2D坐标转变为理论的有色彩的像素。

图形渲染管线能够被划分为几个阶段,每个阶段将会把前一个阶段的输入作为输出。所有这些阶段都是高度专门化的(它们都有一个特定的函数),并且很容易并行执行。正是因为它们具备并行执行的个性,当今大多数显卡都有成千上万的小解决外围,它们在GPU上为每一个(渲染管线)阶段运行各自的小程序,从而在图形渲染管线中疾速解决你的数据。这些小程序叫做着色器(Shader)。

着色器

着色器是进行三维图形学编程的先进办法,从某种意义上来说 Shader 的呈现是图形学中的一种”退化”,因为在这之前所有的性能都间接由固定管线提供,而开发人员只须要为其指定参数(如光照属性、旋转角度等),然而因为 Shader 的呈现这些性能当初都须要开发者本人通过 Shader 实现。
尽管如此,这种可编程性可能提供给开发者更多的灵活性和创造性。

下图是Opengl图形渲染管线的形象过程:

蓝色局部代表的是咱们能够注入自定义的着色器的局部。也就说咱们通过自定义顶点着色器、几何着色器和片段着色器就能够实现咱们想要各种成果,同时几何着色器又是可选的,个别应用默认的即可,因而咱们只需将重点放在顶点着色器和片段着色器即可。

上面参照上图,简略介绍一下各个阶段的次要性能职责:

  • 顶点着色器
    图形渲染管线的第一个局部是顶点着色器(Vertex Shader),它把一个独自的顶点作为输出。顶点着色器次要的目标是把3D坐标转为另一种3D坐标,同时顶点着色器容许咱们对顶点属性进行一些根本解决,例如矩阵变换等。
  • 图元拆卸
    图元拆卸阶段将顶点着色器输入的所有顶点作为输出,并所有的点装配成指定图元的形态。例如绘制正方形时,就是通过两个三角形组装成正方形,绘制多面体时,就是将n多个三角形组装成多面体的过程。
  • 几何着色器
    图元拆卸阶段的输入会传递给几何着色器。几何着色器把图元模式的一系列顶点的汇合作为输出,它能够通过产生新顶点结构出新的(或是其它的)图元来生成其余形态。
  • 光栅化
    几何着色器的输入会被传入光栅化阶段,这里它会把图元映射为最终屏幕上相应的像素,生成供片段着色器(Fragment Shader)应用的片段(Fragment)。在片段着色器运行之前会执行裁切(Clipping)。裁切会抛弃超出你的视图以外的所有像素,用来晋升执行效率。
  • 片段着色器
    片段着色器的次要目标是计算一个像素的最终色彩,这也是所有OpenGL高级成果产生的中央。通常,片段着色器蕴含3D场景的数据(比方光照、暗影、光的色彩等等),这些数据能够被用来计算最终像素的色彩。
  • 测试与混合
    这个阶段检测片段的对应的深度(和模板(Stencil))值,用它们来判断这个像素是其它物体的后面还是前面,决定是否应该抛弃。这个阶段也会查看alpha值(alpha值定义了一个物体的透明度)并对物体进行混合(Blend)。
    所以,即便在片段着色器中计算出来了一个像素输入的色彩,在渲染多个三角形的时候最初的像素色彩也可能齐全不同。

GLSL

OpenGL着色器是用OpenGL着色器语言(OpenGL Shading Language, GLSL)写成的。

在OpenGL中,咱们必须定义至多一个顶点着色器和一个片段着色器,因为GPU中没有默认的顶点/片段着色器。

咱们来看一个最简略的顶点着色器的例子:

#version 300 esin vec4 aPos;void main(){    gl_Position = aPos;}

GLSL的语法与C语言很相似,置信有过编程教训的搭档们大略都能看懂以上这个着色器。

顶点着色器的第一行#version 300 es首先申明了着色器版本号,300的意思是代表着色器语言须要是3.0之后的版本。

第二行in vec4 aPos;申明了一个名为aPos的4个重量的输出变量。其中int代表的是定义输出变量,也就是说能够利用这个变量通过CPU向GPU传递数据,
与之对应的是应用out关键字申明的输入变量。留神,int和out是Opengl 3.0之后所做的批改,在Opengl 2.0中与之对应的关键字是attribute和varying,也就是说在Opengl 3中attribute改成了in而varying改成了out。

前面定义的就是一个main函数,这个没什么好解析的,就像所有语言程序一样,作为程序的一个入口,而gl_Position是Opengl中顶点着色器的一个内建输入变量,代表的是最终通过解决后的顶点坐标。

上面咱们再来看一个简略的片段着色器的例子:

#version 300 esprecision mediaump float;out vec4 FragColor;void main(){    FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);} 

正如顶点着色器那样,片段着色器的第一行也是申明着色器的版本。第二行precision mediaump float;则是申明着色器中浮点变量的默认精度。

在Opengl ES 3的版本中咱们须要手动地应用out关键字申明输入着色器的色彩变量,例如上述例子中的第三行,而在Opengl ES2中这不是必须的,因为在Opengl ES2中间接应用内置的变量gl_FragColor即可。
至于为什么这样做,笔者也不得而知...

这里只是通过两个简略的例子让大家对着色器有一个初步的理解,更多对于着色器GLSL的编写标准大家能够参考相干的材料或者书籍进行自我学习,常识繁多,不是喋喋不休可形容分明,这里就不再累赘。。。

着色器链接与应用

下面咱们对着色器有了一个初步的意识,那么这些着色器小程序如何应用起来呢?

如上图所示,着色器小程序须要通过创立、编译、链接、应用这些重要步骤。这些步骤也没什么好解析的,就像一套规定,大家遵循就是了。

以下是笔者写的一个加载应用着色器的一个工具ShaderUtils.h:

#ifndef NDK_OPENGLES_LEARN_SHADERUTILS_H#define NDK_OPENGLES_LEARN_SHADERUTILS_H#include "GLES3/gl3.h"#include "Log.h"static int loadShaders(int shaderType, const char *code){    // 依照类型,创立着色器    int shader = glCreateShader(shaderType);    // 加载着色器代码    glShaderSource(shader, 1, &code, 0);    // 编译    glCompileShader(shader);    // 检测编译状态    int result = GL_FALSE;    glGetShaderiv(shader,GL_COMPILE_STATUS,&result);    if(result != GL_TRUE){        GLint infoLen = 0;        glGetShaderiv(shader,GL_INFO_LOG_LENGTH,&infoLen);        char error[infoLen + 1];        // 获取编译谬误        glGetShaderInfoLog(shader, sizeof(error) / sizeof(error[0]),&infoLen,error);        LOGE("着色器编译失败:%s,%s",error,code);    }    return shader;}static int createProgram(const char *vertex , const char * fragment){    int vertexShader = loadShaders(GL_VERTEX_SHADER, vertex);    int fragmentShader = loadShaders(GL_FRAGMENT_SHADER, fragment);    int program = glCreateProgram();    glAttachShader(program, vertexShader);    glAttachShader(program, fragmentShader);    glLinkProgram(program);    // 获取一下是否有谬误    GLint status = GL_FALSE;    glGetProgramiv(program,GL_LINK_STATUS,&status);    if(status != GL_TRUE){        GLint infoLen = 0;        glGetProgramiv(program,GL_INFO_LOG_LENGTH,&infoLen);        char error[infoLen + 1];        glGetProgramInfoLog(program,sizeof(error) / sizeof(error[0]) ,&infoLen,error);        LOGE("着色器程序链接失败:",error);    }    // 更谨严一点可通过glValidateProgram()验证着色器程序    // 删除着色器    glDeleteShader(vertexShader);    glDeleteShader(fragmentShader);    return program;}#endif //NDK_OPENGLES_LEARN_SHADERUTILS_H

对于着色器的介绍就到这里,要想用好着色器,须要在弄懂GLSL语法之后多加实际...

参考资料

《LearnOpenGL CN》
《OPENGL ES 3.0编程指南》
...

往期笔记

OpenglEs之EGL环境搭建

关注我,一起提高,人生不止coding!!!