由于主流的 Shader 编程网站,如 ShaderToy, gl-transitions 都是基于 GLSL 开发 Shader ,加上 MSL 和 GLSL 语法上差别不大,后面系列文章将以 GLSL 为主来介绍 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 函数优化下。
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 ,最后规则的圆会被拉成椭圆。
有两种解决办法:
- 归一化之前就是计算出半径;
- 归一化之后计算出 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;
#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); // 其他区域透明
}
}
勉强可以用,但是不够优雅,如果是曲线就懵逼了。
下面我们实现一个绘制曲线的通用函数,实现原理可以简单理解为,两幅图相减。
定义一个简单的函数曲线 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);
}
— END —
进技术交流群,扫码添加我的微信:Byte-Flow
获取相关资料和源码
本文来自作者投稿,版权归原作者所有。如需转载,请注明出处:https://www.nxrte.com/jishu/28964.html