当前位置: 首页 > news >正文

Unity Shader 学习13:屏幕后处理 - 使用高斯模糊的Bloom辉光效果

        目录

一、基本的后处理流程 - 以将画面转化为灰度图为例

1. C#调用shader

2. Shader实现效果

二、Bloom辉光效果

1. 主要变量

2. Shader效果

(1)提取较亮区域 - pass1

(2)高斯模糊 - pass2&3

(3)图像混合 - pass4

3. C#调用流程


一、基本的后处理流程 - 以将画面转化为灰度图为例

需要使用到2个文件:Shader用来写效果处理,C#在每帧渲染时调用shader

① shader文件就和普通的效果一样正常写,只是处理对象是 整个场景渲染好后(此时已经是一张平面贴图)的贴图:_MainTex以及可以省略顶点着色器的输入结构体,用unity提供的appdata_img代替。

② 而C#脚本则是在OnRenderImage函数中根据算法逻辑按需使用pass进行渲染。这里将图片转为灰度图是不需要什么逻辑啦,基本上就是可以直接进行渲染,但是后面讲的bloom效果就需要加点东西。

1. C#调用shader

使用 Shader.Find 找到相应shader并创建对应的材质material,在OnRenderImage中可以利用 m.setxxx( ) 来给shader的参数赋值,再利用 Graphics.Blit(src, dest, m) 使该材质作用于_MainTex并渲染到屏幕上。

这里要传的参数就是变灰的程度。

public class BWEffect : MonoBehaviour
{Material m;[Range(0, 1)] public float bwBlend = 0;void Awake(){m = new Material(Shader.Find("Hidden/BWDiffuse"));}void OnRenderImage(RenderTexture src, RenderTexture dest){Debug.Log("OnRenderImage called");m.SetFloat("_bwBlend", bwBlend); //传参Graphics.Blit(src, dest, m); //渲染}
}

2. Shader实现效果

在片元着色器中,对_MainTex采样,并用 col.r * 0.3 + col.g * 0.59 + col.b * 0.11 提取出其灰度,用 C# 传来的 灰度程度值 原图和灰图之间做插值

Shader "Hidden/BWDiffuse"
{Properties{_MainTex ("Texture", 2D) = "white" {}_bwBlend ("WBlend", Range(0,1)) = 0}SubShader{Cull Off ZWrite Off ZTest AlwaysPass{CGPROGRAM#pragma vertex vert_img#pragma fragment frag#include "UnityCG.cginc"uniform sampler2D _MainTex;uniform float _bwBlend;fixed4 frag (v2f_img i) : SV_Target{fixed4 col = tex2D(_MainTex, i.uv); //原色float lum = col.r * 0.3 + col.g * 0.59 + col.b * 0.11; float4 bw = float4(lum, lum, lum, 1); //灰色float4 result = lerp(col, bw, _bwBlend); //插值return result;}ENDCG}}
}


二、Bloom辉光效果

Bloom效果的实现可以分为3步:

① 提取较亮区域 

② 用高斯模糊,模拟亮区的光线扩散 

③模糊图与原图混合

1. 主要变量

[Range(0, 4)] public int iterations = 3;//高斯模糊迭代次数
[Range(0.2f, 3.0f)] public float blurSpread = 0.6f;//每次迭代模糊范围的增长速度
[Range(1, 8)] public int downSample = 2;//将图片像素量减少的降采样系数,能减少需要处理的像素量,提高性能
[Range(0.0f, 4.0f)] public float luminaceThreshold = 0.6f;//模糊阈值

luminaceThreshold:模糊阈值,它能决定提取亮部的区域范围
iterations:迭代次数,可以对图片进行多次的模糊
blurSpread:每次迭代模糊后,都要对模糊范围 (BlurSize) 进行扩大,其控制每次扩大的速度
blurSize:就是上述的blurSize,其与blurSpread的关系为 1.0f + 迭代次数i * blurSpeed ,加一是为了保证值最小能为1
downSample:对原图进行降采样,也就是降低图片的像素,这样既能优化性能,又能获得更平滑的模糊效果

2. Shader效果

(1)提取较亮区域 - pass1

将图片转为灰度,灰度就能表示该像素的亮度,之后对亮度减去阈值,此时只有原本亮度值大于阈值的值能够依然保持为正数

不知道有没有人和我一样对最后一步的 c * val 有疑惑,确实暗部区域归0了,但是亮部区域也可能会变得比原本暗,这对吗?最后我的理解是,因为在最后一个pass中,将模糊后的图和原图混合的方式是 “相加”,也就是在原图亮度的基础上进行一个提亮,所以这样处理也能让辉光更加柔和,当然只是我的想法啦~

v2fExtractBright vertExtractBright (appdata_img v){v2fExtractBright o;o.pos = UnityObjectToClipPos(v.vertex);o.uv = v.texcoord;return o;
}fixed luminance(fixed4 col){//计算灰度值return col.r * 0.3 + col.g * 0.59 + col.b * 0.11 ;
} fixed4 fragExtractBright(v2fExtractBright i): SV_TARGET0{fixed4 c = tex2D(_MainTex, i.uv);fixed lum = luminance(c);fixed val = clamp(lum - _LuminaceThreshold, 0.0, 1.0);return c * val;//截取较亮区域
}

(2)高斯模糊 - pass2&3

高斯模糊的本质是对每个顶点,利用他附近的点的颜色进行平均,使得图片变得模糊。做法就不说啦,有点老生常谈,讲几个写代码时需要对算法进行优化的点:

① 优化1:

将高斯模糊分为两个pass实现:将高斯的卷积核(比如是5x5)成了一个纵向向量(5x1)与一个横向向量(1x5),也就是先对图片在纵向上模糊一次,再在横向上模糊一次,反过来也成立,这就是高斯核的分离性。

这样能节省性能开销,因为 不拆的时候,假如原图有1000x1000个像素,那么模糊需要的采样数则为1000x1000(总像素数)x5x5(每个卷积核有25个值);而如果拆成两个一维向量的乘积 进行两次模糊,就只需要1000x1000x5x2次采样。

② 优化2:

另外,由于卷积核是对称的,所以在写代码时,仅用3个位置就能表示出一个完整的高斯核。
 

_MainTex_TexelSize指的是 纹理单个像素的大小

v2fBlur vertBlurVertical (appdata_img v){v2fBlur o;o.pos = UnityObjectToClipPos(v.vertex);half2 uv = v.texcoord;//计算邻域的纹理坐标(纵向5维向量)o.uv[0] = uv;o.uv[1] = uv + float2(0.0, _MainTex_TexelSize.y * 1.0) * _BlurSize;//上移1个单位o.uv[2] = uv - float2(0.0, _MainTex_TexelSize.y * 1.0) * _BlurSize;//下移1个单位o.uv[3] = uv + float2(0.0, _MainTex_TexelSize.y * 2.0) * _BlurSize;//上移2个单位o.uv[4] = uv - float2(0.0, _MainTex_TexelSize.y * 2.0) * _BlurSize;//下移2个单位return o;
}v2fBlur vertBlurHorizontal (appdata_img v){v2fBlur o;o.pos = UnityObjectToClipPos(v.vertex);half2 uv = v.texcoord;//计算邻域的纹理坐标(横向5维向量)o.uv[0] = uv;o.uv[1] = uv + float2(_MainTex_TexelSize.x * 1.0, 0.0) * _BlurSize;//右移1个单位o.uv[2] = uv - float2(_MainTex_TexelSize.x * 1.0, 0.0) * _BlurSize;//左移1个单位o.uv[3] = uv + float2(_MainTex_TexelSize.x * 2.0, 0.0) * _BlurSize;//右移2个单位o.uv[4] = uv - float2(_MainTex_TexelSize.x * 2.0, 0.0) * _BlurSize;//左移2个单位return o;
}fixed4 fragBlur(v2fBlur i): SV_TARGET0{float weight[3] = {0.4026, 0.2442, 0.0545};//高斯核的权重值fixed3 sum;//5个权重值之和sum = tex2D(_MainTex, i.uv[0]).rbg * weight[0];for(int it = 1; it < 3; it++){sum += tex2D(_MainTex, i.uv[it*2-1]).rgb * weight[it];sum += tex2D(_MainTex, i.uv[it*2]).rgb * weight[it];}return fixed4(sum, 1.0);
}

(3)图像混合 - pass4

这就是将原图的颜色直接与模糊图的亮度进行一个叠加啦,用的是加法。

v2fBloom vertBloom (appdata_img v){v2fBloom o;o.pos = UnityObjectToClipPos(v.vertex);o.uv.xy = v.texcoord;//xy存储_MainTex的纹理坐标o.uv.zw = v.texcoord;//zw存储_Bloom的纹理坐标//平台差异兼容,做翻转处理#if UNITY_UV_STARTS_AT_TOPif(_MainTex_TexelSize.y < 0.0)o.uv.w = 1.0 - o.uv.w;#endifreturn o;
}fixed4 fragBloom(v2fBloom i): SV_TARGET0{return tex2D(_MainTex, i.uv.xy) + tex2D(_Bloom, i.uv.zw);
}

3. C#调用流程

Graphics.Blit(src, buffer0, m, 0): 先将图片降采样,用降采样后的宽高 创建临时的RenderTexture - buffer0,提取亮部存于 buffer0 中;
 Graphics.Blit(buffer0, buffer1, m, 1):之后就可以对 buffer0 进行纵向的高斯模糊,将计算结果存于新创建的buffer1
Graphics.Blit(buffer0, buffer1, m, 2)将buffer1给到buffer0,继续对 buffer0 进行横向的高斯模糊,将计算结果存于buffer1;
Graphics.Blit(buffer0, dest, m, 3)将buffer1给到buffer0,对buffer0进行原图叠加,显示到屏幕上。

每次交换缓冲区时,代码为:
RenderTexture.ReleaseTemporary(buffer0);
buffer0 = buffer1;
为什么要先释放再交换?因为 buffer 只是引用变量,后面的 “=” 不是赋值,而是只改变了引用指向,所以如果不先进行释放,原指向数据就会永远保留在内存中,有可能会引起内存泄漏。

public class BloomEffect : MonoBehaviour
{Material m;[Range(0, 4)] public int iterations = 3;//高斯模糊迭代次数[Range(0.2f, 3.0f)] public float blurSpread = 0.6f;//每次迭代模糊范围的增长速度[Range(1, 8)] public int downSample = 2;//将图片像素量减少的降采样系数,能减少需要处理的像素量,提高性能[Range(0.0f, 4.0f)] public float luminaceThreshold = 0.6f;//模糊阈值private void Awake(){m = new Material(Shader.Find("Hidden/Bloom"));}void OnRenderImage(RenderTexture src, RenderTexture dest){Debug.Log("OnRenderImage called");//降采样int rtW = src.width / downSample;int rtH = src.height / downSample;RenderTexture buffer0 = RenderTexture.GetTemporary(rtW, rtH, 0);buffer0.filterMode = FilterMode.Bilinear;//pass1,提取亮区m.SetFloat("_LuminaceThreshold", luminaceThreshold);Graphics.Blit(src, buffer0, m, 0);//pass2&3,高斯for(int i = 0; i < iterations; i++){m.SetFloat("_BlurSize", 1.0f + i * blurSpread);RenderTexture buffer1 = RenderTexture.GetTemporary(rtW, rtH, 0);Graphics.Blit(buffer0, buffer1, m, 1);//纵向RenderTexture.ReleaseTemporary(buffer0);buffer0 = buffer1;buffer1 = RenderTexture.GetTemporary(rtW, rtH, 0);Graphics.Blit(buffer0, buffer1, m, 2);//横向RenderTexture.ReleaseTemporary(buffer0);buffer0 = buffer1;}//pass4,混合m.SetTexture("_Bloom", buffer0);Graphics.Blit(buffer0, dest, m, 3);RenderTexture.ReleaseTemporary(buffer0);Graphics.Blit(src, dest, m);}
}

相关文章:

Unity Shader 学习13:屏幕后处理 - 使用高斯模糊的Bloom辉光效果

目录 一、基本的后处理流程 - 以将画面转化为灰度图为例 1. C#调用shader 2. Shader实现效果 二、Bloom辉光效果 1. 主要变量 2. Shader效果 &#xff08;1&#xff09;提取较亮区域 - pass1 &#xff08;2&#xff09;高斯模糊 - pass2&3 &#xff08;3&#xff…...

小迪安全-24天-文件管理,显示上传,黑白名单,访问控制

上节课回顾&#xff0c;token问题 没有更新token值&#xff0c;造成了复用 加上这段代码就好了&#xff0c;就不会复用了 文件管理-文件上传 upload.html文件&#xff0c;找ai生成就行 uoload.php接受文件上传的信息 这里在写个临时文件存储换个地方 因为上面临时文件存在c盘…...

java23种设计模式-建造者模式

建造者模式&#xff08;Builder Pattern&#xff09;学习笔记 1. 模式定义 建造者模式是一种创建型设计模式&#xff0c;通过分步构建复杂对象的方式&#xff0c;将对象的构建过程与表示分离。允许使用相同的构建过程创建不同的对象表示。 2. 适用场景 ✅ 需要创建包含多个…...

JMeter 中实现 100 个用户在 3 秒内并发登录

在 JMeter 中实现 100 个用户在 3 秒内并发登录,需要合理配置线程组、定时器和测试逻辑。以下是具体步骤: 1. 创建测试计划 打开 JMeter。右键点击“Test Plan”,选择 Add > Threads (Users) > Thread Group。 : 设置为 100(模拟 100 个用户)。 : 设置为 3...

SOME/IP-SD -- 协议英文原文讲解2

前言 SOME/IP协议越来越多的用于汽车电子行业中&#xff0c;关于协议详细完全的中文资料却没有&#xff0c;所以我将结合工作经验并对照英文原版协议做一系列的文章。基本分三大块&#xff1a; 1. SOME/IP协议讲解 2. SOME/IP-SD协议讲解 3. python/C举例调试讲解 5.1.2.2 S…...

IntelliJ IDEA中Maven配置全指南

一、环境准备与基础配置 1.1 Windows 环境下载并配置 Maven 见此篇博文&#xff1a;环境配置 1.2 IDEA配置步骤 打开设置面板&#xff1a;File → Settings → Build → Build Tools → Maven 关键配置项&#xff1a; Maven home path E:\apache-maven-3.9.9 &#xff08;…...

第438场周赛:判断操作后字符串中的数字是否相等、提取至多 K 个元素的最大总和、判断操作后字符串中的数字是否相等 Ⅱ、正方形上的点之间的最大距离

Q1、判断操作后字符串中的数字是否相等 1、题目描述 给你一个由数字组成的字符串 s 。重复执行以下操作&#xff0c;直到字符串恰好包含 两个 数字&#xff1a; 从第一个数字开始&#xff0c;对于 s 中的每一对连续数字&#xff0c;计算这两个数字的和 模 10。用计算得到的新…...

20-R 绘图 - 饼图

R 绘图 - 饼图 R 语言提供来大量的库来实现绘图功能。 饼图&#xff0c;或称饼状图&#xff0c;是一个划分为几个扇形的圆形统计图表&#xff0c;用于描述量、频率或百分比之间的相对关系。 R 语言使用 pie() 函数来实现饼图&#xff0c;语法格式如下&#xff1a; pie(x, l…...

【LLM】R1复现项目(SimpleRL、OpenR1、LogitRL、TinyZero)持续更新

note &#xff08;1&#xff09;未来的工作需亟待解决&#xff1a; 支持大规模 RL 训练&#xff08;PPO、GRPO 等&#xff09;的开源基础框架用于稳定训练的 GRPO 训练超参的自动化调优RL 训练数据的配比&#xff08;难度、领域、任务等&#xff09;基于 Instruct 模型训练 R…...

Linux 内核网络设备驱动编程:私有协议支持

一、struct net_device的通用性与私有协议的使用 struct net_device是Linux内核中用于描述网络设备的核心数据结构,它不仅限于TCP/IP协议,还可以用于支持各种类型的网络协议,包括私有协议。其原因如下: 协议无关性:struct net_device的设计是通用的,它本身并不依赖于任何…...

20241130 RocketMQ本机安装与SpringBoot整合

目录 一、RocketMQ简介 ???1.1、核心概念 ???1.2、应用场景 ???1.3、架构设计 2、RocketMQ Server安装 3、RocketMQ可视化控制台安装与使用 4、SpringBoot整合RocketMQ实现消息发送和接收? ? ? ? ? 4.1、添加maven依赖 ???4.2、yaml配置 ???4.3、…...

FFmpeg进化论:从av_register_all手动注册到编译期自动加载的技术跃迁

介绍 音视频开发都知道 FFmpeg,因此对 av_register_all 这个 API 都很熟悉,但ffmpeg 4.0 版本开始就已经废弃了,是旧版本中用于全局初始化的重要接口。 基本功能 核心作用:av_register_all() 用于注册所有封装器(muxer)、解封装器(demuxer)和协议处理器(protocol),…...

Http升级为Https - 开发/测试服环境

1.应用场景 主要用于开发/测试服环境将http升级为https, 防止前端web(浏览器)出现Mixed Content报错; 2.学习/操作 1.文档阅读 deepseek 问答; 2.整理输出 报错信息: Mixed Content: The page at <URL> was loaded over HTTPS, but requested an insecure XMLHttpRequ…...

C语言预编译

大家好&#xff0c;这里是小编的博客频道 小编的博客&#xff1a;就爱学编程 很高兴在CSDN这个大家庭与大家相识&#xff0c;希望能在这里与大家共同进步&#xff0c;共同收获更好的自己&#xff01;&#xff01;&#xff01; 本文目录 引言正文一、预处理的作用与流程&#xf…...

算法刷题-字符串-151.反转单词

题目 给一串字符串&#xff0c;里面有若干单词&#xff0c;以空格界定单词的结束&#xff0c;翻转其中的单词 输入&#xff1a;s " hello world " 输出&#xff1a;“world hello” 需要注意的是&#xff0c;给定的字符串可能存在头空格、尾空格以及中间的空格数量…...

单片机裸机编程:状态机与其他高效编程框架

在单片机裸机编程中&#xff0c;状态机是一种非常强大的工具&#xff0c;能够有效管理复杂的逻辑和任务切换。除了状态机&#xff0c;还有其他几种编程模式可以在不使用 RTOS 的情况下实现高效的程序设计。以下是一些常见的方法&#xff1a; 1. 状态机编程 状态机通过定义系统…...

图表控件Aspose.Diagram入门教程:使用 Python 将 VSDX 转换为 PDF

将VSDX转换为PDF可让用户轻松共享图表。PDF 文件保留原始文档的布局和设计。它们广泛用于演示文稿、报告和文档。在这篇博文中&#xff0c;我们将探讨如何在 Python 中将 VSDX 转换为 PDF。 本文涵盖以下主题&#xff1a; Python VSDX 到 PDF 转换器库使用 Python 将 VSDX 转…...

DPVS-1:编译安装DPVS (ubuntu22.04)

操作系统 rootubuntu22:~# lsb_release -a No LSB modules are available. Distributor ID: Ubuntu Description: Ubuntu 22.04.3 LTS Release: 22.04 Codename: jammy rootubuntu22:~# 前置软件准备 apt install git apt install meson apt install gcc ap…...

即将发布书籍 - Yocto项目实战教程:高效定制嵌入式Linux系统

以下这本书《Yocto项目实战教程&#xff1a;高效定制嵌入式Linux系统》即将发布&#xff0c;现在请哪位大佬出山写一个序或者推荐&#xff0c;有兴趣的大佬&#xff0c;请联系我&#xff01; Git仓库地址&#xff1a; https://github.com/jerrysundev/Yocto-Project-Book.git …...

Git 常用指令及其说明

配置相关 # 配置全局用户名 git config --global user.name "YourUsername"# 配置全局邮箱 git config --global user.email "your.emailexample.com"说明&#xff1a;这两条命令用于设置 Git 全局的用户名和邮箱&#xff0c;在提交代码时&#xff0c;这些…...

未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?

编辑&#xff1a;陈萍萍的公主一点人工一点智能 未来机器人的大脑&#xff1a;如何用神经网络模拟器实现更智能的决策&#xff1f;RWM通过双自回归机制有效解决了复合误差、部分可观测性和随机动力学等关键挑战&#xff0c;在不依赖领域特定归纳偏见的条件下实现了卓越的预测准…...

React 第五十五节 Router 中 useAsyncError的使用详解

前言 useAsyncError 是 React Router v6.4 引入的一个钩子&#xff0c;用于处理异步操作&#xff08;如数据加载&#xff09;中的错误。下面我将详细解释其用途并提供代码示例。 一、useAsyncError 用途 处理异步错误&#xff1a;捕获在 loader 或 action 中发生的异步错误替…...

7.4.分块查找

一.分块查找的算法思想&#xff1a; 1.实例&#xff1a; 以上述图片的顺序表为例&#xff0c; 该顺序表的数据元素从整体来看是乱序的&#xff0c;但如果把这些数据元素分成一块一块的小区间&#xff0c; 第一个区间[0,1]索引上的数据元素都是小于等于10的&#xff0c; 第二…...

基于FPGA的PID算法学习———实现PID比例控制算法

基于FPGA的PID算法学习 前言一、PID算法分析二、PID仿真分析1. PID代码2.PI代码3.P代码4.顶层5.测试文件6.仿真波形 总结 前言 学习内容&#xff1a;参考网站&#xff1a; PID算法控制 PID即&#xff1a;Proportional&#xff08;比例&#xff09;、Integral&#xff08;积分&…...

python爬虫:Newspaper3k 的详细使用(好用的新闻网站文章抓取和解析的Python库)

更多内容请见: 爬虫和逆向教程-专栏介绍和目录 文章目录 一、Newspaper3k 概述1.1 Newspaper3k 介绍1.2 主要功能1.3 典型应用场景1.4 安装二、基本用法2.2 提取单篇文章的内容2.2 处理多篇文档三、高级选项3.1 自定义配置3.2 分析文章情感四、实战案例4.1 构建新闻摘要聚合器…...

Mac软件卸载指南,简单易懂!

刚和Adobe分手&#xff0c;它却总在Library里给你写"回忆录"&#xff1f;卸载的Final Cut Pro像电子幽灵般阴魂不散&#xff1f;总是会有残留文件&#xff0c;别慌&#xff01;这份Mac软件卸载指南&#xff0c;将用最硬核的方式教你"数字分手术"&#xff0…...

相机Camera日志分析之三十一:高通Camx HAL十种流程基础分析关键字汇总(后续持续更新中)

【关注我,后续持续新增专题博文,谢谢!!!】 上一篇我们讲了:有对最普通的场景进行各个日志注释讲解,但相机场景太多,日志差异也巨大。后面将展示各种场景下的日志。 通过notepad++打开场景下的日志,通过下列分类关键字搜索,即可清晰的分析不同场景的相机运行流程差异…...

Matlab | matlab常用命令总结

常用命令 一、 基础操作与环境二、 矩阵与数组操作(核心)三、 绘图与可视化四、 编程与控制流五、 符号计算 (Symbolic Math Toolbox)六、 文件与数据 I/O七、 常用函数类别重要提示这是一份 MATLAB 常用命令和功能的总结,涵盖了基础操作、矩阵运算、绘图、编程和文件处理等…...

SpringTask-03.入门案例

一.入门案例 启动类&#xff1a; package com.sky;import lombok.extern.slf4j.Slf4j; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cache.annotation.EnableCach…...

2023赣州旅游投资集团

单选题 1.“不登高山&#xff0c;不知天之高也&#xff1b;不临深溪&#xff0c;不知地之厚也。”这句话说明_____。 A、人的意识具有创造性 B、人的认识是独立于实践之外的 C、实践在认识过程中具有决定作用 D、人的一切知识都是从直接经验中获得的 参考答案: C 本题解…...