可视化学习:如何使用后期处理通道增强图像效果

news/2025/5/16 3:28:42/文章来源:https://www.cnblogs.com/beckyyyy/p/18247720

前言

大家好,本文分享的是如何使用后期处理通道增强图像效果,通过前面几篇文章,我们了解了一些动态生成纹理的方法,比如符号距离场SDF、基于参数方程生成图案、基于噪声生成纹理,等等。这些生成纹理的技术有相似的地方,就是根据片元的纹理坐标,对片元着色,直接生成纹理。

因为GPU是并行渲染的,每个像素的着色器程序是并行执行的,这样的渲染很高效。但是在实际需求中,有时我们计算片元色值时,需要依赖周围像素点或者某个其他位置像素点的颜色信息,这样的话想要一次性完成绘制就无法做到了。我们需要先通过第一次的绘制,来得到动态生成的纹理,接着我们才能根据纹理坐标获取到这个纹理上任一位置的颜色信息,再做后续处理。也就是我们至少要执行两次处理,才能实现我们最终想要的效果。

那么具体要怎么做呢,下面我就用一个高斯模糊的例子来进行演示。

高斯模糊的例子

假设我们通过以下Shader代码绘制了随机的三角形图案。

const fragment = `#ifdef GL_ESprecision highp float;#endifvarying vec2 vUv;${distance.base}${noise.random2d}${color.hsb}void main() {vec2 st = vUv;st *= 10.0;vec2 i_st = floor(st);vec2 f_st = 2.0 * fract(st) - vec2(1);float r = random(i_st);float sign = 2.0 * step(0.5, r) - 1.0;float d = triangle_distance(f_st,vec2(-1),vec2(1),sign * vec2(1, -1));gl_FragColor.rgb = (smoothstep(-0.85, -0.6, d) - smoothstep(0.0, 0.05, d)) * hsb2rgb(vec3(r + 1.2, 0.5, r));gl_FragColor.a = 1.0;}
`;

image

以上就是动态生成的纹理,在生成的过程中我们无法直接给纹理添加高斯模糊的滤镜。

为了使用这个第一次渲染的结果,我们需要准备一个新的片元着色器。

## blurFragment
#ifdef GL_ES
precision highp float;
#endifvarying vec2 vUv;
uniform sampler2D tMap;void main() {vec4 color = texture2D(tMap, vUv);gl_FragColor.rgb = color.rgb;gl_FragColor.a = color.a;
}

这里的变量tMap就是第一次渲染生成的纹理。那么我们要怎么获取这个纹理呢?这就要用到WebGL中的帧缓冲对象,Frame Buffer Object。

当我们没有绑定帧缓冲对象时,Shader生成的图形会使用默认的缓冲区,直接输出绘制到画布上,当然这样我们是拿不到渲染结果的,这里为了对渲染结果二次加工,我们需要在执行渲染前绑定帧缓冲对象,这样在渲染时就会实现类似OffscreenCanvas的离屏绘制,将渲染结果输出到帧缓冲对象中。

const fbo = renderer.createFBO(); // 创建帧缓冲对象
renderer.bindFBO(fbo); // 绑定,指定输出到的帧缓冲对象
renderer.render(); // 输出到帧缓冲对象
renderer.bindFBO(null); // 解除绑定const blurProgram = renderer.compileSync(blurFragment, vertex);
renderer.useProgram(blurProgram);
renderer.setMeshData(program.meshData);
renderer.uniforms.tMap = fbo.texture; // 将前一个着色器程序生成的纹理作为新着色器的 tMap 变量
renderer.render();

在完成输出后,就解除绑定,并使用新的片元着色器创建一个新的着色器程序,并开启使用。

此时我们可以通过fbo.texture获取到前一个着色器程序生成的纹理,并传递给新的着色器使用。

可以看到,现在画布上的图案和之前的并没有什么区别,这是因为我们的第二次渲染,是通过纹理坐标映射直接采样、获取到颜色信息并着色。

现在我们就来添加高斯模糊的处理代码。

对高斯模糊不了解的小伙伴可以参考这篇博客,它的原理简单来说就是:

按照高斯分布的权重,对当前像素点及其周围像素点的颜色按照高斯分布的权重 加权平均。这样能让图片各像素色值与周围色值的差异减小,从而达到平滑,或者说是模糊的效果。

varying vec2 vUv; // 当前片元映射的纹理坐标
uniform sampler2D tMap;
uniform int axis; // 标记对哪个坐标轴进行高斯模糊的处理void main() {vec4 color = texture2D(tMap, vUv);// 高斯矩阵的权重值float weight[5];weight[0] = 0.227027;weight[1] = 0.1945946;weight[2] = 0.1216216;weight[3] = 0.054054;weight[4] = 0.016216;// 每一个相邻像素的坐标间隔,这里的512可以用实际的Canvas像素宽代替float tex_offset = 1.0 / 512.0;vec3 result = color.rgb;result *= weight[0];for (int i = 1; i < 5; ++ i) {float f = float(i);if (axis == 0) { // X轴的高斯模糊result += texture2D(tMap, vUv + vec2(tex_offset * f, 0.0)).rgb * weight[i];result += texture2D(tMap, vUv - vec2(tex_offset * f, 0.0)).rgb * weight[i];} else { // Y轴的高斯模糊result += texture2D(tMap, vUv + vec2(0.0, tex_offset * f)).rgb * weight[i];result += texture2D(tMap, vUv - vec2(0.0, tex_offset * f)).rgb * weight[i];}}gl_FragColor.rgb = result.rgb;gl_FragColor.a = color.a;
}

因为我们设置画布宽高是512,所以这个tex_offset表示1个像素在WebGL画布上的单位长度,通过加减tex_offset * f,就能根据坐标得到附近像素点的颜色信息,完成高斯模糊的处理。

因为高斯模糊有两个方向,所以至少要执行两次渲染,当然如果想要达到更好的效果,可以执行多次渲染。接下来我们就来修改JavaScript部分的代码。

// 创建两个FBO对象交替使用
const fbo1 = renderer.createFBO();
const fbo2 = renderer.createFBO();
// 第一次,渲染原始图形
renderer.bindFBO(fbo1);
renderer.render();
const blurProgram1 = renderer.compileSync(blurFragment1, vertex);
// 第二次,对X轴高斯模糊
renderer.useProgram(blurProgram1);
renderer.setMeshData(program.meshData);
renderer.bindFBO(fbo2);  // 绑定帧缓冲对象
renderer.uniforms.tMap = fbo1.texture;
renderer.uniforms.axis = 0;
renderer.render(); // 将第二次的绘制结果输出到帧缓冲对象
// 第三次,对Y轴高斯模糊
renderer.useProgram(blurProgram1);
renderer.bindFBO(fbo1);
renderer.uniforms.tMap = fbo2.texture;
renderer.uniforms.axis = 1;
renderer.render(); // 将第三次的绘制结果输出到帧缓冲对象
// 第四次,对X轴高斯模糊
renderer.useProgram(blurProgram1);
renderer.bindFBO(fbo2);
renderer.uniforms.tMap = fbo1.texture;
renderer.uniforms.axis = 0;
renderer.render(); // 将第四次的绘制结果输出到帧缓冲对象
// 第五次,对Y轴高斯模糊
renderer.useProgram(blurProgram1);
renderer.bindFBO(null); // 解除FBO绑定
renderer.uniforms.tMap = fbo2.texture;
renderer.uniforms.axis = 1;
renderer.render(); // 将第五次的绘制结果输出到画布上

在以上代码中,我们执行了五次渲染,第一次渲染是生成初始纹理并输出到FBO对象,后面四次是对纹理进行高斯模糊处理,在执行之后一次渲染之前,我们对FBO对象解除绑定,这样最终的渲染结果就会绘制到屏幕上。

这样我们就通过后期处理通道实现了动态纹理的平滑模糊的滤镜效果。

image

当然除了实现高斯模糊之外,我们还可以通过后期处理通道实现其他类型的二次加工。

核心原理

通过上面这个简单的例子,相信大家都知道如何去使用后期处理通道来增强图像的视觉效果了,简单来说就三个步骤:

第一步,是把第一次渲染后的图案输出到帧缓冲对象FBO中;

第二步,就是把FBO对象的内容作为纹理,再进行下一次渲染;这一步的渲染过程可以根据需要重复若干次。

第三步,就是把最终结果输出到屏幕上。

这样我们就对动态生成的纹理实现了二次加工。

总结

我相信大家看下来应该都知道怎么做了,可以自己动手尝试一下,有兴趣的小伙伴还可以去尝试实现更多的视觉效果,比如辉光效果、烟雾效果等等。

烟雾效果参考文章

高斯模糊例子

完整代码

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/724758.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Android Binder 机制之客户端注册过程

Android Binder 机制之 Service 模块 以 Hal 层进程作为 Binder 机制中服务进程模块讲解服务端如何通过 Binder 驱动向 ServiceManager 注册服务的。 以 Broadcastradiohal2.0 为例,Android 版本 Android 9.0 broadcastradiohal 启动 收音机的 Broadcastradiohal2.0 源码在 ha…

wkhtmltopdf的下载和使用

wkhtmltopdf 是一个开源的命令行工具,用于将 HTML 页面转换为 PDF 文档。它使用 Qt WebKit 渲染引擎,支持 CSS、JavaScript 等前端技术,并提供丰富的配置选项,使用户能够控制转换过程。 下载步骤 (1)打开官网链接:https://wkhtmltopdf.org/downloads.html根据自己电脑系…

centos设置自定义服务并开机自启动

1、编写自定义脚本 cat /root/test.sh !/bin/bash 每隔5s打印当前时间并输出到指定文件里 while true do echo "date +%Y-%m-%d %H:%M:%S" >> /root/a.log sleep 5 done 2、将脚本设置成服务 vim /etc/systemd/system/zdy.service [Unit] Description=zidingyi…

zig vs rust

unsafe rust VS zig https://zackoverflow.dev/writing/unsafe-rust-vs-zig/ 总结: 对于大型项目,底层用zig来代替unsafe rust 再往上层抽象时,使用rust,获得编译时检查错误。

Rockylinux学习笔记

1.镜像下载https://rockylinux.org/download2.操作系统安装和centos7安装方式一样,Rocky9默认不允许root用户登陆系统,如果需要的话,需要在安装操作系统的时候,勾选运行root用户登陆选项 3.配置IP地址 3.1 rocky 8.10 配置IP地址[root@localhost ~]# cat /etc/sysconfig/ne…

认识Linux以及常见的命令

常用的操作系统有哪些: 【1】Windows操作系统:》不同的版本:Windows XP,Windows 7,Windows 10 【2】Linux操作系统:》不同的版本:centos6.5,red hat红帽,Ubuntu乌班图 centos用的比较多,但版本比较老,服务器首选,内核比较稳定 Ubuntu用的也比较多,版本比较新 【3】…

chrome恢复旧版风格

打开 chrome://flags 参考 https://zhuanlan.zhihu.com/p/688623497 https://fast.v2ex.com/t/1000131