在本节中,我们将学习如何在Babylon.js中创建阴影。阴影现在实现的是动态阴影,它们会根据光源动态生成。你可能需要首先查看一个案例:点击这里。

实现阴影

使用Babylon.js的ShadowGenerator对象很容易实现阴影。此功能使用到了阴影贴图:从光源的角度生成场景的贴图。
创建阴影生成器需要两个参数:阴影贴图的大小,以及用于阴影贴图计算的光。

var shadowGenerator = new BABYLON.ShadowGenerator(1024, light);

接下来,你必须要定义渲染的阴影。这里,我们需要渲染圆环的阴影,只需要将模型对象添加到渲染阴影的数组内即可:

shadowGenerator.getShadowMap().renderList.push(torus);

在Babylon.js v3.1版本中,又新增了两个函数来添加删除:

  • addShadowCaster(mesh, includeDescendants):辅助函数,用于将模型及其后代添加到阴影渲染列表中。
  • removeShadowCaster(mesh, includeDescendants):辅助函数,从阴影渲染列表中将模型删除。
    最后,你必须将模型的接收阴影参数设置为真来定义阴影的显示位置:
ground.receiveShadows = true;

柔化阴影

如果你想更进一步,可以激活阴影过滤,柔化阴影边缘创建更好看的阴影。
过滤的方式有三种:

泊松采样 Poisson sampling

shadowGenerator.usePoissonSampling = true;

如果将此值设置为true,则将禁用方差阴影贴图。此滤镜使用泊松采样来柔化阴影。结果更好,但速度更慢。

指数阴影贴图 Exponential shadow map

shadowGenerator.useExponentialShadowMap = true;

默认的过滤方式,因为这种方式对混淆的阴影方式很管用。如果你想减少计算时间,可以随意关闭它。你可以通过设置shadowGenerator.depthScale来更新阴影贴图的深度的缩放值。默认值为50.0,但如果你的场景的深度比例(MinZ和MaxZ之间的距离)很小,你可能需要修改它。

模糊指数阴影贴图 Blur exponential shadow map

shadowGenerator.useBlurExponentialShadowMap = true;

这是更好的柔化阴影滤镜,但速度也较慢。它使用模糊的指数阴影贴图。
模糊的质量由以下属性定义:

  • shadowGenerator.blurScale:在应用模糊后处理之前,定义用于缩小阴影贴图的比例。默认情况下,该值为2
  • shadowGenerator.blurBoxOffset:定义用于应用模糊阴影边缘的偏移量。默认情况下,该值为1(这意味着该框将在两个方向上从-1到1,从而导致模糊后处理读取9个值。)
  • shadowGenerator.useKernelBlur:设置使用内核模糊还是边缘模糊。虽然使用内核模糊运算量更大,但阴影质量会好很多。你可以使用shadowGenerator.blurKernel,默认值为1,来控制内核大小。

以下是模糊阴影的示例:点击这里

近似指数阴影贴图 Close exponential shadow map

从Babylon.js v3.0版本开始,我们引入了一种新的方法来解决指数阴影自身阴影问题:Close Exponential Shadow Map (CESM)。使用CESM,你可以准确的自阴影,但你需要定义它的参数:

  • 你必须通过光源的light.shadowMinZ属性和light.shadowMaxZ设置光源的深度值范围。范围越小,阴影效果就越好。
  • 你必须确保光源尽可能的靠近阴影。

你可以使用以下命令启用CESM:

shadowGenerator.useCloseExponentialShadowMap = true;

或者你想要模糊的阴影:

shadowGenerator.useBlurCloseExponentialShadowMap = true;

以下是CESM的官方示例

更接近的百分比过滤 Percentage Closer Filtering(仅限WebGL2)

从Babylon.js v3.2版本开始,添加了一种处理阴影贴图的新方法。这极大改善了阴影的性能和设置。
PCF(Percentage Closer Filtering)阴影受益于WebGL2中可用过滤功能的新特性,并产生更平滑的泊松采样版本。当WebGL2不受支持时,它将回退到标准泊松采样。
你可以启用PCF:

shadowGenerator.usePercentageCloserFiltering = true;

以下是PCF的官方示例

由于PCF会在小型设备上占用过多资源,因此你需要通过设置filteringQuality属性来在质量和性能之间进行权衡(质量越低,性能越好)。

shadowGenerator.filteringQuality = BABYLON.ShadowGenerator.QUALITY_LOW;

PCF目前仅支持点光源和平行光。

联系硬化阴影 Contact hardening shadow(仅限WebGL2)

从Babylon.js v3.2版本开始,引入了基于PCSS阴影的联系硬化阴影。
PCSS被视为PCF的改进版,尽管看起来更好,但它们的代价也更昂贵,应在桌面端应用内使用。与PCF一样,如果当前环境不支持WebGL2,它们将自动回退到泊松采样(Poisson Sampling)。
在PCSS中,当阴影远离物体投射时,阴影会变得更柔和,模拟现实生活中发生的情况。
为了获得更准确的结果,你需要定义其它参数:

  • 你必须通过光源的light.shadowMinZ属性和light.shadowMaxZ设置光源的深度值范围。范围越小,阴影效果就越好。
  • 你还可以通过修改contactHardeningLightSizeUVRatio来更改阴影的软化速度(在0到1之间)。

通过以下方式启用PCSS:

shadowGenerator.useContactHardeningShadow = true;
  • 以下是官方的PCSS的示例

由于PCSS会在小型设备上占用过多资源,因此你需要通过设置filteringQuality属性来在质量和性能之间进行权衡(质量越低,性能越好)。

shadowGenerator.filteringQuality = BABYLON.ShadowGenerator.QUALITY_LOW;
  • 以下案例能更清楚的了解阴影随着距离变化的效果:点击这里
    PCSS目前仅支持点光源和平行光。

示例

你可以在这里找到一个示例:点击这里

这里显示了一些阴影与聚光灯的效果
16 Babylonjs基础入门 阴影-编程之家
没有过滤

16 Babylonjs基础入门 阴影-编程之家
泊松采样 Poisson sampling
16 Babylonjs基础入门 阴影-编程之家
指数阴影贴图 Exponential shadow map
16 Babylonjs基础入门 阴影-编程之家
模糊指数阴影贴图 Blur exponential shadow map
16 Babylonjs基础入门 阴影-编程之家
更接近的百分比过滤 Percentage Closer Filtering(仅限WebGL2)
16 Babylonjs基础入门 阴影-编程之家
联系硬化阴影 Contact hardening shadow(仅限WebGL2)

光源

注意,一个阴影生成器只能和一个光源配合使用。如果需要另一个光源产生阴影效果,则需要另外创建一个阴影生成器。
只有点光源,聚光灯和方向光才能投射阴影。

点光源

点光源使用的是立方体贴图渲染,因此启用阴影时请务必小心,因为可能会导致一些性能问题。你可以点击此处查看案例
此外,BlurExponentialShadowMap并CloseBlurExponentialShadowMap没有被点光源支持(主要是因为模糊的六个面立方贴图的过于昂贵)。
为了优化渲染,如果你确定所有的阴影都位于光源的一侧,你还可以使用点光源就想无限的聚光灯一样。要实现这一点,只需要指定光源的一个方向,Babylon.js就会自动为阴影贴图使用一个简单的纹理,而不是立方体纹理。

聚光灯

聚光灯光源使用透视投影来计算阴影贴图。

平行光

平行光使用正交投影。灯光的位置会自动评估,以便您获得尽可能最好的阴影贴图。你可以通过设置light.autoUpdateExtends来关闭自动此行为。你还可以通过修改其中一个属性来控制投影窗口的大小:

  • light.shadowOrthoScale:默认值为0.1,表示投影窗口从最佳大小增加10%。
  • light.shadowFrustumSize:默认情况下为关闭状态,值为0 。你可以指定一个值,该值将用于定义使用的平截头体的平方大小。

灯光的位置以及你添加到阴影渲染列表的模型决定了阴影的显示位置。请注意,从该位置的光照点必须查看渲染器列表中的所有网格。否则不能渲染阴影。

自定义投影矩阵

所有的光源都需要给阴影生成器提供投影矩阵才能构建阴影贴图。你可以通过设置light.customProjectionMatrixBuilder值来定义自己的需要:

light.customProjectionMatrixBuilder = function(viewMatrix: Matrix, renderList: Array<AbstractMesh>) {return BABYLON.Matrix.PerspectiveFovLH(angle, 1.0, activeCamera.minZ, this.shadowMaxZ);
}

故障排除

阴影贴图是一项很棒的技术,但并不完美。可以调整几个参数以帮助改进最终渲染。

偏差

你可能希望减少由于不精确的阴影贴图导致的阴影痤疮。为此,你可以定义偏差(默认为0.00005):

shadowGenerator.bias = 0.01;

阴影生成器将每个像素的深度与从光的角度看到的遮挡物(阴影轮廓)的深度进行比较。当我们处理低精度纹理时(当Babylon.js使用浮动纹理但底端设备仅支持整型纹理时),你可能希望增加遮挡物的深度,以方便生成自阴影。

背面渲染

你可以通过设置shadowGenerator.forceBackFacesOnlytrue改善自阴影问题。这将强制阴影生成器将网格的背面渲染到阴影贴图。这可以明显提高整体精度并减少对偏差的需求。

提高投影矩阵精度

默认情况下,灯光的投影矩阵使用主摄像机的minZ和maxZ。但是你可能想控制它,以便通过减少minZ和maxZ之间的距离来获得更精确的阴影贴图。为此,您可以设置light.shadowMinZ和light.shadowMaxZ。

使用最优设置进行自阴影

如前面所说,如果你想要在自阴影上模糊阴影,最好的选择可能是使用近似指数阴影贴图( close exponential shadow map)。

截锥体边缘衰减

根据你设置的阴影生成器,当对象靠近阴影贴图的边缘时,可能会生成一些奇怪的衰减。要优雅的解决此问题,你可以设置frustumEdgeFalloff的值

shadowGenerator.frustumEdgeFalloff = 1.0;

你可以在这里找到一个例子:点击这里

此属性控制阴影在截锥体边缘淡出的程度。它仅用于平行光和聚光灯。默认情况下,该值设置为0(没有衰减)和1.0(完全衰减)。

在静态场景中冻结阴影

如果你有一个静态游戏世界(带有投射阴影)- 没有必要每秒进行60次相同的阴影计算。仅创建一次阴影就足够了。这大大提高了性能,并且可以设置更高的阴影分辨率。
阴影生成器可以改为静态:

shadowGenerator.getShadowMap().refreshRate = BABYLON.RenderTargetTexture.REFRESHRATE_RENDER_ONCE;

要求光源不再从新计算阴影位置

light.autoUpdateExtends = false;

清除骨骼矩阵权重

模型动画的骨骼的权重错误或不精确可能会导致阴影不正确。在这种情况下,你可以使用以下代码加载自动清理权重:

BABYLON.SceneLoader.CleanBoneMatrixWeights = true;

你应该在加载场景或模型之前设置它。

自阴影

在设置阴影中,我们需要着重注意自阴影的问题。让我们尝试在以下场景设置自阴影:https://playground.babylonjs.com/#FH3FM2#1

第一步是在阴影生成器中添加生成阴影的模型和设置它们都接收阴影(我们还将阴影偏差强制设置为0以突出效果)https://playground.babylonjs.com/#FH3FM2#4

你可以注意到,在自阴影物体的表面到处都是奇怪的图案。这被称为阴影痤疮(更多相关信息)
好处在于,在Babylon.js中,我们确实有办法解决这个问题。

偏差

如前面的OpenGL教程中所描述的那样,你可以通过设置偏差值来避免产生偏差。https://playground.babylonjs.com/#FH3FM2#5

无法避免的是,这样会产生一种叫peter panning(彼得平移)的副作用,阴影将不会附着在它们的物体上。
16 Babylonjs基础入门 阴影-编程之家
你可以从babylon.js v3.2版本中获取正常偏差。

正常偏差(从v3.2版本开始)

首先我们先查看一下彼得平移效果的极限:https://playground.babylonjs.com/#FH3FM2#6

正如你所见,现在阴影生成的过渡处会出现一些斑点,表面与光线方向平行:
16 Babylonjs基础入门 阴影-编程之家
这是添加一些正常的偏差。基本上,在阴影图的生成过程中,如果表面与光线平行将沿法向方向插入几何体:https://playground.babylonjs.com/#FH3FM2#7

几何体上面的痤疮都不见了,接下来,让我们的阴影变得好看一些。

软化阴影

首先将阴影生成器改为联系硬化阴影 Contact hardening shadow(仅限WebGL2):https://playground.babylonjs.com/#FH3FM2#8

首先,你无法看到联系硬化效果,不仅如此,你还可以再次看到阴影痤疮。注意到关于PCSS的部分,你会发现需要将阴影的生成最小值和最大值调的尽可能近:https://playground.babylonjs.com/#FH3FM2#10

现在的阴影有联系硬化效果,但是几何体上的痤疮甚至更厉害了。不幸的是,偏差应用于标准化的坐标深度(0-1),因此更改灯光的近距和远距值会影响偏差的大小。

所以,回到前面在看到彼得平移之前将阴影偏差值设置最大,然后应用一些正常的偏差来移除其余的痤疮,产生了以下效果:https://playground.babylonjs.com/#FH3FM2#11

现在你的阴影很正常,并且没有痤疮和彼得平移效果。

自定义阴影贴图着色器

从Babylon.js v4.0版本开始,你可以指定自己的着色器来渲染阴影贴图。要定义该着色器,你可以通过shaddowGenerator.customShaderOption属性来设置

shadowGenerator.customShaderOptions = {  shaderName: "customShadowMap",uniforms: ["customWorld"]
}

除了shaderName是必须设置的意外。你可以设置的其它属性为:

  • attributes: 用于指定你在着色器里面用到的attributes
  • uniforms: 用于指定你在着色器里面用到的uniforms
  • samplers: 用于指定你在着色器里面用到的samplers
  • defines: 用于指定你在着色器里面用到的defines

阴影贴图生成是一项复杂的任务,你还需要考虑几个定义(如阴影贴图的类型是int还是float,或者是否需要alpha test)。建议在此处检测默认着色器:

  • 顶点着色器:https://github.com/BabylonJS/Babylon.js/blob/master/src/Shaders/shadowMap.vertex.fx
  • 片元着色器: https://github.com/BabylonJS/Babylon.js/blob/master/src/Shaders/shadowMap.fragment.fx

为了更好的检查你自己的着色器,你可以依靠shadowGenerator.onBeforeShadowMapRenderObservable观察。每次渲染阴影贴图时都会调用它,它将为您提供当前编译的效果。
你可以在这里找到一个完整的例子:https://www.babylonjs-playground.com/#IJH4VG#0

基础入门结语

到这里,Babylon.js的基础入门也算翻译完成了。由于自己能力有限,相关内容有很多问题,以及自己都没有理解的内容。如果你有更好的解释方式,请联系我,希望我们共同进步。