先看一下最终效果
这是二次Bezier曲线
这是三次Bezier曲线
我本人的ShaderToy个人主页链接,有兴趣可以看一下,还有一些RayMarching的内容https://www.shadertoy.com/profile/?show=shadershttps://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);
}