ShaderToy代码解析

shaderToy是个什么东西呢?shaderToy是一个使用WEBGL实时渲染shader效果的网站,语言为GLSL。

shaderToy网址:https://www.shadertoy.com/

看这篇文章之前推荐先熟悉shaderToy的基本页面以及一些基本概念,虽然我也会尽可能全面得解释。

本文章选用shaderToy作者udosc在2023-03-07日发布的shader效果Cairo Tiling 2进行讲解,如有侵权请联系我。

作者作品链接:https://www.shadertoy.com/view/DdVGR1

为了讲解方便我做了一些修改,去除了一些功能并做迭代以降低阅读门槛,第一次的功能是这个样子。区块颜色会随时间发生变化。

纯净版:

vec3 CalColor(vec2 p) {
    p = 0.5 + 0.5*fract(p*vec2(0.5, 0.5));
    return abs(vec3(fract(p.x+p.y), fract(p.y-p.x), fract(p.y)));
}

vec2 Block(vec2 uv) {
    vec2 id = floor(uv);
    return vec2(id);
}

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
    vec2 uv = 3.0 * fragCoord/iResolution.xy;

    vec3 col = vec3(0);
    vec2 region = Block(uv);

    vec3 tile_color =  0.5 + 0.5*sin(CalColor(region.xy)*5.0+iTime*0.5);

    col += tile_color;
    fragColor = vec4(col,1.0);
}

解析版:

vec3 CalColor(vec2 p) {
/*只是一个颜色计算函数,fract是内置函数用于取小数*/
    p = 0.5 + 0.5*fract(p*vec2(0.5, 0.5));
    return abs(vec3(fract(p.x+p.y), fract(p.y-p.x), fract(p.y)));
}

vec2 Block(vec2 uv) {
    vec2 id = floor(uv);
/*floor函数是shaderToy的内置函数,向下取整,因为我们的uv是(0,0)到(3,3),所以id会分块为9个区域,返回值分别为(0,0),(0,1),(0,2),(1,0),(1,1),(1,2),(2,0),(2,1),(2,2)九个区域,可以理解为每一块区域都是左下(0,0)右上(1,1)的uv*/
    return vec2(id);
}

/*这个是shaderToy的入口方法,有两个参数,fragCoord前面有一个in,这表示输入的坐标值,fragColor前面有一个out,这表示输出的颜色值。
对于一个着色器来说,他的工作就是输出对应坐标的颜色,每一个坐标都输出一个颜色就组成了我们看到的图像,比如我的屏幕分辨率是1920*1080,那么我需要输出1920*1080个像素值,而着色器调用一次计算一个坐标的颜色,那也就是渲染一帧需要调用这个着色器方法1920*1080次以获得1920*1080个像素的颜色。*/
/*vec4以及vec2是常用的数据结构,2就是有xy两个值,4就是有四个值*/
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
    vec2 uv = 3.0 * fragCoord/iResolution.xy;
/*shaderToy有一些输入值,可以在帮助中查看,比如iResolutions就是窗口的分辨率,就是说它不需要你自己赋值,他自己就会拿到对应的值,就拿刚才的例子如果我的窗口是1920*1080,那么iResolution.x就是1920,iResolution.y就是1080,而iResolution.xy的表达就是vec2类型*/
/*这一句的具体意义可以一步一步分析
fragCoord/iResolution.xy还记得这两个变量代表的值吗,要求取颜色的坐标除去窗口的分辨率,一般在glfw创建的window中屏幕的左上角为(0,0),右下角为最大值比如(1920,1080),在这里传入的坐标值已经是左下角为(0,0),右上角为最大值比如(1920,1080),正好符合uv在左下最小右上最大的原则,所以直接除去分辨率可以获得左下比如(0,0)右上比如(1,1)的uv值,所以之后的uv值不论是x还是y都在【0,1】的区间中。这一步可以理解为映射你要计算颜色的坐标到一个边长为1的正方形里,而*3就是映射到一个边长为3的正方形里,方便后面的计算。*/
    vec3 col = vec3(0);
/*这里只是创建一个变量*/
    vec2 region = Block(uv);
/*调用Block方法,这个方法主要作用是分块,具体操作移步函数内部,用region接收返回的区域*/  
    vec3 tile_color =  0.5 + 0.5*sin(CalColor(region.xy)*5.0+iTime*0.5);
/*这里分解说明
sin(CalColor(region.xy)*5.0+iTime*0.5)就是把刚才的区域传进颜色计算函数,5.0的乘数只是为了调整颜色,随后加上iTime添加时间颜色变化,0.5只是时间影响大小的参数
因为sin值区间是【-1,1】而颜色空间是【0,1】,所以需要*0.5+0.5以映射到【0,1】区间求得颜色*/
    col += tile_color;
/*赋值颜色输出到屏幕*/
    fragColor = vec4(col,1.0);
}

如果你看懂了上面的例子,我们先不急着在此之上添加功能,我们先学习一下另一个功能,请耐心看下去

迭代后是这样子的,可惜这里没找到怎么放视频,各位最好复制贴到shadertoy自己运行看效果

纯净版:

vec3 CalColor(vec2 p) {
    return vec3(fract(p.x+p.y), fract(p.y-p.x), 0.0);
}

vec2 Block(vec2 uv, float time) {

    vec2 pointSet = vec2(0.0, 0.0);
    
    uv -= vec2(0.5, 0.5);
    vec2 newPoint = abs(uv);
    
    float angle = (0.75 + 0.25*sin(time))*3.1415;
    vec2 n = vec2(sin(angle), cos(angle));
    float d = dot(newPoint, n);
    
    if(d > 0.0)
        pointSet.x = uv.x;
    else
        pointSet.y = uv.y;
        
    return vec2(pointSet);
}

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
    vec2 uv = fragCoord/iResolution.xy;
    vec3 col = vec3(0);
    
    vec2 point = Block(uv, iTime);
    
    vec3 tile_color = CalColor(point);
    
    col += tile_color;

    fragColor = vec4(col,1.0);
}

解析版:要看懂这个可能要花些时间,我也会做一些配图尽量帮助读者理解

vec3 CalColor(vec2 p) {
/*现在你不光要知道他是个计算颜色用的函数,你还要知道他的特性,对于四个坐标轴,他们的xy坐标分别是0+,-0,0-,+0,带入return这个式子可得(+,+,0),(-,+,0),(-,-,0),(+,-,0)即这个函数对四个坐标轴所得出的颜色做了区分,主要是符号不一样会带来很大视觉的变化*/
    return vec3(fract(p.x+p.y), fract(p.y-p.x), 0.0);
}
/*这个函数引入了时间变量,用于处理颜色的变化*/
vec2 Block(vec2 uv, float time) {
/*初始化一个00的坐标之后用*/
    vec2 pointSet = vec2(0.0, 0.0);
/*把uv的00移动到中心,因为原本左下角为00右上角为11,而现在左下角为-0.5-0.5,中心为00*/
    uv -= vec2(0.5, 0.5);
    vec2 newPoint = abs(uv);
/*计算坐标的绝对值,即第一象限不变,第二象限变为-x,y依此类推,这么做的目的主要是为了在之后的计算中可以减少逻辑判断,只处理第一象限的逻辑即可*/

/*这里用时间计算角度,把时间变化换算成在【Π/2,Π】*/    
    float angle = (0.75 + 0.25*sin(time))*3.1415;
/*使用这个角度求出向量*/
    vec2 n = vec2(sin(angle), cos(angle));
/*这里是关键了,一定要理解这里,点乘的基础知识在这里就不提了,结论在【0,Π】里当两个向量的夹角为钝角则结果为负数,反之为正数*/
    float d = dot(newPoint, n);
/*根据d的正负来决定这个点落在哪一个坐标轴上,钝角就落在y轴,锐角落在x轴,这里看下面的配图会更好理解*/
    if(d > 0.0)
        pointSet.x = uv.x;
    else
        pointSet.y = uv.y;
        
    return vec2(pointSet);
}

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
/*这里面比较简单就不特别说明了*/
    vec2 uv = fragCoord/iResolution.xy;
    vec3 col = vec3(0);
    
    vec2 point = Block(uv, iTime);
    
    vec3 tile_color = CalColor(point);
    
    col += tile_color;

    fragColor = vec4(col,1.0);
}

所以结果就是假设n向量现在是这个位置的话,涂了对应颜色的区块会全部落在对应颜色的坐标轴上,

因为n向量会随着时间在【Π/2,Π】变化所以区块面积也会随时间发生变化,就造就了我们所看到的效果

再进行一次迭代,这次把两个效果融合在一起但添加其他效果,效果是这样的

观察每一分块的效果其实和之前的那一块是一样的。

vec3 CalColor(vec2 p) {
    p = 0.5 + 0.5*fract(p*vec2(0.5, 0.5));
    return abs(vec3(fract(p.x+p.y), fract(p.y-p.x), fract(p.y)));
}

vec2 Block(vec2 uv, float time) {
    vec2 id = floor(uv);
    float check = mod(id.x+id.y, 2.0);
    
    vec2 pointSet = vec2(0.0,0.0);
    
    uv = fract(uv)-vec2(0.5, 0.5);
    vec2 newPoint  = abs(uv);
    
    if(check == 1.0) newPoint = newPoint.yx;
    
    float angle = (0.75 + 0.25*sin(time))*3.1415;
    vec2 n = vec2(sin(angle), cos(angle));
    float d = dot(newPoint -0.5, n);
    
    if(d*(check-0.5) < 0.0)
        pointSet.x += sign(uv.x)*0.5;
    else
        pointSet.y += sign(uv.y)*0.5;
        
    return vec2(pointSet);
}

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
    vec2 uv = 3.0 * fragCoord/iResolution.xy;
    vec3 col = vec3(0);
    
    vec2 region = Block(uv, iTime);
    /*这里添加了时间变量使得全局颜色随时间变化+iTime*0.5*/
    vec3 tile_color =  0.5 + 0.5*sin(CalColor(region.xy)*5.0+iTime*0.5);
    
    col += tile_color;

    fragColor = vec4(col,1.0);
}

接下来是最终迭代,效果是这样的,还是推荐各位黏贴到shadertoy中自己查看效果

纯净版:

vec3 CalColor(vec2 p) {
    p = 0.5 + 0.5*fract(p*vec2(0.5, 0.5));
    return abs(vec3(fract(p.x+p.y), fract(p.y-p.x), fract(p.y)));
}

vec2 Block(vec2 uv, float time) {
    vec2 pointSet = floor(uv);
    float check = mod(pointSet.x+pointSet.y, 2.0);
    
    uv = fract(uv)-vec2(0.5, 0.5);
    vec2 newPoint  = abs(uv);
    
    if(check == 1.0) newPoint = newPoint.yx;
    
    float angle = (0.75 + 0.25*sin(time))*3.1415;
    vec2 n = vec2(sin(angle), cos(angle));
    float d = dot(newPoint -0.5, n);
    
    if(d*(check-0.5) < 0.0)
        pointSet.x += sign(uv.x)*0.5;
    else
        pointSet.y += sign(uv.y)*0.5;
        
    return vec2(pointSet);
}

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
    vec2 uv = 3.0 * fragCoord/iResolution.xy;
    vec3 col = vec3(0);
    
    vec2 region = Block(uv, iTime);
    
    vec3 tile_color =  0.5 + 0.5*sin(CalColor(region.xy)*5.0+iTime*0.5);
    
    col += tile_color;

    fragColor = vec4(col,1.0);
}

解析版:当你看懂了上面的那两个,现在以它的难度应该不至于无从分析

/*这个函数还是老样子*/
vec3 CalColor(vec2 p) {
    p = 0.5 + 0.5*fract(p*vec2(0.5, 0.5));
    return abs(vec3(fract(p.x+p.y), fract(p.y-p.x), fract(p.y)));
}
/*这里有些不一样了,因为他要在分块的同时做每一块的坐标轴映射(第二次迭代中的操作)*/
vec2 Block(vec2 uv, float time) {
    vec2 pointSet = floor(uv);
/*这个变量是为了对奇偶位块做出区分,也是这次迭代的关键*/
    float check = mod(pointSet.x+pointSet.y, 2.0);
    
    uv = fract(uv)-vec2(0.5, 0.5);
    vec2 newPoint  = abs(uv);

    if(check == 1.0) newPoint = newPoint.yx;
    /*这些应该已经比较熟悉了*/
    float angle = (0.75 + 0.25*sin(time))*3.1415;
    vec2 n = vec2(sin(angle), cos(angle));
    float d = dot(newPoint -0.5, n);
    
    if(d*(check-0.5) < 0.0)
        pointSet.x += sign(uv.x)*0.5;
    else
        pointSet.y += sign(uv.y)*0.5;
        
    return vec2(pointSet);
}

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
    vec2 uv = 3.0 * fragCoord/iResolution.xy;
    vec3 col = vec3(0);
    
    vec2 region = Block(uv, iTime);
    
    vec3 tile_color =  0.5 + 0.5*sin(CalColor(region.xy)*5.0+iTime*0.5);
    
    col += tile_color;

    fragColor = vec4(col,1.0);
}

至此完结,在原作者的作品中还有许多细节,虽然我为了讲解删掉了,但如果各位有兴趣可以去作者的网址学习。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值