Arisa Blog - 2D
type: channel
link: https://blog.fufubest.com/index.php/category/2d/
description: 2D部分
items: 滤镜
items: 随机与噪声
items: Shader里的img
items: 绘画之UV其三：Mix函数 & 重复图案
items: 绘画之UV其二：变换 & 运算
items: 绘画之UV其一：图形效果 & SDF
items: GLSL语言
items: Shader的第一步
items: 「shader的魔法」记录：Shader是？

滤镜
type: item
date: 2024-07-18T14:57:00+08:00
description: 在图像渲染完成之后，进行进一步的处理的过程，就叫做图像处理（postprocessing）。其实就是滤镜。学过CSS的人都知道属性filter，这里面就包含了各式各样的滤镜：模糊、亮度、对比度、灰度、反转之类的。这些滤镜是是直接就能用的，一行简单的：filter: xxx();就搞定了。但是，在Shader中，并没有任何内置的滤镜函数可以给我们使用，想要实现上面的滤镜效果，我们只能亲自动手去写，不过，这样做会给我们极大的灵活性，我们可以不需要按照公式化的API去做效果。染色第一个滤镜，就是染色滤镜实现非常简单，把输出结果乘上染色的颜色值就可以了#iChannel0 "https://s2.loli.net/2024/07/10/oshNYlIBJ5eAu7k.gif" void mainImage(out vec4 fragColor, in vec2 fragCoord) { vec2 uv = fragCoord / iResolution.xy; vec3 tex = texture(iChannel0, uv).xyz; vec3 col = tex; vec3 tintColor=vec3(.220,.380,.651); col *= tintColor; fragColor = vec4(col, 1.); }这样就给整体图片笼罩了一层蓝色。RGB 位移看到这个标题你想到了什么？是的，就是抖音的 logo：抖音的 logo 就是中间一个白色的音符，两侧有青和红的投影，这种其实就是色调偏移的效果。RGB位移滤镜和这种就很像，他可以将 R、G、B 三个颜色分量进行偏移，产生一种色差的效果。我们来尝试实现一下色差的效果：// 复制三份uv vec2 rUv = uv; vec2 gUv = uv; vec2 bUv = uv; // 设置一个较大的偏移量数值，方便观察 float offset = .1; // 给R和B通道进行一正一负的偏移 rUv += offset; bUv -= offset; // 采样三次纹理 vec4 rTex = texture(iChannel0, rUv); vec4 gTex = texture(iChannel0, gUv); vec4 bTex = texture(iChannel0, bUv); vec4 col = vec4(rTex.r, gTex.g, bTex.b, gTex.a); fragColor = vec4(col);效果出现了，偏移量设置的 0.1，主要是看效果。我们把偏移量变更为 0.009，就能看到可以看出来的色差，但是不会突兀的感觉。除了一般的固定便宜之外，我们还可以把上一篇的“随机”拿出来用，给偏移整点噪声。完整的代码：#iChannel0 "https://s2.loli.net/2024/07/10/oshNYlIBJ5eAu7k.gif" // 随机函数 highp float random(vec2 co) { highp float a=12.9898; highp float b=78.233; highp float c=43758.5453; highp float dt=dot(co.xy,vec2(a,b)); highp float sn=mod(dt,3.14); return fract(sin(sn)*c); } void mainImage(out vec4 fragColor, in vec2 fragCoord) { vec2 uv = fragCoord / iResolution.xy; vec3 tex = texture(iChannel0, uv).xyz; // 为了将噪声值控制在0到1 float noise=random(uv)*.5+.5; // 使用sin和cos调整一下噪点的偏移量位置 vec2 offset=.0075*vec2(cos(noise),sin(noise)); // 复制三份uv vec2 rUv = uv; vec2 gUv = uv; vec2 bUv = uv; // 给R和B通道进行一正一负的偏移 rUv += offset; bUv -= offset; // 采样三次纹理 vec4 rTex = texture(iChannel0, rUv); vec4 gTex = texture(iChannel0, gUv); vec4 bTex = texture(iChannel0, bUv); vec4 col = vec4(rTex.r, gTex.g, bTex.b, gTex.a); fragColor = vec4(col); }把噪点的值设置为 0.075 …

随机与噪声
type: item
date: 2024-07-09T17:09:00+08:00
description: 我们截止到目前为止，大部分图案都是有序和有规律的，但是现实里，大部分景色都是无序和混沌的。上面这张图，就是一个月光照在海面的景色。但是这张图，使用 Shader 画出来的。下面这张图，也是一样：一样是 Shader 写的。思考点在于：shader 怎么表现出无序？由于 shader 和数学是紧密相关的，在数学里，和这个概念接近的就是“随机”。随机函数在JavaScript中，我们可以使用内置的函数Math.random来模拟随机，但是，直到现在，GLSL好像还没有内置的随机函数可用，所幸，在Github上，已经有人封装了随机函数：glsl-random，来看看是什么样子的：highp float random(vec2 co) { highp float a=12.9898; highp float b=78.233; highp float c=43758.5453; highp float dt=dot(co.xy,vec2(a,b)); highp float sn=mod(dt,3.14); return fract(sin(sn)*c); }注意：highp 的意思就是高精度。这个随机函数的意义就是取sin函数偏后面的小数部分来模拟随机数。简单解释一下这个随机函数的意义吧：a、b、c 这些常数值是用于生成随机数的魔法数字，通常由经验或试验得出，目的是确保生成的随机数序列具有良好的随机性。这里 dot 函数计算 co 与 vec2(a, b) 的点积。点积操作将两个向量组合成一个标量值，这个值会用于后续的随机数生成。使用 mod 函数对 dt 取余，除以 3.14 使得结果限制在一个较小的范围内。这一步是为了将 dt 转换为一个周期性值，从而增强随机性。通过 sin 函数将 sn 转换成一个非线性值，然后乘以常数 c。fract 函数提取小数部分，使得返回值在 [0, 1) 范围内。跟一段 chatgpt 的总结：这个 random 函数生成一个基于输入向量 co 的伪随机数。它的原理是通过数学运算（点积、取余、正弦函数、小数部分）将输入映射到一个伪随机数，这种方法常用于着色器中生成伪随机值。这类随机函数的优点是计算简便且不需要保存状态，适合在 GPU 着色器中使用。不过，由于其伪随机性，它并不适合高要求的随机数生成场景。我们可以传入一个UV坐标，看看输出的效果：void mainImage(out vec4 fragColor,in vec2 fragCoord){ vec2 uv=fragCoord/iResolution.xy; float noise=random(uv); vec3 col=vec3(noise); fragColor=vec4(col,1.); }这就仿佛是一张坏掉的电视屏幕一般的噪点图。颗粒化滤镜如果我们将这些噪点和纹理叠加到一起，会怎么样？我们继续使用上一篇文章的纹理图链接，给我们的纹理加上噪点：（这里只附上一部分代码）vec3 col=texture(iChannel0,uv).xyz; col+=noise;可以看到噪点的强度非常的高，怎么办呢？我们可以给噪点降一些强度，也就是把噪点 noise 乘上一个 0.几的数值vec3 col=texture(iChannel0,uv).xyz; col+=noise*.2;诶，这样我们就得到了一个颗粒化的滤镜的效果，如果感觉白色太明显，我们可以再减去一个 0.5col+=(noise-.5)*.2;直接使用 noise，那么 noise 的范围在 [0, 1)，这意味着增加的值范围是 [0, 0.2)。这样会整体上增加颜色的亮度，使得画面偏白。通过 noise - 0.5，调整后的噪声值范围是 [-0.5, 0.5)，再乘以 0.2 后范围是 [-0.1, 0.1)。这使得对颜色的调整是有正有负的，部分像素的亮度会增加，部分会减少，整体上避免了颜色偏白的现象。这样就好多了，我们还可以给 random 函数加上 iTime 参数，让噪点运动起来！float noise=random(uv+iTime);但是有一个问题，因为我演示代码基本上都是用的 vscode 的 shader toy 工具，但是和浏览器对于精度的控制是不一样的，所以如果你在跟随我的代码进行编写的时候发现效果有差异，是正常的。这是 shader toy 的浏览效果：这是网页上的浏览效果：噪声噪声也是实现“随机”的一种手段，只不过它能使实现的效果更加偏向“自然”一些。平时我们在生活中看到的云雾、岩石、火焰等，都有噪声的存在。到这篇文章编写的时间为止，跟缺少内置的随机函数一样，GLSL 里同样也没有内置的噪声函数。但是在 GitHub 上，也已经有人封装好了一些常用的噪声函数——…

Shader里的img
type: item
date: 2024-07-08T13:46:00+08:00
description: 作为一个前端开发的人员，基本上离不开一个用来显示图片的标签，那就是img。业界有云：UI给出来的效果我实现不出来？那就让UI负责切图吧！在Shader里，没有img，取而代之的是纹理接下来这节内容就是把纹理，带入我们的程序中了。启动模板这边提供一个简单的启动模板和测试用图片链接：#iChannel0 "https://s2.loli.net/2023/09/10/QozT59R6KsYmb3q.jpg" void mainImage(out vec4 fragColor,in vec2 fragCoord){ vec2 uv=fragCoord/iResolution.xy; vec3 tex=texture(iChannel0,uv).xyz; fragColor=vec4(tex,1.); } 注意，关于上述的代码的一些补充说明，在之前的文章里，我们提到了Shader的编写方式分为：GLSL文件内，并且使用了vscode以及插件Shader Toy来预览Shadertoy网站上引入编写的那么，如果你是在GLSL文件内引入的，就可以直接参考我的代码来引入纹理如果，你是在Shadertoy网站上编写的Shader，步骤如下：点击右下角的黑框iChannel0点击“纹理”，然后从下面的图片列表中随便选一张即可，选完后点击右上方的叉叉关闭。接下来，引入了纹理，我们为了将它显示在屏幕上，要进行的就是采样操作，GLSL内置了一个采样函数texture，我们直接用这个将纹理给采样出来就行了，就是我上面发出的代码了。采样函数texture接受两个参数：第一个是我们的纹理iChannel0，第二个是UV坐标变量uv，采样的结果命名是tex，取得它的前3维度（暂时不需要考虑纹理的透明度），最后用变量vec4(tex, 1.)输出到屏幕上。顺利的话，我们看到的是这样的：这里要特别警告一下：对于用编辑器的朋友们来说，有可能有人在运用HTML搭载GLSL的时候，屏幕会变成黑色，并且控制台会报错，红色的错误。原因就在于，你的浏览器可能不支持WebGL2，有两个办法：1.升级你的浏览器；2.将纹理采样函数texture改名为texture2D。好，我们将纹理搬到了屏幕上，接下来就是要对他进行一些有意思的操作了！扭曲第一种操作是扭曲，通过改变纹理的UV坐标来扭曲整个图片的形状。让我们来创建一个扭曲函数，就叫做distort吧。同时将uv经过他的处理，再交由texture采样函数：#iChannel0 "https://s2.loli.net/2023/09/10/QozT59R6KsYmb3q.jpg" vec2 distort (vec2 uv) { uv.x += sin(uv.y); return uv; } void mainImage(out vec4 fragColor,in vec2 fragCoord){ vec2 uv=fragCoord/iResolution.xy; vec3 tex=texture(iChannel0,distort(uv)).xyz; fragColor=vec4(tex,1.); } 效果就是这样的：我们可以看到这图片被夸张地扭曲了，扭曲程度太大，我们需要对扭曲量进行调整调试函数这里就要用到一个非常不错的网站Graphtoy，是的，这也是iq几何大佬创建的网站，它可以非常方便的对Shader的函数进行可视化的调试。首次进入网站的时候，它会展示好几张函数的默认图像，我们直接跳过，点击Clear按钮清空，我们输入sin(x)：sin函数默认的变化幅度是蛮大的，我们要缩小幅度，要怎么做呢，那就是给x乘上一个数字，相当于变相的增加了sin函数的频率我们再整体除以50，其实相当于减少了sin函数的振幅如果我们再给sin函数内部的值加上t，也就是时间参数，他就会动起来：所以我们可以做一个有规律的律动效果：#iChannel0 "https://s2.loli.net/2023/09/10/QozT59R6KsYmb3q.jpg" vec2 distort (vec2 uv) { uv.x += sin(uv.y * 10. + iTime) / 50.; return uv; } void mainImage(out vec4 fragColor,in vec2 fragCoord){ vec2 uv=fragCoord/iResolution.xy; vec3 tex=texture(iChannel0,distort(uv)).xyz; fragColor=vec4(tex,1.); } 转场第二种操作是转场，简而言之就是从一张纹理转变成另外一张纹理。这种操作需要两张图片。引入第二张：#iChannel1 "https…

绘画之UV其三：Mix函数 & 重复图案
type: item
date: 2024-07-03T16:20:00+08:00
description: mix函数到我们现在为止，我们做出的示例图基本都是黑色和白色，非常的单调，那么我该怎么做，才可以给这些图形染上颜色呢？这就是我们马上要提到的函数，mix的工作了。这是一个GLSL的内部函数。也被称之为混合函数其实现逻辑很简单，一行代码就可以望到头#define mix(x,y,t) x*(1.-t)+y*t它接受 3 个参数：前 2 个参数x和y分别对应 2 个值，最后一个参数t代表混合程度，如果t为 0，则值就等于x；如果t为 1，则值就等于y，如果t为 0 到 1 内的值，则值就等于x与y之间逐渐变化的值。创建渐变色void mainImage(out vec4 fragColor,in vec2 fragCoord){ vec2 uvCenter=(2. * fragCoord - iResolution.xy)/iResolution.y; vec2 uv = fragCoord / iResolution.xy; vec3 col1=vec3(1.,0.,0.); vec3 col2=vec3(0.,1.,0.); vec3 col=mix(col1,col2,uvCenter.x); fragColor=vec4(col, 1.); }我声明了两个uv，一个是居中以及根据画布尺寸做了调整的归一化坐标，另一个是没有处理过的归一化坐标，我们使用了mix方法来指定了两个颜色，红色和绿色，混合程度就是uv的x轴这样一来，我们就得到了一个沿着x轴渐变的颜色给图形染色mix可以给图形染色特定的颜色。前文中，我们聊到了SDF函数，在一个形状的外部，他的值为整数，在内部，就是负数。根据这个特性，我们可以定义两个颜色值，一个是Inner一个是Outer，分别代表了形状内部和外部的颜色，我们再使用mix函数将他们混合起来，混合程度就用SDF函数平滑之后的效果float sdBox( in vec2 p, in vec2 b ) { vec2 d = abs(p)-b; return length(max(d,0.0)) + min(max(d.x,d.y),0.0); } void mainImage(out vec4 fragColor,in vec2 fragCoord){ vec2 uv =(2. * fragCoord - iResolution.xy)/iResolution.y; float d = sdBox(uv, vec2(.6, .3)); float c = smoothstep(0., .002, d); vec3 colInner = vec3(1., 0., 0.); vec3 colOuter = vec3(1., 1., 1.); vec3 col = mix(colInner, colOuter, c); fragColor = vec4(col, 1.); } 这里解释一下为什么要把平滑处理之后的结果作为混合程度：smoothstep(0., .002, d)生成了一个从0到1平滑过渡的值，具体来说：当d接近0时，c接近0，这表示该点非常接近或在矩形内部。当d接近0.002时，c接近1，这表示该点接近矩形边缘。这样做的目的是为了在矩形的边缘创建一个平滑的过渡效果，而不是产生一个硬边。这使得颜色从内部的红色（colInner）平滑过渡到外部的白色（colOuter）。smoothstep的平滑过渡效果避免了颜色的突然变化，生成视觉上更为柔和和自然的边缘。通过这种方式，c的值平滑地从0变到1，从而使mix(colInner, colOuter, c)能够创建一个从红色到白色的平滑过渡，而不是一个硬边界。这种方法通常用于反走样，以减少边缘的锯齿效应。形状转变效果mix函数不仅可以混合颜色，还可以混合别的东西，比如说形状，只要是维度相同的两个值，都可以拿来混合。比方说我现在创建两个SDF图形，长方形和圆形，把其形状通过mix混合起来，混合程度参数就用iTime。外部包裹了两层函数：1.sin函数负责周期性的变化2.abs函数负责确保混合程度的值是正数的OK，我们得到了一个随着时间流逝，在圆和长方形之间反复变化的图形重复图案之前都是单个图形进行绘制，实际上在UV变化那部分的讲解时，我们已经尝试过使用fract函数来实现“重复”的操作了，利用这一点，我们可以创建出其他的常见的重复图案。这个函数的原理是取小数点。条纹我们可以先用step函数绘制出一根线条（其实就是靠右边的一块颜色块儿）再使用fract函数来重复UV，注意这一步要写在绘制线条之前。void mainImage(out vec4 fragColor,in vec2 fragCoord){ vec2 uv = fragCoord / iResoluti…

绘画之UV其二：变换 & 运算
type: item
date: 2024-06-26T10:07:00+08:00
description: UV变换基于上一篇文章我们画好的长方形，我们开始进行UV变换相关的实践。注意：UV变换的相关代码要写在SDF函数调用之前平移总之先尝试给uv的x和y加上一些值： uv.x += .5; uv.y += .5;为什么加上值会往左下角移动呢？我们把uv坐标分布的情况用fragColor画出来就知道了：之前位于中间的原点值是(0,0)，现在则变成了(0.5,0.5)，那么上一个(0,0)则是跑到了左下角的如图位置，而SDF函数输入的坐标值的原点值是(0,0)，正好对应左下方的那个点，因此图形才会整体往左下方移动。简单来说：要确认SDF图形位置的变化，要看目前(0,0)这个点的位置变化。相反，如果要往右上方平移的话，就用减法就行了，顺带一提可以简化为： uv -= vec2(.5);缩放给UV的x和y坐标做乘运算以及除运算，可以实现缩放效果：与上面的加减法差不多的，原理一致，不赘述了，贴一张图在这里：翻转要实现翻转我们需要找一个三角形的SDF函数，sdEquilateralTriangle float sdEquilateralTriangle( in vec2 p, in float r ) { const float k = sqrt(3.0); p.x = abs(p.x) - r; p.y = p.y + r/k; if( p.x+k*p.y>0.0 ) p = vec2(p.x-k*p.y,-k*p.x-p.y)/2.0; p.x -= clamp( p.x, -2.0*r, 0.0 ); return -length(p)*sign(p.y); } ... float d = sdEquilateralTriangle(uv, .5); float c = smoothstep(0., .02, d); fragColor = vec4(vec3(c), 1.); ...我们得到的是一个三角形，要实现翻转，我们只需将uv的y乘上-1就行了。简单的逻辑原理图是这样的：之前的虚线三角形的上顶点是(0.,0.5)，乘上-1 后就变成了(0.,-0.5)，也就变成了实际三角形的下顶点。旋转这个效果有点复杂，但是好在，有已经封装好的库，2D相关的旋转函数我们直接拿来用：mat2 rotation2d(float angle){ float s=sin(angle); float c=cos(angle); return mat2( c,-s, s,c ); } vec2 rotate(vec2 v,float angle){ return rotation2d(angle)*v; }我们可以简单地讲解一下这个函数的意义，有一点需要提前了解：在数学和计算机图形学中，二维平面上的旋转通常是默认逆时针方向的。这个约定基于右手坐标系的定义，在这种坐标系中，正角度表示逆时针旋转。这其实相当于简单的复习了一下学生时代的知识。所以上面的旋转函数的实现就相当于是：通过二维矩阵的方式对uv进行乘法，得到的就是旋转之后的新的坐标。另外我们在进行旋转操作时经常会用到PI这个常量，代表 180 度。 const float PI = 3.14159265359; uv = rotate(uv, PI / 2.);最后会变成：在之前的文章中我们提到了iTime的变量，表示Shader从开始到现在执行所经过的时间。我们可以以此来做一个简单的旋转的动画效果：uv = rotate(uv, iTime)重复我们来认识一个新的内置内涵fract，它的作用很简单，就是获取一个数的小数部分。比如我们fract(1145.14)，那我们得到的值就是.14。来自2024年7月1日的修正：因此，fract函数对于负数返回的是其小数部分作为正数，这是由floor函数的特性决定的。这个方法有什么用呢？我们来试试用上图的代码运行出来的结果也在右边显示出来了。为什么会这样？我稍微做一下标记：将坐标的值乘以2，我图中给出了几个象征意义的关键点，大于1的部分的点的整数部分都被去掉了，剩下的就是他们的小数点部分，这样其实就可以达成重复的效果。那我们重新回到三角形来咯，四个小三角。怎么实现的，不用再赘述了吧。注意一点：我是把uv先乘了2.之后，再进行的居中和适应画布。这样就可以得到四个均等的区域，并且大家都是居中对齐的啦。镜像关于uv的变换，我们最后再来认识一个函数吧，大家高中有可能听说过但是大学经常会用到的：abs。它的作用是获取一个数的绝对值。函数图像长这样：由此可以看出，两个函数都是基于对应的变量轴对称，由此可以得出结论：这个函数具有“对称性”那我们来举个例子：这是镜像之前的：这个是镜像之后的：非常明显的差距，原本UV坐标的左下角的部分是负数，现在全部镜像翻转为了正数，大小还和x轴对称…

绘画之UV其一：图形效果 & SDF
type: item
date: 2024-05-20T11:17:00+08:00
description: UV坐标这是主体代码：void mainImage(out vec4 fragColor,in vec2 fragCoord){ }前面我们已经直到了，这个是主体函数mainImage，里面有两个参数，一个是四维变量 fragColor，代表输颜色、fragCoord代表输入坐标，并且我们还有一个内置的变量iResolution代表着画布的大小。在Shader中，一个变量的基本值都是分布在[0. , 1.]之间的，也就是：在图形处理中，归一化 UV 坐标将每个轴的坐标限制在 0 到 1 之间。这是大多数图形API和硬件期望的纹理坐标范围。而现在的输入坐标的值远远地超过了它，我们也就需要将它给“归一化”。UV坐标的解释U坐标：通常对应的是纹理的水平方向，对应的是传统的X轴V坐标：通常对应的是纹理的竖直方向，对应的是传统的Y轴在处理纹理时，归一化的坐标系统让你能够独立于纹理的实际像素尺寸，统一处理纹理坐标。例如，无论纹理的实际尺寸是 256x256 像素还是 1024x1024 像素，UV坐标 (0.5, 0.5) 总是指向纹理的中心点。用输入坐标fragCoord除以画布大小iResolution.xy，我们就能得到一个归一化的坐标，把它命名为uv。 vec2 uv = fragCood / iResolution.xy;来看一下x轴坐标分布情况：现在我们代码里第三个值是0，我们暂时只看前两个，可以看到x坐标从左边开始是黑色，值是(0, 0)，在最右边是红色，值是(1,0)，中间则是分布在(0, 1)之间的值。从整体上来看，我们得到的是一个从左到右渐变的图案。一样的，我们再来看一下y坐标的分布：可以看到y坐标从底下开始是黑色，值是(0,0)，到最上面是纯绿色，值是(0,1)，而中间则是分布在(0,1)之间的值。从整体上看，我们得到了一个纵向的渐变图案。接下来，我们同时输出x坐标和y坐标的分布：直接传递一个vec2作为vec4的第一个参数，剩下只需要两个参数位置了：左下角原点是黑色，值是(0,0)，右下角是红色，值是(1,0)，左上角是绿色，值是(0,1)，右上角是黄色，值是(1,1)，中间的所有值在(0,0)到(1,1)这 2 个区间分布。从整体上看，我们得到了一个有多种颜色的渐变图案。这就是所谓的UV坐标，它代表了图像（这里指画布）上所有像素的归一化后的坐标位置，其中U代表水平方向，V代表垂直方向。图形的绘制图形界最常见的东西，就是圆。说一下设计思路：先计算uv坐标上的点到原点的距离，这个可以使用GLSL自带的方法length来实现length 函数返回一个向量的长度（模），它是一个标量（即一个实数），始终为非负数。这个长度是向量从原点到指定点的距离，因此总是正数或零。会计算 uv 向量从原点 (0, 0) 到指定点 (uv.x, uv.y) 的距离，这个距离总是非负的。 float d = length(uv); fragColor = vec4(vec3(d), 1.0);实现效果是这样的：左下角是黑色的原点，值是(0,0)，从原点向右上方向辐射的径向渐变，上面每个点的值代表的就是该点到原点的距离，越靠近原点距离越小，越接近黑色，反之越远离原点距离越大，越接近白色。我们怎么把这个玩意儿挪到中间呢？前面说了，坐标是在(0,0)到(1,1)这个区间分布的，那么要居中的话，我们可以让区间扩大到(-1,-1)到(1,1)。这样会发生什么？那就是(0,0)这个原点会出现在屏幕的正中间（注意这一行代码要放在length函数代码的上面。）于是我们有了：怎么样，是不是有那种味儿了？原点黑色在中央位置，发散到四面八方，越远颜色越接近白色但是目前好像并不是完好的圆形，我们稍微把preview的窗口拉小一点：这就是一个椭圆了，这是什么问题？因为，uv坐标的值并不会去自动适应画布的大小，我们该怎么做呢？我们需要计算画布的比例，将画布长除以画布宽就能算出，再将UV的x坐标与比例相乘即可。就是这句关键的语句： uv.x *= iResolution.x / iResolution.y;调整 uv.x 可以确保纹理在不同分辨率的画布上保持正确的比例，而 uv.y 不需要调整，因为它已经在 [0.0, 1.0] 的范围内且不受画布宽高比变化的影响。那么我们整理一下：UV的最终合理的表达式是： vec2 uv = (fragCoord / iResolution.xy) * 2. - 1.; uv.x *= iResolution.x / iResolution.y;我们可以把它简化为： vec2 uv = (2.0*fragCoord-iResolution.xy)/iResolution.y;这是怎么计算出来的？首先，我们将 uv.x 调整宽高比：uv…

GLSL语言
type: item
date: 2024-05-15T22:34:00+08:00
description: 这篇文章总结了一些GLSL语言的基础知识GLSL总体上的写法和C语言相似变量声明变量的格式是这样的：格式是变量类型 变量名=值;比如我声明一个foo：float foo=1.;这样我就给float浮点类型的数字变量foo赋值了一个1.0在写的时候是可以忽视0的，1.0可以写成1.，0.5可以写成.5浮点型变量必须要有小数点，不能省略！在写GLSL的时候一定要记得：语句结尾一定要加一个分号;。这是新手最容易会犯的错误。浮点型float可以说是GLSL里最常用的类型。除此之外，还有比较常用的是int整型，代表了整数，它无需加小数点int foo=1;还有一种bool布尔型，代表逻辑值，即真（true）或假（false），通常用于条件判断和控制流程。 bool foolBool = true; bool bar = fragCoord.x < iResolution.x * .25; if (bar) { fragColor = vec4(1., 0., 0., 1.); } else { fragColor = vec4(0., 1., 0., 1.); }上面提到的变量都是一维变量（标量），如果想要声明更高维度的变量的话，就要用到vec类型（也就是向量类型）。vec2 a=vec2(1.,0.); vec3 b=vec3(1.,.5,0.); vec4 c=vec4(1.,.5,0.,1.); vec类型支持 3 种维度：二维vec2、三维vec3和四维vec4，四个维度分别为x、y、z、w（也可以写为r、g、b、a）。如果想对多维度变量进行取值或赋值操作，就要用到.符号，并且四个维度可以任意组合（称为Swizzling）。vec2 d=b.xy;// d的值为vec2(1.,.5) d.y=2.;// d的值为vec2(1.,2.) vec3 e=c.yxy;// e的值为vec3(.5,1.,.5)，c.yxy可以理解为vec3(c.y,c.x,c.y) e.zx=vec2(1.);// e的值为vec3(1.,1.,1.) 除了vec类型有更高的维度支持外，mat类型（也就是矩阵类型）亦是如此。mat2类型代表了一个大小是2x2的矩阵，mat3类型则代表了一个3x3的矩阵、mat4类型是4x4的矩阵。矩阵是一个二维数组，它的每一行代表一个向量。矩阵的每一列代表一个向量的分量。矩阵的行数和列数决定了矩阵的维度。矩阵的维度是一个二维向量，它的x分量代表行数，y分量代表列数。 mat2 f = mat2(1., 0., 0., 1.); mat3 g = mat3(1., 0., 0., 0., 1., 0., 0., 0., 1.); mat4 h = mat4(1., 0., 0., 0., 0., 1., 0., 0., 0., 0., 1., 0., 0., 0., 0., 1.);而我们要调用它也是很简单的： // 渲染矩阵 fragColor = vec4(f[0].x, f[0].y, f[1].x, f[1].y);矩阵如何调用的？矩阵的调用方式是矩阵名行号。行号和列号都是从0开始的。矩阵的行号和列号都是整数，所以可以用整数变量来调用矩阵的元素。f[0]就是矩阵f的第一行，f[1]就是矩阵f的第二行。f[0].x就是矩阵f的第一行的第一列，f[0].y就是矩阵f的第一行的第二列。如果想把多个变量捆绑到一个变量上，可以使用结构体struct，它的使用方式如下所示 struct Foo { vec2 a; vec3 b; vec4 c; }; Foo i; i.a = a; i.b = b; i.c = c; fragColor = vec4(i.a, i.b.x, i.c.y);顺便提一嘴，vec4的变量是可以合起来的，比如上面的代码示例，vec4四维变量可以用vec2（x，y）+ z + w来组合起来，很灵活在创建变量时，我们也可以先声明变量，然后再对它进行赋值操作，并且赋值是从右到左进行的，因此，我们可以同时给多个变量赋予同一个值。 float one, two, three; one = two = three = 1.;运算符算数运算符执行基本的四则运算 float computA = 1.; float computB = 2.; float computC = computA + computB; // 3. float computD = computA - computB; // -1. float computE = computA * computB; // 2. float computF = computA / computB; // 0.5 float g = mod(computA/co…

Shader的第一步
type: item
date: 2024-05-14T23:23:00+08:00
description: 在我们进行编码之前，我们应该弄一下准备工作编译语言的选择目前来说Shader的编程语言有四种GLSL：主要用于OpenGL平台的图形APIHLSL ：微软主导开发，主要用于DirectX 平台的图形APICg：Nvidia开发，可以编译为GLSL 和HLSLWGSL ：主要用于webGPU的图形API因为我的本业是前端开发，我们学习这个东西主要是在Web环境上进行的，而这个环境有两种图形API，分别是：WebGL和WebGPU。WebGL本身是基于OpenGL ES的一个JS图形API，然后OpenGL ES本身也属于OpenGL，所以我们可以在GLSL和WGSL中进行选择。（不考虑Cg，因为它主要被用于Unity平台。）WebGPU尽管较新，但浏览器的支持率并不是很高，这使得写出的效果只能在少数的浏览器下运行，用来初学不是很合适。而WebGL则是主流的浏览器几乎都支持，并且大部分网站的Shader效果也是基于这个图形平台实现的，因此我们将选择WebGL平台所对应的语言——GLSLWebGL 渲染管线流程由于GLSL是属于WebGL平台的，因此我们有必要了解下WebGL渲染管线的整个流程是什么样的。说的通俗点，就是一个物体是如何被渲染到屏幕上的这一过程。下图是我用无边记 APP 画出来的示意图：整个流程是这样的：我们在JS中提供顶点的数据（通常是 Float32Array 类型的数组，包含了顶点的位置等信息），将这些数据传递给顶点着色器，让它计算每个顶点的位置，然后WebGL将顶点装配成图元（如三角形），图元再被转换成屏幕上的空像素（光栅化），让片元着色器来计算每个像素的颜色并填充上去，最终将物体渲染到屏幕上。其中，图元装配和光栅化是WebGL自带的操作，我们无法进行额外的定制，但顶点着色器和片元着色器则是完全可通过编程来定制化的。我记录下这次的学习记录，其实也就就是要把对它们两者的定制化给做到极致。前面的内容，会专注于学习片元着色器，要渲染的物体仅仅是一个占满屏幕的平面。到后期，当接触到顶点着色器时，就可以来玩 3D 的物体了Shader 开发环境搭建Shader 的开发环境主要有 2 种：网站和编辑器。但是基于个人习惯，我选择的是 vscode 来编写在网站里开发 Shader但是我们还是不能跳过网站的编写，因为这个网站可以说是 Shader 学习者一定要了解的东西：shadertoy 是一个充满着大量优秀 Shader 作品的网站，当然，你也可以在上面直接创作你的 Shader，只需点击右上角的“新建”，就能从一个最基础的模板开始 Shader。在编辑器内开发 Shader以 VSCode 编辑器为例以下列举了一些助力 Shader 开发的插件，它们是 VSCode 编辑器专用的，如果你用的是别的编辑器，请自行寻找对应的替代品。Shader 语言支持（必须）首先，我们的 Shader 文件的代码需要有完整的高亮支持。在插件中搜索 Shader languages support for VS Code，安装即可。Shader 实时预览（必须）其次，我们希望能实时预览我们 Shader 渲染的效果。在插件中搜索 Shader Toy，安装即可。HTML 实时预览（必须）在后面的某几个章节里，我们将会把 Shader 代码直接作为字符串写入 html 文件，因此需要一个能直接预览 html 文件渲染结果的插件。在插件中搜索 Live Preview，安装即可。JS 中的 Shader 高亮（可选）在后面的某几个章节里，我们的 html 文件的 JS 部分会有 Shader 代码的字符串，它们也需要得到高亮提示。在插件中搜索 es6-string-html，安装即可。Shader 格式化（可选）有一个比较实用的插件，它为 Shader 代码提供了格式化功能，同时也提供了取色器，方便调整 3 维变量的颜色值。在插件中搜索 glsl-canvas，安装即可。第一个 Shader 程序GLSL语言的 Shader 文件后缀名就是.glsl，新建一个文件，命名为first-shader.glsl。首先我们定义 Shader 的主体：void mainImage(out vec4 fragColor,in vec2 fragCoord){ }我们定义了一个mainImage函数，它不返回任何值，故返回类型为void，它接受 2 个参数：一个是 4 维的fragColor，代表输出的像素颜色；另一个是 2 维的fragCoord，代表输入的像素坐标。直到顶点着色器之前，我们都是使用的 Shadertoy 环境，主函数 main 需要携程 mainImage，输出颜色 gl_FragColor 需写成 fragColor，输入坐标 gl_F…

「shader的魔法」记录：Shader是？
type: item
date: 2024-05-14T13:43:00+08:00
description: Shader，也就是着色器，对于我们大部分人来讲，可以说是一个既熟悉又陌生的小伙伴。经常玩游戏的小伙伴应该熟悉，《着色器编译中》，经常很费时间。让我们来看看着色器shader会在哪些地方用到吧：目前市面上大部分的3D游戏，里面的一些视觉效果都用到了Shader：上图是游戏《原神》中一个波光粼粼的水面效果，除了水体的光泽、扰动外，还有物理光学的折射现象，它们都是由Shader渲染出来的。除了3D游戏外，Shader在图像处理领域也有一定的应用。上图是一款名叫 PhotoMosh 的图像处理应用，里面内置了非常多的图像效果，比如上图用的Wobble效果可以使图片发生扭曲变形，如果你查看了网站的源码，就会发现它背后用的不是Canvas2D，而是Shader来实现的。图像处理，既可以是处理静态的图片，也可以是处理动态的视频。上图是一款名叫剪映专业版的视频剪辑软件，同样地也内置了很多视频特效，这些特效背后也是用Shader实现的。在Web页面上，我们可以看到很多图像，用Shader可以给这些内容注入一种奇妙的“魔法”，例如上图中 taotajima 网站的鼠标悬浮图片特效，体验一下还是挺有意思的。还有下面这个：云朵效果这个也是完完全全用shader制作的。那么我们来到正式的内容：shader是什么？Shader，是运行于GPU显卡里的一段程序。GPU负责整个的图形渲染流程（也被称之为“渲染管线”），而Shader就是用于让我们能够使用编程的手段来控制渲染管线的一部分的工具。最主要的Shader有两种：顶点着色器Vertex Shader 和片元着色器Fragment Shader。顶点着色器负责确定图形的每一个顶点的位置，片元着色器负责渲染图形的每一个片元（像素）的颜色。shader和其他技术的关系对于我现在的职业，前端来说，复习一下知识点吧：前端常用的图形技术有：css、svg、Canvas2D 、webGLCSS创作的效果基本都是直线式的，上图就是用CSS的animation属性配合transform属性创作的一个轨道运动的动画效果。而SVG就是拿来创造折线式或曲线式的效果。SVG有一个path标签，它可以定义出任意形状的路径，再配合stroke-dasharray、stroke-dashoffset等属性，我们能很轻松地实现如上图所示的线条运动的动画但是对于创作欲望强烈的前端开发来说，svg是无法满足的，那么canvas应运而生。它能对画面进行像素级的操控。用Canvas2D配合一些数学运算和随机函数，就可以实现这个效果。但是canvas2D对3D的效果不是太好，亦或者说是不太适合，那么我们就要搬出来webGL了WebGL是一种能让我们在浏览器中高效地渲染3D图形的技术，它把图形渲染到屏幕上的方式被称为“渲染管线”，而用编程的方式来控制渲染管线其中的一部分，则正是Shader的职责所在。因此，Shader是WebGL的一个重要的组成部分。由于原生的WebGL非常难用，我们会更倾向于使用一些封装好的框架，如 three.js，这个框架封装了很多WebGL的API，同时也封装了很多Shader，用它我们就能避免很多冗余的代码，轻松地创建出一个3D场景。但是因为封装的太多，很多场景的重合率太高了，没有个性。舍弃了个性和死掉了没什么两样所以我们这个shader分类专题，就是为了学习如何自定义shader，创造我们自己的东西。Shader的本质是用各种数学计算来操控屏幕上的像素，这带给了我们极高的自由度：小到写各种创意的图形demo，大到实现3D游戏里的场景特效，对于我们前端来说，则可以实现各种新颖的网页特效。比如这些网站：kentatoshikurahomunculushsmkrt1996nightingaleunseenzero除此之外，shader在GPU显卡的并行计算的帮加持下，渲染像素的效率非常的高。在很久以前，英伟达发布GTX280的时候，现场通过简单易懂的演示介绍了CPU和GPU的效率对比。 .video-container { position: relative; padding-bottom: 56.25%; / 16:9 aspect ratio / height: 0; overflow: hidden; max-width: 100%; background: #000;}.video-container iframe { position: absolute; top: 0; left: 0; width: 100%; height: 100%;}这个视频很形象的介绍了GPU和CPU的差距，再拿我自己的电脑举例：我的电脑的显示器是这样的：帧率是120帧，那么我的电脑的GPU需要处理的数据是：显示器分辨率和刷新率：…