有趣的Shader

目录

1.Shader简单介绍以及入门。
2.抖音特效经典滤镜实现(包含灵魂出窍、抖动)。
3.用Shader创造一些新鲜有趣的效果吧。

1.1 什么是Fragment Shader(片段着色器)?

我们把 shaders 和古腾堡印刷术相提并论。为什么这样类比呢?更重要的是,什么是 shader?


From Letter-by-Letter, Right: William Blades (1891). To Page-by-page, Left: Rolt-Wheeler (1920).

如果你曾经有用计算机绘图的经验,你就知道在这个过程中你需要画一个圆,然后一个长方形,一条线,一些三角形……直到画出你想要的图像。这个过程很像用手写一封信或一本书 —— 都是一系列的指令,需要你一件一件完成。
Shaders 也是一系列的指令,但是这些指令会对屏幕上的每个像素同时下达。也就是说,你的代码必须根据像素在屏幕上的不同位置执行不同的操作。就像活字印刷,你的程序就像一个 function(函数),输入位置信息,输出颜色信息,当它编译完之后会以相当快的速度运行。


Chinese movable type.
1.2 为什么 shaders 运行特别快?

为了回答这个问题,不得不给大家介绍并行处理(parallel processing)的神奇之处。
想象你的 CPU 是一个大的工业管道,然后每一个任务都是通过这个管道的某些东西 —— 就像一个生产流水线那样。有些任务要比别的大,也就是说要花费更多时间和精力去处理。我们就称它要求更强的处理能力。由于计算机自身的架构,这些任务需要串行;即一次一个地依序完成。现代计算机通常有一组四个处理器,就像这个管道一样运行,一个接一个地处理这些任务,从而使计算机流畅运行。每个管道通常被称为线程。


CPU

CPU视频游戏和其他图形应用比起别的程序来说,需要高得多的处理能力。因为它们的图形内容需要操作无数像素。想想看,屏幕上的每一个像素都需要计算,而在 3D 游戏中几何和透视也都需要计算。
让我们回到开始那个关于管道和任务的比喻。屏幕上的每个像素都代表一个最简单的任务。单独来看完成任何一个像素的任务对 CPU 来说都很容易,那么问题来了,屏幕上的每一个像素都需要解决这样的小任务!也就是说,哪怕是对于一个老式的屏幕(分辨率 800x600)来说,都需要每帧处理480000个像素,即每秒进行14400000次计算!是的,这对于微处理器就是大问题了!而对于一个现代的 2800x1800 视网膜屏,每秒运行60帧,就需要每秒进行311040000次计算。图形工程师是如何解决这个问题的?


image

这个时候,并行处理就是最好的解决方案。比起用三五个强大的微处理器(或者说“管道”)来处理这些信息,用一大堆小的微处理器来并行计算,就要好得多。这就是图形处理器(GPU : Graphic Processor Unit)的来由。
[图片上传失败...(image-9a0dd3-1556266552619)]
把GPU设想成一堆小型微处理器排成一个平面的画面,假设每个像素的数据是乒乓球。14400000个乒乓球可以在一秒内阻塞几乎任何管道。但是一面800x600的管道墙,每秒接收30波480000个像素的信息就可以流畅完成。这在更高的分辨率下也是成立的 —— 并行的处理器越多,可以处理的数据流就越大。

另一个 GPU 的魔法是特殊数学函数可通过硬件加速。非常复杂的数学操作可以直接被微芯片解决,而无须通过软件。这就表示可以有更快的三角和矩阵运算 —— 和电流一样快。

1.3 Hello world!

“Hello world!”通常都是学习一个新语言的第一个例子。这是一个非常简单,只有一行的程序。它既是一个热情的欢迎,也传达了编程所能带来的可能性。

然而在 GPU 的世界里,第一步就渲染一行文字太难了,所以我们改为选择一个鲜艳的欢迎色,来吧躁起来!

#ifdef GL_ES
precision mediump float;
#endif
void main() {
    gl_FragColor = vec4(1.0,0.0,1.0,1.0);
}

尽管这几行简单的代码看起来不像有很多内容,我们还是可以据此推测出一些知识点:

1.shader 语言 有一个 main 函数,会在最后返回颜色值。这点和 C 语言很像。

2.最终的像素颜色取决于预设的全局变量 gl_FragColor。

3.这个类 C 语言有内建的变量(像gl_FragColor),函数和数据类型。在本例中我们刚刚介绍了vec4(四分量浮点向量)。之后我们会见到更多的类型,像 vec3 (三分量浮点向量)和 vec2 (二分量浮点向量),还有非常著名的:float(单精度浮点型), int(整型) 和 bool(布尔型)。

4.如果我们仔细观察 vec4 类型,可以推测这四个变元分别响应红,绿,蓝和透明度通道。同时我们也可以看到这些变量是规范化的,意思是它们的值是从0到1的。之后我们会学习如何规范化变量,使得在变量间map(映射)数值更加容易。

5.另一个可以从本例看出来的很重要的类 C 语言特征是,预处理程序的宏指令。宏指令是预编译的一部分。有了宏才可以 #define (定义)全局变量和进行一些基础的条件运算(通过使用 #ifdef 和 #endif)。所有的宏都以 # 开头。预编译会在编译前一刻发生,把所有的命令复制到 #defines 里,检查#ifdef 条件句是否已被定义, #ifndef 条件句是否没有被定义。在我们刚刚的“hello world!”的例子中,我们在第2行检查了 GL_ES 是否被定义,这个通常用在移动端或浏览器的编译中。

6.float类型在 shaders 中非常重要,所以精度非常重要。更低的精度会有更快的渲染速度,但是会以质量为代价。你可以选择每一个浮点值的精度。在第一行(precision mediump float;)我们就是设定了所有的浮点值都是中等精度。但我们也可以选择把这个值设为“低”(precision lowp float;)或者“高”(precision highp float;)。

7.最后可能也是最重要的细节是,GLSL 语言规范并不保证变量会被自动转换类别。这句话是什么意思呢?显卡的硬件制造商各有不同的显卡加速方式,但是却被要求有最精简的语言规范。因而,自动强制类型转换并没有包括在其中。在我们的“hello world!”例子中,vec4 精确到单精度浮点,所以应被赋予 float 格式。但是如果你想要代码前后一致,不要之后花费大量时间 debug 的话,最好养成在 float 型数值里加一个 . 的好习惯。如下这种代码就可能不能正常运行:

1.4 运行我们的 shader

现在你可能跃跃欲试,想在你熟悉的平台上小试牛刀了。
1.你可以下载glslViewer。这个 MacOS+树莓派程序直接在终端运行.
2.如果你想用 WebGL 显示 shader,并不关心其他平台,你可以用glslCanvas
3.这里提安利个桌面小工具ShaderDesigner,这个 MacOS的程序可以直接调用摄像头并预览,加入你的shader代码来创造有趣的效果。

2.1 运行我们的 ShaderDesigner.app

下载ShaderDesigner并在MacOS环境下运行如下图

ShaderDesigner.app 运行效果

我们来看下Fragment Shader代码

varying vec2 textureCoordinate;
uniform sampler2D inputImageTexture;
uniform float u_time;
void main()
{
    gl_FragColor = texture2D(inputImageTexture, textureCoordinate);
}
  1. textureCoordinate 视口分辨率(以像素计)
  2. inputImageTexture 接收一个图片的引用,当做2D的纹理,这个数据类型就是smpler2D。
  3. u_time shader 运行时间(以秒计)
  4. texture2D(Texture,v_texCoord)方法
  5. 最终的像素颜色取决于vec4类型 gl_FragColor
2.2 小试牛刀解决镜像翻转
varying vec2 textureCoordinate;
uniform sampler2D inputImageTexture;
uniform float u_time;
void main()
void main()
{
    vec2 st = vec2(1.0 - textureCoordinate.x, textureCoordinate.y);
    gl_FragColor = texture2D(inputImageTexture, st);
}
2.3 试试抖音的灵魂出窍

先来看看效果吧(gif加载请等待⌛️)。

抖音-灵魂出窍.gif

varying vec2 textureCoordinate;
uniform sampler2D inputImageTexture;
uniform float u_time;
vec2 scaleFromCenter(vec2 coord, float scale) {
  if (scale > 1.0 || scale < 0.0) { return coord; }
  vec2 scaleCenter = vec2(0.5);
  return (coord - scaleCenter) * scale + scaleCenter;
}
void main()
{
    vec2 st = vec2(1.0 - textureCoordinate.x, textureCoordinate.y);
    float scale = 1.0 - mod(u_time * 1.4, 0.8) + 0.4;
    if (scale < 0.0) {
    gl_FragColor = texture2D(inputImageTexture, st);
    return;
    }
    vec2 newCoord = scaleFromCenter(st, scale);
    float colorScale = scale * 0.5;
    vec4 resultColor = texture2D(inputImageTexture, st) * (1.0 - colorScale + 0.2);
    vec4 newCoordColor = texture2D(inputImageTexture, newCoord) * (colorScale - 0.2);
    vec4 result = (resultColor + newCoordColor);
    gl_FragColor = result;
}
2.4 试试抖音的抖动效果吧

先来看看效果吧(gif加载请等待⌛️)

抖音-抖动.gif

varying vec2 textureCoordinate;
uniform sampler2D inputImageTexture;
uniform float u_time;
vec2 scaleFromCenter(vec2 coord, float scale) {
  if (scale > 1.0 || scale < 0.0) { return coord; }
  vec2 scaleCenter = vec2(0.55, 0.45);
  return (coord - scaleCenter) * scale + scaleCenter;
}

vec2 scaleFromCenter2(vec2 coord, float scale) {
  if (scale > 1.0 || scale < 0.0) { return coord; }
  vec2 scaleCenter = vec2(0.5);
  return (coord - scaleCenter) * scale + scaleCenter;
}

vec2 scaleFromCenter3(vec2 coord, float scale) {
  if (scale > 1.0 || scale < 0.0) { return coord; }
  vec2 scaleCenter = vec2(0.45);
  return (coord - scaleCenter) * scale + scaleCenter;
}
void main()
{
     vec2 st = vec2(1.0 - textureCoordinate.x, textureCoordinate.y);
    float scale = 1.0 - mod(u_time * 1.2, 0.8) + 0.5;
    if (scale < 0.0) {
    gl_FragColor = texture2D(inputImageTexture, st);
    return;
    }
    vec2 newCoord = scaleFromCenter(st, scale);
    vec4 result = texture2D(inputImageTexture, newCoord);
    vec2 newCoord2 = scaleFromCenter2(st, scale);
    vec4 result2 = texture2D(inputImageTexture, newCoord2);
    vec2 newCoord3 = scaleFromCenter3(st, scale);
    vec4 result3 = texture2D(inputImageTexture, newCoord3);
    vec4 xx = result * vec4(0.0,0.0,1.0,1.0) +
    result2 * vec4(0.0,1.0,0.0,1.0) +
    result3 * vec4(1.0,0.0,0.0,1.0) ;
   gl_FragColor = vec4(xx.rgb, 1.0);
}

3.1用Shader创造一些新鲜有趣的效果吧—四合一。
有趣的四合一
varying vec2 textureCoordinate;
uniform sampler2D inputImageTexture;
uniform float u_time;
vec2 scaleFromCenter2D(vec2 coord, vec2 scale) {
  vec2 scaleCenter = scale;
  vec2 st = coord - scaleCenter;
  st = st * scale + scaleCenter;
  if (st.x < 0.0) {
    st.x = mod(st.x, 1.0);
  }
  else if (st.x > 1.0) {
    st.x = fract(st.x);
  }
  if (st.y < 0.0) {
    st.y = mod(st.y, 1.0);
  }
  else if (st.y > 1.0) {
    st.y = fract(st.y);
  }
  return st;
}

void main()
{
    vec2 st = vec2(1.0 - textureCoordinate.x, textureCoordinate.y);
    st = scaleFromCenter2D(st, vec2(2.0));
    gl_FragColor = texture2D(inputImageTexture, st);

}

3.2用Shader创造一些新鲜有趣的效果吧—三合一。
有趣的三合一
varying vec2 textureCoordinate;
uniform sampler2D inputImageTexture;
uniform float u_time;
void main()
{
    vec2 st = vec2(1.0 - textureCoordinate.x, textureCoordinate.y);

    if (st.y <=  0.333){
       gl_FragColor = texture2D(inputImageTexture, vec2(st.x,st.y + 0.333));
    }else if(st.y > 0.333 && st.y<= 0.666){
       gl_FragColor = texture2D(inputImageTexture, st);
    }else{
       gl_FragColor = texture2D(inputImageTexture, vec2(st.x,st.y - 0.333));
    }
}
3.3用Shader创造一些新鲜有趣的效果吧—中间镜像。
有趣的中间镜像
varying vec2 textureCoordinate;
uniform sampler2D inputImageTexture;
uniform float u_time;
void main()
{
    vec2 st = vec2(1.0 - textureCoordinate.x, textureCoordinate.y);
    if (st.x <=  0.5){
        gl_FragColor = texture2D(inputImageTexture, vec2(  0.5 - st.x,st.y ));
       }else{
        gl_FragColor = texture2D(inputImageTexture, vec2( st.x - 0.5,st.y ));
       }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,569评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,499评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,271评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,087评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,474评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,670评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,911评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,636评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,397评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,607评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,093评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,418评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,074评论 3 237
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,092评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,865评论 0 196
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,726评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,627评论 2 270