美文网首页
Shadertoy--旋涡效果分析

Shadertoy--旋涡效果分析

作者: 奔向火星005 | 来源:发表于2019-12-26 17:16 被阅读0次

    前言

    Shadertoy是一个神奇的网站,有无数图形学大神分享的用shader写的各种让人匪夷所思的效果,而代码仅仅只有数十行至数百行。虽然很多实现无法直接应用到我们开发的实时渲染上,但分析它们的实现可以让我们理解shader以及带来很多灵感。

    本文分析一下Shadertoy上一个比较通用的涡旋效果,也是个人非常喜欢的一个效果(主要是像写轮眼的神威),当然分析过程纯属个人猜测,不代表原作者思路,原文地址如下:
    https://www.shadertoy.com/view/Ml2GDR
    原效果如下:

    正文

    原文的实现除了扭曲外还加了光照和混合等,为了简化分析,我去掉了一些代码,如下:

    void mainImage( out vec4 fragColor, in vec2 fragCoord ) {
        vec2 uv = fragCoord.xy/iResolution.xy-vec2(.5);
        uv.y *= iResolution.y/iResolution.x;
        
        float dist = length(uv);
        float angle = atan(uv.y,uv.x);
        
        uv = vec2(cos(angle+dist*3.),dist+(iTime*0.2));
    
       fragColor = texture(iChannel0,uv);
    }
    

    另外为了看清特效如何实现,我换了纹理,得到的效果如下:


    xuanwo.gif

    第一步:纹理采样。

    代码如下:

    void mainImage( out vec4 fragColor, in vec2 fragCoord ) {
        vec2 uv = fragCoord.xy/iResolution.xy;
        fragColor = texture(iChannel0,uv);
    }
    

    效果如下:


    第一步先把纹理采样到屏幕上,可以看到一张没有任何扭曲效果的原图。这里需要注意是屏幕空间和纹理空间的概念。如下图,


    代码中的iResolution代表屏幕分辨率(640, 360),fragCoord.xy/iResolution.xy得到的uv的范围就是(0, 1),也就是它在纹理空间中的坐标。

    其实扭曲的原理就是,改变顶点坐标或者纹理坐标。因为Shadertoy中只有像素着色器,我们只能通过改变纹理坐标达到扭曲的效果

    第二步,求出当前像素点距离原点(0, 0)的距离,以及它与x轴的角度。

    代码如下:

    void mainImage( out vec4 fragColor, in vec2 fragCoord ) {
        vec2 uv = fragCoord.xy/iResolution.xy;
    
        float dist = length(uv);    //到原点(0,0)的距离
        float angle = atan(uv.y, uv.x);   //与x轴角度
    
       fragColor = texture(iChannel0,uv);
    }
    

    第三步,改变纹理坐标

    3.1 现在我们稍稍改变一下纹理坐标,看看画面会变成什么样子。

    现在我们把采样坐标uv改成了uv = vec2(cos(angle),0.0)。画面瞬间变得面目全非。我们来仔细分析一下向量vec2(cos(angle), 0.0),我把uv的y置为0.0,什么意思呢?其实就是,在纹理空间中,限定了只在v==0.0的那条线上面进行采样
    那么uv的x,也就是cos(angle)意味着上面呢。意味着,所有在屏幕空间中,angle相同的坐标,它们的颜色是一样的!这就是为什么我们看到渲染的画面是一道道不同角度的直线组成的。如下图:

    3.2 接下来我们再改变一下代码,把uv = vec2(cos(angle),0.0)换成uv = vec2(cos(angle + dist),0.0)。

    画面会变成下面这样:


    把原来的angle变成angle+dist,意味着,在所有angle+dist相同的点,它们的颜色是一样的,我们知道所有angle相同的点连起来是一条直线,那么所有angle+dist相同的点连起来是个什么形状呢?从渲染出来的画面我们看到是一条曲线,那为啥会是以这样的角度弯曲的曲线呢,看下图:


    假设屏幕空间上有两点(图中的橙点和红点),红点的距离为dist1,角度为angle1,橙点的距离为dist2,角度为angle2。假设他们的dist1 + angle1 = dist2 + angle2,如果dist1>dist2,那么angle1就必然小于angle2,也就是距离越大,那么它的角度必然要越小,所以这些点组成的曲线必然是往右下方弯曲的!

    3.3 我们把cos(angle+dist)改回cos(angle),同时把uv = vec2(cos(angle),0.0)的0.0改为uv.y

    渲染出来的画面是这样的:


    前面我们说过,uv = vec2(cos(angle), 0.0)表示在屏幕空间中所有angle相同的点的颜色都是相同的,该颜色就是在纹理空间中u==cos(angle),v==0.0的那一点的颜色。由此推出,uv = vec2(cos(angle), uv.y)意味着,在屏幕空间中所有angle相同的点的颜色,是对映射纹理空间中u==cos(angle),v==uv.v的颜色,如下图:


    通过这个修改,屏幕空间中angle相同的直线上所有点的颜色不再是一个单一的颜色,而是采样了纹理贴图中的某一条u==cos(angle)的直线上的颜色,因此渲染出来的画面已经可以看出原图的内容了,只不过是被拉伸了。

    3.4 把uv = vec2(cos(angle), uv.y)改成uv = vec2(cos(angle), dist)

    渲染出来的画面是这样的:


    那么,对于屏幕空间中angle的直线,仍然是采样纹理贴图中u==cos(angle)的直线上的颜色,只不过采样的步长变了,如下:


    我们可以看到uv = vec2(cos(angle), dist)时渲染的画面没有uv = vec2(cos(angle), uv.y)拉伸的那么厉害,还需要注意的是,当uv == vec2(cos(angle), dist)的dist大于1.0时,采样坐标就超出了纹理贴图的范围,由于纹理是wrap设置的是repeat的,因此会重复采样。


    3.5 把前面的效果都组合起来,将uv设为uv = vec2(cos(angle + dist * 3.), dist)

    (dist *3中的3.是调整弯曲的程度)。得到下面的渲染效果:


    第四步, “转”起来

    对uv坐标加入时间变化因素,例如设为uv = vec2(cos(angle+dist3.), dist+(iTime0.2));那么对于每一个屏幕空间中的像素点,它们采样的纹理贴图的坐标不再是固定不变的,采样坐标的uv.v会随着iTime不断增大,超出1.0后会以wrap==repeat规则继续循环采样。可以看到下面的动效:

    xuanwo3.gif
    第五步,调整纹理坐标
    5.1 渲染椭圆

    现在我们有的是一个椭圆形的四分之一,如果我们想要渲染出完整的椭圆形该如何做呢?首先我们把扭曲效果先屏蔽,然后调整一下uv坐标,得到如下效果:


    我们只在代码第一句,将uv坐标减去vec2(0.5, 0.5),它的效果是对采样坐标平移了(-0.5, -0.5),那么在纹理空间中采样的位置就会改变,如下:


    蓝色虚线可以认为是我们的屏幕,我们看到屏幕的右上角四分之一采样的是纹理的左下角的四分之一,而其他四分之三并没有在纹理贴图(0,0)~(1,1)的范围,但是他们会以wrap==repeat的规则采样。

    我们再把扭曲的效果加回来(把uv = vec2(cos(angle+dist3.),dist+(iTime0.2));前面的注释去掉),就可以得到如下动效:

    5.2 渲染圆

    现在的效果已经非常接近我们开头的效果了,只是开头呈现的旋涡是圆形的,而我们现在的是椭圆形的,这是由于屏幕的宽高比不等于1造成的。如果想要得到圆形的旋涡效果,只需要把uv的宽高逼调整一下即可,加入uv.y *= iResolution.y/iResolution.x;这句,得到


    相关文章

      网友评论

          本文标题:Shadertoy--旋涡效果分析

          本文链接:https://www.haomeiwen.com/subject/pcyinctx.html