2791146769ad632523823cfaf761badc
Metal与OpenGL中的着色器知识

目录

  • 前言
    • 网站推荐
    • 前言
    • 目标
  • 目前主流图形框架
  • OpenGL 着色器(GLSL)
    • 顶点着色器
    • 片段着色器
  • Metal 着色器(MSL)
    • 顶点着色器
    • 片段着色器
    • lookup分割滤镜
  • 用一个简版 GPUImage Demo来验证想法吧!

几个小伙伴开了一个专栏,推荐下【琴师们的形与构】

网站推荐:

一个知名的OpenGL CN 在线学习网站:戳我

这个网站是我一开始接触OpenGL 时的最佳学习网站,而且是中文,有作者的理解。

前言

本文写了很久,一直处于删减状态;担心写的详细变成了长篇大论,又担心写少了,难以理解。于是本着认知转化的原则,写出了这篇文章,如果有理解不到位的,欢迎指正。

建议:

学习OpenGL、Metal最难的就是理解,理解这种思维模式,就像我们在工作中使用组件化,MVVM或者是某种思想;OpenGL、Metal是一种图形编码接口,请注意OpenGL、Metal 不是一门语言。

目标:

本文,你只需关注着色器,不要关心其他实现,理解着色器(vertex、fragment)的工作原理就好。

此外,学完本文之后,大家可以模仿写几个着色器:放到我下文提到的Demo中。

建议实现以下着色器:

  • 只显示图片的一部分,比如:圆形区域、左边显示图片,右边显示黑色
  • 动态改变,显示的圆形区域的半径(参考SplitFilter)

目前主流图形框架

  • Metal
  • Vukan
  • OpenGL
  • DX12

图形程序接口知多少 | OpenGL、OpenCL、Vulkan、OpenGL ES、WebGL、Metal、Directx

OpenGL 着色器(GLSL)

名词:顶点着色器 Vertex Shader 、片段着色器 Fragment Shader

OpenGL 的渲染流程

在开始本文之前,请确认:自己曾经了解过着色器,了解过渲染流程。否则请常查看「戳我」

看完这篇文章需要了解:

  • 顶点着色器:图形渲染管线的第一个部分
  • 片段着色器:主要目的是计算一个像素的最终颜色
  • VAO、VBO的概念
  • 着色器也是一种非常独立的程序,因为它们之间不能相互通信;它们之间唯一的沟通只有通过输入和输出
  • 在OpenGL ES中 表示输入输出的关键字为 inout
  • Uniform是一种从CPU中的应用向GPU中的着色器发送数据的方式,但uniform和顶点属性有些不同。首先,uniform是全局的(Global)、通常用作变量,下文会介绍。
  • 在整个渲染流程中我们只编程 顶点着色器 以及 片段着色器就可以了,能满足我们的绝大部分需求。

我用通俗的话解释一下这个流程,假如我们渲染一张图片到屏幕上,假如就是下边的这个图片:

需要渲染的图片

需要渲染的图片

首先我们显示一个矩形,内部颜色是红色:
红色

红色

  • 我们知道红色对应的rgb 是 -> (255,0,0) ,这种对应关系是规定。
  • 矩形需要四个点,按照一定顺序用直线连接起来(虽然是三角形拼接的,但是先这样理解)。顶点着色器 接受这些点的数据
  • 片段着色器 就是决定最后输出的颜色的,所以我们既然选择红色,那么就不存在,也不需要,如果这个颜色是根据时间动态变化的,那么Uniform 就是那个变值,具体的变化情况,由映射关系确定。
  • 为了方便我这边copy 来自 上文的 着色器。

GLSL 中文手册 感兴趣建议收藏

顶点着色器

#version 330 core
layout (location = 0) in vec3 position; // position变量的属性位置值为0

out vec4 vertexColor; // 为片段着色器指定一个颜色输出

void main()
{
    gl_Position = vec4(position, 1.0); // 注意我们如何把一个vec3作为vec4的构造器的参数
    vertexColor = vec4(1.0f, 0.0f, 0.0f, 1.0f); // 把输出变量设置为红色
}

glsl中的向量(vec2,vec3,vec4)往往有特殊的含义,比如可能代表了一个空间坐标(x,y,z,w),或者代表了一个颜色(r,g,b,a),再或者代表一个纹理坐标(s,t,p,q) 所以glsl提供了一些更人性化的分量访问方式.

  • vector.xyzw 其中xyzw 可以任意组合

  • vector.rgba 其中rgba 可以任意组合

  • vector.stpq 其中stpq 可以任意组合

类型 说明
void 空类型,即不返回任何值
bool 布尔类型 true,false
float 带符号的浮点数 floating scalar
vec2, vec3, vec4 n维浮点数向量 n-component floating point vector
sampler2D 2D纹理 a 2D texture

片段着色器

#version 330 core
in vec4 vertexColor; // 从顶点着色器传来的输入变量(名称相同、类型相同)

out vec4 color; // 片段着色器输出的变量名可以任意命名,类型必须是vec4

void main()
{
    color = vertexColor;
}

uniform的作用

#version 330 core
out vec4 color;

uniform vec4 ourColor; // 在OpenGL程序代码中设定这个变量

void main()
{
    color = ourColor;
}  

Uniform是全局变量,可用于vertex shader和fragment shader。在vertex shader中通常是变换矩阵、光照参数、颜色等。在fragment shader中通常是雾化参数、纹理参数等。OpenGLES 2.0规定所有实现应该支持的最大vertex shader的uniform变量个数不能少于128个,而最大fragment shader的uniform变量个数不能少于16个。简书

  • 着色器是各自独立的小程序,它们都是一个整体的一部分
  • 每个着色器都有输入和输出,这样才能进行数据交流和传递。
  • GLSL定义了in和out关键字专门来实现这个目的。
  • 每个着色器使用这两个关键字设定输入和输出,只要一个输出变量与下一个着色器阶段的输入匹配,它就会传递下去。但在顶点和片段着色器中会有点不同。

上边显示的是一个红色的矩形,那么我该如何显示一张图片(纹理)、图片是颜色的组合。
我们猜到(上文),片段着色器决定最终的输出颜色,那么是不是针对片段着色器(fragment shader),做一些操作就可以呢?
没错,我们可以直接修改,通过使用Uniform,改造如下:

#version 330 core
out vec4 FragColor;

in vec3 ourColor;
in vec2 TexCoord;

uniform sampler2D ourTexture;

void main()
{
    FragColor = texture(ourTexture, TexCoord);
}

GLSL有一个供纹理对象使用的内建数据类型,叫做采样器(Sampler)

TexCoord 就像顶点坐标一样,我们需要他来指定纹理显示的位置。

我们需要调整顶点着色器使其能够接受纹理的顶点坐标为一个顶点属性,并把坐标传给片段着色器
所以顶点坐标也要做如下修改:

#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aColor;
layout (location = 2) in vec2 aTexCoord;

out vec3 ourColor;
out vec2 TexCoord;

void main()
{
    gl_Position = vec4(aPos, 1.0);
    ourColor = aColor;
    TexCoord = aTexCoord;
}

如果有疑惑,请在阅读一遍。

关于OpenGL ES 的流程如下:
```
// 创建对象
unsigned int objectId = 0;
glGenObject(1, &objectId);
// 绑定对象至上下文
glBindObject(GL_WINDOW_TARGET, objectId);
// 设置当前绑定到 GL_WINDOW_TARGET 的对象的一些选项

top Created with Sketch.