[关闭]
@qidiandasheng 2020-07-16T08:56:27.000000Z 字数 5948 阅读 963

OpenGL ES着色器

音视频


介绍

着色器(Shader)是运行在GPU上的小程序。这些小程序为图形渲染管线的某个特定部分而运行。从基本意义上来说,着色器只是一种把输入转化为输出的程序。着色器也是一种非常独立的程序,因为它们之间不能相互通信;它们之间唯一的沟通只有通过输入和输出。

GLSL是OpenGL着色器语言,GLSL是一门特殊的有着类似于C语言的语法,在图形管道(graphic pipeline)中直接可执行的OpenGL着色语言。

最常用到的两种着色器分别为顶点着色器(Vertex Shader)和片元着色器(Fragment Shader)。

顶点着色器

什么是顶点呢?

比如你用 OpenGL 画一个三角形,那就是创建了三个顶点。

而顶点着色器就是每个顶点调用一次的程序。

在顶点着色器中,可以访问到顶点的三维位置、颜色、法向量等信息。可以通过修改这些值,或者将其传递到片元着色器中,实现特定的渲染效果。

顶点着色器代码

顶点着色器其目的是设置 gl_Position 变量 -- 这是一个特殊的全局内置变量, 它是用来存储当前顶点的位置。

  1. attribute vec4 position;
  2. attribute vec4 inputTextureCoordinate;
  3. varying vec2 textureCoordinate;
  4. void main()
  5. {
  6. gl_Position = position;
  7. textureCoordinate = inputTextureCoordinate.xy;
  8. }

这个 void main() 函数是定义全局gl_Position变量的标准方式. 所有在这个函数里面的代码都会被着色器执行. 如果将3D空间中的位置投射到2D屏幕上这些信息都会保存在计算结果的变量中。

片元着色器

“片元”的概念大家可能相对陌生一些。但是一个相似的概念是“像素”,这你一定听说过。

场景渲染到显示器的过程中,有一个步骤叫光栅化(Rasterization)。由于我们现在的显示器绝大多数是基于像素的(就是由一个个非常小的红绿蓝 LED 组成的显示单元),所以“连续”的三维场景,要显示到“离散”的显示器上,需要经过的变化操作就叫光栅化。

光栅化后得到的就得到了一个个“片元”。片元和像素已经非常接近了,但两者仍是有区别的。用一种通俗的说法来解释的话,就是比如三维空间内有两个从摄像机角度看过去一前一后的三角形,它们重叠部分的显示区域,每个像素对应两个片元;不重叠的部分,像素和片元一一对应。当然,这个例子是我简化过的,真实的对应关系可能更复杂一些。

这里我们并不需要了解片元的概念太深刻,只要知道它是非常接近像素,但是又不等同于像素的就可以了。

同样,片元着色器就是每个片元调用一次的程序。

在片元着色器中,可以访问到片元在二维屏幕上的坐标、深度信息、颜色等信息。通过改变这些值,可以实现特定的渲染效果。

片段着色器代码

片段 (或者纹理) 着色器 在计算时定义了每像素的 RGBA 颜色 — 每个像素只调用一次片段着色器. 这个着色器的作用是设置 gl_FragColor 变量, 也是一个GLSL内置变量:

  1. varying highp vec2 textureCoordinate;
  2. uniform sampler2D inputImageTexture;
  3. void main()
  4. {
  5. gl_FragColor = texture2D(inputImageTexture, textureCoordinate);
  6. }

计算结果包含RGBA颜色信息。

变量类型

attribute

attribute 修饰符只存在于顶点着色器中,用于储存每个顶点信息的输入,比如这里定义了 PositionTextureCoords ,用于接收顶点的位置和纹理信息。

varying

varying 修饰符指顶点着色器的输出,同时也是片段着色器的输入,要求顶点着色器和片段着色器中都同时声明,并完全一致,则在片段着色器中可以获取到顶点着色器中的数据。

uniform

uniform 用来保存传递进来的只读值,该值在顶点着色器和片段着色器中都不会被修改。顶点着色器和片段着色器共享了 uniform 变量的命名空间,uniform 变量在全局区声明,同个 uniform 变量在顶点着色器和片段着色器中都能访问到。

数据类型

和其他编程语言一样,GLSL有数据类型可以来指定变量的种类。GLSL中包含C等其它语言大部分的默认基础数据类型:int、float、double、uint和bool。GLSL也有两种容器类型,分别是向量(Vector)和矩阵(Matrix)。

向量(Vector)

GLSL中的向量是一个可以包含有1、2、3或者4个分量的容器,分量的类型可以是前面默认基础类型的任意一个。它们可以是下面的形式(n代表分量的数量):

类型 含义
vecn 包含n个float分量的默认向量
bvecn 包含n个bool分量的向量
ivecn 包含n个int分量的向量
uvecn 包含n个unsigned int分量的向量
dvecn 包含n个double分量的向量

大多数时候我们使用vecn,因为float足够满足大多数要求了。

一个向量的分量可以通过vec.x这种方式获取,这里x是指这个向量的第一个分量。你可以分别使用.x.y.z.w来获取它们的第1、2、3、4个分量。GLSL也允许你对颜色使用rgba,或是对纹理坐标使用stpq访问相同的分量。

向量这一数据类型也允许一些有趣而灵活的分量选择方式,叫做重组(Swizzling)。重组允许这样的语法:

  1. vec2 someVec;
  2. vec4 differentVec = someVec.xyxx;
  3. vec3 anotherVec = differentVec.zyw;
  4. vec4 otherVec = someVec.xxxx + anotherVec.yxzy;

你可以使用上面4个字母任意组合来创建一个和原来向量一样长的(同类型)新向量,只要原来向量有那些分量即可;然而,你不允许在一个vec2向量中去获取.z元素。我们也可以把一个向量作为一个参数传给不同的向量构造函数,以减少需求参数的数量:

  1. vec2 vect = vec2(0.5f, 0.7f);
  2. vec4 result = vec4(vect, 0.0f, 0.0f);
  3. vec4 otherResult = vec4(result.xyz, 1.0f);

函数

texture2D()

方法可以根据纹理坐标,获取对应的颜色信息。

  1. //inputImageTexture:纹理 textureCoordinate:纹理坐标
  2. vec4 textureColor = texture2D(inputImageTexture, textureCoordinate);

例子

编写着色器

顶点着色器:

  1. attribute vec4 Position;
  2. attribute vec2 TextureCoords;
  3. varying vec2 TextureCoordsVarying;
  4. void main (void) {
  5. gl_Position = Position;
  6. TextureCoordsVarying = TextureCoords;
  7. }

片元着色器:

  1. precision mediump float;
  2. uniform sampler2D Texture;
  3. varying vec2 TextureCoordsVarying;
  4. void main (void) {
  5. vec4 mask = texture2D(Texture, TextureCoordsVarying);
  6. gl_FragColor = vec4(mask.rgb, 1.0);
  7. }

编译着色器

  1. // 创建一个 shader 对象
  2. GLuint shader = glCreateShader(shaderType);
  3. // 获取 shader 的内容
  4. const char *shaderStringUTF8 = [shaderString UTF8String];
  5. int shaderStringLength = (int)[shaderString length];
  6. glShaderSource(shader, 1, &shaderStringUTF8, &shaderStringLength);
  7. // 编译shader
  8. glCompileShader(shader);

挂载着色器到着色器程序

  1. // 根据上一步的编译着色器得到顶点着色器和片元着色器
  2. GLuint vertexShader = [self compileShaderWithShaderString:vertexShaderString type:GL_VERTEX_SHADER];
  3. GLuint fragmentShader = [self compileShaderWithShaderString:fragmentShaderString type:GL_FRAGMENT_SHADER];
  4. // 挂载 shader 到 program 上
  5. GLuint program = glCreateProgram();
  6. glAttachShader(program, vertexShader);
  7. glAttachShader(program, fragmentShader);
  8. // 链接 program
  9. glLinkProgram(program);
  10. // 使用 program
  11. glUseProgram(program);

给着色器设置数据

获取着色器中的参数

  1. // 顶点坐标
  2. GLuint positionSlot = glGetAttribLocation(program, "Position");
  3. // 纹理坐标
  4. GLuint textureCoordsSlot = glGetAttribLocation(program, "TextureCoords");
  5. // 纹理
  6. GLuint textureSlot = glGetUniformLocation(program, "Texture");

将纹理传给着色器

  1. // 激活纹理单元0
  2. glActiveTexture(GL_TEXTURE0);
  3. // 绑定纹理到纹理单元下的target:GL_TEXTURE_2D
  4. glBindTexture(GL_TEXTURE_2D, textureID);
  5. // 设置着色器textureSlot的值为纹理单元0
  6. glUniform1i(textureSlot, 0);

将顶点坐标数据传给着色器

  1. static const GLfloat vertices[] = {
  2. -1.0f, -1.0f, 0.0f,
  3. 1.0f, -1.0f, 0.0f,
  4. -1.0f, 1.0f, 0.0f,
  5. 1.0f, 1.0f, 0.0f,
  6. };
  7. glEnableVertexAttribArray(positionSlot);
  8. glVertexAttribPointer(positionSlot, 3, GL_FLOAT, 0, 0, vertices);

将纹理坐标数据传给着色器

  1. static const GLfloat textureCoordinates[] = {
  2. 0.0f, 1.0f,
  3. 1.0f, 1.0f,
  4. 0.0f, 0.0f,
  5. 1.0f, 0.0f,
  6. };
  7. glEnableVertexAttribArray(textureCoordsSlot);
  8. glVertexAttribPointer(textureCoordsSlot, 2, GL_FLOAT, 0, 0, textureCoordinates);

开始绘制

  1. glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

例子:颜色转换着色器

着色器编写

顶点着色器:

  1. attribute vec4 position;
  2. attribute vec4 inputTextureCoordinate;
  3. varying vec2 textureCoordinate;
  4. void main()
  5. {
  6. gl_Position = position;
  7. textureCoordinate = inputTextureCoordinate.xy;
  8. }

片元着色器:

  1. varying highp vec2 textureCoordinate;
  2. uniform sampler2D inputImageTexture;
  3. uniform lowp mat4 colorMatrix;
  4. uniform lowp float intensity;
  5. void main()
  6. {
  7. lowp vec4 textureColor = texture2D(inputImageTexture, textureCoordinate);
  8. lowp vec4 outputColor = textureColor * colorMatrix;
  9. gl_FragColor = (intensity * outputColor) + ((1.0 - intensity) * textureColor);
  10. }

步骤描述

  1. 顶点着色器获取设置给它的数据:顶点坐标(position)、纹理坐标(inputTextureCoordinate
  2. 顶点坐标赋值给gl_Position用来渲染顶点,纹理坐标赋值给textureCoordinate用来传给片元着色器
  3. 片元着色器获取设置给它的数据:纹理(inputImageTexture),颜色变换矩阵(colorMatrix)、强度(intensity)
  4. 根据纹理(inputImageTexture)和顶点着色器传进来的纹理坐标获对应的纹理颜色:vec4 textureColor = texture2D(inputImageTexture, textureCoordinate);
  5. 通过颜色变换矩阵和纹理颜色相乘对纹理颜色进行处理获得颜色向量outputColor
  6. 通过矩阵变换过的颜色(outputColor)再跟强度值(intensity)进行计算获得颜色向量赋值给gl_FragColor来渲染纹理

矩阵变换

  1. R G B A
  2. | 1 0 0 0 | red
  3. | 0 1 0 0 | green
  4. | 0 0 1 0 | blue
  5. | 0 0 0 1 | alpha
  6. R G B A
  7. | a b c d | red
  8. | e f g h | green
  9. | i j k l | blue
  10. | m n o p | alpha
  11. R' = a*R + b*G + c*B + d*A
  12. G' = e*R + f*G + g*B + h*A
  13. B' = i*R + j*G + k*B + l*A
  14. A' = m*R + n*G + o*B + p*A
  15. | a b c d | |R| |R'|
  16. | e f g h | x |G| = |G'|
  17. | i j k l | |B| |B'|
  18. | m n o p | |A| |A'|

深褐色滤镜的颜色转换矩阵如下:

  1. {
  2. {0.3588, 0.7044, 0.1368, 0.0},
  3. {0.2990, 0.5870, 0.1140, 0.0},
  4. {0.2392, 0.4696, 0.0912 ,0.0},
  5. {0,0,0,1.0},
  6. };
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注