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 Always
        Pass
        {
            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_TOP
    if(_MainTex_TexelSize.y < 0.0)
        o.uv.w = 1.0 - o.uv.w;
    #endif

    return 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);
    }
}

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

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

相关文章

学习路程五 向量数据库Milvus操作

前序 前面安装好了docker且成功拉取Milvus镜像&#xff0c;启动。通过python成功连接上了数据。接下来就继续更多Milvus的操作 在开始之前&#xff0c;先来简单了解一下向量数据库内一些东西的基本概念 概念描述数据库&#xff08;Database&#xff09;类似与MySQL的database…

pycharm安装教程-PyCharm安装破解步骤【MAC版】

pycharm安装教程-PyCharm2023安装破解步骤【MAC版】 破解安装安装包获取 今天来给大家分享 Mac 系统安装 PyCharm&#xff0c;附带资源 破解安装&#xff0c; PyCharm 相关就不叙述了&#xff0c;直接开始安装&#xff01; 破解安装 打开下载的安装包 PyCharm2023.dmg&#x…

简单介绍 SSL 证书类型: DV、OV、EV 的区别

SSL证书类型DV、OV、EV 区别&#xff1a; DV(域名验证型)SSL证书 OV(组织验证型)SSL证书 EV(扩展验证型)SSL证书

NLP的预处理数据

处理文本数据的主要工具是Tokenizer。Tokenizer根据一组规则将文本拆分为tokens。然后将这些tokens转换为数字&#xff0c;然后转换为张量&#xff0c;成为模型的输入。模型所需的任何附加输入都由Tokenizer添加。 如果您计划使用预训练模型&#xff0c;重要的是使用与之关联的…

[Web 安全] PHP 反序列化漏洞 —— PHP 序列化 反序列化

关注这个专栏的其他相关笔记&#xff1a;[Web 安全] 反序列化漏洞 - 学习笔记-CSDN博客 0x01&#xff1a;PHP 序列化 — Serialize 序列化就是将对象的状态信息转化为可以存储或传输的形式的过程&#xff0c;在 PHP 中&#xff0c;通常使用 serialize() 函数来完成序列化的操作…

国科大——数据挖掘(0812课程)——课后作业

前沿&#xff1a; 此文章记录了2024年度秋季学期数据挖掘课程的三次课后作业&#xff0c;答案仅供参考。 第一次作业 1 假定数据仓库中包含4个维&#xff1a;date, product, vendor, location&#xff1b;和两个度量&#xff1a;sales_volume和sales_cost。 1&#xff09;画…

从电子管到量子计算:计算机技术的未来趋势

计算机发展的历史 自古以来人类就在不断地发明和改进计算工具,从结绳计数到算盘,计算尺,手摇计算机,直到1946年第一台电子计算机诞生,虽然电子计算机至今虽然只有短短的半个多世纪,但取得了惊人的发展吗,已经经历了五代的变革。计算机的发展和电子技术的发展密切相关,…

Redis核心数据结构与底层实现

5种基础数据结构 String 字符串list 列表hash 字典set 集合zset 有序集合 deepseek的回答 String 内部编码 redis根据当前值的类型和长度决定使用哪种内部编码&#xff0c;共3种内部编码&#xff1a; int &#xff1a;value为整数时embstr : 短字符串&#xff08;长度<…

【我的Android进阶之旅】Android Studio SDK Update Site 国内的腾讯云镜像配置指南

一、腾讯云的镜像 https://mirrors.cloud.tencent.com/AndroidSDK/ 二、 打开 Android Studio‌的SDK Manager 路径:Tools–>SDK Manager 在右侧找到 SDK Update Sites 列表‌‌,添加如下链接,像下面一样,一个一个添加 将下面几个链接都加上去 https:

C++知识整理day9——继承(基类与派生类之间的转换、派生类的默认成员函数、多继承问题)

文章目录 1.继承的概念和定义2.基类与派生类之间的转换3.继承中的作用域4.派生类的默认成员函数5.实现一个不能被继承的类6.继承与友元7.继承与静态成员8.多继承和菱形继承问题8.1 继承分类及菱形继承8.2 虚继承 1.继承的概念和定义 概念&#xff1a; 继承(inheritance)机制是⾯…

OpenCV计算摄影学(2)图像去噪函数denoise_TVL1()

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 原始-对偶算法是用于解决特定类型变分问题&#xff08;即&#xff0c;寻找一个函数以最小化某个泛函&#xff09;的算法。特别地&#xff0c;图像…

【Kimi】自动生成PPT-并支持下载和在线编辑--全部免费

【Kimi】免费生成PPT并免费下载 用了好几个大模型&#xff0c;有些能生成PPT内容&#xff1b; 有些能生成PPT&#xff0c;但下载需要付费&#xff1b; 目前只有Kimi生成的PPT&#xff0c;能选择模板、能在线编辑、能下载&#xff0c;关键全部免费&#xff01; 一、用kimi生成PP…

SQL注入(order by,limit),seacms的报错注入以及系统库的绕过

1&#xff1a;如果information_schema被过滤了&#xff0c;该怎么绕过 1.1&#xff1a;介绍一下information_schema这个库 information_schema 是一个非常重要的系统数据库&#xff0c;它在SQL标准中定义&#xff0c;并且被许多关系型数据库管理系统&#xff08;RDBMS&#x…

猿大师播放器:交通水利、公安消防Web端Vue网页播放20路RTSP H.265 1080P监控视频流

随着互联网技术的飞速发展&#xff0c;视频监控已成为各行各业不可或缺的一部分。无论是交通物流、公安消防&#xff0c;还是水利农业、园区校园&#xff0c;视频监控都扮演着至关重要的角色。然而&#xff0c;传统的视频监控解决方案往往依赖于特定的客户端软件&#xff0c;这…

Vue3 + Spring WebMVC 验证码案例中的跨域问题与解决方法

最近在基于vue3 SpringWebMVC前后端分离的开发环境中实现一个验证码的案例&#xff0c;在开发过程中遇到了一些复杂的跨域问题&#xff0c;现已解决&#xff0c;故将解决方法分享&#xff0c;希望能帮到有需要的人。 出现的问题&#xff1a; 对于验证码的实现&#xff0c;我选…

Mac 版 本地部署deepseek ➕ RAGflow 知识库搭建流程分享(附问题解决方法)

安装&#xff1a; 1、首先按照此视频的流程一步一步进行安装&#xff1a;(macos版&#xff09;ragflowdeepseek 私域知识库搭建流程分享_哔哩哔哩_bilibili 2、RAGflow 官网文档指南&#xff1a;https://ragflow.io 3、RAGflow 下载地址&#xff1a;https://github.com/infi…

蛋白质研究常用数据库系列1

一系列常用的蛋白质研究数据库 一 蛋白综合数据库 1.1 Uniprot UniProt&#xff08;Universal Protein Resource&#xff0c;https://www.uniprot.org/&#xff09;是一个免费开放的综合性蛋白质数据库。该数据库蛋白信息来源于EMBL、GenBank、DDBJ等公共数据库&#xff08;非…

minio作为K8S后端存储

docker部署minio mkdir -p /minio/datadocker run -d \-p 9000:9000 \-p 9001:9001 \--name minio \-v /minio/data:/data \-e "MINIO_ROOT_USERjbk" \-e "MINIO_ROOT_PASSWORDjbjbjb123" \quay.io/minio/minio server /data --console-address ":90…

深圳南柯电子|医疗设备EMC测试整改检测:零到一,保障医疗安全

在当今医疗科技飞速发展的时代&#xff0c;医疗设备的电磁兼容性&#xff08;EMC&#xff09;已成为确保其安全、有效运行的关键要素之一。EMC测试整改检测不仅关乎设备的性能稳定性&#xff0c;更是保障患者安全、避免电磁干扰引发医疗事故的重要措施。 一、医疗设备EMC测试整…

安装TortoiseGit时,显示需要安装驱动?!

安装TortoiseGit时&#xff0c;显示需要安装驱动&#xff1f; 原因分析&#xff1a; 出现上述情况&#xff0c;单纯是被捆绑了&#xff0c;TortoiseGit是不需要任何插件 解决方案&#xff1a; 在电脑上选择应用Windows安装程序