基于Shadow map的实时阴影效果实现

有光则有影,即时渲染的阴影可以使场景看上去更加真实。

下图显示了实时阴影系统在一个3D项目中的应用:

 

算法描述: 

ShadowMap是一种基于阴影图的阴影生成方法,阴影图是一张2D贴图。阴影图中的每个像素都记录了从光源到遮挡物(遮挡物就是阴影生成物体)的每个“可见”像素的距离。这里的“可见”像素是指,以光源为观察点,光的方向为观察方向,设置观察矩阵并渲染所有遮挡物,最终出现在渲染表面上的像素。使用ShadowMap渲染阴影主要分两个过程:生成阴影图和使用阴影图渲染。下面的代码取自生成阴影图的PS代码

float distance=distance(lightpos,input.PWorld);
distance*=distancescale;            
distance-=depthbais;               
return distance;                 

 
     distance是HLSL的内置函数用于计算两点距离,这里获得了光源到当前像素的距离。
     disancescaler用于对距离值进行缩放,这个值可以通过应用程序设置,使用它就可以对阴影进行即时可见的调整。
     depthbais同样用于阴影图调整。
     返回距离值,实际上此时的渲染目标已经被设置成阴影图,阴影图的设置方法和上节的立方体环境贴图类似,不过这里只需要将遮挡物渲染一次。
下图显示了一个场景和其使用到的阴影图。
 
需要在阴影图中保存从光源到“可见”点的距离是因为:利用这个距离就可以直接判
断一个像素是否在阴影中。如果一个世界空间的“点”到光源的距离大于阴影图中对
应像素的值,那么这个点肯定已经被另一个离光源更近的点遮挡住了,所以它肯定在
阴影中。基于这个原理就可以很容易的编写渲染阴影的代码了。
下面的代码取自渲染阴影的PS代码
 
float CurrentPixelDistance=distance(lightpos,input.PWorld)*distancescale+depthbais;①
Shadow = (CurrentPixelDistance > tex2Dproj(shadowmap,input.ShadowMapCord))②
Shadow*=ShadowDensity;③
Return Shadow;
  
     这里获得当前像素到光源的距离。
     首先对阴影图采样,再用当前像素的值和采样所得的值进行比较。大于则当前像素在阴影中,Shadow=1;否则Shadow=0;
     这里ShadowDensity同样由应用程序调整,用于设置阴影浓度。
该PS返回了阴影值,该数值越大表示阴影越浓。但是直接将其输出是不行的,因为这里只有阴影,而场景必须将场景本身的颜色和阴影“混合”输出。这里的解决方案是AlphaBlend,简单的说就是首先按照指定的方式渲染场景,这里可以使用前面说的任何渲染方式;接着渲染阴影,在渲染阴影的时候启用AlphaBlend,这样前后两次渲染的结果就可以通过AlphaBlend“混合”在一起了。
另外,由于阴影图是一种基于贴图采样的技术。渲染效果和阴影图的分辨率有着极大的关系,当提高分辨率无法满足渲染效果的需要时,有必要采用一些图象处理的方法。例如,对阴影图进行多次采样使得阴影的边缘更加平滑。下面的Shader使用2x2PCF(Percentage Closer Filter)方法对阴影图进行过滤。
 
float ShadowDepth=tex2Dproj(shadowmap,input.ShadowMapCord);①
float ShadowDepth1=tex2Dproj(shadowmap,input.ShadowMapCord+float4(-SampleOffset,0,0,0));
float ShadowDepth2=tex2Dproj(shadowmap,input.ShadowMapCord+float4(0,-SampleOffset,0,0));
float ShadowDepth3=tex2Dproj(shadowmap,input.ShadowMapCord+float4(-SampleOffset,0,0,0));
float Shadow = (CurrentPixelDistance > ShadowDepth);②
float Shadow1=CurrentPixelDistance> ShadowDepth1;
float Shadow2=CurrentPixelDistance> ShadowDepth2;
float Shadow3=CurrentPixelDistance> ShadowDepth3;
……………………………………….
finalshadow=(Shadow+Shadow1+Shadow2+Shadow3)/4;③
 
 
     这四行代码对阴影图执行了4次采样,每次采样以Sample Offset为偏移量,获得阴影图中对应像素周围4个像素所保存的光源距离。
     这四行代码用每一个距离值和当前像素距离值比较得到4个阴影值。
     平均四个阴影值,得到最终的阴影值。
很显然,当一个像素处于阴影边缘的时候,4次采样中可能会有部分结果为0,部分为1。平均以后就可以得到一个
0,1之间的值,这个值将会表现在阴影的浓度上,这样就可以形成平滑的阴影边缘。但是,提升效果的代价也是明
显的,使用多次采样会明显的降低速度。由于PS2.0最多可用64条指令,编写3x3PCF刚好够用,效果更好,但也
更慢。
下图对各种采样方式做了一个比较。从左到右逐渐提高了PCF(百分比渐进过滤)的采样次数:

 

结论,本文给出了一个ShadowMap的实现方式。需要注意的是,文中所提到的PCF实现方式并不是 最佳的,因为现在的很多图形硬件已经从硬件上支持多次采样的PCF技术,这种技术能够获得更好的执行效率并且能够免除ps指令条数的限制。

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Unity中可以使用ShadowMap实现阴影效果,并且可以通过阴影的颜色、透明度等参数来实现阴影的分析和变化。以下是一个简单的实现步骤: 1.在Unity中创建一个场景,并将需要投射阴影的物体放置在场景中。 2.在场景中创建一个光源,并将其设置为投影模式。在光源的参数设置中,选择Shadow Type为Hard Shadows或Soft Shadows,并将Resolution设置为需要的大小。 3.在被投射阴影的物体上,将其接收阴影的属性设置为true。这可以通过在物体的Renderer组件中设置Cast Shadows为On,或者通过代码设置。 4.在阴影的Material中,可以设置颜色、透明度等参数来实现阴影的分析和变化。例如,可以根据时间、位置等因素来改变阴影的颜色或透明度。 以下是一个示例代码,可以在Update函数中实现根据时间变化阴影颜色的效果: ```csharp public class ShadowColor : MonoBehaviour { public Material shadowMaterial; //阴影的材质 public Color startColor; //开始颜色 public Color endColor; //结束颜色 public float duration = 2.0f; //变化时长 private float startTime; //开始时间 void Start() { startTime = Time.time; } void Update() { float t = (Time.time - startTime) / duration; shadowMaterial.color = Color.Lerp(startColor, endColor, t); } } ``` 在该代码中,首先定义了阴影的材质和起始、结束颜色。然后,在Start函数中记录了开始时间,每帧根据时间变化计算阴影颜色,最终将其赋值给阴影的材质。可以根据需要修改该代码来实现不同的阴影分析和变化效果

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值