ODOShader

ODOS | 一只几把猫的诞生

by ERIN.Z, 2022-01-27


Snipaste_2022-01-27_21-39-56.png 把数据库的编码方式改了,可以发emoji啦😉立即滥用! meow

本篇将涉及在shadertoy中绘制基本几何图形,基础变换与布尔运算,然后用这些函数画一只几把猫(嘘 主要是毛色比较简单)。还是最基础的内容训练!Let's Go 🐈

点·线·面

理论上而言,点运动形成了线,线运动形成了面。但是在图像上,为了让我们能看到点与线,我们其实是绘制了一个小小的圆与窄窄的矩形。 圆,在数学定义上就是到圆心距离等于半径的点构成的集合。根据定义就可以确定像素是否在圆的范围内: float circle( vec2 uv, vec2 p, float r, float blur){ float d = length(uv-p); float c = smoothstep(r,r-blur,d);//AA return c; } 这里用smoothstep进行了简单的抗锯齿处理,看看对比: circle

线

根据残存的一丢丢高中数学知识,我们知道可以用斜率来表示一条直线,且需要注意竖直的情况。而点到直线的距离公式是|kx0-y0+b|/√(k^2+1)......我现查的。 //1点+垂直 直线 float lineVer(vec2 uv, vec2 p,float width, float blur){ width/=2.; uv -= p; return smoothstep(-width-blur,-width,uv.x)smoothstep(width+blur,width,uv.x); } //1点+斜率 直线 float line(vec2 uv, vec2 p, float k, float width, float blur){ width/=2.; uv -= p; float d = abs(kuv.x-uv.y)/sqrt(kk+1.); return smoothstep(-width-blur,-width,d)smoothstep(width+blur,width,d); } 使用这两个函数,就可以画出经过左侧的点垂直与k=1的两条线: Line

如果用两个点来确定直线或线段,这里使用了iq大佬基于sdf的公式来表示线段。因为基于距离,所以也可以拿来画胶囊体~ sdf segment

//2点 直线
float line(vec2 uv, vec2 p1, vec2 p2, float width, float blur){
    width/=2.;blur/=2.;
    vec2 pa = uv-p1,ba = p2-p1;
    float h = dot(pa,ba)/dot(ba,ba);
    float d = length(pa-ba*h);
    return smoothstep(width+blur,width-blur,d);
}
//2点 线段
float segment(vec2 uv, vec2 p1, vec2 p2, float width, float blur){
    width/=2.;blur/=2.;
    vec2 pa = uv-p1,ba = p2-p1;
    float h = clamp(dot(pa,ba)/dot(ba,ba),0.,1.);
    float d = length(pa-ba*h);
    return smoothstep(width+blur,width-blur,d);
}

segment

直线都有了,下一步我们掰弯它! 首先是圆弧。iq大佬的sdArc函数有4个输入,分别是圆心,1/2圆心角(弧度制)的sin与cos,半径,宽度 float sdArc( in vec2 p, in vec2 sc, in float ra, float rb ) { p.x = abs(p.x); return ((sc.yp.x>sc.xp.y) ? length(p-scra) : abs(length(p)-ra)) - rb; } iq-sdArc 其中圆心角的控制是镜像对称的,而我希望可以通过起始点角度-终点角度来控制圆弧,因此修改如下。 //圆弧 a1a2为弧度制 a1<a2 a1,a2∈[0,6.28] float arc(vec2 uv, vec2 o,float r,float a1,float a2, float width, float blur){ uv -=o; width/=2.;blur/=2.; float ap =atan(uv.y/uv.x)+1.57;//求夹角 vec2 p1= vec2(sin(a1)r,cos(a1)r); vec2 p2= vec2(sin(a2)r,cos(a2)*r); float d =(ap>a1&&ap<a2)?(abs(length(uv)-r)): min(length(uv-p1),length(uv-p2)); return smoothstep(width+blur,width-blur,d); } 当然iq大佬不用atan的方法计算量会更小一些! Arc

在这里留一个bezier的坑。累了呜呜,只想画猫...

还要准备一些基础图形,比如猫猫耳朵至少需要一些三角形。 //等边三角形 float triangle(vec2 uv,vec2 o,float a,float blur){ uv -= o; const float k = sqrt(3.); uv.x = abs(uv.x)-a;//轴对称图形,沿y镜像 -a用于控制图形大小 uv.y +=1./ka;//同理控制图形大小 if(uv.x+kuv.y >0.) uv=vec2(uv.x-kuv.y,-kuv.x-uv.y)/2.;//可以理解为先对y对称, //然后旋转120度,目的是斜边与水平边一起处理 uv.x -= clamp( uv.x, -2.0a, 0.0 );//与线段的那个同理,线段内d就是y的值,所以x=0; //线段边缘外则要求到顶点的距离 float d = -length(uv)sign(uv.y); return smoothstep(0.+blur,0.,d); } 改这段码真是费死劲了,还找了帕老师帮忙。我的线代真是只停留在理论阶段😓 Triangle 以及最基础的长方形。 //长方形 左上右下点 float rectangle(vec2 uv,vec2 p1,vec2 p2,float blur){ blur /= 2.; return smoothstep(p1.x-blur,p1.x+blur,uv.x) smoothstep(p2.x+blur,p2.x-blur,uv.x) smoothstep(p1.y+blur,p1.y-blur,uv.y) smoothstep(p2.y-blur,p2.y+blur,uv.y); } Rectangle 最后为了图形有更丰富的变化,准备一个旋转矩阵。 //绕任意点旋转 vec2 rotate(vec2 uv, vec2 p, float a){ uv-=p; vec2 q; q.x = cos(a)uv.x + sin(a)uv.y; q.y = -sin(a)uv.x + cos(a)*uv.y; q+=p; return q; } Rotate

DrawingTime!

先糊个背景,用uv.y混个渐变。 //BackGround vec3 c1 = vec3(1.,0.4,0.); vec3 c2 = vec3(1.,0.,0.4); vec3 col = mix(c2,c1,uv.y); 1.background

加两个圆做脑袋和身体,底部减掉一点做屁股。 float bmask = circle(uv,vec2(0.4,0.6),0.13,0.005);//head bmask += circle(uv,vec2(0.5,0.45),0.21,0.005);//body bmask *= smoothstep(0.28,0.285,uv.y); //butt 2.body

激动人心的画jb脸时间,团子变身! float wmask = segment(uv, vec2(.401,.60), vec2(.41,.56), 0.04, 0.005);//nose wmask += circle(uv,vec2(.405,.53),0.025,0.005);//mouth wmask += circle(uv,vec2(.425,.545),0.025,0.005);//mouth wmask += rectangle(uv,vec2(.365,.56),vec2(.385,.55),0.005);//righteye wmask += rectangle(rotate(uv,vec2(.435,.598),-2.4),vec2(.425,.603),vec2(.445,.593),0.005);//lefteye bmask += triangle(rotate(uv,vec2(.35,.69),2.7),vec2(.35,.69),0.05,0.005);//rightear bmask += triangle(rotate(uv,vec2(.43,.715),-.15),vec2(.43,.715),0.05,0.005);//leftear 3.face

最后补个尾巴和jiojio—— wmask += circle(uv,vec2(.42,.3),0.03,0.005);//foot bmask += segment(uv, vec2(.595,.45), vec2(.595,.2), 0.08, 0.005);//tail1 bmask += arc(uv, vec2(.53,.2),.115,1.57,4.2,0.08, 0.005);//tail2 wmask += arc(uv, vec2(.53,.2),.115,4.2,4.71,0.08, 0.005);//tail3 4.finished!

还偷偷整了几个变体: Siamese cat Tiger 👉完整代码👈 感谢帕老师对猫猫耳朵的巨大贡献!!!

Today's Tips

  • GLSL没有lerp函数,对应的函数是mix.
  • 今天的练习通通有一个大问题!因为希望以uv做定位点的输入,但画图像是采用的又是归一化后的绝对长度,所以当屏幕的比例不是100562左右的时候——————会爆炸———————————— 请输入图片描述 后来看iq大佬的代码,最好还是输入函数前就先把坐标轴归整好,并将原点置于中心,之后所有的数据处理都是成比例的。 vec2 p = (2.0fragCoord.xy-iResolution.xy)/iResolution.y; p *= 2.0;

参考资料

ODOS —— One Day One Shader 努力日更的学习计划。 包括但不限于基于Shadertoy、UnityShaderLab等平台的shader学习笔记

by ERIN.Z

2024 © typecho & elise