前面发了一些关于 Shader 编程的文章,有读者反馈太碎片化了,希望这里能整理出来一个系列,方便系统的学习一下 Shader 编程。
由于主流的 Shader 编程网站,如 ShaderToy, gl-transitions 都是基于 GLSL 开发 Shader ,加上 MSL 和 GLSL 语法上差别不大,后面系列文章将以 GLSL 为主来介绍 Shader 编程。
后面 Shader 编程将使用 VSCode + ShaderToy 插件作为编程环境,步骤如下:
-
下载安装 VSCode https://code.visualstudio.com/download;
-
安装 ShaderToy 插件;
-
新建以 .frag 为后缀名的文件,复制粘贴本文的代码;
-
当前代码,点击鼠标右键,选择 ShaderToy:Show GLSL Preview , 然后就可以愉快地调试特效了。
图片拉伸变形问题
#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;
fragColor = texture2D(iChannel0, uv);
}
我们使用上述代码对纹理通道进行采样,渲染一张图像,可以看到当调整窗口尺寸(iResolution)的时候,图像会因为铺满整个窗口而产生拉伸变形情况。
变形的原因这里其实就很好理解了,就是图片宽高比和窗口(视口)的宽高比不同导致的,图像在横轴和纵轴方向产生不同的 resize 强度,最终渲染出来的结果会有拉伸或者压缩的感觉。
iChannelResolution 纹理尺寸
vec3 iChannelResolution[4]
表示各个纹理通道的分辨率(宽度、高度和深度)。通道0对应sampler2D iChannel0
,通道1对应sampler2D iChannel1
,以此类推。
这个 ShaderToy 全局变量单独拿出来讲,因为纹理尺寸在实际开发中会频繁用到,主要用来解决图像的拉伸问题。
有了纹理尺寸,我们就可以在窗口中找一块宽高比和图像一样的区域,只让图像渲染到这块区域,从而避免图像拉伸。
#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;
//纹理尺寸
vec2 imgSize = iChannelResolution[0].xy;
//窗口尺寸
vec2 viewPort = iResolution.xy;
//图像宽高比
float imgRatio = imgSize.x / imgSize.y;
//窗口宽高比
float screenRatio = viewPort.x / viewPort.y;
//resizeTarget 表示窗口中与图像宽高比保持一致的区域大小
vec2 resizeTarget = viewPort;
//窗口中与图像宽高比保持一致的区域的位置
vec2 startPos = vec2(0.0);
//与窗口的一个边对齐,使图像渲染在窗口中央
if(imgRatio > screenRatio) {
resizeTarget.x = viewPort.x;
resizeTarget.y = resizeTarget.x / imgRatio;
startPos.y = (viewPort.y - resizeTarget.y) / 2.0;
} else {
resizeTarget.y = viewPort.y;
resizeTarget.x = resizeTarget.y * imgRatio;
startPos.x = (viewPort.x - resizeTarget.x) / 2.0;
}
//窗口中与图像宽高比保持一致的区域内渲染图像
if(fragCoord.x >= startPos.x && fragCoord.x <= startPos.x + resizeTarget.x
&& fragCoord.y >= startPos.y && fragCoord.y <= startPos.y + resizeTarget.y) {
uv.x = (fragCoord.x - startPos.x) / resizeTarget.x;
uv.y = (fragCoord.y - startPos.y) / resizeTarget.y;
fragColor = texture2D(iChannel0, uv);
} else {
fragColor = vec4(0.0);
}
}
后续安排
后面 OpenGL & Metal Shader 编程系列文章大致安排:
-
ShaderToy 内置全局变量
-
重要的内置函数
-
基本图形
-
距离场
-
噪声函数
-
基础特效…
-
转场特效…
-
高阶特效…
联系交流
技术交流/获取视频教程可以添加我的微信:Byte-Flow