59.屏幕后期处理效果-效果实现-亮度饱和度对比度-具体实现
59.1 知识点
知识回顾
亮度计算规则
对图像的每个像素颜色进行乘法运算,最终颜色 = 原始颜色 * 亮度变量。
饱和度计算规则
相对于灰度颜色进行插值。
- 第一步:灰度值(亮度)L = 0.2126 * R + 0.7152 * G + 0.0722 * B。
- 第二步:灰度颜色 = (L, L, L)。
- 第三步:最终颜色 = lerp( 灰度颜色, 原始颜色, 饱和度变量 )。
对比度计算规则
相对于中性灰色进行插值。
- 第一步:中性灰颜色 = (0.5, 0.5, 0.5)。
- 第二步:最终颜色 = lerp( 中性灰色, 原始颜色, 对比度变量 )。
实现亮度、饱和度、对比度屏幕后期处理效果对应 Shader
主要步骤
- 新建一个Shader,名为
Lesson59_BrightnessSaturationContrast
(亮度饱和度对比度),并删除其中无用代码。 - 声明变量,并进行属性映射:
- 主纹理
_MainTex
2D。 - 亮度
_Brightness
Float。 - 饱和度
_Saturation
Float。 - 对比度
_Contrast
Float。
- 主纹理
- 设置深度测试、剔除、深度写入:
ZTest Always
:开启深度测试。Cull Off
:关闭剔除。Zwrite Off
:关闭深度写入。这样的设置是屏幕后处理的标配。
- 结构体相关:顶点、纹理坐标。
- 顶点着色器:顶点转裁剪空间,uv缩放偏移。
- 片元着色器:对主纹理进行采样,分别利用公式计算亮度、饱和度、对比度,返回处理后的颜色。
新建一个Shader,名为新建一个Shader,名为Lesson59_BrightnessSaturationContrast(亮度饱和度对比度)(亮度饱和度对比度),删除其中无用代码
Shader "Unlit/Lesson59_BrightnessSaturationContrast"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
Tags
{
"RenderType"="Opaque"
}
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
float4 _MainTex_ST;
ENDCG
}
}
Fallback off
}
声明变量,并进行属性映射。包括主纹理,亮度,饱和度和对比度
Properties
{
// 主纹理
_MainTex ("Texture", 2D) = "white" {}
// 亮度变量
_Brightness("Brightness", Float) = 1
// 饱和度变量
_Saturation("Saturation", Float) = 1
// 对比度变量
_Contrast("Contrast", Float) = 1
}
// 主纹理采样器
sampler2D _MainTex;
float4 _MainTex_ST;
// 亮度
half _Brightness;
// 饱和度
half _Saturation;
// 对比度
half _Contrast;
设置深度测试、剔除和深度写入为 ZTest Always
(始终通过深度测试)、Cull Off
(关闭剔除)、ZWrite Off
(关闭深度写入)。这种配置是屏幕后处理的标准做法,因为屏幕后处理效果实际上是在场景上绘制一个与屏幕同宽高的四边形面片,这样可以避免其“遮挡”后面的渲染对象。例如,如果在 OnRenderImage
前加上 [ImageEffectOpaque]
特性,透明物体会在该屏幕后处理效果之后渲染,关闭深度写入则能避免影响后续的透明渲染 Pass。
ZTest Always // 始终通过深度测试
Cull Off // 关闭背面剔除
ZWrite Off // 不写入深度缓存
结构体相关保留顶点、纹理坐标
struct v2f
{
float2 uv : TEXCOORD0; // 纹理坐标
float4 vertex : SV_POSITION; // 顶点位置
};
顶点函数将顶点转换为裁剪空间并计算纹理坐标
// 顶点着色器
v2f vert(appdata_base appdata_base)
{
v2f v2f;
v2f.vertex = UnityObjectToClipPos(appdata_base.vertex); // 将顶点转换为裁剪空间
v2f.uv = TRANSFORM_TEX(appdata_base.texcoord, _MainTex); // 计算纹理坐标
return v2f;
}
片元函数中对对主纹理进行采样,分别利用公式计算 亮度、饱和度、对比度后返回处理后的颜色
// 片元着色器
fixed4 frag(v2f v2f) : SV_Target
{
// 从主纹理中采样颜色
fixed4 renderTexColor = tex2D(_MainTex, v2f.uv);
// 亮度调整
fixed3 finalColor = renderTexColor.rgb * _Brightness; // 根据亮度变量调整颜色
// 饱和度调整
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); // 根据饱和度插值得到最终颜色
// 对比度调整
fixed3 avgColor = fixed3(0.5, 0.5, 0.5); // 平均灰色值
finalColor = lerp(avgColor, finalColor, _Contrast); // 根据对比度插值得到最终颜色
// 返回最终颜色,alpha设为1
return fixed4(finalColor.rgb, 1);
}
实现亮度、饱和度、对比度屏幕后期处理效果对应 C# 代码
主要步骤
- 创建 C# 脚本,名为
Lesson59_BrightnessSaturationContrast
(亮度饱和度对比度)。 - 继承屏幕后处理基类
PostEffectBase
。 - 声明亮度、饱和度、对比度变量,用于控制效果变化。
- 重写
OnRenderImage
方法,在其中设置材质球对应属性;也可以抽出一个修改材质属性的虚函数让子类实现。
建C#脚本,名为Lesson59_BrightnessSaturationContrast(亮度饱和度对比度),继承屏幕后处理基类,声明亮度饱和度对比度变量,用于控制效果变化PostEffectBase
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Lesson59_BrightnessSaturationContrast : PostEffectBase
{
[Range(0, 5)] public float Brightness = 1;
[Range(0, 5)] public float Saturation = 1;
[Range(0, 5)] public float Contrast = 1;
}
修改屏幕后处理效果基类,抽出一个更新材质球属性的虚函数,会在OnRenderImage调用
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[ExecuteInEditMode]
[RequireComponent(typeof(Camera))]
public class PostEffectBase : MonoBehaviour
{
//...
protected virtual void OnRenderImage(RenderTexture source, RenderTexture destination)
{
//在进行渲染之前 先更新属性 在子类中重写即可
UpdateProperty();
//判断这个材质球是否为空 如果不为空 就证明这个shader能用来处理屏幕后处理效果
if (material != null)
{
Graphics.Blit(source, destination, material);
}
//如果为空 就不用处理后处理效果了 直接显示原画面就可以了
else
{
Graphics.Blit(source, destination);
}
}
/// <summary>
/// 更新材质球属性
/// </summary>
protected virtual void UpdateProperty()
{
}
//...
}
Lesson59_BrightnessSaturationContrast重写更新材质球属性的方法
/// <summary>
/// 更新相关属性
/// </summary>
protected override void UpdateProperty()
{
if (material != null)
{
material.SetFloat("_Brightness", Brightness);
material.SetFloat("_Saturation", Saturation);
material.SetFloat("_Contrast", Contrast);
}
}
挂载Lesson59_BrightnessSaturationContrast脚本到摄像机上,关联shader,修改亮度、饱和度、对比度可以看到效果
59.2 知识点代码
Lesson59_屏幕后期处理效果_效果实现_亮度饱和度对比度_具体实现.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Lesson59_屏幕后期处理效果_效果实现_亮度饱和度对比度_具体实现 : MonoBehaviour
{
void Start()
{
#region 知识回顾
//1.亮度计算规则
// 对图像的每个像素颜色进行乘法运算
// 最终颜色 = 原始颜色 * 亮度变量
//2.饱和度计算规则
// 相对于灰度颜色进行插值
// 第一步:灰度值(亮度)L = 0.2126*R + 0.7152*G + 0.0722*B
// 第二步:灰度颜色 = (L, L, L)
// 第三步:最终颜色 = lerp( 灰度颜色, 原始颜色, 饱和度变量 )
//3.对比度计算规则
// 相对于中性灰色进行插值
// 第一步:中性灰颜色 = (0.5,0.5,0.5)
// 第二步:最终颜色 = lerp( 中性灰色, 原始颜色, 对比度变量 )
#endregion
#region 知识点一 实现亮度、饱和度、对比度屏幕后期处理效果对应 Shader
//1.新建一个Shader,名为Lesson59_BrightnessSaturationContrast(亮度饱和度对比度)
// 删除其中无用代码
//2.声明变量,并进行属性映射
// 主纹理 _MainTex 2D
// 亮度 _Brightness Float
// 饱和度 _Saturation Float
// 对比度 _Contrast Float
//3.设置深度测试、剔除、深度写入
// ZTest Always 开启深度测试
// Cull Off 关闭剔除
// Zwrite Off 关闭深度写入
// 这样的设置是屏幕后处理的标配
// 因为屏幕后处理效果相当于在场景上绘制了一个与屏幕同宽高的四边形面片
// 这样做的目的是避免它"挡住"后面的渲染物体
// 比如我们在OnRenderImage前加入[ImageEffectOpaque]特性时
// 透明物体会晚于该该屏幕后处理效果渲染,如果不关闭深度写入会影响后面的透明相关Pass
//4.结构体相关
// 顶点、纹理坐标
//5.顶点着色器
// 顶点转裁剪空间,uv缩放偏移
//6.片元着色器
// 对主纹理进行采样
// 分别利用公式计算 亮度、饱和度、对比度
// 返回处理后的颜色
#endregion
#region 知识点二 实现亮度、饱和度、对比度屏幕后期处理效果对应 C#代码
//1.创建C#脚本,名为Lesson59_BrightnessSaturationContrast(亮度饱和度对比度)
//2.继承屏幕后处理基类PostEffectBase
//3.声明亮度饱和度对比度变量,用于控制效果变化
//4.重写OnRenderImage方法,在其中设置材质球对应属性,也可以抽出一个修改材质属性的虚函数让子类实现
#endregion
}
}
Lesson59_BrightnessSaturationContrast.shader
Shader "Unlit/Lesson59_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" // 包含Unity的CG库
// 主纹理采样器
sampler2D _MainTex;
float4 _MainTex_ST;
// 亮度
half _Brightness;
// 饱和度
half _Saturation;
// 对比度
half _Contrast;
struct v2f
{
float2 uv : TEXCOORD0; // 纹理坐标
float4 vertex : SV_POSITION; // 顶点位置
};
// 顶点着色器
v2f vert(appdata_base appdata_base)
{
v2f v2f;
v2f.vertex = UnityObjectToClipPos(appdata_base.vertex); // 将顶点转换为裁剪空间
v2f.uv = TRANSFORM_TEX(appdata_base.texcoord, _MainTex); // 计算纹理坐标
return v2f;
}
// 片元着色器
fixed4 frag(v2f v2f) : SV_Target
{
// 从主纹理中采样颜色
fixed4 renderTexColor = tex2D(_MainTex, v2f.uv);
// 亮度调整
fixed3 finalColor = renderTexColor.rgb * _Brightness; // 根据亮度变量调整颜色
// 饱和度调整
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); // 根据饱和度插值得到最终颜色
// 对比度调整
fixed3 avgColor = fixed3(0.5, 0.5, 0.5); // 平均灰色值
finalColor = lerp(avgColor, finalColor, _Contrast); // 根据对比度插值得到最终颜色
// 返回最终颜色,alpha设为1
return fixed4(finalColor.rgb, 1);
}
ENDCG
}
}
Fallback off // 不使用任何备用着色器
}
Lesson59_BrightnessSaturationContrast.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Lesson59_BrightnessSaturationContrast : PostEffectBase
{
[Range(0, 5)] public float Brightness = 1;
[Range(0, 5)] public float Saturation = 1;
[Range(0, 5)] public float Contrast = 1;
/// <summary>
/// 更新相关属性
/// </summary>
protected override void UpdateProperty()
{
if (material != null)
{
material.SetFloat("_Brightness", Brightness);
material.SetFloat("_Saturation", Saturation);
material.SetFloat("_Contrast", Contrast);
}
}
}
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com