前言
在后面咱们介绍了 [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 es
in 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 es
precision 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!!!