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

  大家好,我是阿赵。
  继续介绍屏幕后处理,这一期介绍一下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);


    }
}

在这里插入图片描述

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

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

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

相关文章

SpringBoot前后端分离项目中实现将图片上传至Linux服务器(极简)

FileController /*** 文件上传至服务器 */ ApiOperation("文件上传") PostMapping("/upload") public R upload(MultipartFile file){String uploadUrl fileService.upload(file);return R.ok().message("文件上传成功").data("url",…

idea使用命令将jar包导入到maven仓库中

因为今天突然忘了命令&#xff0c;记下来方便以后查看 pom文件的依赖 jar包路径 进入idea中命令窗 输入命令 mvn install:install-file -DfileD:\Project\spring-cloud\dubbo-api\target\dubbo-api-1.0-SNAPSHOT.jar -DgroupIdcom.wmx -DartifactIddubbo-api -Dversion1.0…

百度智能云连拿四年第一,为什么要深耕AI公有云市场

AI是过去几年云计算市场中的最大变量&#xff0c;而大模型的成熟&#xff0c;毫无疑问将指数级增强这个变量。 记得在2022年年底&#xff0c;生成式AI与大模型开始爆火的时候&#xff0c;我们就曾讨论过一个问题&#xff1a;这轮AI浪潮中&#xff0c;最先受到深刻影响的将是云计…

性能优化问题

提升首屏的加载速度&#xff0c;是前端性能优化中「最重要」的环节&#xff0c;这里笔者梳理出一些 常规且有效 的首屏优化建议 1、路由懒加载 SPA 项目&#xff0c;一个路由对应一个页面&#xff0c;如果不做处理&#xff0c;项目打包后&#xff0c;会把所有页面打包成一个文…

递归对比对象函数

在JavaScript中&#xff0c;对象之间的比较通常通过引用进行。当你使用运算符比较两个对象时&#xff0c;它会检查它们是否引用了同一个内存地址&#xff0c;而不是逐个比较对象的属性。 上图可见&#xff0c;obj1和{}是两个不同的对象&#xff0c;尽管它们具有相同的结构&…

集装箱装卸作业相关的知识-Part1

1.角件 Corner Fitting of Container or called Corner Casting. there are eigth of it of one container. 国家标准|GB/T 1835-2006https://openstd.samr.gov.cn/bzgk/gb/newGbInfo?hcnoD35857F2200FA115CAA217A114F5EF12 中国的国标&#xff1a;GB/T 1835-2006《系列1集…

智慧城市环境污染数据采集远程监控方案4G工业路由器应用

随着科技水平的发展和人民生活水平的提高&#xff0c;城市环境污染问题日渐严峻&#xff0c;尤其是在发展迅速的国家&#xff0c;环境污染问题便更为突出。许多发达国家将重污染工厂搬到发展中国家&#xff0c;这导致发展中国家的环境污染日益严重。严重的环境污染也带来了一系…

【uni-app2.0】实现登录页记住密码功能

使用uni-app的uni.setStorageSync()和uni.getStorageSync()方法来存储和读取密码 在登录页中添加一个记住密码的u-checkbox选项&#xff0c;并在data里面添加一个rememberPwd的布尔值&#xff0c;在每次点击记住密码change的时候来记录用户的选择 <u-checkbox-group place…

作为开发人员,无代码开发平台 iVX 你有必要了解一下

低代码开发平台作为一种快速、简化应用程序开发的方法&#xff0c;正在越来越受到关注。今天我们来了解下 iVX —— 首个通用无代码开发平台。 那么什么是iVX呢&#xff1f;下边的图就比较形象了。 文章目录 低代码未来的发展方向整合硬件和AI能力 编程真的很困难吗&#xff1…

SpringBoot创建和使⽤

1.什么是Spring Boot&#xff1f;为什么要学Spring Boot&#xff1f; Spring 的诞⽣是为了简化 Java 程序的开发的&#xff0c;⽽ Spring Boot 的诞⽣是为了简化 Spring 程序开发 的。 Spring Boot 翻译⼀下就是 Spring 脚⼿架&#xff0c;什么是脚⼿架呢&#xff1f;如下图所…

分享200+个关于AI的网站

分享200个关于AI的网站 欢迎大家访问&#xff1a;https://tools.haiyong.site/ai 快速导航 AI 应用AI 写作AI 编程AI 设计AI 作图AI 训练模型AI 影音编辑AI 效率助手 AI 应用 文心一言: https://yiyan.baidu.com/ 百度出品的人工智能语言模型 ChatGPT: https://chat.openai.c…

数据可视化(3)

1.饼状图 #饼状图 #pie&#xff08;x,labels,colors,labeldistance,autopct,startangle,radius,center,textprops&#xff09; #x,每一块饼状图的比例 #labels:每一块饼形图外侧显示的文字说明 #labeldistance&#xff1a;标记的绘制位置&#xff0c;相对于半径的比例&#xf…

js获取本js文件名称

使用 script 标签的 src 属性&#xff1a; 如果您的 JavaScript 文件是通过 script 标签引入的&#xff0c;您可以使用以下方法来获取当前脚本的文件名&#xff1a; var scripts document.getElementsByTagName(script); var currentScript scripts[scripts.length - 1]; va…

1 js嵌入html使用

1.1 直接在html内部使用js代码 使用script标签&#xff0c;在前后标签内部写的代码即为js代码。 <body><p id"p1">初始段落</p> <!--id是为了定位需要更改内容的标签--><button type"button" onclick"showNum()">…

测试开源C#人脸识别模块ViewFaceCore(2:人脸关键点定位器和活体检测)

ViewFaceCore模块中的FaceLandmarker类支持识别人脸关键点&#xff0c;也即人脸上的关键位置的坐标&#xff0c;其中主要调用Mark函数返回图片中指定人脸的关键点位置集合&#xff0c;该类需配合FaceDetector类共同使用。   FaceLandmarker类支持识别3种类型的人脸关键点&…

Vben Admin学习笔记

Modal 弹窗 modal弹窗一般作为单文件组件被引用&#xff0c;下面是两段示例代码&#xff1a; 弹窗文件 Modal.vue // Modal.vue <template><BasicModal v-bind"$attrs" title"Modal Title" :helpMessage"[提示1, 提示2]">Modal I…

Linux centos7.x系统将/home磁盘分配给/

1.解除挂载并删除/home卷 umount /home如果出现以下报错 &#xff1a; 可以使用以下命令查看哪些进程在占用 fuser -mv /home杀死这些进程就行 kill -9 进程号然后再执行umount /home就可以成功了 &#xff0c; 同时执行以下命令把逻辑卷删除了 lvremove /dev/centos/home…

AI加速游戏开发 亚马逊云科技适配3大场景,打造下一代游戏体验

随着疫情的消散&#xff0c;中国游戏产业正在快速前进。在伴随着游戏产业升级的同时&#xff0c;整个行业都在面临着新的挑战与新的诉求。亚马逊云科技游戏研发解决方案和服务&#xff0c;覆盖端到端3大场景&#xff0c;为游戏公司与游戏开发人员赋能。 场景1&#xff1a;AI辅助…

【Nodejs】操作mysql数据库

1.mysql 介绍 付费的商用数据库&#xff1a; Oracle&#xff0c;典型的高富帅&#xff1b;SQL Server&#xff0c;微软自家产品&#xff0c;Windows定制专款&#xff1b;DB2&#xff0c;IBM的产品&#xff0c;听起来挺高端&#xff1b;Sybase&#xff0c;曾经跟微软是好基友&a…

护眼台灯哪个牌子好?三款主流品牌横向对比测评

随着暑假的到来&#xff0c;不少家长想添置或者换新的护眼台灯给孩子使用&#xff0c;护眼台灯正是线下一款炙手可热的护眼神器&#xff0c;很多家长纷纷想给自己孩子买一款真正护眼的台灯。不过面对市场上各种品牌和型号的护眼台灯&#xff0c;对于不熟悉或者是第一次购买护眼…