unity中的球谐光照

unity3D的内置shader库中定义了若干工具函数,可以用来计算球谐光照。
unity3D使用了3阶的伴随勒让德多项式作为基函数,即l的最大取值为2。
直角坐标系下3阶球谐函数的系数如表7-6所示 ,共9个,其中:
在这里插入图片描述

在这里插入图片描述
最终的光照函数就是上表中,每一项Yl,m和基函数系数Cl,m的叠加,又因为球谐光照所讨论的球面空间是在一个单位球空间中的,所以r项等于1。所以在考察的单位球面上的位置点(x,y,z)组成的向量也是一个单位化的向量。

在重建光照过程中 ,因为使用的光照颜色是RGB颜色,每一个分量都需要和这9个系数进行运算操作,并且因为每个光颜色分量的波长不同,所以对应的Cl,m也不同。因此需要27个数字去做这些操作,这27个数字存储在7个float变量中,这7个float4变量在文件中定义,由引擎在程序运行时传递进来。

  1. 球谐光照要用到的系数
    UnityShaderVariables.cginc
    定义如下:
// SH lighting environment
half4 unity_SHAr;
half4 unity_SHAg;
half4 unity_SHAb;
half4 unity_SHBr;
half4 unity_SHBg;
half4 unity_SHBb;
half4 unity_SHC;

unity_SHAr前三个分量对应表7-6中l=1时的各项Yl,m与红色分量对应的Cl,m的乘积;最后一个分量则对应于l=0时Yl,m常数值与对应的Cl,m的乘积;
unity_SHAg对应于绿色分量;
unity_SHAb对应于蓝色分量。

  1. SHEvalLinearL0L1函数
// normal should be normalized, w=1.0
half3 SHEvalLinearL0L1 (half4 normal)
{
    half3 x;
    // Linear (L1) + constant (L0) polynomial terms
    x.r = dot(unity_SHAr,normal);
    x.g = dot(unity_SHAg,normal);
    x.b = dot(unity_SHAb,normal);
    return x;
}

补充:因为normal.w的值为1,所以unity_SHAr最后一个分量和normal.w的乘积和传入球面上的点的位置无关系。最后一项乘出来的结果是l=0时的多项式Y(m,l)的值。
而前三个分量与normal的乘积是l=1时的多项式Y(m,l)的值。两者合并起来就是l=0和l=1时的累加重建效果。

实质就是把每个待计算的球面上的某个点传递给分别执行表中l=1时的各项多项式进行计算操作,得到各项的乘积之后,再叠加起来返回。

  1. SHEvalLinearL2函数
// normal should be normalized, w=1.0
half3 SHEvalLinearL2 (half4 normal)
{
    half3 x1, x2;
    // 4 of the quadratic (L2) polynomials
    half4 vB = normal.xyzz * normal.yzzx;
    x1.r = dot(unity_SHBr,vB);
    x1.g = dot(unity_SHBg,vB);
    x1.b = dot(unity_SHBb,vB);

    // Final (5th) quadratic (L2) polynomial
    half vC = normal.x*normal.x - normal.y*normal.y;
    x2 = unity_SHC.rgb * vC;

    return x1 + x2;
}

函数SHEvalLinearL2的功能则是计算l=2时的各个对应值。
上面代码中的half4 vB = normal.xyzz * normal.yzzx,就是构造出表7-6中l=2时左数前4项的多项式中的xy,yz,z平方,xz。变量 half vC = normal.xnormal.x - normal.ynormal.y就对应表7-6中l=2右数第一项中的x平方-y平方。
有了l=0,1,2时的光照结果的函数,便可以把他们组合起来,提供3阶的球谐光照计算,函数ShadeSH9就用于实现该功能。

  1. ShadeSH9函数
// normal should be normalized, w=1.0
// output in active color space
half3 ShadeSH9 (half4 normal)
{
    // Linear + constant polynomial terms
    half3 res = SHEvalLinearL0L1 (normal);
    // Quadratic polynomials
    res += SHEvalLinearL2 (normal);=
	#ifdef UNITY_COLORSPACE_GAMMA
        res = LinearToGammaSpace (res);
	#endif
    return res;
}

函数ShadeSH9是将SHEvalLinearL0L1和SHEvalLinearL2两个函数累加起来的结果。然后根据gamma空间的宏决定是否转换到gamma空间。

  1. ShadeSH3Order函数
// OBSOLETE: for backwards compatibility with 5.0
half3 ShadeSH3Order(half4 normal)
{
    // Quadratic polynomials
    half3 res = SHEvalLinearL2 (normal);

	#ifdef UNITY_COLORSPACE_GAMMA
        res = LinearToGammaSpace (res);
	#endif
    return res;
}

ShadeSH3Order函数则是只保留l=2时重建光照计算部分的ShadeSH9函数。这个是unity 5.x版本之前的函数,为了兼容使用。后面的版本不再使用。

  1. ShadeSH12Order函数
// normal should be normalized, w=1.0
half3 ShadeSH12Order (half4 normal)
{
    // Linear + constant polynomial terms
    half3 res = SHEvalLinearL0L1 (normal);
	#ifdef UNITY_COLORSPACE_GAMMA
        res = LinearToGammaSpace (res);
	#endif
    return res;
}

ShadeSH12Order函数的功能是保留l=0,1时的重建光照计算部分的ShadeSH9函数的内容。

  1. SHEvalLinearL0L1_SampleProbeVolume函数
#if UNITY_LIGHT_PROBE_PROXY_VOLUME
// normal should be normalized, w=1.0
half3 SHEvalLinearL0L1_SampleProbeVolume (half4 normal, float3 worldPos)
{
    const float transformToLocal = unity_ProbeVolumeParams.y;
    const float texelSizeX = unity_ProbeVolumeParams.z;

    //The SH coefficients textures and probe occlusion are packed into 1 atlas.
    //-------------------------
    //| ShR | ShG | ShB | Occ |
    //-------------------------

    float3 position = (transformToLocal == 1.0f) ? mul(unity_ProbeVolumeWorldToObject, float4(worldPos, 1.0)).xyz : worldPos;
    float3 texCoord = (position - unity_ProbeVolumeMin.xyz) * unity_ProbeVolumeSizeInv.xyz;
    texCoord.x = texCoord.x * 0.25f;

    // We need to compute proper X coordinate to sample.
    // Clamp the coordinate otherwize we'll have leaking between RGB coefficients
    float texCoordX = clamp(texCoord.x, 0.5f * texelSizeX, 0.25f - 0.5f * texelSizeX);

    // sampler state comes from SHr (all SH textures share the same sampler)
    texCoord.x = texCoordX;
    half4 SHAr = UNITY_SAMPLE_TEX3D_SAMPLER(unity_ProbeVolumeSH, unity_ProbeVolumeSH, texCoord);

    texCoord.x = texCoordX + 0.25f;
    half4 SHAg = UNITY_SAMPLE_TEX3D_SAMPLER(unity_ProbeVolumeSH, unity_ProbeVolumeSH, texCoord);

    texCoord.x = texCoordX + 0.5f;
    half4 SHAb = UNITY_SAMPLE_TEX3D_SAMPLER(unity_ProbeVolumeSH, unity_ProbeVolumeSH, texCoord);

    // Linear + constant polynomial terms
    half3 x1;
    x1.r = dot(SHAr, normal);
    x1.g = dot(SHAg, normal);
    x1.b = dot(SHAb, normal);

    return x1;
}
#endif

Cg/HLSL代码中声明为Texture3D类型的纹理是一种异于一般二维的称为立方体纹理(volume texture)的纹理。立体纹理是传统二维纹理在逻辑上的扩展。二维纹理是一张简单的位图片,用来给三维模型提供表面颜色值。而一个三维纹理可以认为是由多张二维纹理“堆叠而成”的,用于描述三维空间数据的图片。立体纹理通过三维纹理坐标进行访问。三维纹理可以有一系列细节级别,每一级都较上一级缩小1/2,如图所示:
在这里插入图片描述
unity3D还提供了对立体纹理进行采样操作的宏,依据使用不同的着色器编译和目标平台,这个采样操作宏对应着不同的实现。

如:

 #define UNITY_SAMPLE_TEX3D(tex,coord) tex.Sample (sampler##tex,coord)

在预计算阶段,光探针把空间某一点的球谐函数系数编码进一个立体纹理中,在运行时再从立体纹理中采样得到系数,然后根据该点的法线值重建出光照效果。SHEvalLinearL0L1_SampleProbeVolume函数就是实现该功能。如函数名所示,该函数从纹理中采样得到的颜色信息,其数值就是当l=1和l=0时的各项Yl,m与对应的Cl,m的乘积。

SHEvalLinearL0L1_SampleProbeVolume函数把对纹理进行采样时使用的纹理映射坐标按R/G/B分段压缩到长度为0.25的一个段中,在对颜色取样时就需要重新计算出各R/G/B分量对应的纹理映射坐标,代码中已经注释如何计算。该计算方法可以参考下图:

在这里插入图片描述

发布了610 篇原创文章 · 获赞 96 · 访问量 33万+

猜你喜欢

转载自blog.csdn.net/wodownload2/article/details/104208469