@qidiandasheng
2020-07-16T16:56:27.000000Z
字数 5948
阅读 1518
音视频
着色器(Shader)是运行在GPU上的小程序。这些小程序为图形渲染管线的某个特定部分而运行。从基本意义上来说,着色器只是一种把输入转化为输出的程序。着色器也是一种非常独立的程序,因为它们之间不能相互通信;它们之间唯一的沟通只有通过输入和输出。
GLSL是OpenGL着色器语言,GLSL是一门特殊的有着类似于C语言的语法,在图形管道(graphic pipeline)中直接可执行的OpenGL着色语言。
最常用到的两种着色器分别为顶点着色器(Vertex Shader
)和片元着色器(Fragment Shader
)。
什么是顶点呢?
比如你用 OpenGL 画一个三角形,那就是创建了三个顶点。
而顶点着色器就是每个顶点调用一次的程序。
在顶点着色器中,可以访问到顶点的三维位置、颜色、法向量等信息。可以通过修改这些值,或者将其传递到片元着色器中,实现特定的渲染效果。
顶点着色器其目的是设置 gl_Position
变量 -- 这是一个特殊的全局内置变量, 它是用来存储当前顶点的位置。
attribute vec4 position;
attribute vec4 inputTextureCoordinate;
varying vec2 textureCoordinate;
void main()
{
gl_Position = position;
textureCoordinate = inputTextureCoordinate.xy;
}
这个 void main()
函数是定义全局gl_Position
变量的标准方式. 所有在这个函数里面的代码都会被着色器执行. 如果将3D空间中的位置投射到2D屏幕上这些信息都会保存在计算结果的变量中。
“片元”的概念大家可能相对陌生一些。但是一个相似的概念是“像素”,这你一定听说过。
场景渲染到显示器的过程中,有一个步骤叫光栅化(Rasterization)。由于我们现在的显示器绝大多数是基于像素的(就是由一个个非常小的红绿蓝 LED 组成的显示单元),所以“连续”的三维场景,要显示到“离散”的显示器上,需要经过的变化操作就叫光栅化。
光栅化后得到的就得到了一个个“片元”。片元和像素已经非常接近了,但两者仍是有区别的。用一种通俗的说法来解释的话,就是比如三维空间内有两个从摄像机角度看过去一前一后的三角形,它们重叠部分的显示区域,每个像素对应两个片元;不重叠的部分,像素和片元一一对应。当然,这个例子是我简化过的,真实的对应关系可能更复杂一些。
这里我们并不需要了解片元的概念太深刻,只要知道它是非常接近像素,但是又不等同于像素的就可以了。
同样,片元着色器就是每个片元调用一次的程序。
在片元着色器中,可以访问到片元在二维屏幕上的坐标、深度信息、颜色等信息。通过改变这些值,可以实现特定的渲染效果。
片段 (或者纹理) 着色器 在计算时定义了每像素的 RGBA 颜色 — 每个像素只调用一次片段着色器. 这个着色器的作用是设置 gl_FragColor
变量, 也是一个GLSL内置变量:
varying highp vec2 textureCoordinate;
uniform sampler2D inputImageTexture;
void main()
{
gl_FragColor = texture2D(inputImageTexture, textureCoordinate);
}
计算结果包含RGBA颜色信息。
attribute
修饰符只存在于顶点着色器中,用于储存每个顶点信息的输入,比如这里定义了 Position
和 TextureCoords
,用于接收顶点的位置和纹理信息。
varying
修饰符指顶点着色器的输出,同时也是片段着色器的输入,要求顶点着色器和片段着色器中都同时声明,并完全一致,则在片段着色器中可以获取到顶点着色器中的数据。
uniform
用来保存传递进来的只读值,该值在顶点着色器和片段着色器中都不会被修改。顶点着色器和片段着色器共享了 uniform
变量的命名空间,uniform
变量在全局区声明,同个 uniform
变量在顶点着色器和片段着色器中都能访问到。
和其他编程语言一样,GLSL有数据类型可以来指定变量的种类。GLSL中包含C等其它语言大部分的默认基础数据类型:int、float、double、uint和bool。GLSL也有两种容器类型,分别是向量(Vector)和矩阵(Matrix)。
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)。重组允许这样的语法:
vec2 someVec;
vec4 differentVec = someVec.xyxx;
vec3 anotherVec = differentVec.zyw;
vec4 otherVec = someVec.xxxx + anotherVec.yxzy;
你可以使用上面4个字母任意组合来创建一个和原来向量一样长的(同类型)新向量,只要原来向量有那些分量即可;然而,你不允许在一个vec2向量中去获取.z元素。我们也可以把一个向量作为一个参数传给不同的向量构造函数,以减少需求参数的数量:
vec2 vect = vec2(0.5f, 0.7f);
vec4 result = vec4(vect, 0.0f, 0.0f);
vec4 otherResult = vec4(result.xyz, 1.0f);
方法可以根据纹理坐标,获取对应的颜色信息。
//inputImageTexture:纹理 textureCoordinate:纹理坐标
vec4 textureColor = texture2D(inputImageTexture, textureCoordinate);
顶点着色器:
attribute vec4 Position;
attribute vec2 TextureCoords;
varying vec2 TextureCoordsVarying;
void main (void) {
gl_Position = Position;
TextureCoordsVarying = TextureCoords;
}
片元着色器:
precision mediump float;
uniform sampler2D Texture;
varying vec2 TextureCoordsVarying;
void main (void) {
vec4 mask = texture2D(Texture, TextureCoordsVarying);
gl_FragColor = vec4(mask.rgb, 1.0);
}
GL_VERTEX_SHADER
:顶点着色器,GL_FRAGMENT_SHADER
:片元着色器
// 创建一个 shader 对象
GLuint shader = glCreateShader(shaderType);
// 获取 shader 的内容
const char *shaderStringUTF8 = [shaderString UTF8String];
int shaderStringLength = (int)[shaderString length];
glShaderSource(shader, 1, &shaderStringUTF8, &shaderStringLength);
// 编译shader
glCompileShader(shader);
// 根据上一步的编译着色器得到顶点着色器和片元着色器
GLuint vertexShader = [self compileShaderWithShaderString:vertexShaderString type:GL_VERTEX_SHADER];
GLuint fragmentShader = [self compileShaderWithShaderString:fragmentShaderString type:GL_FRAGMENT_SHADER];
// 挂载 shader 到 program 上
GLuint program = glCreateProgram();
glAttachShader(program, vertexShader);
glAttachShader(program, fragmentShader);
// 链接 program
glLinkProgram(program);
// 使用 program
glUseProgram(program);
获取着色器中的参数
// 顶点坐标
GLuint positionSlot = glGetAttribLocation(program, "Position");
// 纹理坐标
GLuint textureCoordsSlot = glGetAttribLocation(program, "TextureCoords");
// 纹理
GLuint textureSlot = glGetUniformLocation(program, "Texture");
将纹理传给着色器
// 激活纹理单元0
glActiveTexture(GL_TEXTURE0);
// 绑定纹理到纹理单元下的target:GL_TEXTURE_2D
glBindTexture(GL_TEXTURE_2D, textureID);
// 设置着色器textureSlot的值为纹理单元0
glUniform1i(textureSlot, 0);
将顶点坐标数据传给着色器
static const GLfloat vertices[] = {
-1.0f, -1.0f, 0.0f,
1.0f, -1.0f, 0.0f,
-1.0f, 1.0f, 0.0f,
1.0f, 1.0f, 0.0f,
};
glEnableVertexAttribArray(positionSlot);
glVertexAttribPointer(positionSlot, 3, GL_FLOAT, 0, 0, vertices);
将纹理坐标数据传给着色器
static const GLfloat textureCoordinates[] = {
0.0f, 1.0f,
1.0f, 1.0f,
0.0f, 0.0f,
1.0f, 0.0f,
};
glEnableVertexAttribArray(textureCoordsSlot);
glVertexAttribPointer(textureCoordsSlot, 2, GL_FLOAT, 0, 0, textureCoordinates);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
顶点着色器:
attribute vec4 position;
attribute vec4 inputTextureCoordinate;
varying vec2 textureCoordinate;
void main()
{
gl_Position = position;
textureCoordinate = inputTextureCoordinate.xy;
}
片元着色器:
varying highp vec2 textureCoordinate;
uniform sampler2D inputImageTexture;
uniform lowp mat4 colorMatrix;
uniform lowp float intensity;
void main()
{
lowp vec4 textureColor = texture2D(inputImageTexture, textureCoordinate);
lowp vec4 outputColor = textureColor * colorMatrix;
gl_FragColor = (intensity * outputColor) + ((1.0 - intensity) * textureColor);
}
position
)、纹理坐标(inputTextureCoordinate
)gl_Position
用来渲染顶点,纹理坐标赋值给textureCoordinate
用来传给片元着色器inputImageTexture
),颜色变换矩阵(colorMatrix)、强度(intensity)inputImageTexture
)和顶点着色器传进来的纹理坐标获对应的纹理颜色:vec4 textureColor = texture2D(inputImageTexture, textureCoordinate);
outputColor
outputColor
)再跟强度值(intensity
)进行计算获得颜色向量赋值给gl_FragColor
来渲染纹理
R G B A
| 1 0 0 0 | red
| 0 1 0 0 | green
| 0 0 1 0 | blue
| 0 0 0 1 | alpha
R G B A
| a b c d | red
| e f g h | green
| i j k l | blue
| m n o p | alpha
R' = a*R + b*G + c*B + d*A
G' = e*R + f*G + g*B + h*A
B' = i*R + j*G + k*B + l*A
A' = m*R + n*G + o*B + p*A
| a b c d | |R| |R'|
| e f g h | x |G| = |G'|
| i j k l | |B| |B'|
| m n o p | |A| |A'|
深褐色滤镜的颜色转换矩阵如下:
{
{0.3588, 0.7044, 0.1368, 0.0},
{0.2990, 0.5870, 0.1140, 0.0},
{0.2392, 0.4696, 0.0912 ,0.0},
{0,0,0,1.0},
};