引用自 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的坐标系,来实现一些非常梦幻的平面效果。