OpenGL & Metal Shader 编程:解决图片拉伸变形问题

751 阅读2分钟

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

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

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

  1. 下载安装 VSCode code.visualstudio.com/download;

  2. 安装 ShaderToy 插件;

shaderToy.png

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

  2. 当前代码,点击鼠标右键,选择 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)的时候,图像会因为铺满整个窗口而产生拉伸变形情况。

拉伸.gif

变形的原因这里其实就很好理解了,就是图片宽高比和窗口(视口)的宽高比不同导致的,图像在横轴和纵轴方向产生不同的 resize 强度,最终渲染出来的结果会有拉伸或者压缩的感觉。

iChannelResolution 纹理尺寸

vec3 iChannelResolution[4] 表示各个纹理通道的分辨率(宽度、高度和深度)。通道0对应sampler2D iChannel0,通道1对应sampler2D iChannel1,以此类推。

这个 ShaderToy 全局变量单独拿出来讲,因为纹理尺寸在实际开发中会频繁用到,主要用来解决图像的拉伸问题。

拉伸不变形 2.gif

有了纹理尺寸,我们就可以在窗口中找一块宽高比和图像一样的区域,只让图像渲染到这块区域,从而避免图像拉伸。


#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 编程系列文章大致安排:

  1. ShaderToy 内置全局变量

  2. 重要的内置函数

  3. 基本图形

  4. 距离场

  5. 噪声函数

  6. 基础特效…

  7. 转场特效…

  8. 高阶特效…

联系交流

添加作者微信:Byte-Flow ,进技术交流群讨论。