Shader 编程基本图形:圆和曲线

由于主流的 Shader 编程网站,如 ShaderToy, gl-transitions 都是基于 GLSL 开发 Shader ,加上 MSL 和 GLSL 语法上差别不大,后面系列文章将以 GLSL 为主来介绍 Shader 编程。

Shader 编程基本图形:圆和曲线

Shader 编程基本图形:圆和曲线

通过 Shader 实现圆形可以借助 distance 函数,用于计算两点之间的距离。我们可以通过距离某个点的距离 r , 来确定以此点为圆心半径为 r 的圆。

#iChannel0 "https://img-baofun.zhhainiao.com/pcwallpaper_ugc_mobile/static/2ddf8479959f1f3d9f52d0d561d281fe.jpg"

void mainImage(out vec4 fragColor, in vec2 fragCoord)
{
    vec2 uv = fragCoord / iResolution.xy;

    float R = 0.5;

    //计算当前像素到中心点(0.5, 0.5)的距离
    float r = distance(uv, vec2(0.5));

    if(r < R) {
        fragColor = texture2D(iChannel0, uv);
    } else {
        fragColor = vec4(0.0,0.0,0.0,1.0);
    }
}

看着边缘有锯齿,使用平滑过渡函数 smoothstep 和 mix 函数优化下。

Shader 编程基本图形:圆和曲线

GLSL 中的 mix 函数用于根据插值因子在两个值之间进行线性插值。它的函数签名如下:

mix(T x, T y, T a)

mix函数接受三个参数:

x 和 y :要进行插值的值。它们可以是任何标量或矢量类型。
a:插值因子。它可以是与 x 和 y 相同类型的标量或矢量。
mix 函数返回一个值,该值是基于插值因子a在x和y之间进行线性插值的结果。计算结果如下:

result = x * (1 - a) + y * a

优化锯齿的代码:

#iChannel0 "https://img-baofun.zhhainiao.com/pcwallpaper_ugc_mobile/static/2ddf8479959f1f3d9f52d0d561d281fe.jpg"

void mainImage(out vec4 fragColor, in vec2 fragCoord)
{
    vec2 uv = fragCoord / iResolution.xy;

    float R = 0.5;

    //计算当前像素到中心点(0.5, 0.5)的距离
    float r = distance(uv, vec2(0.5));

    vec4 imgColor = texture2D(iChannel0, uv);
    fragColor = mix(imgColor, vec4(0.0,0.0,0.0,1.0), smoothstep(R - 0.001, R + 0.001, r));
}

等等,文章的标题不是画圆吗?这为什么整了个椭圆?

其实,这里视口的宽和高并不是 1:1,但归一化之后的范围都是 0.0~1.0 ,导致 S 方向和 T 方向相同的采样值对应采样的权重不一样,比如 100×200 的视口,S 的 1.0 表示 100, T 的 1.0 表示 200 ,最后规则的圆会被拉成椭圆。

有两种解决办法:

  1. 归一化之前就是计算出半径;
  2. 归一化之后计算出 x,y 在 S,T 方向的采样权重.

归一化之前就是计算出半径

Shader 编程基本图形:圆和曲线

归一化之前就是计算出半径,这个就很好理解了,直接在像素层面做计算就没有了采样权重不同的问题。

#iChannel0 "https://img-baofun.zhhainiao.com/pcwallpaper_ugc_mobile/static/2ddf8479959f1f3d9f52d0d561d281fe.jpg"

void mainImage(out vec4 fragColor, in vec2 fragCoord)
{
    vec2 uv = fragCoord / iResolution.xy;

    float R = 0.5 * min(iResolution.x, iResolution.y);

    //计算当前像素到中心点(0.5, 0.5)的距离
    float r = distance(fragCoord, iResolution.xy / 2.0);

    vec4 imgColor = texture2D(iChannel0, uv);
    fragColor = mix(imgColor, vec4(0.0,0.0,0.0,1.0), smoothstep(R - 2.0, R + 2.0, r));
}

归一化之后计算采样权重

Shader 编程基本图形:圆和曲线

采样权重比实际上就是视口的宽高比 ratio, y 轴方向的权重比是 1.0 ,x 轴方向采样权重比就是 ratio 。

#iChannel0 "https://img-baofun.zhhainiao.com/pcwallpaper_ugc_mobile/static/2ddf8479959f1f3d9f52d0d561d281fe.jpg"

void mainImage(out vec4 fragColor, in vec2 fragCoord)
{
    vec2 uv = fragCoord / iResolution.xy;

    float ratio = iResolution.x / iResolution.y;

    float R = 0.5 * ratio;

    //计算当前像素到中心点(0.5, 0.5)的距离
    vec2 newUv = vec2(uv.x * ratio, uv.y);
    vec2 center = vec2(0.5 * ratio, 0.5);
    float r = distance(newUv, center);

    vec4 imgColor = texture2D(iChannel0, uv);
    fragColor = mix(imgColor, vec4(0.0,0.0,0.0,1.0), smoothstep(R - 0.001, R + 0.001, r));
}

线条

使用 glsl 如何画线呢?先问问 ChatGPT ,以下是 AI 给出的实现方法:

#ifdef GL_ES
precision mediump float;
#endif

uniform vec2 resolution;

void main() {
    vec2 st = gl_FragCoord.xy / resolution.xy; // 获取屏幕上每个像素的坐标

    vec3 color = vec3(1.0, 1.0, 1.0); // 设置线的颜色为白色

    // 定义起点和终点坐标
    vec2 startPoint = vec2(0.2, 0.5);
    vec2 endPoint = vec2(0.8, 0.5);

    // 计算线的斜率
    float slope = (endPoint.y - startPoint.y) / (endPoint.x - startPoint.x);

    // 计算与屏幕上每个像素的距离
    float distance = abs(st.y - startPoint.y - slope * (st.x - startPoint.x)) / sqrt(1.0 + slope * slope);

    // 设置线的宽度
    float lineWidth = 0.01;

    // 绘制线,判断像素与线距离小于线宽的范围内为线的颜色
    if (distance < lineWidth) {
        gl_FragColor = vec4(color, 1.0);
    } else {
        gl_FragColor = vec4(0.0, 0.0, 0.0, 0.0); // 其他区域透明
    }
}

勉强可以用,但是不够优雅,如果是曲线就懵逼了。

下面我们实现一个绘制曲线的通用函数,实现原理可以简单理解为,两幅图相减。

Shader 编程基本图形:圆和曲线

定义一个简单的函数曲线 y=x*x 。

void mainImage(out vec4 fragColor, in vec2 fragCoord)
{
    vec2 uv = fragCoord / iResolution.xy;

    float func = uv.x * uv.x;

    float col = step(0.0, uv.y - func);//y值大的区域变成白色

    fragColor = vec4(vec3(col), 1.0);
}

接下来我们再画一个图形,让这个白色区域向下移动一部分。

Shader 编程基本图形:圆和曲线

然后让两幅图相减,最后就留下一个偏移的线。

void mainImage(out vec4 fragColor, in vec2 fragCoord)
{
    vec2 uv = fragCoord / iResolution.xy;

    float func = uv.x * uv.x;

    float col1 = step(0.0, uv.y - func);//第一个颜色,y值大的区域变成白色

    float col2 = step(-0.01, uv.y - func);//第二个颜色,白色区域向下偏移

    float col = col2 - col1; //两幅图像相减

    fragColor = vec4(vec3(col), 1.0);
}

一条曲线勉勉强强画好了,锯齿我们用 smoothstep 优化一下,曲线位置调整一下。

优化后的代码:

float func_curve(float func, float y, float width) {
    return smoothstep(-width, 0.0, y - func) - smoothstep(0.0, width, y - func);
}

void mainImage(out vec4 fragColor, in vec2 fragCoord)
{
    vec2 uv = fragCoord / iResolution.xy;

    float func = uv.x * uv.x;

    float col = func_curve(func, uv.y, 0.01);

    fragColor = vec4(vec3(col), 1.0);
}

— END —

进技术交流群,扫码添加我的微信:Byte-Flow

字节流动

获取相关资料和源码

本文来自作者投稿,版权归原作者所有。如需转载,请注明出处:https://www.nxrte.com/jishu/28964.html

(0)

相关推荐

发表回复

登录后才能评论