ShaderToy上模拟二次三次Bezier曲线的生成

先看一下最终效果

                                                                              这是二次Bezier曲线

                                                                           这是三次Bezier曲线

我本人的ShaderToy个人主页链接,有兴趣可以看一下,还有一些RayMarching的内容https://www.shadertoy.com/profile/?show=shadersicon-default.png?t=M4ADhttps://www.shadertoy.com/profile/?show=shaders

其实光看动图就能够看明白这个曲线上的点都是由多条线段多次插值得到的

首先把我自己使用的画点函数和画线函数展示一下


//画点函数,uv,即屏幕上任意一点,p为需要绘制的点,size.x是点的半径,
//size.y是边缘模糊百分比,color是点颜色
vec3 DrawPoint(vec2 uv,vec2 p,vec2 size,vec3 color)
{
    float d = length(uv-p);
    d = smoothstep(size.x,size.x-size.y*size.x,d);
    
    return d*color;
}
//length()函数,顾名思义,计算两个点的距离
smoothstep()详解如下图

 

所以我可以通过控制size.x以及size.y来控制点的大小,并且由于是smoothstep函数而不是step函数,所以这个点圆是光滑过渡的,而不是step的直接从0变成1。

再然后是画线函数

//画线函数,p为uv,即屏幕上任意一点,ab为线段两端点,size.x是线段宽,
//size.y是线段边缘模糊百分比,color是线段颜色
vec3 DrawLine(vec2 uv,vec2 a,vec2 b,vec2 size,vec3 color)
{
    vec2 ap = uv-a;
    vec2 ab = b-a;
    
    float t = dot(ap,ab)/dot(ab,ab);//点p在线段上投影占ab的百分比,不理解可以看后面的图解
    t = clamp(t,0.0,1.0);//如果点p到ab做垂线,垂足要是不在ab线段上就截断在ab线段上
    
    //ap-ab*t表示点p-点p到线段ab的垂点,即d是点p到线段的距离
    float d = length(ap-ab*t);
    float s = smoothstep(size.x,size.x-size.y*size.x,d);
    
    return s*color;
}

 

 d = length(ap-ab*t),简单来说就是点p到垂足(点p到线段ab的垂足)的距离,不过因为点p到线段ab的垂足可能在a的右上方,也可能在b的左下方,所以用clamp来将垂足截断到a,b两个点上。最后乘以线段颜色color

既然画点和线段的函数都OK了,那么剩下的内容就不成问题了

//首先这是main函数里的全部代码,但不要急,我们一部分一部分看
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
    vec2 uv = fragCoord/iResolution.y;
    float w = iResolution.x/iResolution.y;

    vec3 col = vec3(0.0f);
    
    if(true) //为True画二次Bezier曲线
    {
        //Draw Three Point,三个控制点
        vec2 a = vec2(0.1*w,0.15);
        vec2 b = vec2(0.5*w,0.8);
        vec2 c = vec2(0.8*w,0.25);

        col += DrawPoint(uv,a,vec2(0.015,0.1),vec3(1.0));
        col += DrawPoint(uv,b,vec2(0.015,0.1),vec3(1.0));
        col += DrawPoint(uv,c,vec2(0.015,0.1),vec3(1.0));

        //Draw Two Line,三个控制的形成的两条直线
        col += DrawLine(uv,a,b,vec2(0.005,0.1),vec3(1.0,0.0,1.0));
        col += DrawLine(uv,b,c,vec2(0.005,0.1),vec3(1.0,0.0,1.0));

        float t = fract(iTime/5.0);//Time,每五秒一个循环

        //两条直线上的某一个点,随t的变化而变化(用t值进行插值得到)
        vec2 p1 = mix(a,b,t);
        col += DrawPoint(uv,p1,vec2(0.015,0.1),vec3(1.0));
        vec2 p2 = mix(b,c,t);
        col += DrawPoint(uv,p2,vec2(0.015,0.1),vec3(1.0));

        //画出p1与p2的连线
        col += DrawLine(uv,p1,p2,vec2(0.005,0.1),vec3(1.0,1.0,0.0));

        //同样用t在直线p1与p2直接进行插值得到的点就是Bezier曲线上的点
        vec2 p = mix(p1,p2,t);
        col += DrawPoint(uv,p,vec2(0.015,0.1),vec3(1.0));

        //用相同的方法画出Bezier曲线用于对比
        col += Bezier(uv,a,b,c,vec2(0.005,0.1),vec3(1.0));
    }
    else  //否则画三次Bezier曲线
    {
        //Draw Four Point,四个控制点
        vec2 a = vec2(0.1*w,0.15);
        vec2 b = vec2(0.3*w,0.85);
        vec2 c = vec2(0.65*w,0.8);
        vec2 d = vec2(0.8*w,0.25);

        col += DrawPoint(uv,a,vec2(0.015,0.1),vec3(1.0));
        col += DrawPoint(uv,b,vec2(0.015,0.1),vec3(1.0));
        col += DrawPoint(uv,c,vec2(0.015,0.1),vec3(1.0));
        col += DrawPoint(uv,d,vec2(0.015,0.1),vec3(1.0));

        //Draw Three Line,四个控制的形成的三条直线
        col += DrawLine(uv,a,b,vec2(0.005,0.1),vec3(1.0,0.0,1.0));
        col += DrawLine(uv,b,c,vec2(0.005,0.1),vec3(1.0,0.0,1.0));
        col += DrawLine(uv,c,d,vec2(0.005,0.1),vec3(1.0,0.0,1.0));

        float t = fract(iTime/5.0);//Time,每五秒一个循环

        //三条直线上的某一个点,随t的变化而变化(用t值进行插值得到)
        vec2 p1 = mix(a,b,t);
        col += DrawPoint(uv,p1,vec2(0.015,0.1),vec3(1.0));
        vec2 p2 = mix(b,c,t);
        col += DrawPoint(uv,p2,vec2(0.015,0.1),vec3(1.0));
        vec2 p3 = mix(c,d,t);
        col += DrawPoint(uv,p3,vec2(0.015,0.1),vec3(1.0));

        //画出p1,p2,p3的连线
        col += DrawLine(uv,p1,p2,vec2(0.005,0.1),vec3(1.0,1.0,0.0));
        col += DrawLine(uv,p2,p3,vec2(0.005,0.1),vec3(1.0,1.0,0.0));

        //同样用t在直线p1,p2,p3直接进行插值得到两个点
        vec2 p11 = mix(p1,p2,t);
        col += DrawPoint(uv,p11,vec2(0.015,0.1),vec3(1.0));
        vec2 p12 = mix(p2,p3,t);
        col += DrawPoint(uv,p12,vec2(0.015,0.1),vec3(1.0));
        //画得到的这两个点的曲线
        col += DrawLine(uv,p11,p12,vec2(0.005,0.1),vec3(0.0,1.0,1.0));
        
        //同样用t在直线p11与p12直接进行插值得到的点就是Bezier曲线上的点
        vec2 p = mix(p11,p12,t);
        col += DrawPoint(uv,p,vec2(0.015,0.1),vec3(1.0));
        
        //用相同的方法画出Bezier曲线用于对比
        col += Bezier(uv,a,b,c,d,vec2(0.005,0.1),vec3(1.0));
    }
    
    fragColor = vec4(col,1.0);
}

if里面画的是二次Bezier曲线,else里画的是三次Bezier曲线。区别不大,只是三次曲线需要多画一点

有些坐标的x会乘以w,只是因为屏幕分辨率的宽和高不同,乘以屏幕分辨率的宽高比达到x,y方向都是0-1。

iTime是一个全局变量,它代表着点开始运行之后的时间。fract是一个取余函数,如1.2取余之后等于0.2。所以fract(itTime/5.0)得到的结果就是每五秒t从0到1循环一次

如果是三个控制点两条直线,那么通过用t做系数分别来插值这两条条线段的端点可以得到两个点

然后再通过同样的方法对新得到的这两个点进行插值得到的点p就是这三个控制点所决定的Bezier曲线上的点。

vec2 p1 = mix(a,b,t) 实际代码等于 vec2 p1 = (1-t)*a+t*b;

当t=0的时候p1=a,t=1时,p1=b;也就是说t等于0-1之间时,p1点位于a,b两点之间。这就是插值函数,在hlsl代码中相当于lerp()。

if(true) //为True画二次Bezier曲线
    {
        //Draw Three Point,三个控制点
        vec2 a = vec2(0.1*w,0.15);
        vec2 b = vec2(0.5*w,0.8);
        vec2 c = vec2(0.8*w,0.25);

        col += DrawPoint(uv,a,vec2(0.015,0.1),vec3(1.0));
        col += DrawPoint(uv,b,vec2(0.015,0.1),vec3(1.0));
        col += DrawPoint(uv,c,vec2(0.015,0.1),vec3(1.0));

        //Draw Two Line,三个控制的形成的两条直线
        col += DrawLine(uv,a,b,vec2(0.005,0.1),vec3(1.0,0.0,1.0));
        col += DrawLine(uv,b,c,vec2(0.005,0.1),vec3(1.0,0.0,1.0));

        float t = fract(iTime/5.0);//Time,每五秒一个循环

        //两条直线上的某一个点,随t的变化而变化(用t值进行插值得到)
        vec2 p1 = mix(a,b,t);
        col += DrawPoint(uv,p1,vec2(0.015,0.1),vec3(1.0));
        vec2 p2 = mix(b,c,t);
        col += DrawPoint(uv,p2,vec2(0.015,0.1),vec3(1.0));

        //画出p1与p2的连线
        col += DrawLine(uv,p1,p2,vec2(0.005,0.1),vec3(1.0,1.0,0.0));

        //同样用t在直线p1与p2直接进行插值得到的点就是Bezier曲线上的点
        vec2 p = mix(p1,p2,t);
        col += DrawPoint(uv,p,vec2(0.015,0.1),vec3(1.0));

        //用相同的方法画出Bezier曲线用于对比
        col += Bezier(uv,a,b,c,vec2(0.005,0.1),vec3(1.0));
    }
    else  //否

三次Bezier曲线不过是比二次Bezier曲线需要多插值几次。如二次Bezier曲线,abc三个控制点的两条线段ab,bc用t进行插值获得到两个点p1,p2。然后再用t对p1和p2进行插值,得到的点p就是二次Bezier曲线上的一个点。而三次Bezier曲线,abcd四个控制点的三条线段ab,bc,cd。先用t进行插值,得到p1,p2,p3三个点,然后再用t对p1,p2,p3三个点形成的两条线段进行插值,得到p11,p12。最后再用t对p11和p12形成的线段进行插值得到的点p就是三次Bezier曲线上的点

用上述说的方法把Bezier曲线画出来和点p形成对比

                                                                  二次Bezier曲线函数

//画Beizer曲线,abc为二次Bezier曲线的三个控制点,通过一个个点表示曲线,
//size为距离点的大小以及模糊程度,color为每个点的颜色
vec3 Bezier(vec2 uv,vec2 a,vec2 b,vec2 c,vec2 size,vec3 color)
{
    vec2 p1 = vec2(0.0);
    vec2 p2 = vec2(0.0);
    vec2 p = vec2(0.0);
    vec3 col = vec3(0.0);
    for(float t = 0.0;t<=1.0;t+=0.005)
    {
        p1 = mix(a,b,t);
        p2 = mix(b,c,t);
        p = mix(p1,p2,t);
        col += DrawPoint(uv,p,size,color);
    }
    return col;
}
//
//画Beizer曲线,abcd为三次Bezier曲线的三个控制点,通过一个个点表示曲线,
//size为距离点的大小以及模糊程度,color为每个点的颜色
vec3 Bezier(vec2 uv,vec2 a,vec2 b,vec2 c,vec2 d,vec2 size,vec3 color)
{
    vec2 p1 = vec2(0.0);
    vec2 p2 = vec2(0.0);
    vec2 p3 = vec2(0.0);
    
    vec2 p11 = vec2(0.0);
    vec2 p12 = vec2(0.0);
    
    vec2 p = vec2(0.0);
    vec3 col = vec3(0.0);
    
    for(float t = 0.0;t<=1.0;t+=0.005)
    {
        p1 = mix(a,b,t);
        p2 = mix(b,c,t);
        p3 = mix(c,d,t);
        
        p11 = mix(p1,p2,t);
        p12 = mix(p2,p3,t);
        
        p = mix(p11,p12,t);
        col += DrawPoint(uv,p,size,color);
    }
    return col;
}

                                                                 三次Bezier曲线函数

最后的最后将不同点和线段以及曲线辅以不同的大小和颜色,使得画面更加美观

完整ShaderToy代码

//画线函数,p为uv,即屏幕上任意一点,ab为线段两端点,size.x是线段宽,
//size.y是线段边缘模糊百分比,color是线段颜色
vec3 DrawLine(vec2 uv,vec2 a,vec2 b,vec2 size,vec3 color)
{
    vec2 ap = uv-a;
    vec2 ab = b-a;
    
    float t = dot(ap,ab)/dot(ab,ab);//点p在线段上投影占ab的百分比
    t = clamp(t,0.0,1.0);
    
    //ap-ab*t表示点p-点p到线段ab的垂点,即d是点p到线段的距离
    float d = length(ap-ab*t);
    float s = smoothstep(size.x,size.x-size.y*size.x,d);
    
    return s*color;
}
vec3 DrawPoint(vec2 uv,vec2 p,vec2 size,vec3 color)
{
    float d = length(uv-p);
    d = smoothstep(size.x,size.x-size.y*size.x,d);
    
    return d*color;
}
//画Beizer曲线,abc为二次Bezier曲线的三个控制点,通过一个个点表示曲线,
//size为距离点的大小以及模糊程度,color为每个点的颜色
vec3 Bezier(vec2 uv,vec2 a,vec2 b,vec2 c,vec2 size,vec3 color)
{
    vec2 p1 = vec2(0.0);
    vec2 p2 = vec2(0.0);
    vec2 p = vec2(0.0);
    vec3 col = vec3(0.0);
    for(float t = 0.0;t<=1.0;t+=0.005)
    {
        p1 = mix(a,b,t);
        p2 = mix(b,c,t);
        p = mix(p1,p2,t);
        col += DrawPoint(uv,p,size,color);
    }
    return col;
}
//画Beizer曲线,abcd为三次Bezier曲线的三个控制点,通过一个个点表示曲线,
//size为距离点的大小以及模糊程度,color为每个点的颜色
vec3 Bezier(vec2 uv,vec2 a,vec2 b,vec2 c,vec2 d,vec2 size,vec3 color)
{
    vec2 p1 = vec2(0.0);
    vec2 p2 = vec2(0.0);
    vec2 p3 = vec2(0.0);
    
    vec2 p11 = vec2(0.0);
    vec2 p12 = vec2(0.0);
    
    vec2 p = vec2(0.0);
    vec3 col = vec3(0.0);
    
    for(float t = 0.0;t<=1.0;t+=0.005)
    {
        p1 = mix(a,b,t);
        p2 = mix(b,c,t);
        p3 = mix(c,d,t);
        
        p11 = mix(p1,p2,t);
        p12 = mix(p2,p3,t);
        
        p = mix(p11,p12,t);
        col += DrawPoint(uv,p,size,color);
    }
    return col;
}

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
    vec2 uv = fragCoord/iResolution.y;
    float w = iResolution.x/iResolution.y;

    vec3 col = vec3(0.0f);
    
    if(false) //为True画二次Bezier曲线
    {
        //Draw Three Point,三个控制点
        vec2 a = vec2(0.1*w,0.15);
        vec2 b = vec2(0.5*w,0.8);
        vec2 c = vec2(0.8*w,0.25);

        col += DrawPoint(uv,a,vec2(0.015,0.1),vec3(1.0));
        col += DrawPoint(uv,b,vec2(0.015,0.1),vec3(1.0));
        col += DrawPoint(uv,c,vec2(0.015,0.1),vec3(1.0));

        //Draw Two Line,三个控制的形成的两条直线
        col += DrawLine(uv,a,b,vec2(0.005,0.1),vec3(1.0,0.0,1.0));
        col += DrawLine(uv,b,c,vec2(0.005,0.1),vec3(1.0,0.0,1.0));

        float t = fract(iTime/5.0);//Time,每五秒一个循环

        //两条直线上的某一个点,随t的变化而变化(用t值进行插值得到)
        vec2 p1 = mix(a,b,t);
        col += DrawPoint(uv,p1,vec2(0.015,0.1),vec3(1.0));
        vec2 p2 = mix(b,c,t);
        col += DrawPoint(uv,p2,vec2(0.015,0.1),vec3(1.0));

        //画出p1与p2的连线
        col += DrawLine(uv,p1,p2,vec2(0.005,0.1),vec3(1.0,1.0,0.0));

        //同样用t在直线p1与p2直接进行插值得到的点就是Bezier曲线上的点
        vec2 p = mix(p1,p2,t);
        col += DrawPoint(uv,p,vec2(0.015,0.1),vec3(1.0));

        //用相同的方法画出Bezier曲线用于对比
        col += Bezier(uv,a,b,c,vec2(0.005,0.1),vec3(1.0));
    }
    else  //否则画三次Bezier曲线
    {
        //Draw Four Point,四个控制点
        vec2 a = vec2(0.1*w,0.15);
        vec2 b = vec2(0.3*w,0.85);
        vec2 c = vec2(0.65*w,0.8);
        vec2 d = vec2(0.8*w,0.25);

        col += DrawPoint(uv,a,vec2(0.015,0.1),vec3(1.0));
        col += DrawPoint(uv,b,vec2(0.015,0.1),vec3(1.0));
        col += DrawPoint(uv,c,vec2(0.015,0.1),vec3(1.0));
        col += DrawPoint(uv,d,vec2(0.015,0.1),vec3(1.0));

        //Draw Three Line,四个控制的形成的三条直线
        col += DrawLine(uv,a,b,vec2(0.005,0.1),vec3(1.0,0.0,1.0));
        col += DrawLine(uv,b,c,vec2(0.005,0.1),vec3(1.0,0.0,1.0));
        col += DrawLine(uv,c,d,vec2(0.005,0.1),vec3(1.0,0.0,1.0));

        float t = fract(iTime/5.0);//Time,每五秒一个循环

        //三条直线上的某一个点,随t的变化而变化(用t值进行插值得到)
        vec2 p1 = mix(a,b,t);
        col += DrawPoint(uv,p1,vec2(0.015,0.1),vec3(1.0));
        vec2 p2 = mix(b,c,t);
        col += DrawPoint(uv,p2,vec2(0.015,0.1),vec3(1.0));
        vec2 p3 = mix(c,d,t);
        col += DrawPoint(uv,p3,vec2(0.015,0.1),vec3(1.0));

        //画出p1,p2,p3的连线
        col += DrawLine(uv,p1,p2,vec2(0.005,0.1),vec3(1.0,1.0,0.0));
        col += DrawLine(uv,p2,p3,vec2(0.005,0.1),vec3(1.0,1.0,0.0));

        //同样用t在直线p1,p2,p3直接进行插值得到两个点
        vec2 p11 = mix(p1,p2,t);
        col += DrawPoint(uv,p11,vec2(0.015,0.1),vec3(1.0));
        vec2 p12 = mix(p2,p3,t);
        col += DrawPoint(uv,p12,vec2(0.015,0.1),vec3(1.0));
        //画得到的这两个点的曲线
        col += DrawLine(uv,p11,p12,vec2(0.005,0.1),vec3(0.0,1.0,1.0));
        
        //同样用t在直线p11与p12直接进行插值得到的点就是Bezier曲线上的点
        vec2 p = mix(p11,p12,t);
        col += DrawPoint(uv,p,vec2(0.015,0.1),vec3(1.0));
        
        //用相同的方法画出Bezier曲线用于对比
        col += Bezier(uv,a,b,c,d,vec2(0.005,0.1),vec3(1.0));
    }
    
    fragColor = vec4(col,1.0);
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Go语言(也称为Golang)是由Google开发的一种静态强类型、编译型的编程语言。它旨在成为一门简单、高效、安全和并发的编程语言,特别适用于构建高性能的服务器和分布式系统。以下是Go语言的一些主要特点和优势: 简洁性:Go语言的语法简单直观,易于学习和使用。它避免了复杂的语法特性,如继承、重载等,转而采用组合和接口来实现代码的复用和扩展。 高性能:Go语言具有出色的性能,可以媲美C和C++。它使用静态类型系统和编译型语言的优势,能够生成高效的机器码。 并发性:Go语言内置了对并发的支持,通过轻量级的goroutine和channel机制,可以轻松实现并发编程。这使得Go语言在构建高性能的服务器和分布式系统时具有天然的优势。 安全性:Go语言具有强大的类型系统和内存管理机制,能够减少运行时错误和内存泄漏等问题。它还支持编译时检查,可以在编译阶段就发现潜在的问题。 标准库:Go语言的标准库非常丰富,包含了大量的实用功能和工具,如网络编程、文件操作、加密解密等。这使得开发者可以更加专注于业务逻辑的实现,而无需花费太多时间在底层功能的实现上。 跨平台:Go语言支持多种操作系统和平台,包括Windows、Linux、macOS等。它使用统一的构建系统(如Go Modules),可以轻松地跨平台编译和运行代码。 开源和社区支持:Go语言是开源的,具有庞大的社区支持和丰富的资源。开发者可以通过社区获取帮助、分享经验和学习资料。 总之,Go语言是一种简单、高效、安全、并发的编程语言,特别适用于构建高性能的服务器和分布式系统。如果你正在寻找一种易于学习和使用的编程语言,并且需要处理大量的并发请求和数据,那么Go语言可能是一个不错的选择。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值