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

news/2024/9/29 23:37:38

前言

大家好,本文分享的是如何使用后期处理通道增强图像效果,通过前面几篇文章,我们了解了一些动态生成纹理的方法,比如符号距离场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.hjln.cn/news/45334.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈,一经查实,立即删除!

相关文章

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…

Vue 3深度探索:自定义渲染器与服务端渲染

这篇文章介绍了如何在Vue框架中实现自定义渲染器以增强组件功能,探讨了虚拟DOM的工作原理,以及如何通过SSR和服务端预取数据优化首屏加载速度。同时,讲解了同构应用的开发方式与状态管理技巧,助力构建高性能前端应用。title: Vue 3深度探索:自定义渲染器与服务端渲染 date…

【干货分享】.NET人脸识别解决方案

前言 前段时间有同学在DotNetGuide技术社区交流群提问:.NET做人脸识别功能有什么好的解决方案推荐的吗?今天大姚给大家推荐2款.NET开源、免费、跨平台、使用简单的人脸识别库,希望可以帮助到有需要的同学。 人脸识别应用场景 现如今人脸识别应用场景比较广泛如:安防监控、人…

git-jenkins阶段04 jenkins自由风格项目(发布, 自动触发器, 返回状态给gitlab), maven项目, pipeline流水线项目

1.Jenkins创建项目点击创建一个新任务#类型: 自由风格项目: php,c,python项目 maven项目:要编译的,像java项目 pipeline:流水线操作创建一个自由风格项目,如图 项目配置中,可以选择丢弃旧的构建,只保存7天的构建记录,最多保留10个构建记录。(可配可不配) 构建一般只用shel…

ABC355E Guess the Sum 题解

前缀和上建图,有点典,但赛时没想出来ABC355E Guess the Sum 题目大意 给定一个长度为 \(2^n\) 的序列 \((A_0,A_1,\dots,A_{2^n-1})\),每次可以询问一个长度为 \(2^i\) 的区间 \([l,r]\),满足 \(l\) 是 \(2^i\) 的倍数,标准输入会返回 \([l,r]\) 的区间和 \(\bmod 10\) 的…

单文件静默安装包 2024年6月14日

单文件静默安装包 2024年6月14日"D:\Prog\7z SFX Builder\单文件静默安装包.txt" "D:\Prog\7z SFX Builder\单文件静默安装包.txt" Version 1.0 Builder 2024年6月14日1、目的目标 制作Windows系统平台上的应用软件的静默安装包, 例如:一键安装MS-Offic…

c/c++设计模式----命令模式

1. 通过一个范例引出命令模式代码编写方法//红烧鱼,锅包肉#include <iostream> #include <list>#ifdef _DEBUG //只在Debug(调试)模式下 #ifndef DEBUG_NEW #define DEBUG_NEW new(_NORMAL_BLOCK,__FILE__,__LINE__) //重新定义new运算符 #define new DEBUG_N…