shader 算法 分析

引用自 http://cn.cocos2d-x.org/tutorial/show?id=2552



几何变换

在shader中经常看到如下的代码:

1
2
3
vec2 uv = gl_FragCoord.xy / resolution.xy;   //A;
vec2 pos = uv * 2.0 - 1.0;  //B;
pos.x *= resolution.x / resolution.y;  //C

gl_FragCoord表示的是fragment shader(以后使用FS来表示)中的坐标,分别是x、y、z、1/w;

resolution表示的是显示尺寸的大小,一般是窗口屏幕的大小。

因此A操作得到的值是[0.0, 1.0]之间。这和texture坐标是一样的,所以使用uv来表示。(当然,你可以使用别的符号来表示)

B操作,把uv从[0.0, 1.0]映射到[-1.0, 1.0]之间。对opengl熟悉的同学,知道这是NDC的坐标。可以理解为坐标NDC化。

这样A和B变完成了从像素坐标到DNC坐标的变换。(这2个变换不是必须的)C主要是为了调整纵横比。

如果使用uv坐标来表示,视图窗口的左下角为笛卡尔坐标系的原点,u即x轴, v为y轴。x、y的范围在[0.0, 1.0]之间。如果使用NDC(pos)坐标来表示,视图窗口的中央为笛卡尔坐标系的原点。

下面以NDC坐标为例,在圆心centre(0.5, 0.0)的位置画一个半径为0.3的圆:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// in main
     c = drawCir(pos, centre, 0.3);
     gl_FragColor = vec4(c);
 
// draw cir
     drawCir(vec2 pos, vec2 centre,  float  radius)
    {
         float  dist = distance(pos, centre);
         
         if  (dist < radius)
             return  1.0;
         else
             return  0.0;
    }

这样变可以在DNC坐标中,画出位置在centre,半径为r的圆。虽然这个圆有点难看,你会看到非常明显的锯齿。造成锯齿的原因是刚好落在圆环(圆周上)的像素点和刚好落在圆之外的像素点值落差太大。即前者为1.0,或者是0.0;好比电平一样,一下子从1.0下降到了0.0,中间没有过渡带。直接在屏幕上一条直线也是一样,会看到很明显的锯齿。

因此要对它做平滑处理:

1
2
3
4
5
6
7
8
9
10
// draw cir
     drawCir(vec2 pos, vec2 centre,  float  radius)
    {
         float  dist = distance(pos, centre);
         
         if  (dist < radius)
             return  1.0-dist/radius;
         else
             return  0.0;
    }

这样就好看多了,但是,总觉得不是很亮,而且明到暗的过渡带太长了。

1
2
3
4
5
6
7
8
9
10
// draw cir
     drawCir(vec2 pos, vec2 centre,  float  radius)
    {
         float  dist = distance(pos, centre);
         
         if  (dist < radius)
             return  1.0 -  pow (dist/radius, 3.0); //开方我的理解是比如dis/radius 为1/2的话,开3次方就是1/8,更小了,过渡带比原来变短了。
         else
             return  0.0;
    }

这样变亮了很多,但是如果希望控制明暗的阀值和下降的快慢,可以这么写:

1
2
3
4
5
6
7
8
9
10
// draw cir
     drawCir(vec2 pos, vec2 centre,  float  radius)
    {
         float  dist = distance(pos, centre);
         
         if  (dist < radius)
             return  smoothstep(begin, end, 1.0 -  pow (dist/radius, 3.0)); //hermite插值
         else
             return  0.0;
    }

begin和end控制阀值,两者之差控制下降的快慢。


好的,现在希望圆画任意一个位置,好比(0.5,0.5)或者说希望圆随着时间的变化出现在不同的位置,或者围绕原点做圆周运动。因此我们需要对其做平移变换。

我们选用矩阵来实现平面几何的坐标变换。可以通过旋转坐标系来实现,也可以通过旋转圆在坐标系中的位置来实现。

平面旋转:

1
2
3
4
5
6
7
8
9
vec2 rota(vec2 pos,  float  rad)
{
     float  c =  cos (rad);
     float  s =  sin (rad);
     
     mat2 m = mat2(c, -s, s, c);
     
     return  pos*m;
}

具体推导过程,自己推导下,有利于加深对矩阵和坐标系的理解。建议采用极坐标系来推导,推导会非常简洁明了。

a、通过旋转NDC坐标系来达到效果(使圆逆时针旋转45度):

1
2
3
4
// in main
     pos = rota(pos, -45.0);
     c = drawCir(pos, centre, 0.3);
     gl_FragColor = vec4(c);

b、通过旋转圆心来达到效果:

1
2
3
// in main
     c = drawCir(pos, rota(centre, 45.0), 0.3);
     gl_FragColor = vec4(c);

细心的就可以看到前者旋转了-45度,后者是+45度。

在平面几何里,我们一般都是采用b来进行旋转的。很少会旋转坐标系。这2者的关系好比坐火车时,你可以选用火车作为参考系(地面往后面退,故为负值),也可以使用地面来做参考系(火车在前进,故为正)。但我们都习惯使用地面来做参考系。所以使用坐标轴来旋转的时候,总感觉别扭。但使用a的好处在于,如果场景里有很多物体,都需要做相同的旋转时,只需要旋转一次,便可以了。而后者就要对每一个做旋转。当然,这只是一个比较直观的例子,随着深入,你会发现旋转坐标系更合适,往往可以做出意想不到的效果来。这就是所谓的“空间扭曲”。例如你对x、y轴做一个sin函数的变换。相当于你把空间扭曲成了一个sin周期的函数空间。你可以使用一些特别的sampling函数,扭曲shader的坐标系,来实现一些非常梦幻的平面效果。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值