Shader 编程:圆和曲线

news/2025/2/20 7:11:05/

该原创文章首发于微信公众号:字节流动
未经作者(微信ID:Byte-Flow)允许,禁止转载

前面发了一些关于 Shader 编程的文章,有读者反馈太碎片化了,希望这里能整理出来一个系列,方便系统的学习一下 Shader 编程。

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

后面 Shader 编程将使用 VSCode + ShaderToy 插件作为编程环境,步骤如下:

  1. 下载安装 VSCode https://code.visualstudio.com/download;
    在这里插入图片描述

  2. 安装 ShaderToy 插件;

  3. 新建以 .frag 为后缀名的文件,复制粘贴本文的代码;

  4. 当前代码,点击鼠标右键,选择 ShaderToy:Show GLSL Preview , 然后就可以愉快地调试特效了。


在这里插入图片描述

通过 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 函数优化下。

在这里插入图片描述

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 方向相同的采样值对应采样的权重不一样,比如 100x200 的视口,S 的 1.0 表示 100, T 的 1.0 表示 200 ,最后规则的圆会被拉成椭圆。

有两种解决办法:

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

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

在这里插入图片描述

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

#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));
}

归一化之后计算采样权重

采样权重比实际上就是视口的宽高比 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;
#endifuniform 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); // 其他区域透明}
}

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

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

定义一个简单的函数曲线 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);
}

效果

在这里插入图片描述

接下来我们再画一个图形,让这个白色区域向下移动一部分,然后让两幅图相减,最后就留下一个偏移的线。

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);
}

##后续安排

后面 OpenGL & Metal Shader 编程系列文章大致安排:

  1. ShaderToy 内置全局变量
  2. 重要的内置函数
  3. 基本图形
  4. 距离场
  5. 噪声函数
  6. 基础特效…
  7. 转场特效…
  8. 高阶特效…

联系交流

技术交流可以添加我的微信:Byte-Flow

联系我


http://www.ppmy.cn/news/1009663.html

相关文章

如何捕获micropython 报错的详细信息 行数 具体错误内容等

电脑端&#xff0c;可以用traceback来找 mpy没有这个模块 咋办&#xff1f; 自己搞啊 翻WIKI 有个sys.print_exception函数 然后简单包装一下 完事儿 从不废话 都是干货 import io import sysclass ERRINFO(io.IOBase):def __init__(self):self.infoself.err_lineself.linemar…

测试平台——项目模块模型类设计

这里写目录标题 一、项目应用1、项目包含接口&#xff1a;2、创建子应用3、项目模块设计a、模型类设计b、序列化器类设计c、视图类设计d、项目的增删改查操作 4、接口模块设计a、模型类设计b、序列化器类设计c、视图类设计d、接口的增删改查查操作 5、环境模块设计a、模型类设计…

【深度学习笔记】深度学习框架

本专栏是网易云课堂人工智能课程《神经网络与深度学习》的学习笔记&#xff0c;视频由网易云课堂与 deeplearning.ai 联合出品&#xff0c;主讲人是吴恩达 Andrew Ng 教授。感兴趣的网友可以观看网易云课堂的视频进行深入学习&#xff0c;视频的链接如下&#xff1a; 神经网络和…

【Linux命令详解 | pwd命令】Linux系统中用于显示当前工作目录的命令

文章标题 简介一&#xff0c;参数列表二&#xff0c;使用介绍1. pwd命令的基本使用2. pwd命令中的参数3. pwd命令的工作机制4. pwd命令的实际应用 总结 简介 pwd命令是Linux中的基础命令之一&#xff0c;使用该命令可以快速查看当前工作目录。在掌握Linux命令时&#xff0c;pw…

git【潦草学习】

初始配置git 查询版本号 初次使用git前配置用户名与邮箱地址 git config --global user.name "your name" git config --global user.name "your email" git config -l 发现最后两行多出了用户名和邮箱&#xff0c;说明配置成功

c++实现Qt对象树机制

文章目录 对象树是什么使用对象树的好处使用c实现对象树 对象树是什么 我们常常听到 QObject 会用对象树来组织管理自己&#xff0c;那什么是对象树&#xff1f;  这个概念非常好理解。因为 QObject 类就有一个私有变量 QList<QObject *>&#xff0c;专门存储这个类的子…

Java实现开发验证码

Java实现开发验证码 需求分析代码实现小结Time 需求分析 分析一下&#xff0c;需求是要我们开发一个程序&#xff0c;生成指定位数的验证码。考虑到实际工作中生成验证码的功能很多地方都会用到&#xff0c;为了提高代码的复用性&#xff0c;我们还是把生成验证码的功能写成方法…

OLED显示原理7T1C

文章目录 背景概念典型显示屏幕OLED与LCD对比----OLED更薄 PPI更高 但寿命低TFT---Thin Film Transistor薄膜场效应晶体管典型TFT材料----a-Si非晶硅、LTPS低温多晶硅、IGZO铟镓锌氧化物LTPO低温多晶氧化物----LTPS和IGZO组合使用的混合产物 OLED屏幕发光关键结构分析单个像素如…