呼伦贝尔市网站建设_网站建设公司_过渡效果_seo优化
2025/12/23 21:15:36 网站建设 项目流程

【技术美术】双向透射分布函数

透射部分的实现,网上资料很少,因此只能根据个人经验进行推导了。

主流的光照模型基于反射,反射分漫反射和镜面反射,而透射也同样可以定义为两种:

  • 漫透射:指透射光穿越介质时经过了多次弹射最终均匀的从背面射出的光。(和漫反射一样,方向信息变的无意义)
  • 镜透射:未经过多弹射,基本保持原方向,像折射一样从正背后射出的光。(和镜反射一样,方向信息会影响光照)

透射光的来源

透射光是从物体内射出的光,但物体不会无缘无故的发光,那它的源头究竟在哪?

从反射到透射

首先从双向反射分布函数考虑,“漫反射”和“镜反射”中,“镜反射”是一次接触直接反射,故肯定不会进入物体内部,但“漫反射”是在物体内部多次弹射的结果,因此显然它是有机会发生“透射”的。所以可以确定,如果发生透射,那它一定是从“漫反射”中分来的。

从漫反射到散射

根据次表面散射一章的知识,我们可知,漫反射中没有理由,入射位置和出射位置就一定是相同的,对于半透明物体,漫反射将升级为散射。

实现散射的方法很简单,其实就是重映射,例如半兰伯特、预计算的LUT,都是试图将反光区域扩展到背面。因此透射作为散射现象的一种,也同样是对漫射的重映射或重分配实现。

只是相比之下,透射对漫射的重分配更加极端,会使正背面的光比侧面光更强,这是如半兰伯特之类所不具备的。

漫透射系数

漫透射的光照类似漫反射,同样是无关观察方向的计算,故只要实现对旧漫反射的重映射即可。

漫透射的映射函数,不再是单调递减的,而是在正背面时还会有个单调递增的过程,从而实现类似碗装的结构。

散射强度映射函数

通过缩放 \(\cos\) 函数就可以很容易的找到一个符合条件的函数: \(\cos(2x) * 0.5 + 0.5\),但该函数不利于用点乘计算,所以可以利用三角函数将其转换为另一种表达方式:

\[(2 cos^2{x}-1)*0.5+0.5 \]

不过该公式描述的只是极端情况,光照背面和正面强度甚至都一样了,这在现实里应该是不存在,所以我们可以将其视作漫透射的上限分布情况,而不漫射时,显然还是旧的兰伯特分布。

如同金属度是用于控制漫反射和镜反射的分配一样,我们也增加一个新属性来控制漫反射和漫透射间的权重分配,于是新的基于散射的分布,就可以用如下代码求出了:

//标准兰伯特光照分布曲线
float lambertTermCurve = dot(n, l); 
//最大透射时光照分布曲线
float scatterTermCurve = (2 * pow(dot(n, l), 2) - 1) * 0.5 + 0.5; 
//插值后的实际漫透射系数
float diffuseTerm = saturate(lerp(lambertTermCurve, scatterTermCurve, scattering));
  • scattering:基于透射的物体散射率。

镜透射系数

镜透射与镜反射类似,其光照方向是稳定可计算。同样的,首先我们要计算出镜透射光的出射方向,然后根据观察角度,来判断最终被眼镜接收到的光强。

镜透射的出射光于入射光基本一致,不过由于物体内部不是均匀的,外加折射率等影响,光照方向会发生一定的偏移。所以我们可以用光源方向的反方向作为基础出射光方向,接着利用物体表面的法线扭曲该方向。简要起见,我们不考虑物体折射率,扭曲程度仅与光线在内部走过的距离有关(即物体厚度),越厚则光线碰撞概率越大,越容易被扭曲。

接着是如何计算眼睛接收的光强,对应在镜反射中就是高光的计算。可惜透射的资料网上较少,所以没有什么特别的光照计算公式,我们可以模仿 Phong 光照的实现,用最直观的方法计算出基础光强,然后用次数和系数去人为校准它。

结合上述两点,我们便可以写出镜透射系数的计算代码了:

float3 bl = -normalize(l + n * thickness); //出射光
float transmitTerm = pow(dot(bl, v) * 0.5 + 0.5, _TransmitPower) * _TransmitScale;
  • thickness:物体厚度,此处用于扭曲光线。
  • _TransmitPower:镜透射系数的校准次数。
  • _TransmitScale:镜透射系数的校准系数。

该计算方案,用简单的出射光和观察方向的夹角来计算基础光照,同时对其进行半兰伯特映射,将值域从 \((-1,1)\) 转为 \((0,1)\),以方便后续校准,而且这种强度分布更符合散射的特点。

此外,有意在获取反方向光之前用法线扭曲,因为从背面观察时法线和光源方向是相逆的,所以此时用法线扭曲光源的效果会比较强,而且因为基于法线扭曲,其扭曲效果就会和物体表面形状产生关联,因此表现效果也更好。

散射辐照度

在散射中由于光线来自内部,所以与反射的辐照率计算有所不同。首先两者的最初光源是一样的,但反射光中辐照度的衰减主要来自遮挡(阴影),而散射光中的辐照度衰减则来自内部吸收(厚度)。有多少光会发生反射或散射?我们需要一个属性进行权重分配,好在漫透射章节已经提供了一个散射率属性。于是最终,我们便可以计算出散射辐照度:

float3 scatteringIrradiance = light.color * light.distanceAttenuation * lerp(light.shadowAttenuation, 1 - thickness, scattering);
  • thickness:物体厚度,与镜透射章节中参数的一致。
  • scattering:物体散射率,与漫透射章节中的参数一致。

光照结果计算

已经有光照系数和辐照度,其辐射率和最终呈现颜色就很好计算了。不过目前我们有两种系数:“漫透射”和“镜透射”,因此我们一样需要一个类似金属度的参数来分配他们的权重。接着就是物体本身的反照率,显然,因为散射来自漫反射,所以与漫反射的反照率一致即可。最终可得如下代码实现:

float scatteringRadianceTerm = lerp(diffuseTerm, transmitTerm, transmission);
float3 scatteringRadiance = scatteringIrradiance * scatteringRadianceTerm;
float3 scatteringColor = diffuse * scatteringRadiance;
  • transmission:物体镜透射率
  • diffuse:漫反射反照率

间接透射光

间接光同样会产生散射现象,类似对直接光的散射计算,我们需要将旧漫反射逐步改造为散射和镜透射。由于间接光计算中,无法获取光源位置,所以对方向上的计算会有所简化,不过因为间接光的烘焙数据本身就是对范围光照的积分,所以只要选对角度,也会有不错的透射效果。

间接漫透射

首先是漫透射的实现,其与观察角度无关,有关的只有法向。显然最容易的发生漫透射的方向,就是正背面了,这在上文的漫透射光照分布曲线上也可体现。所以我们只要用法线反方向,即可求出漫透射光。当然,也别忘了遮挡和厚度引起的衰减。

float3 diffuseRradiance = lerp(SampleSH(n) * occlusion, SampleSH(-n) * (1 - thickness), scattering);

在间接光中,对遮挡的实现不是基于阴影衰减,而是基于物体的环境光遮蔽属性。不过该属性对散射光无效,因为散射光来自物体内部,其表面不会产生遮挡。或者说其收到的遮挡影响与反射光的不同,与其使用不正确的属性,不如不用,因为环境光遮蔽会抑制散射产生暗部增量的效果。

间接镜透射

接着是镜透射的实现,虽然我们不知道光源位置,但我们知道接收光线最高效的方向就是观察角度正前方。所以我们从视角方向伪造光源方向即可,当然也别忘了厚度对光线方向的扰动,以及镜漫射光的校准操作。

float3 bl = -normalize(-v + n * thickness);
float3 transmitIrradiance = pow(SampleSH(-bl), _IndirectTransmitPower) * _IndirectTransmitScale;
float3 transmitRadiance = transmitIrradiance * (1 - thickness);
  • _IndirectTransmitPower:间接光透射校准次数
  • _IndirectTransmitScale:间接光透射校准系数

由于间接光的辐射率系数和辐照度是混合在一起的,所以没法单独给辐射率系数校准,因此不能和直接光的透射调节系数共用。

SampleSH 也可以替换成 GlossyEnvironmentReflection 实现,这样在某些条件下就可以实现清晰的透射画面,不过漫透射和镜透射混在一起的效果不是很好,如果真想实现透明物体,还是不要考虑漫射现象,单纯从折射现象考虑比较好。

合并光照结果

计算出两种间接透射光的辐射率后,接下来就和直接光的的操作一样了。利用上文的透射率插值得到最终的散射光辐照率,然后混合漫反射反照率,替换掉原本的漫反射累加到间接光即可。由于散射光中已经计算了环境光遮蔽,所以此处注意别多算了。

float3 scatteringColor = diffuse * lerp(diffuseRradiance, transmitRadiance, transmission); //计算出最终的散射颜色结果
finalColor += scatteringColor + specularColor * occlusion; //累加到最终光照
  • specularColor:传统的间接镜射光结果。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询