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

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…...

vue3中Watch和WatchEffect的用法和区别

目录 Ⅰ.Watch 1.基本用法和三个参数的解析 (1).参数1&#xff1a;需要监听的数据源 (2).参数2&#xff1a;当监听数据发生变化时需要执行的回调函数 (3).参数3&#xff1a;配置选项 深层监听器(多种形式)&#xff1a; 关于watch的返回值问题&#xff1a; Ⅱ .WatchEff…...

Css3重点知识讲解

选择器 优先级&#xff1a; id 选择器 > 类选择器 > 标签选择器 类选择器&#xff1a; .myClass {color: blue; }id 选择器&#xff08;全局唯一&#xff09;&#xff1a; #myId {color: green; }标签选择器&#xff1a; p {color: red; }层次选择器&#xff1a; /…...

三、《重学设计模式》-单例模式

单例模式 单例模式分为四大类&#xff0c;饿汉式、懒汉式、静态内部类、枚举 饿汉式 优点&#xff1a;类装载时进行实例化&#xff0c;避免同步问题 缺点&#xff1a;造成内存浪费 实现一 1.构造器私有化 2.内部创建对象实例 3.提供静态方法 public class Type1 {public s…...

SpringBoot3整合Swagger3时出现Type javax.servlet.http.HttpServletRequest not present错误

目录 错误详情 错误原因 解决方法 引入依赖 修改配置信息 创建文件 访问 错误详情 错误原因 SpringBoot3和Swagger3版本不匹配 解决方法 使用springdoc替代springfox&#xff0c;具体步骤如下&#xff1a; 引入依赖 在pom.xml文件中添加如下依赖&#xff1a; <…...

项目实战--网页五子棋(匹配模块)(4)

上期我们完成了游戏大厅的前端部分内容&#xff0c;今天我们实现后端部分内容 1. 维护在线用户 在用户登录成功后&#xff0c;我们可以维护好用户的websocket会话&#xff0c;把用户表示为在线状态&#xff0c;方便获取到用户的websocket会话 package org.ting.j20250110_g…...

Python闭包知多少

目录 目标 Python版本 概述 实战 基本语法 数据隐藏和封装 延迟计算 回调函数 目标 熟悉闭包语法结构&#xff0c;通过案例来了解闭包的使用场景。 Python版本 Python 3.9.18 概述 闭包&#xff08;Closure&#xff09; 闭包是一个函数对象&#xff08;即内部函数或被…...

【Java毕业设计】商城购物系统(附源码+数据库脚本)

本系统是基于JavaEEServletJSPMysql实现的商城购物系统。包括用户登录、用户注册、商品分类、添加购物车、订单支付等基本功能&#xff0c;具体页面及功能如下&#xff1a; 感谢阅读&#xff01; 如需获取完整项目源码及更多项目信息&#xff0c;可添加V&#xff1a;...

css特异性,继承性

html <div class"introduce"><div class"title">介绍</div><div class"card-box"><div class"card"><div class"title">管理</div></div></div> </div> scs…...

POST请求提交数据的三种方式及通过Postman实现

1、什么是POST请求&#xff1f; POST请求是HTPP协议中一种常用的请求方法&#xff0c;它的使用场景是向客户端向服务器提交数据&#xff0c;比如登录、注册、添加等场景。另一种常用的请求方法是GET&#xff0c;它的使用场景是向服务器获取数据。 2、POST请求提交数据的常见编…...

Spring Boot 整合 Spring MVC /(整合Web)笔记

1. Spring Boot 整合 Web 功能 Spring Boot 通过自动配置简化了 Spring MVC 的集成。只需在 pom.xml 中添加 spring-boot-starter-web 依赖&#xff0c;Spring Boot 就会自动配置 Spring MVC 的相关组件。 <dependency><groupId>org.springframework.boot</gr…...

[特殊字符]清华大学:DeepSeek从入门到精通.pdf(清华领航,驾驭DeepSeek,开启AI新境界)

不愧是清华大学出品的deepseek手册&#xff0c;简直是新手 福音&#xff0c;非常实用&#xff01; 这份《DeepSeek&#xff1a;从入门到精通》手册从基础到高 阶&#xff0c;手把手教你玩转DeepSeek&#xff0c;特别适合刚入门的小白&#xff0c;拿来就能用&#xff01; 1.Deep…...

LeetCode 热题100 2. 两数相加

LeetCode 热题100 | 2. 两数相加 大家好&#xff0c;今天我们来解决一道经典的算法题——两数相加。这道题在 LeetCode 上被标记为中等难度&#xff0c;要求我们将两个非空的链表表示的整数相加&#xff0c;并以相同形式返回一个表示和的链表。下面我将详细讲解解题思路&#…...

深度学习技术全景图:从基础架构到工业落地的超级进化指南

&#x1f50d; 目录导航 基础架构革命训练优化秘技未来战场前瞻 &#x1f9e9; 一、基础架构革命 1.1 前馈神经网络&#xff08;FNN&#xff09; ▍核心结构 import torch.nn as nnclass FNN(nn.Module):def __init__(self):super().__init__()self.fc1 nn.Linear(784, 25…...

PyTorch-基础(CUDA、Dataset、transforms、卷积神经网络、VGG16)

PyTorch-基础 环境准备 CUDA Toolkit安装&#xff08;核显跳过此步骤&#xff09; CUDA Toolkit是NVIDIA的开发工具&#xff0c;里面提供了各种工具、如编译器、调试器和库 首先通过NVIDIA控制面板查看本机显卡驱动对应的CUDA版本&#xff0c;如何去下载对应版本的Toolkit工…...

IO/网络IO基础全览

目录 IO基础CPU与外设1. 程序控制IO&#xff08;轮询&#xff09;2. 中断中断相关知识中断分类中断处理过程中断隐指令 3. DMA&#xff08;Direct Memory Access&#xff09; 缓冲区用户空间和内核空间IO操作的拷贝概念传统IO操作的4次拷贝减少一个CPU拷贝的mmap内存映射文件(m…...

【DeepSeek-R1背后的技术】系列十一:RAG原理介绍和本地部署(DeepSeekR1+RAGFlow构建个人知识库)

【DeepSeek-R1背后的技术】系列博文&#xff1a; 第1篇&#xff1a;混合专家模型&#xff08;MoE&#xff09; 第2篇&#xff1a;大模型知识蒸馏&#xff08;Knowledge Distillation&#xff09; 第3篇&#xff1a;强化学习&#xff08;Reinforcement Learning, RL&#xff09;…...

鸿蒙开发深入浅出04(首页数据渲染、搜索、Stack样式堆叠、Grid布局、shadow阴影)

鸿蒙开发深入浅出04&#xff08;首页数据渲染、搜索、Stack样式堆叠、Grid布局、shadow阴影&#xff09; 1、效果展示2、ets/pages/Home.ets3、ets/views/Home/SearchBar.ets4、ets/views/Home/NavList.ets5、ets/views/Home/TileList.ets6、ets/views/Home/PlanList.ets7、后端…...

【数据结构】B树家族详解:B树、B+树、B*

一、B树(B-Tree) 1. 定义 B树是一种平衡多路查找树,自平衡的树,能够保持数据有序,设计目标是为减少磁盘I/O次数。适用于需要频繁读写磁盘的场景(如数据库、文件系统)。 2、B树基本性质 节点键值数量限制: 对于 m 阶 B 树,根节点至少有 1 个键值,最多有 m - 1 个…...

C语言数据结构—二叉树的链式结构实现

目录 1、建立二叉树 1.1 二叉树的结构 1.2 手动建立二叉树 2、二叉树的遍历 2.1 二叉树的三种遍历方式 2.1.1 前序遍历 2.1.2 中序遍历 2.1.2 后序遍历 3、求二叉树的结点数和二叉树的高度 3.1 求二叉树结点数 3.2 求二叉树叶子结点 3.3 求二叉树第k层结点的个数 …...

Java 大视界 —— Java 大数据在智能零售动态定价策略中的应用实战(98)

&#x1f496;亲爱的朋友们&#xff0c;热烈欢迎来到 青云交的博客&#xff01;能与诸位在此相逢&#xff0c;我倍感荣幸。在这飞速更迭的时代&#xff0c;我们都渴望一方心灵净土&#xff0c;而 我的博客 正是这样温暖的所在。这里为你呈上趣味与实用兼具的知识&#xff0c;也…...

如何实现修改jvm中类的属性开源项目

根据你的需求&#xff0c;以下是一些可以实现类似阿里巴巴 Diamond 功能的框架和工具&#xff0c;这些项目可以帮助你动态推送配置信息&#xff0c;从而实现类似的功能&#xff1a; 1. Nacos Nacos 是一个更现代的动态配置服务&#xff0c;支持配置管理、服务发现和元数据管理…...

危化品经营单位安全管理人员的职责及注意事项

危化品经营单位安全管理人员肩负着保障经营活动安全的重要责任&#xff0c;以下是其主要职责及注意事项&#xff1a; 职责 1. 安全制度建设与执行&#xff1a;负责组织制定本单位安全生产规章制度、操作规程和生产安全事故应急救援预案&#xff0c;确保这些制度符合国家相关法…...

腾讯云大模型知识引擎×DeepSeek赋能文旅

腾讯云大模型知识引擎DeepSeek赋能文旅 ——以合肥文旅为例的技术革新与实践路径 一、技术底座&#xff1a;知识引擎与DeepSeek的融合逻辑 腾讯云大模型知识引擎与DeepSeek模型的结合&#xff0c;本质上是**“知识库检索增强生成&#xff08;RAG&#xff09;实时联网能力”**…...

Day 49 卡玛笔记

这是基于代码随想录的每日打卡 1143. 最长公共子序列 给定两个字符串 text1 和 text2&#xff0c;返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 &#xff0c;返回 0 。 一个字符串的 子序列 是指这样一个新的字符串&#xff1a;它是由原字符串在不改变…...

WebXR教学 01 基础介绍

什么是WebXR&#xff1f; 定义 XR VR AR Web上使用XR技术的API WebXR 是一组用于在 Web 浏览器中实现虚拟现实&#xff08;VR&#xff09;和增强现实&#xff08;AR&#xff09;应用的技术标准。它由 W3C 的 Immersive Web 工作组开发&#xff0c;旨在提供跨设备的沉浸式体验…...

DeepSeek+Kimi生成高质量PPT

DeepSeek与Kimi生成PPT全流程解析 一、工具分工原理 DeepSeek核心作用&#xff1a;生成结构化PPT大纲&#xff08;擅长逻辑构建与内容优化&#xff09;Kimi核心作用&#xff1a;将文本转换为视觉化PPT&#xff08;提供模板库与排版引擎&#xff09; 二、操作步骤详解 1. 通…...

hot100---day3

二叉树复习hot100专题 144. 二叉树的前序遍历 - 力扣&#xff08;LeetCode&#xff09; 递归法的前中后序遍历&#xff0c;格式比较一致&#xff1b; class Solution { public:vector<int>& traversal(TreeNode* root, vector<int>& ans){if(rootnullpt…...

clickhouse--表引擎的使用

表引擎决定了如何存储表的数据。包括&#xff1a; 数据的存储方式和位置&#xff0c;写到哪里以及从哪里读取数据。(默认是在安装路径下的 data 路径)支持哪些查询以及如何支持。&#xff08;有些语法只有在特定的引擎下才能用&#xff09;并发数据访问。索引的使用&#xff0…...

tauri输入js脚本的方法和注意事项initialization_script

注入js脚本最常用的就是initialization_script&#xff0c;通过这个方法注入的js脚本在页面每个页面都会执行&#xff0c;这个在tauri文档也可以搜到&#xff1a;WebviewWindowBuilder in tauri::webview - Rust&#xff0c;但是请注意&#xff0c;这个方法只能用在WindowBuild…...