UV变换

基于上一篇文章我们画好的长方形,我们开始进行UV变换相关的实践。

注意:UV变换的相关代码要写在SDF函数调用之前

平移

总之先尝试给uvxy加上一些值:

    uv.x += .5;
    uv.y += .5;

为什么加上值会往左下角移动呢?

我们把uv坐标分布的情况用fragColor画出来就知道了:

之前位于中间的原点值是(0,0),现在则变成了(0.5,0.5),那么上一个(0,0)则是跑到了左下角的如图位置,而SDF函数输入的坐标值的原点值是(0,0),正好对应左下方的那个点,因此图形才会整体往左下方移动。

简单来说:要确认SDF图形位置的变化,要看目前(0,0)这个点的位置变化。

相反,如果要往右上方平移的话,就用减法就行了,顺带一提可以简化为:

  uv -= vec2(.5);

缩放

给UV的x和y坐标做乘运算以及除运算,可以实现缩放效果:

与上面的加减法差不多的,原理一致,不赘述了,贴一张图在这里:

翻转

要实现翻转我们需要找一个三角形的SDF函数,sdEquilateralTriangle

float sdEquilateralTriangle( in vec2 p, in float r )
{
    const float k = sqrt(3.0);
    p.x = abs(p.x) - r;
    p.y = p.y + r/k;
    if( p.x+k*p.y>0.0 ) p = vec2(p.x-k*p.y,-k*p.x-p.y)/2.0;
    p.x -= clamp( p.x, -2.0*r, 0.0 );
    return -length(p)*sign(p.y);
}

...
    float d = sdEquilateralTriangle(uv, .5);
    float c = smoothstep(0., .02, d);
    fragColor = vec4(vec3(c), 1.);
...

我们得到的是一个三角形,要实现翻转,我们只需将uv的y乘上-1就行了。

简单的逻辑原理图是这样的:


之前的虚线三角形的上顶点是(0.,0.5),乘上-1 后就变成了(0.,-0.5),也就变成了实际三角形的下顶点。

旋转

这个效果有点复杂,但是好在,有已经封装好的库,2D相关的旋转函数我们直接拿来用:

mat2 rotation2d(float angle){
    float s=sin(angle);
    float c=cos(angle);

    return mat2(
        c,-s,
        s,c
    );
}

vec2 rotate(vec2 v,float angle){
    return rotation2d(angle)*v;
}

我们可以简单地讲解一下这个函数的意义,有一点需要提前了解:在数学和计算机图形学中,二维平面上的旋转通常是默认逆时针方向的。这个约定基于右手坐标系的定义,在这种坐标系中,正角度表示逆时针旋转。


这其实相当于简单的复习了一下学生时代的知识。所以上面的旋转函数的实现就相当于是:

通过二维矩阵的方式对uv进行乘法,得到的就是旋转之后的新的坐标。

另外我们在进行旋转操作时经常会用到PI这个常量,代表 180 度。

  const float PI = 3.14159265359;
  uv = rotate(uv, PI / 2.);

最后会变成:

在之前的文章中我们提到了iTime的变量,表示Shader从开始到现在执行所经过的时间。

我们可以以此来做一个简单的旋转的动画效果:

uv = rotate(uv, iTime)

重复

我们来认识一个新的内置内涵fract,它的作用很简单,就是获取一个数的小数部分。

比如我们fract(1145.14),那我们得到的值就是.14

来自2024年7月1日的修正:


因此,fract函数对于负数返回的是其小数部分作为正数,这是由floor函数的特性决定的。

这个方法有什么用呢?我们来试试

用上图的代码运行出来的结果也在右边显示出来了。为什么会这样?

我稍微做一下标记:

将坐标的值乘以2,我图中给出了几个象征意义的关键点,大于1的部分的点的整数部分都被去掉了,剩下的就是他们的小数点部分,这样其实就可以达成重复的效果。

那我们重新回到三角形

来咯,四个小三角。怎么实现的,不用再赘述了吧。注意一点:我是把uv先乘了2.之后,再进行的居中和适应画布。这样就可以得到四个均等的区域,并且大家都是居中对齐的啦。

镜像

关于uv的变换,我们最后再来认识一个函数吧,大家高中有可能听说过但是大学经常会用到的:abs。它的作用是获取一个数的绝对值。

函数图像长这样:

由此可以看出,两个函数都是基于对应的变量轴对称,由此可以得出结论:这个函数具有“对称性”

那我们来举个例子:

这是镜像之前的:

这个是镜像之后的:

非常明显的差距,原本UV坐标的左下角的部分是负数,现在全部镜像翻转为了正数,大小还和x轴对称的值一样。

那我们获得一个菱形也很简单:三角形翻转一下不就完事儿了

SDF图形变换

除了UV之外,SDF本身的形状也是可以改变的,变形函数现阶段咱们可以直接从网上搜罗到,搜不到满足不了需求的,也可以使用AI嘛不是。

咱们还是从之前的图形学大佬iq的博客里拿下来一些函数来用,比如:

float onRound (in float d, in float r) {
    return d -r;
}

float opOnion (in float d, in float r) {
    return abs(d) - r;
}

onRound是“圆角”操作,能让图形的角变成圆角

opOnion是“镂空”操作,能挖空图形中间的部分

SDF布尔运算

SDF函数本身能绘制出很多的形状,但是也是有限的,我们想画一个梯形,可以用sdTrapezoid ,但是如果我们想画一个类似于人的骨头关节的那种呢?没有对应的SDF函数是不是就不能画了?NoNoNo,我们可以采用大家都会的
并,交,差运算

同样的,如果自身数学基础不好,没法自己推导的话,可以从Iq大佬的博客copy一份,或者寻求AI的帮助。新时代了,要充分结合AI创造出自己的优势区间

float opUnion(float d1,float d2)
{
    return min(d1,d2);
}

float opIntersection(float d1,float d2)
{
    return max(d1,d2);
}

float opSubtraction(float d1,float d2)
{
    return max(-d1,d2);
}

虽然是copy过来的,但是还是简单的解释一下吧:

  • opUnion:
    实现两个形状的并集,也就是结合两个图形,形成一个新的图形。
    原理:对于每个点,取两个图形中,距离最近的那个。

    • 如果一个点在形状A内部或形状B内部,它就属于新形状。
    • 取较小的距离 min(d1, d2) 就实现了这个效果。

  • opIntersection::实现两个形状的交集,即只保留两个形状共同的部分。
    原理:对于每个点,取两个形状中距离最大的那一个。

    • 如果一个点同时在形状A内部和形状B内部,它才属于新形状。
    • 取较大的距离 max(d1, d2) 就实现了这个效果。

  • opSubtraction::实现两个形状的差集,即从第二个形状中减去第一个形状。
    原理:对于每个点,取第一个形状的负距离和第二个形状的距离中较大的那个。

    • 如果一个点在形状A内部但不在形状B内部,它就属于 新形状。
    • 取 max(-d1, d2) 就实现了这个效果。
    • 没什么好说的,你就可以理解成把d1的内容取个反,比如一个正方形区域,由一个圆形和其余部分组成,一开始d1是那个圆,但是取负处理之后,就成为了剩余的部分,然后这个差集就是把剩余的部分和第二个参数取了一个交集,就是上面我们说到的那个max,应该豁然开朗了吧?

这是并集运算示例:

这是交集运算示例:

这是差集运算示例:

平滑效果

布尔运算还有一种另外的版本:(smooth),能够产生一种更加有机的结果,也即是“粘稠”的感觉。我们可以直接从iq大佬的博客上拿一些示例下来,之后了解的更深了,我们也可以自己写一些SDF函数:

float opSmoothUnion( float d1, float d2, float k )
{
    float h = clamp( 0.5+0.5*(d2-d1)/k, 0.0, 1.0 );
    return mix( d2, d1, h ) - k*h*(1.0-h);
}

float opSmoothSubtraction( float d1, float d2, float k )
{
    float h = clamp( 0.5-0.5*(d2+d1)/k, 0.0, 1.0 );
    return mix( d2, -d1, h ) + k*h*(1.0-h);
}

float opSmoothIntersection( float d1, float d2, float k )
{
    float h = clamp( 0.5-0.5*(d2-d1)/k, 0.0, 1.0 );
    return mix( d2, d1, h ) + k*h*(1.0-h);
}

可以看看这些函数实现的效果:

  • 并集:
  • 交集:
  • 差集:
最后修改:2024 年 08 月 14 日
收款不要了,给孩子补充点点赞数吧