在Shadertoy中制作笑脸[The Art of Code(5)]
2024-07-06
基础框架 #
对坐标轴基操一下
然后逐一完成各个部分代码
- 基础框架
#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;
}
截图 #
笑脸
愤怒
完整代码 #
笑·完整代码 #
#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 播放列表中的第二个小部分,包括制作笑脸和让笑脸动起来两集,之后会把这个系列的所有视频的笔记都发上来。本篇的重点总结:
- 在制作笑脸这集中没讲啥新东西,主要就是二维重映射的应用和操作坐标系的具体实践
- Shadertoy( https://www.shadertoy.com/new )
- 本集原始链接 Youtube
- 保留所有版权
- 首次上传:2024年07月06日
- 最后修改:2024年07月06日