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 updatevoid Start(){}// Update is called once per framevoid 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 depthCull Off ZWrite Off ZTest AlwaysPass{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 updatevoid Start(){//if(colorMat == null||colorMat.shader == null||colorMat.shader.isSupported == false)//{// this.enabled = false;//}}// Update is called once per framevoid 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);}
}
一个脚本控制所有后处理。当然这样的做法只是方便,也不见得很好,我还是比较喜欢根据实际用到多少个效果,单独去写对应的脚本,那样我觉得性能才是最好的。
相关文章:

Unity自定义后处理——Tonemapping色调映射
大家好,我是阿赵。 继续介绍屏幕后处理,这一期介绍一下Tonemapping色调映射 一、Tone Mapping的介绍 Tone Mapping色调映射,是一种颜色的映射关系处理,简单一点说,一般是从原始色调(通常是高动态范围&…...
Redis学习 知识总结 一
Redis学习 知识总结 一 1 Redis初识1.1 Redis八大特性1.2 redis使用场景1.3 Docker安装redis 2 API的理解和使用2.1 通用命令2.2 字符串(String)类型2.3 哈希(Hash)类型2.4 有序列表(list)2.5 集合…...
Webpack5 vue-loader和VueLoaderPlugin
文章目录 vue-loader和VueLoaderPlugin的作用vue-loader具体使用方式注意事项 vue-loader和VueLoaderPlugin的作用 .vue 文件是用户用 HTML-like 的语法编写的 Vue 组件。每个vue 文件都包括三部分 , VueLoaderPlugin 是一个解析 Vue.js 的插件,用于在 webpack 构…...
【传统视觉】模板匹配和卡尺圆检测
模板匹配 粗定位 1、原理:模板匹配是指在当前图像A中匹配与图像B最相似的部分,那么A为输入图像,B为模板图像。 2、匹配方法:B在A上华东,逐个遍历所有像素完成匹配。 3、函数: result cv2.matchTemplate(…...

记一次简单的MySql注入试验
试验环境: 1.已经搭建好的php服务器,并可以通过访问到localhost/index.php; 2.已经安装好数据库,并创建表test,表内有name、age等字段,并随便创建几个假数据用于测试;如图: 开始测…...

软考开发思考(完善中)
软考开发思考 文章目录 软考开发思考1. 互联网媒体:新技术和新应用及当前的趋势和应用1.1 自动化报道1.2. 虚拟和增强现实1.3. 数据新闻1.4. 即时新闻推送1.5 智能助手和聊天机器人1.6 语音播报,语音检索,后台播放、播放倍速。1.6 机器人交互…...

[NLP]LLaMA与LLamMA2解读
摘要 Meta最近提出了LLaMA(开放和高效的基础语言模型)模型参数包括从7B到65B等多个版本。最值得注意的是,LLaMA-13B的性能优于GPT-3,而体积却小了10倍以上,LLaMA-65B与Chinchilla-70B和PaLM-540B具有竞争性。 一、引言 一般而言࿰…...

后端一次返回大量数据,前端做分页处理
问题描述:后端接口返回大量数据,没有做分页处理,不支持传参pageNum,pageSize 本文为转载文章,原文章:后端一次返回大量数据,前端做分页处理 1.template中 分页 <el-paginationsize-chang…...

卷积神经网络识别人脸项目—使用百度飞桨ai计算
卷积神经网络识别人脸项目的详细过程 整个项目需要的准备文件: 下载链接: 链接:https://pan.baidu.com/s/1WEndfi14EhVh-8Vvt62I_w 提取码:7777 链接:https://pan.baidu.com/s/10weqx3r_zbS5gNEq-xGrzg 提取码&#x…...

vue中预览静态pdf文件
方法 // pdf预览 viewFileCompare() { const pdfUrl "/static/wjbd.pdf"; window.open(pdfUrl); }, // 下载 downloadFile(){ var a document.createElement("a"); a.href "/static/wjbd.pdf"; a.…...
Java多进程(详细)
进程的含义 简单理解是正在跑起来的程序,正在运行的程序。没有正在运行的程序不叫进程,同一个程序,运行多次,就可能产生多个进程。 平时所说的程序,值的是一些exe的可执行文件,得把程序跑起来才会涉及到进程…...

OpenCV 4.0+Python机器学习与计算机视觉实战
💂 个人网站:【办公神器】【游戏大全】【神级源码资源网】🤟 前端学习课程:👉【28个案例趣学前端】【400个JS面试题】💅 寻找学习交流、摸鱼划水的小伙伴,请点击【摸鱼学习交流群】 目录 前言第一部分&…...

自学网络安全(黑客)全笔记
一、什么是网络安全 网络安全可以基于攻击和防御视角来分类,我们经常听到的 “红队”、“渗透测试” 等就是研究攻击技术,而“蓝队”、“安全运营”、“安全运维”则研究防御技术。 无论网络、Web、移动、桌面、云等哪个领域,都有攻与防两面…...

WAF/Web应用安全(拦截恶意非法请求)
Web 应用防火墙(Web Application Firewall, WAF)通过对 HTTP(S) 请求进行检测,识别并阻断 SQL 注入、跨站脚本攻击、跨站请求伪造等攻击,保护 Web 服务安全稳定。 Web 安全是所有互联网应用必须具备的功能,…...

Windows环境下git客户端中的git-bash和MinGW64
我们在 Windows10 操作系统下,安装了 git 客户端之后,可以通过 git-bash.exe 打开一个 shell: 执行一些 linux 系统里的命令: 注意到上图紫色的 MINGW64. Mingw-w64 是原始 mingw.org 项目的改进版,旨在支持 Window…...

欧姆龙CX系列PLC串口转以太网欧姆龙cp1hplc以太网连接电脑
你是否还在为工厂设备信息采集困难而烦恼?捷米特JM-ETH-CX转以太网通讯处理器为你解决这个问题! 捷米特JM-ETH-CX转以太网通讯处理器专门为满足工厂设备信息化需求而设计,可以用于欧姆龙多个系列PLC的太网数据采集,非常方便构建生…...

Vue3笔记
1. Vue2 选项式 API vs Vue3 组合式API <script> export default { data(){ return { count:0 } }, methods:{ addCount(){ this.count } } } </script> <script setup> import { ref } from vue const count ref(0) c…...

git相关
gerrit用户指南: 资料:Gerrit 用户指南 gerrit-user-guide 上述有介绍如何review,review并非修改代码之后如何重新提交等操作 jenkins介绍 Jenkins详细教程 - 知乎 一、jenkins是什么? Jenkins是一个开源的、提供友好操作界…...

车道线检测|利用边缘检测的原理对车道线图片进行识别
前言 那么这里博主先安利一些干货满满的专栏了! 这两个都是博主在学习Linux操作系统过程中的记录,希望对大家的学习有帮助! 操作系统Operating Syshttps://blog.csdn.net/yu_cblog/category_12165502.html?spm1001.2014.3001.5482Linux S…...
C++—static关键字详解
引言: C的static有两种用法:面向过程程序设计中的static和面向对象程序设计中的static。前者应用于普通变量和函数,不涉及类;后者主要说明static在类中的作用。 一.面向过程中的static 1.静态全局变量 静态全局变量有以下特点…...
后进先出(LIFO)详解
LIFO 是 Last In, First Out 的缩写,中文译为后进先出。这是一种数据结构的工作原则,类似于一摞盘子或一叠书本: 最后放进去的元素最先出来 -想象往筒状容器里放盘子: (1)你放进的最后一个盘子(…...

K8S认证|CKS题库+答案| 11. AppArmor
目录 11. AppArmor 免费获取并激活 CKA_v1.31_模拟系统 题目 开始操作: 1)、切换集群 2)、切换节点 3)、切换到 apparmor 的目录 4)、执行 apparmor 策略模块 5)、修改 pod 文件 6)、…...
从零实现富文本编辑器#5-编辑器选区模型的状态结构表达
先前我们总结了浏览器选区模型的交互策略,并且实现了基本的选区操作,还调研了自绘选区的实现。那么相对的,我们还需要设计编辑器的选区表达,也可以称为模型选区。编辑器中应用变更时的操作范围,就是以模型选区为基准来…...

安宝特方案丨XRSOP人员作业标准化管理平台:AR智慧点检验收套件
在选煤厂、化工厂、钢铁厂等过程生产型企业,其生产设备的运行效率和非计划停机对工业制造效益有较大影响。 随着企业自动化和智能化建设的推进,需提前预防假检、错检、漏检,推动智慧生产运维系统数据的流动和现场赋能应用。同时,…...

《从零掌握MIPI CSI-2: 协议精解与FPGA摄像头开发实战》-- CSI-2 协议详细解析 (一)
CSI-2 协议详细解析 (一) 1. CSI-2层定义(CSI-2 Layer Definitions) 分层结构 :CSI-2协议分为6层: 物理层(PHY Layer) : 定义电气特性、时钟机制和传输介质(导线&#…...

相机从app启动流程
一、流程框架图 二、具体流程分析 1、得到cameralist和对应的静态信息 目录如下: 重点代码分析: 启动相机前,先要通过getCameraIdList获取camera的个数以及id,然后可以通过getCameraCharacteristics获取对应id camera的capabilities(静态信息)进行一些openCamera前的…...

有限自动机到正规文法转换器v1.0
1 项目简介 这是一个功能强大的有限自动机(Finite Automaton, FA)到正规文法(Regular Grammar)转换器,它配备了一个直观且完整的图形用户界面,使用户能够轻松地进行操作和观察。该程序基于编译原理中的经典…...

mac 安装homebrew (nvm 及git)
mac 安装nvm 及git 万恶之源 mac 安装这些东西离不开Xcode。及homebrew 一、先说安装git步骤 通用: 方法一:使用 Homebrew 安装 Git(推荐) 步骤如下:打开终端(Terminal.app) 1.安装 Homebrew…...
关于uniapp展示PDF的解决方案
在 UniApp 的 H5 环境中使用 pdf-vue3 组件可以实现完整的 PDF 预览功能。以下是详细实现步骤和注意事项: 一、安装依赖 安装 pdf-vue3 和 PDF.js 核心库: npm install pdf-vue3 pdfjs-dist二、基本使用示例 <template><view class"con…...
小木的算法日记-多叉树的递归/层序遍历
🌲 从二叉树到森林:一文彻底搞懂多叉树遍历的艺术 🚀 引言 你好,未来的算法大神! 在数据结构的世界里,“树”无疑是最核心、最迷人的概念之一。我们中的大多数人都是从 二叉树 开始入门的,它…...