很多小伙伴会遇到下面的问题,就是把UI切好的图片放到Unity中,会发现有些颜色的差异,尤其是透明度混合方面会有很大的变化,这些都是由于Unity中颜色空间的设置问题,先给大家看一下Unity中的效果。
可以看到,在Gamma空间下,我是用PS输出的70%透明度的纯黑色的图片,和使用windows自带应用打开的图片没有任何区别,我们打开图片的设置,有一个sRGB选项(下文会详解sRGB选项),不管开启还是关闭这个选项,图片都不会有变。由于PS是使用Gamma空间进行图片制作和输出的,所以我们使用Gamma空间进行设置,可以得到一比一的效果。
那既然我们显示都是正确的,我们为什么不直接使用Gamma空间,而是要把颜色空间设置为Linear呢,下面是Unity的官方解释。
线性或伽马工作流程 - Unity 手册
其中有这样一句话
所以我们在烘焙3d场景,或布置灯光时更倾向于选择Linear颜色空间得到最精确的结果,但是如果我们把颜色空间切换为Linear,就会发现UI有了一些不一样的变化,会发现颜色变浅了,效果如下
下面介绍一下ColorSpace中的Linear和Gamma两种工作流,这里引用一下知乎博主PZZZB的一张图片和Unity官方的一个解释
颜色空间 - Unity 手册
对于我们PS输出的图片,如果我们不做特殊的设置,那么我们的图片就是在Gamma空间下进行输出和制作的,下面有一个很关键的知识点,就是两个颜色空间的透明度混合公式
Linear Color Space
ret = (srcColor^2.2 * srcAlpha + dstColor^2.2 * (1 - srcAlpha) ) ^(1/2.2)
Gamma Color Space
ret = srcColor * srcAlpha + dstColor * (1 - srcAlpha)
对于美术人员来说,他们在PS中通过透明度混合得到的最终效果,是使用Gamma空间下的计算公式进行计算得到的结果,而Unity中的Linear Color Space下却是使用第一个公式进行处理的,我们要做的就是要是用Linear下的公式,通过一些变化,让他得到与Gamma下相同的结果。
下面要介绍一个知识,就是图片属性上的sRGB,对于点了sRGB属性的图片,Unity会默认对其做一次变暗的操作,也就是2.2次幂,也就是流程图上写的Remove Gamma Correction,通过这个操作,会得到Gamma1.0空间下的颜色,然后将得到的颜色放入Shader中处理(Shader默认使用的是Linear Color Space的混合公式)。
经过对上图两个公式分析可得,如果我们把UI的图片的sRGB取消,那么就可以让Remove Gamma Correction过程忽略掉,也就是把Gamma0.45下的颜色直接带入的透明度公式,也就变成了下图公式
ret = (srcColor^0.45^2.2 * srcAlpha + dstColor^0.45^2.2 * (1 - srcAlpha) ) ^(1/2.2)
= (srcColor * srcAlpha + dstColor * (1 - srcAlpha) ) ^(1/2.2)
通过比较可知,这个公式和Gamma空间下的透明度混合公式,只差一个2.2次方,所以我们只需要增加一个摄像机的后处理,将UI摄像机的结果进行一个2.2次方即可达到UI最终的效果。(PS:对于项目中的UI的Canvas我们会使用Scene Camera的模式,这样就可以在对应摄像机上添加后处理,也可以增加各种不同的UI特效)
使用的两个脚本如下所示
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class UICameraLinearCorrect : MonoBehaviour
{
private Material mat;
private Material RenderMat
{
get
{
if (mat == null)
{
mat = Resources.Load("Materials/UILinearCorrectMat");
}
return mat;
}
}
private void OnRenderImage(RenderTexture source, RenderTexture destination)
{
Graphics.Blit(source, destination, RenderMat);
}
}
Shader "MyShader/UILinearCorrectShader"
{
Properties
{
_MainTex("Main Tex", 2D) = "white"{}
}
SubShader
{
Tags { "RenderType" = "Opaque" }
LOD 100
//Blend SrcAlpha OneMinusSrcAlpha
Pass
{
Cull Off ZWrite Off ZTest Always
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
sampler2D _MainTex;
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float4 vertex : SV_POSITION;
float2 uv : TEXCOORD0;
};
v2f vert(appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = v.uv;
return o;
}
fixed4 frag(v2f i) : SV_Target
{
fixed4 color = tex2D(_MainTex, i.uv);
color = pow(color, 2.2);
return color;
}
ENDCG
}
}
}
但是由于我们把整体颜色调暗了,所以场景中的物体的颜色也会变暗,也就像下图
可以发现场景中的UI颜色确实还原了,但是红色方块的颜色变化了 ,所以为了让场景颜色还原,我们需要再添加一个摄像机,将这个摄像机只看场景,原有的摄像机只看UI,同时将场景摄像机的颜色进行一个0.45次幂,还原他原来的颜色,操作如下
对于场景摄像机,我们只需要看除了UI以外的所有层就可以 ,效果如下,可以发现颜色都得到了正确的显示
Scene Camera使用的代码如下
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SceneCameraLinearCorrect : MonoBehaviour
{
private Material mat;
private Material RenderMat
{
get
{
if (mat == null)
{
mat = Resources.Load("Materials/SceneLinearCorrectMat");
}
return mat;
}
}
private void OnRenderImage(RenderTexture source, RenderTexture destination)
{
Graphics.Blit(source, destination, RenderMat);
}
}
Shader "MyShader/SceneLinearCorrectShader"
{
Properties
{
_MainTex("Main Tex", 2D) = "white"{}
}
SubShader
{
Tags { "RenderType" = "Opaque" }
LOD 100
//Blend SrcAlpha OneMinusSrcAlpha
Pass
{
// 制作后处理shader的时候要把这几个属性设置上
Cull Off ZWrite Off ZTest Always
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
sampler2D _MainTex;
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float4 vertex : SV_POSITION;
float2 uv : TEXCOORD0;
};
v2f vert(appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = v.uv;
return o;
}
fixed4 frag(v2f i) : SV_Target
{
fixed4 color = tex2D(_MainTex, i.uv);
color = pow(color, 1.0 / 2.2);
return color;
}
ENDCG
}
}
}
(PS:如果UI打了图集,图集的sRGB也要关闭掉)