在Shadertoy中制作笑脸[The Art of Code(5)]

在Shadertoy中制作笑脸[The Art of Code(5)]

2024-07-06
Shader, Shadertoy

基础框架 #

对坐标轴基操一下

然后逐一完成各个部分代码

  1. 基础框架
#define S(a, b, t) smoothstep(a, b, t)
#define sat(x) clamp(x, 0., 1.)

vec4 Brow(vec2 uv)
{
	vec4 col = vec4(0.);

	return col;
}
vec4 Eye(vec2 uv)
{
    vec4 col = vec4(0.);
    
    return col;
}
vec4 Mouth(vec2 uv)
{
    vec4 col = vec4(0.);
    
    return col;
}
vec4 Head(vec2 uv)
{
    vec4 col = vec4(0.);
    
    return col;
}
vec4 Smiley(vec2 uv)
{
    vec4 col = vec4(0.);
    
    return col;
}

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
    // Normalized pixel coordinates (from 0 to 1)
    vec2 uv = fragCoord/iResolution.xy;
    uv -= .5; 
    uv.x *= iResolution.x/iResolution.y;

    // Output to screen
    fragColor = Smiley(uv);
}

辅助函数 #

//01映射
float remap01(float a, float b, float t)
{
    return sat((t-a)/(b-a));
}
//重映射
float remap(float a, float b, float c, float d, float t)
{
    return sat((t-a)/(b-a)*(d-c) + c);
}
//二维重映射
vec2 inBox(vec2 uv, vec4 rect)
{
    return (uv-rect.xy)/(rect.zw-rect.xy);
}

#

Smiley():
	vec4 head = Head(uv);
	col = mix(col, head, head.a);//以head透明度为参数混合col和head的颜色
vec4 Head(vec2 uv)
{
	vec4 col = vec4(1.);
	float d = length(uv);
	col.a = S(.5, .48, d);//透明的通道用圆形距离场框个半径为.5的圆
	
	//两种颜色中心渐变
	vec3 aCol = mix(vec3(1., .2, .2), vec3(1.), -d);
    vec3 bCol = mix(vec3(1., 1., 0.), vec3(1.), d);
    col.rgb = mix(aCol, bCol, .4);//调整到合适的渐变参数
    //阴影
    float edgeShade = remap01(.35, .5, d);//调整渐变范围
    edgeShade *= edgeShade;//x^2曲线能更好的模拟阴影
    col.rgb *= 1. - edgeShade*.5;//调整曲线强度和方向并作用于col.rgb
    //高光
    float highlight = S(.41, .405, d);//范围
    highlight *= remap(.41, -.1, .65, 0., uv.y);//调整渐变
    ////眼眶
    highlight *= S(.18, .19, length(uv-vec2(.20, -.0)));//范围和位置
    col.rgb = mix(col.rgb, vec3(1.), highlight);//混合颜色到col.rgb中
    //大脸盘子外轮廓
    col.rgb = mix(col.rgb, vec3(.8, .1, .1), S(.45, .48, d));//直接在外轮廓区域混想要的颜色
    //腮红
    d = length(uv - vec2(.25, -.08));//位置
    float cheek =  S(.2, .01, d)*.4;//范围和强度
    cheek *= S(.16, .15, d);//将腮红边缘稍微卡一下,不是完全淡出
    col.rgb = mix(col.rgb, vec3(.8, .1, .1), cheek);//把腮红颜色在腮红范围里与col.rgb混合
    
    return col;
}

眼睛 #

加一个二维重映射函数,可以将坐标重映射到想要的区域,原理和一维的重映射函数相同

vec2 inBox(vec2 uv, vec4 rect)
{
    return (uv-rect.xy)/(rect.zw-rect.xy);
}

完善代码

Smiley():
	vec4 eye = Eye(inBox(uv, vec4(.03, -.15, .35, .17)));//将坐标系重映射到给定的矩形中再传入Eye()
	
	col = mix(col, eye, eye.a);

vec4 Eye(vec2 uv)
{
    uv -= .5;//因为重映射了uv,原点在给定矩形左下角,所以重新中心化
    float d = length(uv);
    //眼球
    vec4 irisCol = vec4(.2, .2, .5, 1.);//设定虹膜颜色
    vec4 col = mix(vec4(1.), vec4(.8, .2, .2, 1.), S(.3, .7, d));//眼白部分稍微来点渐变,更好看
    col.a = S(.5, .48, d);//设定透明度范围
    //虹膜边框
    col.rgb = mix(col.rgb, vec3(0.), S(.3, .28, d));
    //虹膜
    irisCol.rgb *= 1. + S(.3, .05, d);
    col.rgb = mix(col.rgb, irisCol.rgb, S(.28, .25, d));
    //瞳孔
    col.rgb = mix(col.rgb, vec3(0.), S(.16, .14, d));
    //高光
    //float highlight = S(.1, .09, length(uv-vec2(-.15,.15)));//因为是愤怒的表情,所以我不想要上方的高光,看着凶一点
    float highlight = S(.07, .05, length(uv-vec2(.08,-.08)));
    col.rgb = mix(col.rgb, vec3(1.), highlight);
    //鼻梁
    col.rgb *= 1. - S(.45, .5, d)*.5 * sat(-uv.y-uv.x);//sat返回0--1之间的数,对uv来说就是只返回正数,因为加了个负号所以取反
    return col;
}

#

Smiley():
	vec4 mouth = Mouth(inBox(uv, vec4(-.2, -.35, .2, -.05)));
	
	col = mix(col, mouth, mouth.a);

vec4 Mouth(vec2 uv)
{
	//中心化和调整嘴巴的形状
    uv -= .5;
    uv.y *= 3.;
    uv.y += uv.x*uv.x*3.;//上一个教程有提到uv.y是怎么被影响的
    float d = length(uv);
    
    vec4 col = vec4(0.5, .18, .05, 1.);
    col.a = S(.5, .48, d);//嘴透明度范围
    
    float td = length(uv-vec2(0., .4));//牙齿位置
    vec3 toothCol = vec3(1.) * S(.6, .35, d);//牙齿颜色
    
    col.rgb = mix(col.rgb, toothCol, S(.3, .28, td));//应用牙齿
	//下面这部分是笑脸的舌头代码,但我做的事怒脸,就没用上来
	//原理都差不多,设定位置、范围、颜色然后混合
	//td = length(uv+vec2(0., .5));
    //col.rgb = mix(col.rgb, vec3(1., .5, .5), S(.4, .2, td));
    //col.a = S(.5, .48, d);
    return col;
}

眉毛 #

Smiley():
	vec4 brow = Brow(inBox(uv, vec4(.0, .2, .35, .43)));
	
	col = mix(col, brow, brow.a);


vec4 Brow(vec2 uv)
{
	//调整眉毛位置和变形
    float y = uv.y;
    uv.y -= uv.x*.7-1.1; 
    uv -= .5;
    
    vec4 col = vec4(0.);
    
    float blur = .1;
    //眉毛,用两个“圆”相减得到
    float d1 = length(uv);
    float s1 = S(.45, .45-blur, d1);
    float d2 = length(uv-vec2(-.05, -.35)*.7);
    float s2 = S(.5, .5-blur, d2);
    
    float browMask = sat(s1-s2);
    //提亮一部分眉毛,更立体好看一些
    float colMask = remap01(.2, -.3, y)*.3;
    colMask *= S(.6, .9, browMask);
    vec4 browCol = mix(vec4(.4, .2, .2, 1.), vec4(1., .75, .5, 1.), colMask);
    //眉毛的阴影,微调了位置和虚化程度
    uv.y += .1;
    blur += .1;
    d1 = length(uv);
    s1 = S(.45, .45-blur, d1);
    d2 = length(uv-vec2(-.05, -.35)*.7);
    s2 = S(.5, .5-blur, d2);
    float shadowMask = sat(s1-s2);
    
    col = mix(col, vec4(0.,0.,0.,1.), S(.0, 1., shadowMask)*.5);
    col = mix(col, browCol, S(.2, .4, browMask));
    return col;
}

截图 #

MakingASmileyinShadertoyPart1.png

笑脸

MakingASmileyinShadertoyPart1n.png

愤怒

完整代码 #

笑·完整代码 #

#define S(a, b, t) smoothstep(a, b, t)
#define sat(x) clamp(x, 0., 1.)

float remap01(float a, float b, float t)
{
    return sat((t-a)/(b-a));//防止t不在ab范围内
}
float remap(float a, float b, float c, float d, float t)
{
    return sat(c + (d-c)*(t-a)/(b-a));
}
//二维remap01
vec2 within(vec2 uv, vec4 rect)
{
    return (uv-rect.xy)/(rect.zw-rect.xy);
    //如果uv坐标在rect区域左下角,则输出uv为(0., 0.),右上角输出uv为(1., 1.) 
    //和一维的一个道理
}
vec4 Brow(vec2 uv)
{
    float y = uv.y;
    uv.y += uv.x*.5-.1;
    //uv.x -= .1;
    uv -= .5;
    
    vec4 col = vec4(0.);
    
    float blur = .1;
    
    float d1 = length(uv);
    float s1 = S(.45, .45-blur, d1);
    float d2 = length(uv-vec2(.1, -.2)*.7);
    float s2 = S(.5, .5-blur, d2);
    
    float browMask = sat(s1-s2);
    
    float colMask = remap01(.7, .8, y)*.75;
    colMask *= S(.6, .9, browMask);
    vec4 browCol = mix(vec4(.4, .2, .2, 1.), vec4(1., .75, .5, 1.), colMask);
    
    uv.y += .15;
    blur += .1;
    d1 = length(uv);
    s1 = S(.45, .45-blur, d1);
    d2 = length(uv-vec2(.1, -.2)*.7);
    s2 = S(.5, .5-blur, d2);
    float shadowMask = sat(s1-s2);
    
    col = mix(col, vec4(0.,0.,0.,1.), S(.0, 1., shadowMask)*.5);
    col = mix(col, browCol, S(.2, .4, browMask));
    
    return col;
}
vec4 Eye(vec2 uv)
{
    uv -= .5;
    float d = length(uv);
    
    vec4 irisCol = vec4(.3, .4, .1, 1.);
    vec4 col = mix(vec4(1.), irisCol, S(.1, .7, d)*.5);
    
    col.rgb *= 1. - S(.45, .5, d)*.5 * sat(-uv.y-uv.x);
    col.rgb = mix(col.rgb, vec3(0.), S(.3, .28, d));
    
    irisCol.rgb *= 1. + S(.3, .05, d);
    col.rgb = mix(col.rgb, irisCol.rgb, S(.28, .25, d));
    col.rgb = mix(col.rgb, vec3(0.), S(.16, .14, d));

    float highlight = S(.07, .05, length(uv-vec2(.08,-.08)));
    highlight += S(.1, .09, length(uv-vec2(-.15,.15)));
    col.rgb = mix(col.rgb, vec3(1.),highlight);
    col.a = S(.5, .48, d); 
    return col;
}
vec4 Mouth(vec2 uv)
{
    uv -= .5;
    vec4 col = vec4(0.5, .18, .05, 1.);
    uv.y *= 1.5;
    uv.y -= uv.x*uv.x*2.;
    
    float d = length(uv);
    
    float td = length(uv-vec2(0., .6));
    vec3 toothCol = vec3(1.) * S(.6, .35, d);
    col.rgb = mix(col.rgb, toothCol, S(.4, .37, td));
    
    td = length(uv+vec2(0., .5));
    col.rgb = mix(col.rgb, vec3(1., .5, .5), S(.4, .2, td));
    col.a = S(.5, .48, d);
    return col;
}
vec4 Head(vec2 uv)
{
    vec4 col = vec4(.5, .4, .8, 1.);
    
    float d = length(uv);
    
    col.a = S(.5, .49, d);
    
    float edgeShade = remap01(.35, .5, d);
    edgeShade *= edgeShade;    
    col.rgb *= 1.- edgeShade*.5;
    
    col.rgb = mix(col.rgb, vec3(.4, .3, .8), S(.47, .48, d));
    
    float highlight = S(.41, .405, d);
    highlight *= remap(.41, -.1, .75, 0., uv.y);
    highlight *= S(.18, .19, length(uv-vec2(.21, .09)));
    //将0-.41部分的y值重映射到0-.75,再与highlight的1范围相乘,结果为纵向渐变
    col.rgb = mix(col.rgb, vec3(1.), highlight);
    
    d = length(uv-vec2(.25, -.2));
    
    float cheek = S(.2, .01, d)*.4;
    cheek *= S(.16, .15, d);//大于.16压黑
    col.rgb = mix(col.rgb, vec3(.8, .4, .6), cheek); 
    
    return col;
}
vec4 Smiley(vec2 uv)
{
    vec4 col = vec4(0.);
    
    uv.x = abs(uv.x);
    vec4 head = Head(uv);
    vec4 eye = Eye(within(uv, vec4(.03, -.1, .36, .25)));
    vec4 mouth = Mouth(within(uv, vec4(-.3, -.4, .3, -.1)));
    vec4 brow = Brow(within(uv, vec4(.03, .2, .4, .43)));
    
    col = mix(col, head, head.a);//根据head的透明度混合col和head?
    col = mix(col, eye, eye.a);
    col = mix(col, mouth, mouth.a);
    col = mix(col, brow, brow.a);
    return col;
}

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
    // Normalized pixel coordinates (from 0 to 1)
    vec2 uv = fragCoord/iResolution.xy;
    uv -= .5; 
    uv.x *= iResolution.x/iResolution.y;

    // Output to screen
    fragColor = Smiley(uv);
}

怒·完整代码 #

#define S(a, b, t) smoothstep(a, b, t)
#define sat(x) clamp(x, 0., 1.)

float remap01(float a, float b, float t)
{
    return sat((t-a)/(b-a));
}
float remap(float a, float b, float c, float d, float t)
{
    return sat((t-a)/(b-a)*(d-c) + c);
}
vec2 inBox(vec2 uv, vec4 rect)
{
    return (uv-rect.xy)/(rect.zw-rect.xy);
}
vec4 Brow(vec2 uv)
{
    float y = uv.y;
    uv.y -= uv.x*.7-1.1; 
    uv -= .5;
    
    vec4 col = vec4(0.);
    
    float blur = .1;
    
    float d1 = length(uv);
    float s1 = S(.45, .45-blur, d1);
    float d2 = length(uv-vec2(-.05, -.35)*.7);
    float s2 = S(.5, .5-blur, d2);
    
    float browMask = sat(s1-s2);
    
    float colMask = remap01(.2, -.3, y)*.3;
    colMask *= S(.6, .9, browMask);
    vec4 browCol = mix(vec4(.4, .2, .2, 1.), vec4(1., .75, .5, 1.), colMask);
    
    uv.y += .1;
    blur += .1;
    d1 = length(uv);
    s1 = S(.45, .45-blur, d1);
    d2 = length(uv-vec2(-.05, -.35)*.7);
    s2 = S(.5, .5-blur, d2);
    float shadowMask = sat(s1-s2);
    
    col = mix(col, vec4(0.,0.,0.,1.), S(.0, 1., shadowMask)*.5);
    col = mix(col, browCol, S(.2, .4, browMask));
    return col;
}
vec4 Eye(vec2 uv)
{
    uv -= .5;
    float d = length(uv);
    //眼球
    vec4 irisCol = vec4(.2, .2, .5, 1.);
    vec4 col = mix(vec4(1.), vec4(.8, .2, .2, 1.), S(.3, .7, d));
    col.a = S(.5, .48, d);
    //虹膜边框
    col.rgb = mix(col.rgb, vec3(0.), S(.3, .28, d));
    //虹膜
    irisCol.rgb *= 1. + S(.3, .05, d);
    col.rgb = mix(col.rgb, irisCol.rgb, S(.28, .25, d));
    //瞳孔
    col.rgb = mix(col.rgb, vec3(0.), S(.16, .14, d));
    //高光
    //float highlight = S(.1, .09, length(uv-vec2(-.15,.15)));
    float highlight = S(.07, .05, length(uv-vec2(.08,-.08)));
    col.rgb = mix(col.rgb, vec3(1.), highlight);
    //鼻梁
    col.rgb *= 1. - S(.45, .5, d)*.5 * sat(-uv.y-uv.x);
    return col;
}
vec4 Mouth(vec2 uv)
{
    uv -= .5;
    uv.y *= 3.;
    uv.y += uv.x*uv.x*3.;
    float d = length(uv);
    
    vec4 col = vec4(0.5, .18, .05, 1.);
    col.a = S(.5, .48, d);
    
    float td = length(uv-vec2(0., .4));
    vec3 toothCol = vec3(1.) * S(.6, .35, d);
    
    col.rgb = mix(col.rgb, toothCol, S(.3, .28, td));
    
    return col;
}
vec4 Head(vec2 uv)
{
    vec4 col = vec4(1.);

    float d = length(uv);
    col.a = S(.5, .48, d);
    
    //渐变
    vec3 Cola = mix(vec3(1., .2, .2), vec3(1.), -d);
    vec3 Colb = mix(vec3(1., 1., 0.), vec3(1.), d);
    col.rgb = mix(Cola, Colb, .4);
    //阴影
    float edgeShade = remap01(.35, .5, d);
    edgeShade *= edgeShade;
    col.rgb *= 1. - edgeShade*.5;
    //高光
    float highlight = S(.41, .405, d);
    highlight *= remap(.41, -.1, .65, 0., uv.y);
    ////眼眶
    highlight *= S(.18, .19, length(uv-vec2(.20, -.0)));
    col.rgb = mix(col.rgb, vec3(1.), highlight);
    //轮廓
    col.rgb = mix(col.rgb, vec3(.8, .1, .1), S(.45, .48, d));
    //腮红
    d = length(uv - vec2(.25, -.08));
    float cheek =  S(.2, .01, d)*.4;
    cheek *= S(.16, .15, d);
    col.rgb = mix(col.rgb, vec3(.8, .1, .1), cheek);
    
    return col;
}
vec4 Smiley(vec2 uv)
{
    vec4 col = vec4(0.);
    
    vec4 head = Head(uv);
    vec4 eye = Eye(inBox(uv, vec4(.03, -.15, .35, .17)));
    vec4 mouth = Mouth(inBox(uv, vec4(-.2, -.35, .2, -.05)));
    vec4 brow = Brow(inBox(uv, vec4(.0, .2, .35, .43)));
    
    col = mix(col, head, head.a);
    col = mix(col, eye, eye.a);
    col = mix(col, mouth, mouth.a);
    col = mix(col, brow, brow.a);
    return col;
}

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
    // Normalized pixel coordinates (from 0 to 1)
    vec2 uv = fragCoord/iResolution.xy;
    uv -= .5;
    uv.x *= iResolution.x/iResolution.y;
    uv.x = abs(uv.x);
    
    // Output to screen
    fragColor = Smiley(uv);
}

总结 #

这个教程是The Art Of Code的系列教程 Shadertoy Tutorial 播放列表中的第二个小部分,包括制作笑脸和让笑脸动起来两集,之后会把这个系列的所有视频的笔记都发上来。本篇的重点总结:

  1. 在制作笑脸这集中没讲啥新东西,主要就是二维重映射的应用和操作坐标系的具体实践

  • 保留所有版权
  • 首次上传:2024年07月06日
  • 最后修改:2024年07月06日