首页 > 其他分享 >ShadowToy-Smooth Mouse Drawing

ShadowToy-Smooth Mouse Drawing

时间:2024-04-01 15:45:41浏览次数:23  
标签:1.0 0.0 float Smooth vec4 xy fragCoord Mouse ShadowToy

ShadowToy-Smooth Mouse Drawing 源码分析

简概

Smooth Mouse Drawing 源码分析学习。

源码

Image

// A recreation of https://lazybrush.dulnan.net/

// Controls:
// - Mouse to draw
// - L: toggle between quadratic bezier curves and line segments
// - S: toggle SDF visualisation
// - P: toggle mouse points

// Settings in Buffer B

// Modified sdBezier() function originally from
// Quadratic Bezier SDF With L2 - Envy24
// https://www.shadertoy.com/view/7sGyWd

#define LINE_WIDTH (iResolution.y * 0.01)
#define POINT_RADIUS (iResolution.y * 0.007)

const int KEY_L = 76;
const int KEY_S = 83;
const int KEY_P = 80;

bool keyToggled(int keyCode) {
    return texelFetch(iChannel1, ivec2(keyCode, 2), 0).r > 0.0;
}

// 即前景颜色与背景颜色的混合后的透明度。
vec4 blendOver(vec4 front, vec4 back) {
    float a = front.a + back.a * (1.0 - front.a);
    return a > 0.0
        ? vec4((front.rgb * front.a + back.rgb * back.a * (1.0 - front.a)) / a , a)
        : vec4(1.0);
}

void blendInto(inout vec4 dst, vec4 src) {
    dst = blendOver(src, dst);
}

void mainImage(out vec4 fragColor, vec2 fragCoord) {
    fragColor = vec4(1.0);

	// 二次贝塞尔曲线 SDF值
    float qd = texture(iChannel0, fragCoord / iResolution.xy).x;
    // 线段 SDF值
    float ld = texture(iChannel0, fragCoord / iResolution.xy).y;
    // 鼠标点 SDF值
    float pd = texture(iChannel0, fragCoord / iResolution.xy).z;
    // 根据按键状态选择用什么
    float sd = (keyToggled(KEY_L) ? ld : qd) - LINE_WIDTH / 2.0;

// 将 sd 作为透明度与现在的颜色混合,也就是说,距离越近, sd 越小, 0.5-sd 越大,图像越不透明
    blendInto(fragColor, vec4(0.0, 0.0, 0.0, clamp(0.5 - sd, 0.0, 1.0)));
    
    if (!keyToggled(KEY_S)) {
        float spacing = iResolution.y * 0.02;
        float thickness = max(iResolution.y * 0.002, 1.0);
        float opacity = clamp(
            0.5 + 0.5 * thickness - 
            abs(mod(sd - (spacing - thickness) * 0.5, spacing) - spacing * 0.5), 
            0.0, 1.0
        ) * 0.5 * exp(-sd / iResolution.y * 8.0);
        blendInto(fragColor, vec4(0.0, 0.0, 0.0, opacity));
    }
    
    if (keyToggled(KEY_P)) {
        blendInto(fragColor, vec4(1.0, 0.0, 0.0, 0.0));
    }
}

blendInTo and blendOver


vec4 blendOver(vec4 front, vec4 back) {
	// `front.a` 是前景颜色的 alpha 值,表示前景颜色的透明度。
	// `back.a` 是背景颜色的 alpha 值,表示背景颜色的透明度。
	// `(1.0 - front.a)` 表示前景颜色的不透明度,
	// 即前景颜色的 alpha 值的补数,表示背景颜色中不受前景颜色影响的部分。
	// `back.a * (1.0 - front.a)` 表示背景颜色中不受前景颜色影响的部分的透明度。
	// `front.a + back.a * (1.0 - front.a)` 表示合成后的颜色的 alpha 值,
	// 即前景颜色与背景颜色的混合后的透明度。
    float a = front.a + back.a * (1.0 - front.a);
    // 如何混合之后的透明度大于0,也就是有不透明的显示,那么取混合后的颜色,否则取黑色
    return a > 0.0
    // 这部分是关于颜色的感知,颜色如何按照透明度划分的方式来显示,透明度也可以划分么?
    // 仔细想想也不是不可能。
    // 总之,混合一个颜色时,要保证颜色和透明度都是混合之后的,a是混合后的透明度,颜色要对应加权
    // 得到“混合后”的颜色。
        ? vec4((front.rgb * front.a + back.rgb * back.a * (1.0 - front.a)) / a , a)
        : vec4(1.0);
}

BufferB

// This buffer maintains the SDF for the drawing.

// .x: SDF with quadratic bezier curves
// .y: SDF with linear segments
// .z: SDF for mouse points

float sdSegment(vec2 p, vec2 a, vec2 b) {
    vec2 ap = p - a;
    vec2 ab = b - a;
// clamp(dot(ap, ab) / dot(ab, ab), 0.0, 1.0) 投影向量的长度,即||ap -> ab|| 
// ab * clamp(dot(ap, ab) / dot(ab, ab), 0.0, 1.0) 长度*方向,得到投影向量 ap -> ab
// distance(ap, ab * clamp(dot(ap, ab) / dot(ab, ab), 0.0, 1.0)); 得到 p 到 ab 的垂直距离
    return distance(ap, ab * clamp(dot(ap, ab) / dot(ab, ab), 0.0, 1.0));
}

void mainImage(out vec4 fragColor, vec2 fragCoord) {
    float qd = 1e30;
    float ld = 1e30;
    float pd = 1e30;
    
// 取上一帧的二次贝塞尔值,线段值,鼠标点距离
    if (iFrame != 0) {
        qd = texelFetch(iChannel1, ivec2(fragCoord), 0).r;
        ld = texelFetch(iChannel1, ivec2(fragCoord), 0).g;
        pd = texelFetch(iChannel1, ivec2(fragCoord), 0).b;
    }
    
	// 鼠标在前两帧的位置
    vec4 mouseA = iFrame > 0 ? texelFetch(iChannel0, ivec2(0, 0), 0) : vec4(0.0);
    vec4 mouseB = iFrame > 0 ? texelFetch(iChannel0, ivec2(1, 0), 0) : vec4(0.0);
    // 鼠标现在帧的位置
    vec4 mouseC = iFrame > 0 ? texelFetch(iChannel0, ivec2(2, 0), 0) : iMouse;
    
    // A: mouse from previous previous frame
    // B: mouse from previous frame
    // C: mouse from this frame

    mouseA.xy += 0.5;
    mouseB.xy += 0.5;
    mouseC.xy += 0.5;
    
// 鼠标点
	// 现在,鼠标按键按下
    if (mouseC.z > 0.0) {
        pd = min(pd, distance(fragCoord, mouseC.xy));
    }
    
// 线段
    // 现在,鼠标按下,且上一帧按下
    if (mouseB.z > 0.0 && mouseC.z > 0.0) {
    // 三个位置:当前纹理位素,上一帧鼠标位置,当前鼠标位置
    // ld 为当前像素点到鼠标移动方向的距离
        ld = min(ld, sdSegment(fragCoord, mouseB.xy, mouseC.xy));
    } else if (mouseC.z > 0.0) {
    // ld 为当前像素点到鼠标位置的距离
        ld = min(ld, distance(fragCoord, mouseC.xy));
    }

// 二次贝塞尔曲线
    // 现在,鼠标按下,且上一帧未按下
    if (mouseB.z <= 0.0 && mouseC.z > 0.0) {
        qd = min(qd, distance(fragCoord, mouseC.xy));
    } else if (mouseA.z <= 0.0 && mouseB.z > 0.0 && mouseC.z > 0.0) {
    // 现在按下,上一帧按下,上上帧未按下
        qd = min(qd, sdSegment(fragCoord, mouseB.xy, mix(mouseB.xy, mouseC.xy, 0.5)));
    } else if (mouseA.z > 0.0 && mouseB.z > 0.0 && mouseC.z > 0.0) {
    // 三帧全部按下
        qd = min(qd, abs(sdBezier(fragCoord, mix(mouseA.xy, mouseB.xy, 0.5), mouseB.xy, mix(mouseB.xy, mouseC.xy, 0.5))));
    } else if (mouseA.z > 0.0 && mouseB.z > 0.0 && mouseC.z <= 0.0) {
    // 现在松开,上一帧按下,上上帧按下
        qd = min(qd, sdSegment(fragCoord, mix(mouseA.xy, mouseB.xy, 0.5), mouseB.xy));
    }
// 保存
    fragColor.r = qd;
    fragColor.g = ld;
    fragColor.b = pd;
}

BufferA

// This buffer tracks smoothed mouse positions over multiple frames.

// See https://lazybrush.dulnan.net/ for what these mean:
#define RADIUS (iResolution.y * 0.015)
#define FRICTION 0.05

void mainImage(out vec4 fragColor, vec2 fragCoord) {
    if (fragCoord.y != 0.5 || fragCoord.x > 3.0) {
        return;
    }

    if (iFrame == 0) {
        if (fragCoord.x == 2.5) {
            fragColor = iMouse;
        } else {
            fragColor = vec4(0.0);
        }
        
        return;
    }
    
    vec4 iMouse = iMouse;
    const float magic = 1e25;
    
    if (iMouse == vec4(0.0)) {
        float t = iTime * 3.0;
        iMouse.xy = (vec2(cos(3.14159 * t) + sin(0.72834 * t + 0.3), sin(2.781374 * t + 3.47912) + cos(t)) * 0.25 + 0.5) * iResolution.xy;
        iMouse.z = magic;
    }
    
    vec4 mouseA = texelFetch(iChannel0, ivec2(1, 0), 0);
    vec4 mouseB = texelFetch(iChannel0, ivec2(2, 0), 0);
    vec4 mouseC;
    mouseC.zw = iMouse.zw;
    float dist = distance(mouseB.xy, iMouse.xy);
    
    if (mouseB.z > 0.0 && (mouseB.z != magic || iMouse.z == magic) && dist > 0.0) {
        vec2 dir = (iMouse.xy - mouseB.xy) / dist;
        float len = max(dist - RADIUS, 0.0);
        float ease = 1.0 - pow(FRICTION, iTimeDelta * 10.0);
        mouseC.xy = mouseB.xy + dir * len * ease;
    } else {
        mouseC.xy = iMouse.xy;
    }
    
    if (fragCoord.x == 0.5) {
        fragColor = mouseA;
    } else if (fragCoord.x == 1.5) {
        fragColor = mouseB.z == magic && iMouse.z != magic ? vec4(0.0) : mouseB;
    } else {
        fragColor = mouseC;
    }
}

Common

// solveQuadratic(), solveCubic(), solve() and sdBezier() are from
// Quadratic Bezier SDF With L2 - Envy24
// https://www.shadertoy.com/view/7sGyWd
// with modification. Thank you! I tried a lot of different sdBezier()
// implementations from across Shadertoy (including trying to make it
// myself) and all of them had bugs and incorrect edge case handling
// except this one.

int solveQuadratic(float a, float b, float c, out vec2 roots) {
    // Return the number of real roots to the equation
    // a*x^2 + b*x + c = 0 where a != 0 and populate roots.
    float discriminant = b * b - 4.0 * a * c;

    if (discriminant < 0.0) {
        return 0;
    }

    if (discriminant == 0.0) {
        roots[0] = -b / (2.0 * a);
        return 1;
    }

    float SQRT = sqrt(discriminant);
    roots[0] = (-b + SQRT) / (2.0 * a);
    roots[1] = (-b - SQRT) / (2.0 * a);
    return 2;
}

int solveCubic(float a, float b, float c, float d, out vec3 roots) {
    // Return the number of real roots to the equation
    // a*x^3 + b*x^2 + c*x + d = 0 where a != 0 and populate roots.
    const float TAU = 6.2831853071795862;
    float A = b / a;
    float B = c / a;
    float C = d / a;
    float Q = (A * A - 3.0 * B) / 9.0;
    float R = (2.0 * A * A * A - 9.0 * A * B + 27.0 * C) / 54.0;
    float S = Q * Q * Q - R * R;
    float sQ = sqrt(abs(Q));
    roots = vec3(-A / 3.0);

    if (S > 0.0) {
        roots += -2.0 * sQ * cos(acos(R / (sQ * abs(Q))) / 3.0 + vec3(TAU, 0.0, -TAU) / 3.0);
        return 3;
    }
    
    if (Q == 0.0) {
        roots[0] += -pow(C - A * A * A / 27.0, 1.0 / 3.0);
        return 1;
    }
    
    if (S < 0.0) {
        float u = abs(R / (sQ * Q));
        float v = Q > 0.0 ? cosh(acosh(u) / 3.0) : sinh(asinh(u) / 3.0);
        roots[0] += -2.0 * sign(R) * sQ * v;
        return 1;
    }
    
    roots.xy += vec2(-2.0, 1.0) * sign(R) * sQ;
    return 2;
}

int solve(float a, float b, float c, float d, out vec3 roots) {
    // Return the number of real roots to the equation
    // a*x^3 + b*x^2 + c*x + d = 0 and populate roots.
    if (a == 0.0) {
        if (b == 0.0) {
            if (c == 0.0) {
                return 0;
            }
            
            roots[0] = -d/c;
            return 1;
        }
        
        vec2 r;
        int num = solveQuadratic(b, c, d, r);
        roots.xy = r;
        return num;
    }
    
    return solveCubic(a, b, c, d, roots);
}

float sdBezier(vec2 p, vec2 a, vec2 b, vec2 c) {
    vec2 A = a - 2.0 * b + c;
    vec2 B = 2.0 * (b - a);
    vec2 C = a - p;
    vec3 T;
    int num = solve(
        2.0 * dot(A, A),
        3.0 * dot(A, B),
        2.0 * dot(A, C) + dot(B, B),
        dot(B, C),
        T
    );
    T = clamp(T, 0.0, 1.0);
    float best = 1e30;
    
    for (int i = 0; i < num; ++i) {
        float t = T[i];
        float u = 1.0 - t;
        vec2 d = u * u * a + 2.0 * t * u * b + t * t * c - p;
        best = min(best, dot(d, d));
    }
    
    return sqrt(best);
}

ShaderToy 内置成员

iMouse

iMouse:用于获取鼠标的位置和状态信息。
vec4(x, y, z, w),其中(x, y)表示鼠标在屏幕上的坐标位置,(z, w)表示鼠标左右按键按下状态。

fragCoord

gl_FragCoord:contains the window-relative coordinates of the current fragment

https://registry.khronos.org/OpenGL-Refpages/gl4/html/gl_FragCoord.xhtml

texelFetch

texelFetch:在纹理中执行单个纹素的查找

    if (iFrame != 0) {
        qd = texelFetch(iChannel1, ivec2(fragCoord), 0).r;
        ld = texelFetch(iChannel1, ivec2(fragCoord), 0).g;
        pd = texelFetch(iChannel1, ivec2(fragCoord), 0).b;
    }

https://registry.khronos.org/OpenGL-Refpages/gl4/html/texelFetch.xhtml

distance

distance:calculate the distance between two points
https://registry.khronos.org/OpenGL-Refpages/gl4/html/distance.xhtml

参考资料

Smooth Mouse Drawing
Radiance Cascades
Outrun
OpenGL & Metal Shader 编程:ShaderToy 内置全局变量

标签:1.0,0.0,float,Smooth,vec4,xy,fragCoord,Mouse,ShadowToy
From: https://www.cnblogs.com/q10624/p/18108587

相关文章

  • wpf draw rectangle with mouse
    usingSystem;usingSystem.Collections.Generic;usingSystem.Linq;usingSystem.Text;usingSystem.Threading.Tasks;usingSystem.Windows;usingSystem.Windows.Controls;usingSystem.Windows.Data;usingSystem.Windows.Documents;usingSystem.Windows.Input;......
  • BioXCell,BE0393--InVivoMAb anti-mouse myeloperoxidase (MPO)
    BioXCell--BE0393 InVivoMAbanti-mousemyeloperoxidase(MPO)产品描述:6D1单克隆抗体与小鼠髓过氧化物酶(MPO)反应。MPO是一种过氧化物酶,也是过氧化物酶亚家族的一员。它主要由中性粒细胞表达,也可由单核细胞、巨噬细胞和某些类型的白血病细胞表达。MPO是一种溶酶体蛋白,储存......
  • WPF 非Control元素模拟鼠标双击MouseDoubleClick事件
    privatereadonlyDispatcherTimer_mouseLeftTimer=newDispatcherTimer();privatereadonlyDispatcherTimer_mouseRightTimer=newDispatcherTimer();publicClass(){_mouseLeftTimer.Interval=TimeSpan.FromMilliseconds(MOUSE_CLICK_DE......
  • [Violation ] Added non-passive event listener to ascroll- blocking ‘mousewheel
    [Violation]Addednon-passiveeventlistenertoascroll-blocking'mousewheel’eventConsidermarkingeventhandleras’passive’tomakethepagemoreresponsive.--控制台报错解决方法这个错误翻译过来的的意思就是:[违规]在ascroll中添加了非被动事件侦听器-阻......
  • 详解Smooth_L1_Loss函数的计算方式
    详解SmoothL1Loss函数的计算方式在深度学习中,SmoothL1Loss函数是一种用于回归任务的损失函数。它在一定程度上克服了均方误差(MSE)损失函数的局限性,特别适用于处理离群值。简介SmoothL1Loss函数是HuberLoss的一种近似形式。它通过引入平滑因子,使损失函数在离群值附近呈现鲁棒......
  • 为什么不使用mouseenter和mouseleave
    为什么不使用mouseenter和mouseleave之所以不总是首选mouseenter和mouseleave,是因为它们在某些场景下可能不如mouseover和mouseout通用,尤其是在需要处理包含复杂嵌套结构的组件时,有时候开发者会更关心鼠标在整个组件及其子元素范围内的进出行为,这时mouseover和mouseout可能是更......
  • F1. Smooth Sailing (Easy Version)
    F1.SmoothSailing(EasyVersion)Theonlydifferencebetweenthetwoversionsofthisproblemistheconstrainton$q$.Youcanmakehacksonlyifbothversionsoftheproblemaresolved.Thomasissailingaroundanislandsurroundedbytheocean.Theoc......
  • CodeForces 1920F2 Smooth Sailing (Hard Version)
    洛谷传送门CF传送门首先需要知道的一个trick:判断一个点是否在一个闭合回路内部,从这个点向任意方向引一条射线,若不考虑相切,那么和回路的交点为奇数时这个点在回路内部,否则在外部。那么这题要判断一个回路是否包含全部的island,可以找到任意一个island向右引一条射线。给每......
  • Luogu-P4654-[CEOI2017] Mousetrap
    前言模拟赛之后被胁迫上去讲这题,没怎么准备,然后就在几个省的OIer面前当小丑。。倒是把我自己讲得很明白,但感觉对其他人不是很负责任,就来赎罪一下。。更好的阅读体验。题意题目链接。分析以\(t\)为根,我们的目的是让老鼠走到根的操作数最小。观察老鼠的动向,显然老鼠......
  • MouseLeave MouseOut MouseEnter MouseOver
    mouseenter事件的作用与CSS伪类:hover非常相似。MouseLeave:MouseEnter:当鼠标在一个元素本身或者其子元素上移动时,mouseover事件在该元素上触发。MouseOut:MouseOver:https://developer.mozilla.org/zh-CN/docs/Web/API/Element/mouseover_event......