用 Shader 绘制一只可爱的 “小挠斧” 【GLSL】
共 13900字,需浏览 28分钟
·
2023-10-12 12:44
完整效果图
设计思路
我设计的这只 “小挠斧” 的造型十分简单,只用到了三种基本图形
圆形
(圆头)弧线
(圆头)直线
那么在 shader 当中我们该如何绘制这三种基本图形呢?答案是通过 SDF !【想进一步了解的同学可以搜索我的相关专栏文章 ShaderJoy 】
限于篇幅,我这里就直接列出了本文所用到的三种 SDF 的函数
sdCircle
udSegment
sdArc
sdCircle 的使用
sdCircle 具体代码分别如下所示
/// @note 圆形
float sdCircle( in vec2 p, in float r )
{
return length(p) - r;
}
通过圆形我们可以首先绘制出两只耳朵(外耳郭)的基本造型
然后同样,绘制出内耳的造型
进而绘制出 大脸盘子
以及眼睛和嘴
udSegment 的使用
udSegment 具体代码分别如下所示
/// @note (圆头)直线形
/// @param a 起点
/// @param b 终点
float udSegment( in vec2 p, in vec2 a, in vec2 b )
{
vec2 ba = b - a;
vec2 pa = p - a;
float h = clamp( dot(pa, ba) / dot(ba, ba), 0.0, 1.0 );
return length(pa - h * ba);
}
通过它,我们可以给 “小挠斧” 绘制一个鼻子
是不是已经有点可可爱爱了~ 但是这样还是不行的,因为看起来有点像小熊。。。毕竟是 “小挠斧” ,应该给它添加一个 王 的印记!那就要配合使用我们下一个 SDF 函数了。
sdArc 的使用
sdArc 具体代码分别如下所示
/// @note (圆头)弧形
/// @param sc 角度、弧长
/// @param ra 弧长
/// @param rb 粗细
float sdArc( in vec2 p, in vec2 sc, in float ra, float rb )
{
// sc is the sin/cos of the arc's aperture
p.x = abs(p.x);
return ((sc.y * p.x > sc.x * p.y) ? length(p - sc * ra) :
abs(length(p) - ra)) - rb;
}
因为 “王” 的结构是 三横一竖,(三横其实是略带弯曲的),所以我决定用 sdArc
来绘制那 “三横”,udSegment
来绘制那 “一竖” 。
感觉还差点意思,丰富一下它的条纹
以及再给它添加一对灵性的小胡纸
大功就完成了 !
总结
有了这三种基本图形的绘制之后,其实就只需把它们合理地拼接在一起的工作了,但其实也是最需要耐心的工作,因为有许多细节参数需要调教。。。啊呸!是调校!
最后,完整代码如下所示
完整代码
// 版权声明:转载请附上原文出处及链接。
#define YELLOW vec3(255., 214., 34.)/255.
#define ORANGE vec3(255., 80., 2.)/255.
#define BLACK vec3(29., 21., 22.)/255.
#define WHITE vec3(1.)
#define Pi 3.14159
const float AA = 0.007;
/// ---------------- SDF ----------------
/// @note 圆形
float sdCircle( in vec2 p, in float r )
{
return length(p) - r;
}
/// @note (圆头)弧形
/// @param sc 角度、弧长
/// @param ra 弧长
/// @param rb 粗细
float sdArc( in vec2 p, in vec2 sc, in float ra, float rb )
{
// sc is the sin/cos of the arc's aperture
p.x = abs(p.x);
return ((sc.y * p.x > sc.x * p.y) ? length(p - sc * ra) :
abs(length(p) - ra)) - rb;
}
/// @note (圆头)直线形
/// @param a 起点
/// @param b 终点
float udSegment( in vec2 p, in vec2 a, in vec2 b )
{
vec2 ba = b - a;
vec2 pa = p - a;
float h = clamp( dot(pa, ba) / dot(ba, ba), 0.0, 1.0 );
return length(pa - h * ba);
}
/// ---------------- SDF ----------------
/// @note 关于 (0, 0) 点的旋转
vec2 rot(vec2 uv, float a)
{
// [uv.x uv.y] * [cos(a), sin(a),
// -sin(a), cos(a)]
return vec2(uv.x * cos(a) - uv.y * sin(a), uv.y * cos(a) + uv.x * sin(a));
}
vec2 rotCenter(vec2 uv, float a, vec2 center)
{
return rot(uv - center, a) + center;
}
/// @note 融合
float smin( float d1, float d2, float k )
{
float h = clamp( 0.5 + 0.5 * (d2 - d1) / k, 0.0, 1.0 );
return mix( d2, d1, h ) - k * h * (1.0 - h);
}
void drawStripe(vec2 uv, vec2 offset, float tb, float ra, float rb, inout vec3 col)
{
float arc_t = sdArc(uv - offset, vec2(sin(tb), cos(tb)), ra, rb);
float arc_m = sdArc(uv - offset + vec2(.0, .13), vec2(sin(tb), cos(tb)), ra, rb);
float arc_b = sdArc(uv - offset + vec2(.0, .25), vec2(sin(tb), cos(tb)), ra, rb);
float arc_3 = min(1., min(sign(arc_t), min(sign(arc_m), sign(arc_b))));
col = mix(BLACK, col, arc_3);
col = mix( col, BLACK, 1.0 - smoothstep(0.0, AA, abs(arc_t)) ); ///< 边缘平滑 抗锯齿
col = mix( col, BLACK, 1.0 - smoothstep(0.0, AA, abs(arc_m)) );
col = mix( col, BLACK, 1.0 - smoothstep(0.0, AA, abs(arc_b)) );
}
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
/// @note 将屏幕坐标转为 [-1., 1.]
/// 以屏幕中心为原点
vec2 uv = (2.0 * fragCoord - iResolution.xy) / iResolution.y;
/// @note 构建各个组件的 SDF
/// @note 脸
float faceSDF = sdCircle(uv - vec2(0., -.83), 1.02);
/// @note 耳朵
float leftOutterEarSDF = sdCircle(uv - vec2(-.61, -.02), .31);
float rightOutterEarSDF = sdCircle(uv - vec2(+.61, -.02), .31);
float leftInnerEarSDF = sdCircle(uv - vec2(-.61, -.02), .15);
float rightInnerEarSDF = sdCircle(uv - vec2(+.61, -.02), .15);
/// @note 眼睛
float leftEyeSDF = sdCircle(uv - vec2(-.33, -.61), .06);
float rightEyeSDF = sdCircle(uv - vec2(+.33, -.61), .06);
/// @note 嘴
float leftMouthSDF = sdCircle(uv - vec2(-.21, -1.), .291);
float rightMouthSDF = sdCircle(uv - vec2(+.21, -1.), .291);
// 绘制
vec3 col = WHITE; ///< 背景
/// @note 耳朵
col = mix(YELLOW, col, smoothstep(0., .01, leftOutterEarSDF));
col = mix(YELLOW, col, smoothstep(0., .01, rightOutterEarSDF));
col = mix(ORANGE, col, smoothstep(0., .01, leftInnerEarSDF));
col = mix(ORANGE, col, smoothstep(0., .01, rightInnerEarSDF));
/// @note 脸
col = mix(YELLOW, col, smoothstep(0., .01, faceSDF));
/// @note 眼睛
col = mix(BLACK, col, smoothstep(0., .01, leftEyeSDF));
col = mix(BLACK, col, smoothstep(0., .01, rightEyeSDF));
/// @note 嘴
float mouth = smin(leftMouthSDF, rightMouthSDF, 0.05);
col = mix(WHITE, col, smoothstep(0., .01, mouth));
/// @note 鼻子
float nose = udSegment( uv, vec2(-.038, -0.73), vec2(.038, -.73) ) - .085;
col = mix(BLACK, col, smoothstep(0., .01, nose));
/// @note 王
float d = udSegment( uv, vec2(0., -0.25), vec2(.0, -.06) ) - .03; ///< |
col = mix( col, BLACK, 1.0 - smoothstep(0.0, AA, abs(d)) );
float tb = Pi / 12.;
float arc_t = sdArc(uv - vec2(0., -0.9), vec2(sin(tb), cos(tb)), .84, 0.03); ///< 三
float arc_m = sdArc(uv - vec2(0., -1.0), vec2(sin(tb), cos(tb)), .84, 0.03);
float arc_b = sdArc(uv - vec2(0., -1.1), vec2(sin(tb), cos(tb)), .84, 0.03);
float wang = min(1., min(sign(d), min(sign(arc_t), min(sign(arc_m), sign(arc_b)))));
col = mix(BLACK, col, wang);
col = mix( col, BLACK, 1.0 - smoothstep(0.0, AA, abs(arc_t)) ); ///< 边缘平滑 抗锯齿
col = mix( col, BLACK, 1.0 - smoothstep(0.0, AA, abs(arc_m)) );
col = mix( col, BLACK, 1.0 - smoothstep(0.0, AA, abs(arc_b)) );
/// @note 花纹
float ttb = Pi / 20.;
drawStripe(uv, vec2(-.82, -1.52), ttb, .84, 0.035, col);
drawStripe(uv, vec2(.82, -1.52), ttb, .84, 0.035, col);
/// @note 胡子
float angle = Pi / 6.*(.5 * sin(iTime * 3.) + .5);
/// 静态版
float leftBeard = udSegment( uv, vec2(-.4, -0.77), vec2(-.618, -.618)) - .008;
col = mix(BLACK, col, smoothstep(0., .01, leftBeard));
/// 静态版
float rightBeard = udSegment( uv, vec2(.4, -0.77), vec2(.618, -.618) ) - .008;
col = mix(BLACK, col, smoothstep(0., .01, rightBeard));
fragColor = vec4(col, 1.0);
}
原文链接: https://juejin.cn/post/7056666793003188261
-- END --
进技术交流群,扫码添加我的微信:Byte-Flow
获取相关资料和源码
推荐:
全网最全的 Android 音视频和 OpenGL ES 干货,都在这了
面试官:如何利用 Shader 实现 RGBA 到 NV21 图像格式转换?
项目疑难问题解答、大厂内部推荐、面试指导、简历指导、代码指导、offer 选择建议、学习路线规划,可以点击找我一对一解答。