1、颜色亮度的基本原理
想要改变图像颜色的亮度,只需要对图像的每个像素进行加法(减法 - 加负数)或乘法(除法 - 乘小数)运算即可实现
增加亮度就是 增加像素的RGB值;减小亮度则是 减少像素的RGB值
也就是说我们只需要在Shader当中加入一个控制亮度的float类型的变量,然后用颜色的RGB乘以该变量或者加上该变量即可,一般我们会采用乘法的形式,即:
- 最终颜色 = 原始颜色 * 亮度变量
- 亮度变量 > 1 时,图像变亮
- 亮度变量 < 1 时,图像变暗
- 亮度变量 = 1 时,图像不变
2、颜色饱和度的基本原理
想要改变图像颜色的饱和度,只需要对图像的每个像素的颜色值 相对于灰度颜色进行插值来实现
基本原理为以下三步:
(1)计算灰度值(亮度)
利用图像颜色RGB计算一个平均值,得到一个灰度值,但是由于人眼对不同颜色的敏感度不同,所以在计算平均值时不会直接使用算数平均(R + G + B)/ 3,在图形学中我们一般使用加权平均法来计算灰度值
所谓加权平均法就是通过对不同数据分配不同权重,计算出更符合实际情况的平均值,常用的权重基于Rec. 709标准(高清电视和许多数字图像格式中常用的标准)
- R 红色通道的权重:0.2126
- G 绿色通道的权重:0.7152
- B 蓝色通道的权重:0.0722
这些权重反映了人眼对绿色最敏感,对蓝色最不敏感,加权平均法公式:
灰度值(亮度)L = 0.2126 * R + 0.7152 * G + 0.0722 * B
(2)生成灰度颜色
利用第一步中计算出来的灰度值,生成一个灰度颜色,灰度颜色 = (L, L, L)
93)插值计算
使用插值函数lerp,在灰度颜色和原始颜色之间进行插值运算,插值系数就是用于控制饱和度的float类型的变量
公式如下:
- 最终颜色 = lerp(灰度颜色, 原始颜色, 饱和度变量)
- lerp计算原理:最终颜色 = 灰度颜色 + (原始颜色 − 灰度颜色)*饱和度变量
- 饱和度变量 = 0时,结果为灰度颜色
- 饱和度变量 = 1时,保持原始颜色不变
- 饱和度变量 = 0~1之间时,灰度颜色和原始颜色的混合
- 饱和度变量 > 1时,颜色的RGB值超出原始范围,从而使颜色看起来更饱和
3、颜色对比度的基本原理
要改变图像颜色的对比度,只需要对图像的每个像素的颜色值 相对于中性灰色进行插值来实现
基本原理为以下两步:
(1)声明中性灰色变量,即RGB都为0.5的颜色变量,中性灰颜色 = (0.5, 0.5, 0.5)
(2)在中性灰色和原始颜色之间进行插值运算
- 最终颜色 = lerp(中性灰色, 原始颜色, 对比度变量)
- 对比度变量 = 0时,此时对比度降到最低,变为中性灰色
- 对比度变量 = 1时,保持原始颜色不变
- 对比度变量 = 0~1之间时,降低对比度效果,图像的亮度差异减少,使图像颜色看起来更平淡
- 对比度变量 > 1时,颜色的RGB值超出原始范围,从而使颜色亮部更亮,暗部更暗,从而增加对比度
4、基本实现
在 Shader 中要设置 ZTest,Cull,ZWrite,这样的设置是屏幕后处理的标配,因为屏幕后处理效果相当于在场景上绘制了一个与屏幕同宽高的四边形面片,这样做的目的是避免它"挡住"后面的渲染物体,比如我们在OnRenderImage前加入[ImageEffectOpaque]特性时,透明物体会晚于该该屏幕后处理效果渲染,如果不关闭深度写入会影响后面的透明相关Pass
Shader "ShaderProj/9/BrightnessSaturationContrast"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_Brightness ("Brightness", Float) = 1
_Saturation ("Saturation", Float) = 1
_Contrast ("Contrast", Float) = 1
}
SubShader
{
Tags { "RenderType"="Opaque" }
Pass
{
ZTest Always
Cull Off
ZWrite Off
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
float4 _MainTex_ST;
half _Brightness;
half _Saturation;
half _Contrast;
v2f vert (appdata_base v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed4 renderTexColor = tex2D(_MainTex, i.uv);
// Brightness
fixed3 finalColor = renderTexColor.rgb * _Brightness;
// Saturation
fixed L = 0.2126 * finalColor.r + 0.7152 * finalColor.g + 0.722 * finalColor.b;
fixed3 LColor = fixed3(L, L, L);
finalColor = lerp(LColor, finalColor, _Saturation);
// Contrast
fixed3 avgColor = fixed3(0.5, 0.5, 0.5);
finalColor = lerp(avgColor, finalColor, _Contrast);
return fixed4(finalColor, 1);
}
ENDCG
}
}
Fallback Off
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class BrightnessSaturationContrast : PostEffectBase
{
[Range(0, 3)]
public float Brightness = 1;
[Range(0, 3)]
public float Saturation = 1;
[Range(0, 3)]
public float Contrast = 1;
protected override void OnRenderImage(RenderTexture source, RenderTexture destination) {
base.OnRenderImage(source, destination);
}
protected override void UpdateProperty() {
if (material != null) {
material.SetFloat("_Brightness", Brightness);
material.SetFloat("_Saturation", Saturation);
material.SetFloat("_Contrast", Contrast);
}
}
}