用 Shader 绘制一只可爱的 “小挠斧” 【GLSL】

字节流动

共 13900字,需浏览 28分钟

 · 2023-10-12

完整效果图

 

设计思路

我设计的这只 “小挠斧” 的造型十分简单,只用到了三种基本图形

  • 圆形

  • (圆头)弧线

  • (圆头)直线

那么在 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.01.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.01.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 sminfloat d1, float d2, float k )
{
    float h = clamp( 0.5 + 0.5 * (d2 - d1) / k, 0.01.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)), .840.03); ///< 三
    float arc_m = sdArc(uv - vec2(0.-1.0), vec2(sin(tb), cos(tb)), .840.03);
    float arc_b = sdArc(uv - vec2(0.-1.1), vec2(sin(tb), cos(tb)), .840.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, .840.035, col);
    drawStripe(uv, vec2(.82-1.52), ttb, .840.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 FFmpeg 实现带滤镜的微信小视频录制功能

全网最全的 Android 音视频和 OpenGL ES 干货,都在这了

一文掌握 YUV 图像的基本处理

抖音传送带特效是怎么实现的?

所有你想要的图片转场效果,都在这了

面试官:如何利用 Shader 实现 RGBA 到 NV21 图像格式转换?

我用 OpenGL ES 给小姐姐做了几个抖音滤镜


项目疑难问题解答、大厂内部推荐、面试指导、简历指导、代码指导、offer 选择建议、学习路线规划,可以点击找我一对一解答。


浏览 861
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报