如前所述,光估计是一种非常复杂的技术。理想情况下,我们希望能够重建精确的光照模型,但是这需要深入的研究光照和图形学,再用数学为其建立精确的数字模型,无疑,这不是一件轻松的事情。但好消息是,ARCore将这些繁琐的技术进行了封装,提供给用户简单的使用接口,大大的简化了应用光估计的技术门槛。ARCore使用图像分析算法根据采集的当前图像确定光强,然后作为全局光应用到场景中的3D对象。本节,我们就来一探ARCore光照强度估计的秘密。打开最初我们放置狐狸的工程。
一、环境光设置
之前我们创建的狐狸有些暗,原因是我们的场景中没有光源,我们新建一个方向光,在Inspector窗口中,设置一下Color值,适当的调低一点,Intensity调低到0.7,Shadow Type 设置为 No Shadows,通过这些设置,基本上把方向光变成了一个定向的环境光或全局光,如下图所示。
二、ARCore光估计计算
这里虽说是光估计计算,但实际上ARCore已经都计算好了,我们需要做只是将ARCore光估计稍微做一下变换,将其转换成一个光照参数(ColorCorrection),并使用这个参数来对所有的虚拟光照效果进行调整。
在Hierarchy窗口并选择Environmental Light,然后在Inspector窗口会看到一个Environmental Light (Script) 脚本组件,打开这个脚本组件。
public void Update()
{
if (Application.isEditor && (!Application.isPlaying ||
!GoogleARCoreInternal.ARCoreProjectSettings.Instance.IsInstantPreviewEnabled))
{
// 在编辑状态下浏览,设置 _GlobalColorCorrection 为1,如果这个值不设置的话,所有使用光估计的Shader将为全黑。
Shader.SetGlobalColor("_GlobalColorCorrection", Color.white);
// 设置 _GlobalLightEstimation是为了后向兼容
Shader.SetGlobalFloat("_GlobalLightEstimation", 1f);
return;
}
if (Frame.LightEstimate.State != LightEstimateState.Valid)
{
return;
}
// 使用middle gray规一化这个像素强度,这里这个 middle gray 是在伽马颜色空间中(gamma space)。
const float middleGray = 0.466f;
float normalizedIntensity = Frame.LightEstimate.PixelIntensity / middleGray;
// 设置伽马颜色空间中的颜色校准参数
Shader.SetGlobalColor("_GlobalColorCorrection", Frame.LightEstimate.ColorCorrection * normalizedIntensity);
// 设置 _GlobalLightEstimation是为了后向兼容
Shader.SetGlobalFloat("_GlobalLightEstimation", normalizedIntensity);
}
在上面的代码中,我们首先检查代码是否在编辑器中运行,如果代码是在Unity3D编辑器中运行时,我们希望它忽略任何光估计计算。当代码在编辑器中运行时,设置 _GlobalColorCorrection 为1,即为不影响原光照计算结果,如果这个值不设置的话,所有使用光估计的Shader将为全黑。
然后,首先检查帧的光估计是否有效,如果无效则直接返回,不做任何光估计处理。如果有效,则使用middle gray规一化这个像素强度(这是ARCore从摄像机读取图像并确定的当前像素强度值,它是一个浮点值,范围[0,1],表示从全黑到全白),注意,这里这个 middle gray 是在伽马颜色空间中(gamma space)的值,Unity3D默认使用伽马空间,在伽马空间中这个middle gray 值为0.466,在线性空间中为0.18, middle gray的含义是人类眼睛对颜色识别的临界值,小于这个值,人类眼睛就分别不出颜色了。如在晚上光线暗淡时,我们通常看不出树是绿色花是红色的,只能辨识出大致的灰度。
需要说明的是,这段代码ARCore自带的,ARCore使用图像分析技术从摄像机图像中读取光强,并将该值转换为全局光强或颜色,ARCore每帧都会更新这个估计值,然后将这个参数设置到一个全局的光照参数(_GlobalColorCorrection)。我们可以在我们需要使用光估计的Shader中引用这个值来调整颜色即可。
三、光估计Shader
如上节所述,ARCore已经为我们做好光估计的所有计算,我们需要做的是利用这个光照参数(_GlobalColorCorrection)来调整我们的最终的颜色值。编写如下Shader。
// Wrote by David Wang 2018.10.20
Shader "DavidWang/FoxDiffuse"
{
Properties
{
_MainTex("Base (RGB)", 2D) = "white" {}
}
SubShader
{
Pass
{
CGPROGRAM
#pragma exclude_renderers d3d11
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
sampler2D _MainTex;
fixed3 _GlobalColorCorrection;
float4 _MainTex_ST;
struct appdata
{
float4 vertex : POSITION;
float2 texcoord : TEXCOORD0;
};
struct v2f
{
float4 vertex : SV_POSITION;
float2 uv_MainTex : TEXCOORD0;
};
v2f vert(appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv_MainTex = TRANSFORM_TEX(v.texcoord, _MainTex);
return o;
}
fixed4 frag(v2f IN) : SV_Target
{
fixed4 c = tex2D(_MainTex, IN.uv_MainTex);
float3 color = float3(c.rgb * _GlobalColorCorrection);
return float4(color, c.a);
}
ENDCG
}
}
Fallback "Mobile/VertexLit"
}
Shader代码逻辑很简单,需要注意的是,在使用TRANSFORM_TEX时,需要定义_MainTex_ST变量,否则不起作用。
四、应用光估计
上面我们已经编写好了Shader,现在我们要将这个Shader应用到我们模型上,新建一个Material,取名叫FoxDiffuse,Shader选择DavidWang/FoxDiffuse,然后将这个Material应用到Fox模型上,如下图所示。