文章目錄
  1. 1. Shader Toy Introduction
  2. 2. Shader Toy Study
    1. 2.1. fragColor
    2. 2.2. fragCoord
    3. 2.3. Own Coordinate System
    4. 2.4. Cicle Demo
    5. 2.5. Plasma Effect
    6. 2.6. Texture & Video as Input
    7. 2.7. Mouse Input
    8. 2.8. Random Noise
    9. 2.9. 更多学习待更新
  3. 3. Shader Toy Relative Knowledge
    1. 3.1. Shader Toy Inputs

Shader Toy Introduction

之前就听别人提起过这个网站,上面有各式各样只通过Pixel Shader编写绚丽的效果(里面包含了很多数学和算法)。
而且作者编写的代码在网站上一目了然,让你知道这个效果是如何计算得出的。
看一下下面这一张效果:
ShaderToyExmaple
第一眼看到的时候,我很难相信这是通过简单纹理贴图输入加上数学运算得出的图案。

那么我们首先要知道什么是Pixel Shader?
Pixel Shader在OpenGL里也叫做Fragment Shader,可以简单的理解成针对每一个pixel做处理的Shader。

在这个网站上编写Pixel Shader还有一个好处就是快速方便的看到效果,当你要去测试一些数学算式算法的时候,很容易可视化的在上面编写并测试。

Shader Toy Study

接下来是基于ShaderToy上”GLSL 2D Tutorials”教程学习的一些事例。

fragColor

fragColor就是我们在GLSL的fragment shader里最后代表像素颜色的最后输出变量,他控制着我每一个像素最终的颜色值

1
2
3
4
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{

fragColor = vec4(0.0,1.0,1.0,1.0);
}

mainImage(…)是我们的Pixel Shader的函数路口,每一帧都会对每一像素进行调用。
Final Effect:
ShaderToyfragColor

fragCoord

fragCoord是针对像素坐标而言的,因为mainImage会针对每一个像素执行一次,而fragCoord就给出了像素的坐标位置(左下角为原点)。
iResolution是ShaderToy里给出的关于frame的宽高像素信息(主要用于适应屏幕的大小变化,做到按比例而非特定像素值)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{

// fragcoord通过使用像素的fragCoord位置除以iResolution的宽高像素信息,
// 成功将像素位置的width和heigth都映射到了[0.0,1.0]
vec2 fragcoord = vec2(fragCoord.xy / iResolution.xy);
vec3 backgroundcolor = vec3(1.0,1.0,1.0);
vec3 pixelcolor = backgroundcolor;
vec3 gridcolor = vec3(0.5,0.5,0.5);
vec3 axescolor = vec3(0.0,0.0,1.0);
const float thickwidth = 0.1;
for(float i = 0.0; i < 1.0; i+=thickwidth)
{
if(mod(fragcoord.x, thickwidth) < 0.008 || mod(fragcoord.y, thickwidth) < 0.008)
{
pixelcolor = gridcolor;
}
}

if(abs(fragcoord.x ) < 0.006 || abs(fragcoord.y) < 0.006)
{
pixelcolor = axescolor;
}
fragColor = vec4(pixelcolor,1.0);
}

Final Effect:
ShaderToyfragCoord

Own Coordinate System

前一节讲到的把像素坐标映射到了[0.0,1.0],那么如果我们想把坐标信息映射到[-1.0,1.0]并且把屏幕中点作为(0.0,0.0)改如何映射了
而且前一节有一个问题需要注意,我们绘制出的grid是长方形而不是正方形(这主要是由于我们屏幕宽高是不一样,但我们都把x,y映射到了[0.0,1.0]并且用相同的interval即thickwidth去做等分导致的)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{

// 这里值得注意一下,因为一般情况都是屏幕宽大于高,
// 所以我们在映射宽高的时候,只需把高映射到[-1.0,1.0],
// 宽保持和高的比例差即可,即映射到[-width/height, width/height]
// 这样一来,同一个interval对于宽和高来说就一样了
vec2 r = vec2( fragCoord.xy - 0.5*iResolution.xy );
r = 2.0 * r.xy / iResolution.y;
vec3 backgroundcolor = vec3(1.0,1.0,1.0);
vec3 pixelcolor = backgroundcolor;
vec3 gridcolor = vec3(0.5,0.5,0.5);
vec3 axescolor = vec3(0.0,0.0,1.0);
const float thickwidth = 0.1;
for(float i = 0.0; i < 1.0; i+=thickwidth)
{
if(mod(r.x, thickwidth) < 0.008 || mod(r.y, thickwidth) < 0.008)
{
pixelcolor = gridcolor;
}
}

if(abs(r.x ) < 0.006 || abs(r.y) < 0.006)
{
pixelcolor = axescolor;
}
fragColor = vec4(pixelcolor,1.0);
}

Final Effect:
ShaderToyOwnCoordinateSystem

Cicle Demo

接下来我们即将看实现以下功能:

  1. 绘制圆
    针对绘制圆,我们主要是通过判断像素到圆心的位置的距离来决定是否处于圆内。
  2. 让圆之间的颜色实现叠加计算
    针对叠加运算的判断,我们主要是通过一个smoothstep的函数去得出像素在圆内和圆外所参与颜色计算的比例(这里是院内1.0,圆外0.0)
    这里要介绍一下smoothstep函数。
    函数原型:
    float smoothstep(float edge0, float edge1, float x)
    如果我们传递xedge1则返回1.0,如果在中间则返回edge0-edge1的interpolation值(这里我们主要用来实现判断像素是在圆内还是圆外来决定是否参与叠加运算)
  3. 使圆做周期性运动。
    圆做周期性运动主要是通过iGlobalTime(Pixel Shader运行后的一个动态时间)来计算得出圆心的位置来实现的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#define PI 3.14159265359
// 绘制圆并返回是否改圆的该像素是否应该参与像素叠加计算
float disk(vec2 r, vec2 center, float radius, vec3 color, inout vec3 pixel)
{

float rtoclength = length(r - center);
float inside = 0.0;
if(rtoclength < radius)
{
//pixel = vec3(clamp(rtoclength, radius / 4.0,radius / 2.0));
inside = 1.0 - smoothstep(radius - 0.005,radius + 0.005, rtoclength);
}
return inside;
}

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{

vec2 p = vec2(fragCoord.xy / iResolution.xy);
vec2 r = vec2( fragCoord.xy - 0.5*iResolution.xy );
r = 2.0 * r.xy / iResolution.y;
vec3 backgroundcolor = vec3(0.0, 0.0, 0.0);
vec3 color1 = vec3(1.0, 0.0,0.0);
vec3 color2 = vec3(0.0, 1.0, 0.0);
vec3 color3 = vec3(0.0, 0.0, 1.0);
vec2 circle1center1 = vec2(sin(iGlobalTime / 1.0), cos(iGlobalTime / 1.0));
vec2 circle1center2 = vec2(cos(iGlobalTime / 2.0), sin(iGlobalTime / 2.0));
vec2 circle1center3 = vec2(-sin(iGlobalTime / 3.0), -cos(iGlobalTime / 3.0));

vec3 resultcolor = vec3(0.0,0.0,0.0);

vec3 pixel = backgroundcolor;

resultcolor += disk(r, circle1center1, 0.6, color1, pixel) * color1;

resultcolor += disk(r, circle1center2, 0.6, color2, pixel) * color2;

resultcolor += disk(r, circle1center3, 0.6, color3, pixel) * color3;

fragColor = vec4(resultcolor, 1.0);
}

Final Effect:
ShaderToyCircleDemo

Plasma Effect

Plasma Effect
关于这一节还有很多相关知识需要学习理解,暂时只贴代码和效果,后续会进一步深入了解。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{

vec2 p = vec2(fragCoord.xy / iResolution.xy);
vec2 r = vec2( fragCoord.xy - 0.5*iResolution.xy );
r = 2.0 * r.xy / iResolution.y;
vec3 ret = vec3(1.0, 1.0, 1.0);
float t = iGlobalTime;
r = r * 8.0;
float v1 = sin(r.x + t);
float v2 = sin(r.y + t);
float v3 = sin(r.x + r.y + t);
float v4 = sin(sqrt(r.x * r.x + r.y * r.y) + t);
float v5 = v1 + v2 + v3 + v4;

if( p.x < 1.0 / 10.0 )
{
ret = vec3(v1);
}
else if( p.x < 2.0 / 10.0)
{
ret = vec3(v2);
}
else if( p.x < 3.0 / 10.0)
{
ret = vec3(v3);
}
else if( p.x < 4.0 / 10.0)
{
ret = vec3(v4);
}
else if( p.x < 5.0 / 10.0)
{
ret = vec3(v5);
}
else if( p.x < 6.0 / 10.0)
{
ret = vec3(sin(v5));
}
else
{
ret *= vec3(sin(v5), cos(v5), tan(v5));
}

fragColor = vec4(ret, 1.0);
}

Final Effect:
ShaderToyPlasmaEffect

Texture & Video as Input

下面这个效果比较有趣,是通过把两个视频作为输入,通过把其中一个视频作为背景,把另一个含绿色背景的颜色出掉后合二为一实现的效果。
在Shader Toy里,我们可以设置几个Texture到iChannel上,然后通过texture2D(iChannel,*)去访问纹理值。
参考至A Beginner’s Guide to Coding Graphics Shaders

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{

vec4 backgroundtexture = texture2D(iChannel0, p);
vec4 fronttexture = texture2D(iChannel2, p);

if(fronttexture.r + fronttexture.b > fronttexture.g)
{
fragColor = fronttexture;
}
else
{
fragColor = backgroundtexture;
}
}

Final Effect:
ShaderToyTextureAndVideoInput
ShaderToyTextureAndVideoInput2

Mouse Input

这一节讲到ShaderToy里关于如何响应Mouse Input。
ShaderToy里给了一个iMouse变量,用于得到Mouse输入的信息,我们可以通过iMouse变量的值去做出对应的响应效果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
float disk(vec2 r, vec2 center, float radius, vec3 color, inout vec3 pixel)
{

float rtoclength = length(r - center);
float inside = 0.0;
if(rtoclength < radius)
{
//pixel = vec3(clamp(rtoclength, radius / 4.0,radius / 2.0));
inside = 1.0 - smoothstep(radius - 0.005,radius + 0.005, rtoclength);
}
return inside;
}

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{

vec2 r = vec2( fragCoord.xy - 0.5*iResolution.xy );
r = 2.0 * r.xy / iResolution.y;
vec3 backgroundcolor = vec3(iMouse.x / iResolution.x);
vec3 resultcolor = backgroundcolor;
// 这里值得注意的一点是,因为我们把高映射到[-1.0,1.0],但宽是[-aspect, aspect]
// 我们必须把mouse的x也映射到一样的比例才能正确显示在以r为坐标系的位置
vec2 center = 2.0 * vec2(iMouse.xy - 0.5 * iResolution.xy) / iResolution.y;
resultcolor += disk(r, center, 0.3, vec3(1.0,0.0,0.0), resultcolor) * vec3(1.0,0.0,0.0);
fragColor = vec4(resultcolor, 1.0);
}

Final Effect:
ShaderToyMouseInput1
ShaderToyMouseInput2

Random Noise

这一章讲关于OpenGL Shader里随机数的生成,这里还了解的不清晰,暂时只贴出代码和效果,后续会进一步学习修改。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
float disk(vec2 r, vec2 center, float radius, vec3 color, inout vec3 pixel)
{
float rtoclength = length(r - center);
float inside = 0.0;
if(rtoclength < radius)
{
//pixel = vec3(clamp(rtoclength, radius / 4.0,radius / 2.0));
inside = 1.0 - smoothstep(radius - 0.005,radius + 0.005, rtoclength);
}
return inside;
}

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
vec2 p = vec2(fragCoord.xy / iResolution.xy);
vec2 r = vec2( fragCoord.xy - 0.5*iResolution.xy );
r = 2.0 * r.xy / iResolution.y;
vec3 backgroundcolor = vec3(0.0,0.0,0.0);
vec3 resultcolor = backgroundcolor;
float widthratio = iResolution.x / iResolution.y;
vec2 center;
vec2 pos;
for(float i = 0.0; i < 6.0; i++)
{
// 这里有一点要注意,
// 我们需要将随机数映射到正确的宽高映射值才能正确随机显示在整个屏幕上
pos = vec2(2.0 * widthratio * hash(i) - widthratio, 2.0 * hash(i + 0.5) - 1.0);
center = pos;
resultcolor += disk(r, center, hash(i * 5.0 + 10.0) / 5.0, vec3(1.0, 0.0, 0.0), resultcolor) * vec3(1.0, 0.0, 0.0);
}
fragColor = vec4(resultcolor, 1.0);
}

Final Effect:
ShaderToyRandomNumber

更多学习待更新

……

Shader Toy Relative Knowledge

Shader Toy Inputs

ShaderToyInputs

总的来说ShaderToy是一个很好的Shader学习网站,让我们可以在上面方便可视化的测试一些数学算式和算法效果。

文章目錄
  1. 1. Shader Toy Introduction
  2. 2. Shader Toy Study
    1. 2.1. fragColor
    2. 2.2. fragCoord
    3. 2.3. Own Coordinate System
    4. 2.4. Cicle Demo
    5. 2.5. Plasma Effect
    6. 2.6. Texture & Video as Input
    7. 2.7. Mouse Input
    8. 2.8. Random Noise
    9. 2.9. 更多学习待更新
  3. 3. Shader Toy Relative Knowledge
    1. 3.1. Shader Toy Inputs