实时地估计出用户场景中的光照方向,对实现场景光照一致性和增强真实感具有重要意义,光照方向估计问题目前来看还不成熟,在某些场景中估计会比较准确,比如在太阳光强烈的户外,在某些场景中则表现不稳定,如多灯光场景及光线比较昏暗的环境。
ARCore为我们提供了一个可靠的解决方案来估算AR场景中的光照强度。正如我们所了解的,光照方向是场景照明的一个同样重要的部分,对于这个技术难题。ARCore确实为我们提供了足够多的信息来估计光照方—向,但是也还如前所述还不成熟。要想光照方向估算比较准确,则需要一些简单的假设。首先,我们需要假设用户在同一个房间或区域;第二,用户需要在他们的视野中至少扫描180度,或者更简单地说,用户需要环顾四周以便ARCore采集到足够的光照信息;第三,如果现实世界的环境是从遥远的单一光源(如太阳)照明的话,那是最有效的。基于这些假设,我们可以简单地存储用户看到最亮图像的方向,然后用它来反向计算我们的光照方向,这就把问题简单化了,但真实环境并没有简单化,通过测试,这种方向并不总是有效。
一、光照方向计算
对光照方向的计算,我们的解决思路是在利用用户摄像机获取的图像信息找到光照强度最大的点,这时用户摄像机所指向的方向的反方向就作为光照方向。首先我们还是要修改Environmental Light (Script) 脚本。
在Hierarchy窗口并选择Environmental Light,然后在Inspector窗口会看到一个Environmental Light (Script) 脚本组件,打开这个脚本组件,并修改脚本如下:
public class EnvironmentalLight : MonoBehaviour
{
mFirstPersonCamera;
public GameObject mDirectionLight;
private float maxGlobal = float.MinValue;
private Vector3 maxLightDirection;
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);
var pi = Frame.LightEstimate.PixelIntensity;
if (pi > maxGlobal)
{
maxGlobal = pi;
mDirectionLight.transform.rotation = Quaternion.LookRotation(-mFirstPersonCamera.transform.forward);
}
}
}
在上面的代码中,前半部分代码我们都见过,后面的代码先保存当前帧中最大的光照强度,然后与之前保存的所有帧中的最大光照强度比较,如果比之前所保存的最大光照强度值大,那么就更新估计的光照方向。
二、光照方向Shader
我们直接使用脚本调整了方向光的方向,所以不用传递任何多余数据到Shader,但是我们需要重新计算光照,编写如下Shader。
Shader "DavidWang/FoxSpecular"
{
Properties
{
_Shininess("Shininess", Range(0.03, 1)) = 0.078125
_MainTex("Base (RGB) Gloss (A)", 2D) = "white" {}
[NoScaleOffset] _BumpMap("Normalmap", 2D) = "bump" {}
}
SubShader
{
Tags{ "RenderType" = "Opaque" }
LOD 250
CGPROGRAM
#pragma surface surf MobileBlinnPhong exclude_path:prepass nolightmap noforwardadd halfasview interpolateview finalcolor:lightEstimation
inline fixed4 LightingMobileBlinnPhong(SurfaceOutput s, fixed3 lightDir, fixed3 halfDir, fixed atten)
{
fixed diff = max(0, dot(s.Normal, lightDir));
fixed nh = max(0, dot(s.Normal, halfDir));
fixed spec = pow(nh, s.Specular * 128) * s.Gloss;
fixed4 c;
c.rgb = (s.Albedo * _LightColor0.rgb * diff + _LightColor0.rgb * spec) * atten;
UNITY_OPAQUE_ALPHA(c.a);
return c;
}
sampler2D _MainTex;
sampler2D _BumpMap;
half _Shininess;
fixed3 _GlobalColorCorrection;
struct Input
{
float2 uv_MainTex;
};
void lightEstimation(Input IN, SurfaceOutput o, inout fixed4 color)
{
color.rgb *= _GlobalColorCorrection;
}
void surf(Input IN, inout SurfaceOutput o)
{
fixed4 tex = tex2D(_MainTex, IN.uv_MainTex);
o.Albedo = tex.rgb;
o.Gloss = tex.a;
o.Alpha = tex.a;
o.Specular = _Shininess;
o.Normal = UnpackNormal(tex2D(_BumpMap, IN.uv_MainTex));
}
ENDCG
}
FallBack "Mobile/VertexLit"
}
上述Shader与Unity3D自带的Mobile/Bumped Specular几乎一致,使用的是Surface Shader,我们自定义了光照模型,在光照模型首先计算漫反射,然后计算高光反射,最后将漫反射与高光反射相加并乘上衰减因子。需要注意的是UNITY_OPAQUE_ALPHA(c.a),由于渲染的是不透明物体,所以保证最终输出颜色其a通道为1,不然就会得到错误的渲染结果,在最后的时候对颜色进行了校正。
编译,我们在户外进行了测试,效果如下,总体感觉还不错:
三、更新光照方向
上面的代码,我们实现了使用区域内看起来最亮的光源来照明,看起来还不错。但是,因为我们目前不跟踪光线方向的变化,如果你改变房间或改变灯光,好不容易营造的光照就会破灭。例如,在户阳光下,我们跟踪了阳光并使用阳光来对虚拟物体进行照明,这时最大强度的光照源就是太阳,如果这时我们移动到室内,室内的光照相对户外肯定是要弱一些,但最大强度光源已经无法修改了,导致进入到室内后光源光照方向不可再更改,换句话说,如果从强光照环境进入到弱光照环境,光照方向将不可再更改,这是个问题。解决这个问题的思路是我们让最大光照强度随时间减弱,这样,一段时间后,室内的光照强度就会大于原最大光照强度,就可以恢复更新光照方向。这听起来很复杂,但实施起来却很简单,只需要在Environmental Light (Script) 加入一行代码。
...
var pi = Frame.LightEstimate.PixelIntensity; //在这行代码之后加入一行代码
maxGlobal *= 0.999f;
if(pi > maxGlobal){
...
maxGlobal *= 0.999f 这行代码我们叫最大光照强度退化函数。这行简单的代码随着时间的推移,会降低maxGlobal这个值。0.999f值设置衰变的速度,可以通过减少这个值加快退化过程,通过增加这个值减缓退化过程。