首页 > 其他分享 >游戏中的动态阴影(上)

游戏中的动态阴影(上)

时间:2023-05-04 11:58:53浏览次数:45  
标签:游戏 Shadowmap 渲染 阴影 Bias 投射 Shadow 动态

阴影对于提高游戏真实感非常重要,简单总结下游戏中的阴影实现。

先来看下阴影的组成部分,我们可以将阴影大致分成两个部分:全影(Umbra)和半影(Penumbra)。半影区域就是阴影的过渡区,也就是软阴影,有半影的阴影过渡时,视觉效果会好很多。


阴影的组成部分

 

对于静态的场景,我们可以选择将阴影烘焙到Lightmap中,或者直接画在贴图上。这篇文章,我们主要来介绍下动态阴影的相关技术,因为阴影是实时渲染中比较重要的技术,实现的方式也非常多。本篇文章,尽量覆盖到各种常用的阴影渲染技术。

一、简单的手绘假阴影

在手游或者2D游戏中经常能看到这种做法,对于动态的角色,将阴影做成一张贴图,然后贴到脚下的地面上,虽然是很简单的形式,也能极大地增强真实感。


简易的阴影

 

二、平面投射阴影

1. 平面投射阴影的计算
平面投射阴影,就是将需要投射阴影的物体再渲染一次,投射到地面上,来产生阴影。根据平面的位置,我们可以计算出一个投射的矩阵,直接将物体的坐标变换到平面上。

我们先来看简单的情况,如下图左边所示将阴影投射到x轴上的情况,我们在光源l的照射下,需要从点v投射阴影到点p,根据三角形相似原理,我们可以简单地得到:

 

相应地,我们还可以算出z轴上的坐标为:pz =(lyvz-lzvy)/(ly-vy) ,将结果整理成投影矩阵为:

 

这样可以通过矩阵计算投影坐标为:p=Mv 。

 

现在,我们看上图中右边这种更加一般的情况,在这种情况下,我们同样可以根据三角形相似原理,推导出投射阴影的坐标变换方程为:

从v点映射到p点:

 

p=Mv推导后写成矩阵的形式:

 

如果是平行光源,计算的方式也是大致相同,并没有特别的难度。

在进行渲染时,我们可以选择先来渲染阴影,将投射阴影的物体,经过上述矩阵的变换到平面上,然后得到没有光照的黑色地面,此时同时把深度写入。然后再正常渲染地面和投射阴影的物体,为了使地面和阴影之间不会冲突,此时可以为深度值添加一些偏移。

添加偏移的方式可以直接通过图形API来添加,比如OpenGL中的glPolygonOffset和DirectX中的DepthBias设置。当然,你也可以选择在绘制阴影时添加偏移,绘制地面时正常绘制,最终的结果都是相同的。后面我们讲到的各种阴影技术,经常会用到添加偏移(Bias)的技术。

另外一种安全的做法是,先正常渲染地面,然后渲染地面上的阴影,渲染阴影时将深度测试关闭,就不会产生深度冲突的问题。最后再渲染投射阴影的物体,这样可以防止阴影投射到非地面的区域。

如果接受阴影的地面不是一个无穷大的平面,则可能需要通过Stencil Buffer标记出需要接受阴影的部分,这样可以只让阴影产生在需要产生的平面上。

另外一个需要注意的,是如下图所示的情况,在进行计算时,需要保证投射阴影的物体位于光源和接受阴影的地面之间,否则就会出现错误的阴影效果。


右边的情形下不应该绘制出阴影

 

总的来说,这种直接投射阴影的方式,简单直接,适合直接投射在平面上的阴影。目前在手机游戏中,仍然有广泛的应用。

这种直接投射的阴影无法实现软阴影效果。而且由于我们是先渲染出的地面,再将影子的颜色乘以地面的颜色,这样其实并不是完全符合阴影产生的原理。

我们知道,阴影是由于地面没有受到光照而产生的,如果直接将地面的颜色乘以阴影,可能会产生不正确的阴影效果,特别是地面上有高光效果时。这类阴影叫做调制阴影(Modulated shadow),相对普通的阴影,开销要小一些。


游戏中的平面投射阴影

 

2. 借助Texture的投射阴影
上面我们说到的投射阴影,是直接渲染到被投射的平面上,这样我们就无法实现软阴影的效果,因此我们这里将阴影先保存在一张贴图中,再从贴图中投射到平面上。这样还可以先得到阴影图,再渲染地面,得到正确的阴影效果。

和前面的直接投射相比,这种方式因为中间经过了一层转变,如果保存阴影的贴图分辨率很低,就可能会造成投射出来的结果有锯齿感。

这样,我们就可以将贴图中的阴影先进行边缘模糊,再进行投射,就可以非常方便地得到软阴影效果。


投射阴影实现的软阴影,先将阴影投射到贴图中,然后进行模糊,再投射至平面,实现软阴影效果

 

为了提升运行效率,我们还可以将多个物体的Texture打包到一个Shadow Atlas中,这样每个物体的投射阴影,占用整个大贴图的一部分。如果光源和投射阴影的物体都没有改变,我们甚至可以不用更新阴影,实现帧间阴影的复用。

三、Shadow Volume阴影

Shadow Volume以前是一种非常流行的阴影实现方案,目前在游戏中也有一定的应用,特别是后面我们将要讲到的PerObject阴影,因此了解其原理是非常重要的。Shadow Volume需要依赖Stencil Buffer来进行实现。

1. Shadow Volume
Shadow Volume就是从光源沿着模型边缘拉伸至无限远处加上前盖后盖形成的形状。可以说,位于Shadow Volume内部的物体,在渲染时具有阴影,在Shadow Volume外部的物体,在渲染时没有阴影。


shadow volume

 

2. ZPass算法
Shadow Volume阴影的原理就是取一条从视点到目标点的线,每次进入Shadow Volume,Stencil模板计数加一,每次离开计数减一,这样计数为0的部分就是无阴影的地方,计数不为0的地方就是有阴影的地方。

Shadow Volume的实现需要两个Pass,第一个Pass是标记具有阴影的区域,第二个Pass是进行阴影渲染。

第一个Pass,从视点渲染Shadow Volume几何体,屏幕中被Shadow Volume覆盖的区域,就是所有可能产生阴影的位置。我们这里使用Stencil Buffer来标记出实际具有阴影的位置:开启Z-Test,设置Stencil模式为正面部分+1,背面部分-1。这样渲染完成后,Stencil Buffer为0的部分就是无阴影的地方,Stencil Buffer中不为0的部分就是有阴影的地方。


ZPass的原理

 

第二个Pass,同样也是渲染Shadow Volume的几何体,不过此时直接关闭深度测试,使用模板测试,直接在上一步中标记出的位置渲染出阴影。

3. Z-Fail算法
ZPass算法有个缺陷,当摄影机在Shadow Volume中的时候,就会产生错误的结果。


当摄影机位于Shadow Volume中时,ZPass标记阴影区域失效

 

所以就有了Z-Fail的算法,Z-Fail算法和ZPass算法类似,只是改成从物体背面计数,在Z-Test fail的几何体部分,在进入Shdow Volume时计数-1,离开时计数+1,这样就可以规避这个缺陷。


使用Z-Fail算法,标记处正确的阴影位置

 

不过一般来说Z-Fail算法普遍要比ZPass算法慢,因为从背面渲染Shadow Volume,通常会覆盖更多的像素点。

因此在实践中,我们可以先做一个摄影机是否位于Shadow Volume中的判断,来决定使用ZPass或者是Z-Fail算法来进行标记阴影区域。

4. 生成阴影体的步骤
有一种最常见的生成Shadow Volume的方法,不过这种方法要求目标模型是封闭的多边形网格(没有空洞、裂隙、自相交)。

分为三部分:front capping 前盖-> back capping 后盖-> silhouette 轮廓拉伸成的侧面

front capping就是取模型中面向光源的三角面,方向判断可以通过判断面法线和光源方向的乘积的正负值来判断。

back capping就是取模型中背向光源的面,沿光源方向拉伸到无穷远处。

silhouette是判断两个临接面与光源方向不同的边,若认为是轮廓边,则将每条边扩展拉伸到无穷远处形成一个四边形面。

5. 在无穷远出的渲染
如何表示无穷远处的点?使用齐次坐标将w分量置为0,xyz表示方向即可。

如何避免图元在摄影机far clip plane外被裁剪掉?

一种方法是使用GL_DEPTH_CLAMP_NV扩展,将far plane外的点clamp到裁剪空间中。不过这个方法好像是只适用于OpenGL和NVIDIA显卡。

另外一种方法是稍微修改下摄影机的裁剪矩阵,将far plane设置为无穷远。


普通摄影机矩阵

 

变成下面这样:


远裁面在无穷远处的摄影机矩阵

 

当然精度或有微乎其微的减少。

6. 适用于非封闭模型的方法
把模型分成两部分,一部分是面向光源的面,一部分是背向光源的面,分别进行拉伸生成Shadow Volume,就可以支持非封闭模型。缺点是原来的轮廓边相当于生成了两次,造成性能浪费。


左边是面向光源面,右边是背向光源面,两个加在一起形成正确的结果

 

7. 使用Geometry Shader生成Shadow Volume
使用GS可以将生成Shadow Volume的工作移交给GPU,不过必须用TRIANGLE_STRIP的方式来输入模型。

使用GL_TRINGLES_ADJACENCY_EXT模式来向GS中输入三角形图元,就可以获取三角形的邻接面,以此在GS中进行轮廓边判断、输出Shdow Volume等操作。


Geometry Shader中输入的顶点

 

四、Shadowmap-当前最主流的方式

1. Shadowmap的原理
是当下应用最广泛最常见的方法,Shadowmap的使用,需要两个步骤。

假设我们现在要渲染带阴影的场景如下:

 

步骤1:从光源处出发,向光照的方向看去,来构造出光照空间。然后在光照空间,我们渲染需要产生阴影的物体,此时将深度写入到Z-Buffer中,得到保存最近处物体的深度值的Shdowmap。

 

步骤2:然后我们再次正常渲染物体,在渲染时,我们根据渲染物体的世界坐标,变换到上一阶段的光照空间坐标,再计算出该点在Shadowmap中的深度值并进行比较,如果相对光源的距离比Shadowmap中的深度要大,就说明该点处在阴影中,否则就说明不在阴影中。

 

下图显示了整个Lightmap工作的流程:


Shadowmap计算阴影的大致过程

 

对于锥形光源,我们只需要沿着光照方向生成Shadowmap。对于类似太阳光的平行光源,我们就需要使用正交投影来进行计算深度,而且投影体的空间范围,需要包含我们的视锥空间。如果是点光源,就会更加复杂一点,为了能保存各个方向的深度值,我们一般需要使用Cubemap 。如果将一个物体进行六次渲染,每次渲染深度到每个面,那么渲染深度的开销就会比较大,因此我们一般会使用RenderTargetArray配合Gemotry Shader,一次性将一个物体的深度,同时写入到六个面上。

2. Light Space Frustrum的计算
Shadowmap的效果,一般会非常依赖于Shadowmap分辨率的大小和Z-Buffer的精度。因此我们要尽量提高Shadowmap的精度。

如果直接使用整个场景的AABB转化到Light Space,肯定是不行的,这样会造成很多不需要的阴影投射计算:


过大的Light Space边界

 

通常我们会使用下面的方式来计算Light Space Furstrum的边界大小。将世界空间视锥的八个顶点,变换到光照空间,算出在光照空间下,最远和最近的z值,并计算出AABB边界:

 

不过,这样也可能会造成另外一个问题,就是当摄影机的View Frustrum很小时,造成计算出来的Light Space Frustrum非常小,无法正确地投射所有需要投射阴影的物体。

因此我们还会根据整个场景的AABB空间,对得到的Light Space Frustrum进行扩展,使其能否覆盖到可能产生阴影的物体。当然,为了防止Light Space Frustrum的Near Plane 和Far Plane的值相差过大,我们还会在光照中设置一个最大阴影距离,当阴影投射物体,超出这个最大距离后,就不再投射阴影,来提高阴影的精度。


正确的计算方式

 

3. Shadow Bias处理自阴影走样
如下图所示,在进行阴影计算时出现了Self-shadow Aliasing/Shadow Acne,在计算自身的阴影时,因为在Shadowmap中存储的深度值,和物体自身的深度是相同的。因为在写入 Shadowmap时,我们计算的是Shadowmap像素中心点的深度值,这样在进行深度采样时,由于Shadowmap的精度限制,就会使比较的深度值产生误差,造成错误的渲染效果。


自阴影走样,右边是加了Bias的效果

 

一种常见的解决自阴影误差的方式,是使用Bias Factor,对采样时的深度值,沿着光照的方向进行偏移。偏移的值可以是一个常量,这样计算起来比较方便,但是可能会在斜平面上继续产生误差,使用常量时叫做Constant Bias

下图左边展示了Shadow Acne出现的原因,黑色的竖线代表Shadowmap中像素点的位置。左边是未添加Bias的情况,当我们在彩色的位置点进行比较深度时,其实采样到的深度是旁边的竖线处x标记位置的深度,可以看出,绿色点的深度测试是正确的,蓝色和橙色的深度测试是错误。下图中间是使用了Bias的情况,将深度值沿着光照方向进行偏移固定的距离。这样绿色和橙色的点形成了正确的深度值,但是由于偏移的值比较小,蓝色的点的阴影计算,仍然是错误的。


左:出现Shadow Acne的原因;
中:使用Constant Bias;
右:使用Slope Scale Bias

 

我们发现,在斜面角度较大时,一个固定的偏移值就不再适用了,因此一个常见的改进,就是根据斜面角度来改变偏移值,叫做Slope Scaled Depth Bias / Slope Bias。如上图右边所示,可以看出所有的点的阴影计算结果都是正确的。

设平面法线和光照方向的夹角为θ,视锥大小为frustrumSize,Shadowmap的大小为
shadowmapSize,考虑到我们需要半像素的偏移,这样我们可以计算出需要的Slop Bias的偏移值为:

 

不过我们可以注意到,这个偏移值是和tan(θ)成正比的,这样的话,当θ趋近于90度时,偏移值是趋近于无穷大的,因此我们需要为偏移值设置一个最大值。

在实际游戏引擎实践中,我们常常需要结合两种Bias来使用,这样来达到较好的效果。

这两种Bias都可以通过图形API硬件来实现。例如在DX11中,我们可以在OutputMerge阶段中,通过参数指定两种Bias的值[1]DepthBiasSlopeScaledDepthBias,这样总的Bias计算方式为:

Bias = (float)DepthBias * r + SlopeScaledDepthBias * MaxDepthSlope;

  

我们还可以设置DepthBiasClamp的值,防止计算出的Bias值过大:

Bias = min(DepthBiasClamp, Bias)

  

另外一种常用的替代Slope Scaled Depth Bias的方案是Normal Offset Bias,将阴影的计算位置沿着物体表面的法线偏移,通过计算我们可以算出需要偏移的距离为:

 

相对于Slope Scaled Depth Bias,这种方式的一个优点是不用担心θ趋近于90度时,整个偏移值趋近于无穷大。

UE4中,使用的Constant Bias + Slope Scaled Depth Bias

 

Unity中,使用的是Constant Bias + Normal Offset Bias

 

当然,我们的Bias值也不能设置得过大,否则会出现漏光等问题,也叫做Peter Panning


Bias值太大导致的Peter Panning

 

为了保证这种Bias的方式能正确地解决深度冲突。我们应尽量保证物体几何模型是正确的,保证正反面朝向是对的,尽量保证模型封闭,且避免使用太薄的物体模型。

添加Bias可以是在生成Shadowmap阶段完成,也可以在阴影计算阶段,也就是生成Shadowmap时。在Vertex Shader中通过反向添加Bias的方式来偏移计算处的Shadowmap深度值,这样可以节省一些运行开销,且可以简化阴影的计算,这样在采样阴影时,就无需考虑计算偏移的问题。

大部分情况下二者得到的效果是基本接近的,不过在Shadowmap生成阶段添加偏移这种方式也有一些瑕疵:

  1. 不够灵活,所有点的偏移值完全相同,意味着无法根据情况灵活调整Bias值,比如在PCF采样软阴影时,只能提前给出比较大的Bias值,而无法根据PCF Radius的大小灵活调整;
  2. 和Normal Offset Bias,在光照角度比较小的时候,会导致渲染结果错误[2],Unity中的阴影就有这样的缺陷。

 


在光照角度较小时,Unity URP的错误阴影效果

 

还有一种比较少见的解决自阴影的方式,是将物体背面的深度写入到Shadowmap,进行深度测试时,就不会出现深度冲突。但是这种方式有很大限制,要求使用的模型必须是正确封闭的,且正反面没有错误。而且如果物体模型很薄,导致前面和背面深度几乎相等,这种方式仍然会失效。因此这种方式不太通用,现在已经很少能见到。

4. 移动平台的Pack
某些旧的移动平台不支持浮点数纹理,这时需要我们将Shadowmap的深度值Pack到RGBA贴图中,Pack和UnPack的公式如下:

//Pack:
vec4 comp = fract(depth * vec4(255.0 * 255.0 * 255.0, 255.0 * 255.0, 255.0, 1.0));
comp -= comp.xxyz * vec4(0.0, 1.0 / 255.0, 1.0 / 255.0, 1.0 / 255.0);
//UnPack:
float depth = dot(texture((m_tex), (m_uv)), vec4(1.0 / (255.0 * 255.0 * 255.0), 1.0 / (255.0 * 255.0), 1.0 / 255.0, 1.0))

  

这里我们使用的是255作为模来使用,网上也能搜索到使用256作为模的版本。

但是测试结果表明,使用256时精度是不如255的[3],而且还会遇到不同硬件表现不一致的问题,因此强烈建议使用255 作为参数。

五、Shaowmap精度提升

由于Shdowmap的精度限制,我们在渲染中会遇到各种各样的渲染问题。

一种叫做Perspective Aliasing,由于Shadowmap是在Light Sapce中进行计算的,所以在View Frustrum近处观察时,每个像素对应Shaodowmap中Texel的比例就会降低,产生锯齿。


Perspective Aliasing在近处比较明显

 

另外这一种叫做Projective Aliasing,是在斜面上进行渲染时,Shadowmap精度不足产生的,本质上来说和Perspective Aliasing是相同的。


Projective Aliasing

 

通常,提升Shadowmap的分辨率可以改善上面两种渲染问题。但是处于性能考虑,我们不会把Shadowmap的分辨率设置的太大,而是使用一些手段,来提高渲染结果的精度。

1. 使用Perspective Warping
这类方法,通过修改光照空间的投影矩阵,来为视锥近处的物体阴影,提供更高的精度。

常见的有这样几种方式,Perspective Shadow Maps(PSM),Light Space Perspective Shadow Maps(LiSPSM)和Trapezoidal Shadow Maps (TSM)。这些修改投影矩阵的方式原理上大致都是相通的,如下图所示,显示了这类方式的原理:


改变计算Shadwomap时的投影方向
就可以为近处提供更高的精度

 

这类方式虽然使用起来简单,但是有很多无法处理的特殊情况,比如观察方向和光照方向完全相同时,这类方式就完全无法发挥作用。而且在摄影机移动时,这种方式非常的不稳定。

这类方式目前已经被彻底淘汰,这里也就不再深入讲解相关的原理和实现。

2. Cascaded Shadow Maps(CSM)
CSM是目前最常见的提高Shadowmap精度的手段,候也叫做Parallel-Split Shadow Maps。

通常在渲染视角附近的物体时需要更高的Shadowmap精度,而直接生成的Shadowmap往往不符合这个条件,所以将Frustum分割成数个部分,每个部分单独生成一张Shadowmap,最后组合成一张Atlas。


CSM

 

从理论上来说,使用指数分布的CSM划分方案是最佳的,即满足

 

f、n是相机的far、near值,n是指数系数。

比如我们取n=3,f=1000。 这样我们划分出来的三级CSM就是:1-10,10-100, 100-1000。

但是如果我们这样来划分,最近处1-10这个范围的一个CSM划分,物体太少,反而会导致Shadowmap空间的浪费。因此在实践中,常常会结合指数划分和其他划分手段来使用,或者直接由用户手动设置相应的比例值。


Unity中的CSM,不同的颜色代表不同的CSM区域

 

3. Stablize CSM [4]
在使用Shadowmap时,在移动摄影机时,我们经常会遇到阴影闪烁的问题。因为当摄影机移动后,摄影机的View Frustrum会发生改变,同时Light Space的Frustrum会相应改变,就会造成两帧直接的阴影位置不一样,产生闪烁,在没有使用PCF过滤阴影时,会尤其明显。下图显示了这种闪烁的示例,可以看出视角的微小变化,导致阴影产生了剧烈的闪烁:

视频链接

 通常我们会使用Stabilize Cascades来解决这个问题,Stabilize Cascades将相机的移动分成两个部分来处理,分别是相机的旋转和平移。无论相机是如何运动的,都可以分解成沿着视锥中心的旋转和平移。

首先来看绕视锥中心的旋转,当视锥旋转时,因为视锥边界的改变,就会导致计算出来阴影的Light Space Frustrum改变,产生不稳定的结果。要解决这个问题,我们将视锥 Frustrum计算出一个球形的Bounding Volume出来,并用这个球形的Bounding Volume 来算出阴影的Light Space Frustrum,这样当我们的视锥沿着球体中心旋转时,得到的球形Bounding Volume是不变的,算出来的阴影的Light Space Frustrum自然也不会变化。


ab展示的传统的Light Space Frustrum计算过程
cd使用球形BV时的计算过程,在摄影机转动时也是稳定的

 

从Frustrum生成Bounding Box Sphere,可以使用简单方法求出中心点,算最大半径的方式。也可以使用能得到更加紧凑边界的标准算法[5]

接下来就是处理摄影机平移的部分了,这一步的处理,就是通过偏移投影矩阵,来保证两帧之间,世界空间中的同一点,能投影到Shaodwmap中的相同相对像素位置上。为了计算方便,我们常常取世界空间中的零点,作为参考点,将世界空间的零点,变换到Shadowmap坐标中,并通过偏移,确保得到的Shadowmap坐标是对齐于某个像素的。对齐过程实现的大致代码如下:

            // Create the rounding matrix, by projecting the world-space origin and determining
            // the fractional offset in texel space
            XMMATRIX shadowMatrix = shadowCamera.ViewProjectionMatrix().ToSIMD();
// 使用零点作为参考点
            XMVECTOR shadowOrigin = XMVectorSet(0.0f, 0.0f, 0.0f, 1.0f);
// 将参考点变换到 shadowmap的坐标
            shadowOrigin = XMVector4Transform(shadowOrigin, shadowMatrix);
            shadowOrigin = XMVectorScale(shadowOrigin, sMapSize / 2.0f);
// 在shadowmap坐标系中,将坐标对齐到整数坐标线上
            XMVECTOR roundedOrigin = XMVectorRound(shadowOrigin);
            XMVECTOR roundOffset = XMVectorSubtract(roundedOrigin, shadowOrigin);
            roundOffset = XMVectorScale(roundOffset, 2.0f / sMapSize);
            roundOffset = XMVectorSetZ(roundOffset, 0.0f);
            roundOffset = XMVectorSetW(roundOffset, 0.0f);
//应用偏移,得到新的 projection 矩阵
            XMMATRIX shadowProj = shadowCamera.ProjectionMatrix().ToSIMD();
            shadowProj.r[3] = XMVectorAdd(shadowProj.r[3], roundOffset);
            shadowCamera.SetProjection(shadowProj);

  

在大部分游戏引擎中,Stablize CSM都是默认打开的。不过需要注意的一点是,打开Stablize CSM时,因为阴影的有效范围减少了,所以是会导致阴影精度降低的。在可以保证阴影效果足够软而不会产生闪烁的时候,也可以选择关闭这个功能,来提升阴影的精度。

4. CSM Caching
在使用CSM时,我们常常会遇到CSM开销较大的问题,比如现在使用四级CSM级联,就意味着在生成Shaodwmap时,很多物体需要重复绘制四次。因此有的时候我们会对CSM进行一些优化。

一种方式是降低远处CSM的更新频率。比如在原神的PC版中,共有八级的CSM,前四级是每帧都更新的,后四级是逐帧依次更新的,这样相当于每帧需要更新五级的CSM。

另外一种方式是将CSM中算出的阴影动态缓存,对于静态物体的Shadowmap,是可以实现前后两帧之间的复用的。上一帧中静态物体的Shadowmap,经过一些小小的处理,在当前帧仍然是可用的,对于一些没有覆盖的区域,可以动态来检测,重新绘制生成:


CSM Caching

 

参考:
[1] https://learn.microsoft.com/en-us/windows/win32/direct3d11/d3d10-graphics-programming-guide-output-merger-stage-depth-bias
[2] https://zhuanlan.zhihu.com/p/370951892
[3] https://aras-p.info/blog/2009/07/30/encoding-floats-to-rgba-the-final/
[4] ShaderX6 Stable Cascaded Shadow Maps
[5] https://zhuanlan.zhihu.com/p/136752363

更多内容,请关注:
游戏中的动态阴影(下)


这是侑虎科技第1380篇文章,感谢作者张亚坤供稿。欢迎转发分享,未经作者授权请勿转载。如果您有任何独到的见解或者发现也欢迎联系我们,一起探讨。(QQ群:465082844)

作者主页:https://www.zhihu.com/people/tc130-52

【USparkle专栏】如果你深怀绝技,爱“搞点研究”,乐于分享也博采众长,我们期待你的加入,让智慧的火花碰撞交织,让知识的传递生生不息!

标签:游戏,Shadowmap,渲染,阴影,Bias,投射,Shadow,动态
From: https://www.cnblogs.com/uwatech/p/17370712.html

相关文章

  • MATLAB代码:基于主从博弈的电热综合能源系统动态定价与能量管理
    MATLAB代码:基于主从博弈的电热综合能源系统动态定价与能量管理关键词:主从博弈电热综合能源动态定价能量管理参考文档:店主自编文档,完全复现仿真平台:MATLAB平台优势:代码具有一定的深度和创新性,注释清晰,非烂大街的代码,非常精品!主要内容:代码主要做的是电热综合能源系统的动态定......
  • 配电网多目标动态有功网损优化,无功优化 ,基于IEEE33节点配电网,以配电网网损最小,电压偏
    配电网多目标动态有功网损优化,无功优化,基于IEEE33节点配电网,以配电网网损最小,电压偏差最小,运行成本最小目标函数,考虑了24个不同时刻的时间尺度,变压器变比和两个无功补偿接入的容量为优化变量,通过多目标粒子群算法进行求解,得到最佳接入策略,代码本人所写,提供一定的。同时算法一学就......
  • MATLAB代码:基于二阶锥规划的主动配电网动态最优潮流求解
    MATLAB代码:基于二阶锥规划的主动配电网动态最优潮流求解关键词:配电网优化二阶锥优化动态优化最优潮流参考文档:《主动配电网最优潮流研究及其应用实例》仅参考部分模型,非完全复现仿真平台:MATLABYALMIP+CPLEX优势:代码注释详实,适合参考学习主要内容:代码主要主要研究的配电网优化......
  • 微服务 - Nginx网关 · 进程机制 · 限流熔断 · 性能优化 · 动态负载 · 高可用
    系列目录:微服务-概念·应用·通讯·授权·跨域·限流微服务-集群化·服务注册·健康检测·服务发现·负载均衡微服务-Redis缓存·数据结构·持久化·分布式·高并发本文的前提需要了解一些基础的Linux知识。以下围绕Nginx1.23的网关应......
  • [Week 19]每日一题(C++,数学,并查集,动态规划)
    目录[Daimayuan]T1倒数第n个字符串(C++,进制)输入格式输出格式样例输入样例输出解题思路[Daimayuan]T2排队(C++,并查集)输入格式输出格式样例输入1样例输出1样例输入2样例输出2样例输入3样例输出3数据规模解题思路[Daimayuan]T3素数之欢(C++,BFS)数据规模输入格式输出格式样例输入样......
  • 【游戏设计随笔04】关于《桃源》的一些设计运营总结
    1:《桃源》有着非常明显的优势区间“美术”。用出众的美术风格为玩家制造记忆点,依旧是游戏获客的重要方式,尤其是对于那些缺乏用户积累的新IP、新团队来说。2:度过了初期的视觉冲击,就需要用玩法让玩家留下,《桃源》采用了一套简练的核心循环。仅从生产经营的资源循环来看,《桃源》有......
  • 动态物体追踪
    动态物体追踪闲话我个人是比较喜欢捣鼓一些程序设计,算法之类的。但毕竟是人工智能专业的,电子类大赛也必须去打,起初是导师让我来打这个比赛,后面发现还是很有挑战,很有意思的。一开始我对全国大学生电子设计大赛真的一点不懂,之前也没了解过。后来听导师说,我们团队做的都是些控制类......
  • 【游戏设计随笔05】关于“模拟经营”的一些感悟笔记
    一些模拟经营大类的tips:1:模拟经营游戏的定义游戏可以视为一组,在好玩的心态下由玩家执行的规则下的选择。而模拟经营类型的游戏更加偏重于资源分配。如果有一个基本定义,就是完成游戏资源产出机制,到资源分配,再到升级机制,最后强化资源产出,完成闭环的一个结构。2:模拟经营游戏的核心......
  • [动态规划-背包问题入门] 原理,运用,实战
    背包问题--动态规划经典类型动态规划是将问题细分为有限个小问题并通过递推或递归来求得最终值。具象化来说,就是对某一问题的答案,我们转化为dp[n],而对于0<=i<n,dp[i][j]的值会根据前后上下的相关值来变化(i.e.dp[i-1][j]或dp[i][j-1])。注意这时算法强调的不是【容量】,而是......
  • mybatis控制动态SQL拼接标签之if test标签
    if标签通常用于WHERE语句、UPDATE语句、INSERT语句中,通过判断参数值来决定是否使用某个查询条件、判断是否更新某一个字段、判断是否插入某个字段的值。mybatis是一个天才设计,面向对象未必就是真理,相对于JPA等框架,具有更大的自由度和灵活度。简单示例selecthost_nameashos......