Unity自定义后处理——Tonemapping色调映射

news2024/11/24 8:50:14

  大家好,我是阿赵。
  继续介绍屏幕后处理,这一期介绍一下Tonemapping色调映射

一、Tone Mapping的介绍

  Tone Mapping色调映射,是一种颜色的映射关系处理,简单一点说,一般是从原始色调(通常是高动态范围,HDR)映射到目标色调(通常是低动态范围,LDR)。
  由于HDR的颜色值是能超过1的,但实际上在LDR范围,颜色值最大只能是1。如果我们要在LDR的环境下,尽量模拟HDR的效果,超过1的颜色部分怎么办呢?
最直接想到的是两种可能:
1、截断大于1的部分
  大于1的部分,直接等于1,小于1的部分保留。这种做法,会导致超过1的部分全部变成白色,在原始图片亮度比较高的情况下,转换完之后可能就是一片白茫茫的效果。
2、对颜色进行线性的缩放
  把原始颜色的最大值看做1,然后把原始的所有颜色进行整体的等比缩放。这样做,能保留一定的效果。但由于原始的HDR颜色的跨度可能比0到1大很多,所以整体缩小之后,整个画面就会变暗很多了,没有了HDR的通透光亮的感觉。
  为了能让HDR颜色映射到LDR之后,还能保持比较接近的效果,上面两种方式的处理显然都是不好的。
  Tonemapping也是把HDR颜色范围映射到0-1的LDR颜色范围,但它并不是线性缩放,而是曲线的缩放。
在这里插入图片描述

  从上面这个例子可以看出来,Tonemapping映射之后的颜色,有些地方是变暗了,比如深颜色的裤子,但有些地方却是变亮了的,比如头发和肩膀衣服上的阴影。整体的颜色有一种电影校色之后的感觉。
  很多游戏美工在没有技术人员配合的情况下,都很喜欢自己挂后处理,其中Tonemapping应该是除了Bloom以外,美工们最喜欢的一种后处理了,虽然不知道为什么,但就是觉得颜色好看了。
  虽然屏幕后处理看着好像很简单实现,挂个组件调几个参数,就能化腐朽为神奇,把原本平淡无奇的画面变得好看。但其实后处理都是有各种额外消耗的,所以我一直不是很建议美工们只会依靠后处理来扭转画面缺陷的,特别是做手游。

二、Tonemapping的代码实现

1、C#代码

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class TonemappingCtrl : MonoBehaviour
{
    private Material toneMat;
    public bool isTonemapping = false;
    // Start is called before the first frame update
    void Start()
    {

    }

    // Update is called once per frame
    void Update()
    {

    }

    private bool TonemappingFun(RenderTexture source, RenderTexture destination)
    {
        if (toneMat == null)
        {
            toneMat = new Material(Shader.Find("Hidden/ToneMapping"));
        }
        if (toneMat == null || toneMat.shader == null || toneMat.shader.isSupported == false)
        {
            return false;
        }
        Graphics.Blit(source, destination, toneMat);
        return true;
    }

    private void OnRenderImage(RenderTexture source, RenderTexture destination)
    {
        
        if(isTonemapping == false)
        {
            Graphics.Blit(source, destination);
            return;
        }
        RenderTexture finalRt = source;
        if (TonemappingFun(finalRt,finalRt)==false)
        {
            Graphics.Blit(source, destination);
        }
        else
        {
            Graphics.Blit(finalRt, destination);
        }
    }
}

C#部分的代码和其他后处理没区别,都是通过OnRenderImage里面调用Graphics.Blit

2、Shader

Shader "Hidden/ToneMapping"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        // No culling or depth
        Cull Off ZWrite Off ZTest Always

        Pass
        {
            CGPROGRAM
            #pragma vertex vert_img
            #pragma fragment frag

            #include "UnityCG.cginc"

          

            sampler2D _MainTex; 

		float3 ACES_Tonemapping(float3 x)
		{
			float a = 2.51f;
			float b = 0.03f;
			float c = 2.43f;
			float d = 0.59f;
			float e = 0.14f;
			float3 encode_color = saturate((x*(a*x + b)) / (x*(c*x + d) + e));
			return encode_color;
		}

            fixed4 frag (v2f_img i) : SV_Target
            {
                fixed4 col = tex2D(_MainTex, i.uv);
				half3 linear_color = pow(col.rgb, 2.2);
				half3 encode_color = ACES_Tonemapping(linear_color);
				col.rgb = pow(encode_color, 1 / 2.2);
                return col;
            }
            ENDCG
        }
    }
}

需要说明一下:
1.色彩空间的转换
  由于默认显示空间是Gamma空间,所以先通过pow(col.rgb, 2.2)把颜色转换成线性空间,然后再进行Tonemapping映射,最后再pow(encode_color, 1 / 2.2),把颜色转回Gamma空间
2.Tonemapping映射算法

float3 ACES_Tonemapping(float3 x)
	{
		float a = 2.51f;
		float b = 0.03f;
		float c = 2.43f;
		float d = 0.59f;
		float e = 0.14f;
		float3 encode_color = saturate((x*(a*x + b)) / (x*(c*x + d) + e));
		return encode_color;
	}

把颜色进行Tonemapping映射。这个算法是网上都可以百度得到的。

三、Tonemapping和其他后处理的配合

  一般来说,Tonemapping只是一个固定颜色映射效果,所以应该是需要配合着其他的效果一起使用,才会得到比较好的效果。比如我之前介绍过的校色、暗角、Bloom等。
在这里插入图片描述
在这里插入图片描述

  可以做出各种不同的效果,不同于原始颜色的平淡,调整完之后的颜色看起来会比较有电影的感觉。
  这也是我为什么要在Unity有PostProcessing后处理插件的情况下,还要介绍使用自己写Shader实现屏幕后处理的原因。PostProcessing作为一个插件,它可能会存在很多功能,会有很多额外的计算,你可能只需要用到其中的某一个小部分的功能和效果。
  而我们自己写Shader实现屏幕后处理,自由度非常的高,喜欢在哪里添加或者修改一些效果,都可以。
比如,我可以写一个脚本,把之前介绍过的所有后处理效果都加进去:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//[ExecuteInEditMode]
public class ImageEffectCtrl : MonoBehaviour
{
    //--------调色-----------
    private Material colorMat;
    public bool isColorAjust = false;
    [Range(-5,5)]
    public float saturation = 1;
    [Range(-5,5)]
    public float contrast = 1;
    [Range(0,1)]
    public float hueShift = 0;
    [Range(0,5)]
    public float lightVal = 1;
    [Range(0,3)]
    public float vignetteIntensity = 1.8f;
    [Range(0,5)]
    public float vignetteSmoothness = 5;


    //-------模糊-----------
    private Material blurMat;
    public bool isBlur = false;
    [Range(0, 4)]
    public float blurSize = 0;
    [Range(-3,3)]
    public float blurOffset = 1;
    [Range(1,3)]
    public int blurType = 3;

    //-----光晕----------
    private Material brightMat;
    private Material bloomMat;
    public bool isBloom = false;
    [Range(0,1)]
    public float brightCut = 0.5f;
    [Range(0, 4)]
    public float bloomSize = 0;
    [Range(-3, 3)]
    public float bloomOffset = 1;
    public int bloomType = 3;
    [Range(1, 3)]

    //---toneMapping-----
    private Material toneMat;
    public bool isTonemapping = false;





    // Start is called before the first frame update
    void Start()
    {
        //if(colorMat == null||colorMat.shader == null||colorMat.shader.isSupported == false)
        //{
        //    this.enabled = false;
        //}
    }

    // Update is called once per frame
    void Update()
    {
        
    }

    private bool AjustColor(RenderTexture source, RenderTexture destination)
    {
        if(colorMat == null)
        {
            colorMat = new Material(Shader.Find("Hidden/AzhaoAjustColor"));
        }
        if(colorMat == null||colorMat.shader == null||colorMat.shader.isSupported == false)
        {
            return false;
        }
        colorMat.SetFloat("_Saturation", saturation);
        colorMat.SetFloat("_Contrast", contrast);
        colorMat.SetFloat("_HueShift", hueShift);
        colorMat.SetFloat("_Light", lightVal);
        colorMat.SetFloat("_VignetteIntensity", vignetteIntensity);
        colorMat.SetFloat("_VignetteSmoothness", vignetteSmoothness);
        Graphics.Blit(source, destination, colorMat, 0);
        return true;
    }

    private Material GetBlurMat(int bType)
    {
        if(bType == 1)
        {
            return new Material(Shader.Find("Hidden/AzhaoBoxBlur"));
        }
        else if(bType == 2)
        {
            return new Material(Shader.Find("Hidden/AzhaoGaussianBlur"));
        }
        else if(bType == 3)
        {
            return new Material(Shader.Find("Hidden/AzhaoKawaseBlur"));
        }
        else
        {
            return null;
        }
    }

    private bool CheckNeedCreateBlurMat(Material mat,int bType)
    {
        if(mat == null)
        {
            return true;
        }
        if(mat.shader == null)
        {
            return true;
        }
        if(bType == 1)
        {
            if(mat.shader.name != "Hidden/AzhaoBoxBlur")
            {
                return true;
            }
            else
            {
                return false;
            }
        }
        else if(bType == 2)
        {
            if (mat.shader.name != "Hidden/AzhaoGaussianBlur")
            {
                return true;
            }
            else
            {
                return false;
            }
        }
        else if (bType == 3)
        {
            if (mat.shader.name != "Hidden/AzhaoKawaseBlur")
            {
                return true;
            }
            else
            {
                return false;
            }
        }
        else
        {
            return false;
        }
    }

    private bool BlurFun(RenderTexture source, RenderTexture destination,float blurTime,int bType,float offset )
    {
        if(CheckNeedCreateBlurMat(blurMat,bType)==true)
        {
            blurMat = GetBlurMat(bType);
        }
        if (blurMat == null || blurMat.shader == null || blurMat.shader.isSupported == false)
        {
            return false;
        }
        blurMat.SetFloat("_BlurOffset", offset);
        float width = source.width;
        float height = source.height;
        int w = Mathf.FloorToInt(width);
        int h = Mathf.FloorToInt(height);
        RenderTexture rt1 = RenderTexture.GetTemporary(w, h);
        RenderTexture rt2 = RenderTexture.GetTemporary(w, h);
        Graphics.Blit(source, rt1);
        for (int i = 0; i < blurTime; i++)
        {
            ReleaseRT(rt2);
            width = width / 2;
            height = height / 2;
            w = Mathf.FloorToInt(width);
            h = Mathf.FloorToInt(height);
            rt2 = RenderTexture.GetTemporary(w, h);
            Graphics.Blit(rt1, rt2, blurMat, 0);
            width = width / 2;
            height = height / 2;
            w = Mathf.FloorToInt(width);
            h = Mathf.FloorToInt(height);
            ReleaseRT(rt1);
            rt1 = RenderTexture.GetTemporary(w, h);
            Graphics.Blit(rt2, rt1, blurMat, 1);
        }
        for (int i = 0; i < blurTime; i++)
        {
            ReleaseRT(rt2);
            width = width * 2;
            height = height * 2;
            w = Mathf.FloorToInt(width);
            h = Mathf.FloorToInt(height);
            rt2 = RenderTexture.GetTemporary(w, h);
            Graphics.Blit(rt1, rt2, blurMat, 0);
            width = width * 2;
            height = height * 2;
            w = Mathf.FloorToInt(width);
            h = Mathf.FloorToInt(height);
            ReleaseRT(rt1);
            rt1 = RenderTexture.GetTemporary(w, h);
            Graphics.Blit(rt2, rt1, blurMat, 1);
        }
        Graphics.Blit(rt1, destination);
        ReleaseRT(rt1);
        rt1 = null;
        ReleaseRT(rt2);
        rt2 = null;
        return true;
    }

    private bool BrightRangeFun(RenderTexture source, RenderTexture destination)
    {
        if(brightMat == null)
        {
            brightMat = new Material(Shader.Find("Hidden/BrightRange"));
        }
        if (brightMat == null || brightMat.shader == null || brightMat.shader.isSupported == false)
        {
            return false;
        }
        brightMat.SetFloat("_BrightCut", brightCut);
        Graphics.Blit(source, destination, brightMat);
        return true;

    }

    private bool BloomAddFun(RenderTexture source,RenderTexture destination, RenderTexture brightTex)
    {
        if(bloomMat == null)
        {
            bloomMat = new Material(Shader.Find("Hidden/AzhaoBloom"));
        }
        if (bloomMat == null || bloomMat.shader == null || bloomMat.shader.isSupported == false)
        {
            return false;
        }
        bloomMat.SetTexture("_brightTex", brightTex);
        Graphics.Blit(source, destination, bloomMat);
        return true;
    }

    private bool TonemappingFun(RenderTexture source, RenderTexture destination)
    {
        if(toneMat == null)
        {
            toneMat = new Material(Shader.Find("Hidden/ToneMapping"));
        }
        if (toneMat == null || toneMat.shader == null || toneMat.shader.isSupported == false)
        {
            return false;
        }
        Graphics.Blit(source, destination, toneMat);
        return true;
    }

    private void CopyRender(RenderTexture source,RenderTexture destination)
    {
        Graphics.Blit(source, destination);
    }

    private void ReleaseRT(RenderTexture rt)
    {
        if(rt!=null)
        {
            RenderTexture.ReleaseTemporary(rt);
        }
    }

    private void OnRenderImage(RenderTexture source, RenderTexture destination)
    {        
        RenderTexture finalRt = source;
        RenderTexture rt2 = RenderTexture.GetTemporary(source.width, source.height);
        RenderTexture rt3 = RenderTexture.GetTemporary(source.width, source.height);
        if (isBloom == true)
        {
            if(BrightRangeFun(finalRt, rt2)==true)
            {
                if(BlurFun(rt2, rt3, bloomSize,bloomType,bloomOffset)==true)
                {

                    if(BloomAddFun(source, finalRt, rt3)==true)
                    {

                    }                        
                }
            }
        }
        if(isBlur == true)
        {
            if (blurSize > 0)
            {
                if (BlurFun(finalRt, finalRt, blurSize,blurType,blurOffset) == true)
                {

                }


            }
        }

        if (isTonemapping == true)
        {
            if (TonemappingFun(finalRt, finalRt) == true)
            {

            }
        }

        if (isColorAjust == true)
        {           
            if (AjustColor(finalRt, finalRt) == true)
            {

            }

        }



        CopyRender(finalRt, destination);
        ReleaseRT(finalRt);
        ReleaseRT(rt2);
        ReleaseRT(rt3);


    }
}

在这里插入图片描述

  一个脚本控制所有后处理。当然这样的做法只是方便,也不见得很好,我还是比较喜欢根据实际用到多少个效果,单独去写对应的脚本,那样我觉得性能才是最好的。

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

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

相关文章

Langchain 集成 Milvus

Langchain 集成 Milvus 1. 安装 Docker2. 部署 Milvus3.4. Langchain 集成 Milvus 1. 安装 Docker refer: https://docs.docker.com/engine/install/centos/ Milvus 会以容器方式启动&#xff0c;所以先安装 Docker。(本示例使用的是 Alma Linux 9.2) 卸载旧版本&#xff0c…

文件上传--题目

之前有在技能树中学过文件上传&#xff0c;正好借这次进行一个整合&#xff1a; 技能树中所包含的题目类型有 无限制绕过 1.上传一句话木马 2.链接中国蚁剑 前端验证 1.会发现这个网站不让提交php&#xff0c;改后缀为jpg格式&#xff0c;再用burp抓包 2.在用中国蚁剑连接 .…

Python Web 开发及 Django 总结

title: Python Web 开发及 Django 总结 date: 2023-07-24 17:26:26 tags: PythonWeb categories:Python cover: https://cover.png feature: false Python 基础部分见&#xff1a;Python 基础总结 1. 创建项目 1.1 命令行 1、下载安装 Django 在终端输入 pip install djan…

SpringBoot——内置数据源

简单介绍&#xff1a; 在之前我们介绍SpringBoot的数据层解决方案的时候&#xff0c;曾说到过在数据层是由数据源&#xff0c;持久化技术和数据库组成的&#xff0c;之前我们一直使用的都是DruidMyBatisMySQL组合的解决方案。这三种方案在之前我们都介绍过如何整合以及基础的使…

Django模型将模型注释同步到数据库

1、安装django-comment-migrate库 pip install django-comment-migrate 2、将库注册到settings.py文件中 INSTALLED_APPS [...django_comment_migrate, # 表注释... ] 3、加注释 3.1、给模型&#xff08;表&#xff09;加注释 在模型的class Meta中编辑 verbose_name&…

嵌入式:QT Day2

一、继续完善登录框&#xff0c;当登陆成功时&#xff0c;关闭登陆页面&#xff0c;跳转到新的界面中 源码&#xff1a; widget.h #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QDebug> //用于打印输出 #include <QIcon> …

部署 cacti 监控系统

Cacti Cacti&#xff08;流量和性能监测为主&#xff09; Cacti 在英文中的意思是仙人掌的意思&#xff0c;Cacti 是一套基于 PHP、MySQL、SNMP 及 RRDTool 开发的网络流量监测图形分析工具。它通过 snmpget 来获取数据&#xff0c;使用 RRDtool 绘画图形&#xff0c;而且你完…

LiveGBS流媒体平台GB/T28181常见问题-国标设备列表没有数据海康大华宇视华为监控摄像机NVR硬件设备注册不上来如何排查?

LiveGBS中国标设备列表没有数据海康大华宇视华为监控摄像机NVR硬件设备注册不上来如何排查&#xff1f; 1、国标设备列表看不到注册上来的设备2、检查方式2.1、检查设备注册信息2.2、检查服务器防火墙 3、尝试配置免密接入3.1、基础配置->白名单3.2、添加白名单 4、更多排查…

C# 目标平台为x64,自定义控件不可用,显示控件未能加载,错误解决方法

由于项目加载第三方的dll需要编译成x64&#xff0c;设置编译目标为x64 结果打开窗口设计器时&#xff0c;自定义的控件不能显示及加载 错误消息&#xff1a;未能找到类型“XXX”。请确保已引用包含此类型的程序集。如果此类型为开发项目的一部分&#xff0c;请确保已使用针对当…

ChatGPT有几个版本,哪个版本最强,如何选择适合自己的?

​ChatGPT就像内容生产界的瑞士军刀。它可以是数学导师、治疗师、职业顾问、编程助手&#xff0c;甚至是旅行指南。只要你知道如何让它做你想做的事&#xff0c;ChatGPT几乎可以提供你要的任何东西。 但重要的是&#xff0c;你知道哪个版本的ChatGPT最能满足你的需求吗&#x…

STM32CubeIDE(I2C)

目录 一、IIC轮询模式 1.1 配置 1.2 编写AHT20驱动 1.2.1 aht20.h 1.2.2 aht20.c 二、I2C中断 2.1 打开中断 2.2 分离读取流程 2.3 在主函数中重新编写读取流程 2.4 在i2c.c中重新定义stm32f1xx_hal_i2c.h中的两个函数 三、I2CDMA 3.1 配置DMA通道 3.2 代码的修改 一…

【优选算法题练习】day9

文章目录 一、DP35 【模板】二维前缀和1.题目简介2.解题思路3.代码4.运行结果 二、面试题 01.01. 判定字符是否唯一1.题目简介2.解题思路3.代码4.运行结果 三、724. 寻找数组的中心下标1.题目简介2.解题思路3.代码4.运行结果 总结 一、DP35 【模板】二维前缀和 1.题目简介 DP…

LeetCode三步问题(动态规划)

LeetCode三步问题&#xff08;动态规划&#xff09; 编写代码代码优化 链接: 三步问题 编写代码 class Solution { public:int waysToStep(int n) {if(n 1 || n 2) return n;vector<int> dp(n1);const int MOD 1e9 7;dp[0] dp[1] 1;dp[2] 2;for(int i 3;i<n…

WEB:file_include

背景知识 php伪协议 文件包含漏洞 php包含漏洞函数 题目 由题目可知这个是文件包含的题目&#xff0c;先用常用的协议先查看一下 payload ?filenamephp://filter/readconvert.base64-encode/resourceflag.php 出现了 发现filter&#xff0c;base64被过滤了 尝试其他协议 …

Elasticsearch监控工具Cerebro安装

Elasticsearch监控工具Cerebro安装 1、在windwos下的安装 1.1 下载安装包 https://github.com/lmenezes/cerebro/releases/download/v0.9.4/cerebro-0.9.4.zip 1.2 解压 1.3 修改配置文件 如果需要修改相关信息&#xff0c;编辑C:\zsxsoftware\cerebro-0.9.4\conf\applica…

c语言用冒泡排序模拟实现qsort排序

1、简单介绍冒泡排序 冒泡排序就是两两相邻元素进行比较&#xff0c;如果不满足顺序就进行交换。现有一组整数&#xff0c;将其用冒泡排序实现排序为升序。 假设有这样一组整数&#xff1a;9 8 7 6 5 由此可知&#xff0c;如果一个整型数组有num个元素&#xff0c;则需走num…

第一次作业 运维高级 MySQL备份与还原

1.创建student和score表 CREATE TABLE student ( id INT(10) NOT NULL UNIQUE PRIMARY KEY , name VARCHAR(20) NOT NULL , sex VARCHAR(4) , birth YEAR, department VARCHAR(20) , address VARCHAR(50) );CREATE TABLE score ( id INT(10) NOT NULL UNIQUE PRIMARY KEY AUTO…

交叉编译----宿主机x86 ubuntu 64位-目标机ARMv8 aarch64

1.交叉编译是什么&#xff0c;为什么要交叉编译 编译&#xff1a;在一个平台上生成在该平台上的可执行代码交叉编译&#xff1a;在一个平台上生成在另一个平台上的可执行代码交叉编译的例子&#xff1a;如51单片机的可执行代码&#xff08;hex文件&#xff09;是在集成环境kei…

区间预测 | MATLAB实现QRGRU门控循环单元分位数回归多输入单输出区间预测

区间预测 | MATLAB实现QRGRU门控循环单元分位数回归时间序列区间预测 目录 区间预测 | MATLAB实现QRGRU门控循环单元分位数回归时间序列区间预测效果一览基本介绍模型描述程序设计参考资料 效果一览 基本介绍 MATLAB实现QRGRU门控循环单元分位数回归分位数回归多输入单输出区间…

【Docker】安全及日志管理

目录 一、Docker 安全及日志管理1.1 Docker 容器与虚拟机的区别1. 隔离与共享2. 性能与损耗 1.2Docker 存在的安全问题1.Docker 自身漏洞2.Docker 源码问题 1.3 Docker 架构缺陷与安全机制1. 容器之间的局域网攻击2. DDoS 攻击耗尽资源3. 有漏洞的系统调用4. 共享root用户权限 …