之前已在简书上发过,这里重新整理了一下,并附上实现代码。
在unity中实现阴影的文章网上看了不少,包括常用的shadowMap,或直接投射Rendertexture等。比如unity中实现shadowmap,可以通过投射灯光空间的深度图,并在投射物体上进行深度比较,判断是否处于阴影的范围,以此来渲染阴影。将深度图投射到接受阴影的物体上的效果如图所示:
你所需要做的就是在灯光空间渲染一张深度纹理,并投射到接受阴影的物体上,并和接受阴影的物体上对应像素位置的深度(灯光空间)进行比较,来确定当前像素是否处于阴影即可,此外还要考虑深度图的精度以及以此会造成的ZFighting等,当然这并不是本文讨论的重点。
这边我主要介绍一种直接投射灯光空间摄像机的Rendertexture来实现阴影的方法,并将在稍后将其和projector结合。当然同时熟悉这两项技术的开发者应该已经清楚,使用projector实现阴影意味着你将会消耗额外的drawcall,实际上被投射projector并且未在shader中使用"IgnoreProjector"="true"的物体都会在自身shader渲染完(也可能是渲染前,具体看自身渲染队列和projector的shader的渲染队列的先后顺序)后再次使用projector的shader渲染一次,这表示当被projector投射且未标记忽略投影机或不处于投影机忽略的层的物体会再次使用projector的material渲染,所以如果将projector应用在某些场景,比如拥有复杂的场景元素,且大部分是单独的物体而不是合并的,会占用较多drawcall,当然如果projector只影响一小部分元素,比如projector只影响单独模型的地面,则可以不必担心drawcall的问题。
首先比较一下这种技术和shadowmap技术,实际上个人感觉很大程度上两者的技术其实差不多,都需要用到屏幕投影,只不过shadowmap投射的是深度图(深度缓冲),而本文介绍的是直接投射屏幕纹理(帧缓冲),因此投射的纹理是带Alpha通道的。
和shadowmap不同的是,灯光空间的摄像机应该只看到投射阴影的物体:
此时投射后的效果大致如图所示:
需要注意的是,正如刚刚所讲,投射的Rendertexture是带alpha通道的,这意味着被投射物体的贴图的Alpha值会存入这张Rendertexture,如图是灯光空间的摄像机看到的投射阴影的物体:
理论上此时这个摄像机投射的rt应该也是这样的效果(除了背景部分会透明),但是如果投射阴影的物体使用的纹理带有Alpha通道,尽管物体可能并未使用开启blend的shader,比如上图中的物体时默认的Diffuse,但如果使用的纹理格式是带Alpha通道的,比如RGBA Compressed DXT5并且这张纹理本身也具有Alpha通道信息,则也会渲染到RT中,如下图是使用了带Alpha通道的纹理得到的Rendertexture,其身体使用RGBA格式,而头部纹理则不具有Alpha通道信息:
可以看到实际渲染得到的rt也会记录纹理的Alpha。将其纹理格式改成RGB Compressed DXT1就不会渲染出半透明的RT了。
当然使用这种方式投射rendertexture必然造成的一个问题是,由于没有投射接受阴影的平面,导致一旦投射阴影的物体穿透接受阴影的物体时会造成阴影的穿帮:
接受阴影物体Shader主要实现代码:
其中viewMatrix为灯光空间摄像机的worldToCameraMatrix,projMatrix为灯光空间摄像机的投影矩阵。
当然使用这种方式实现阴影的不足之处在于需要明确的知道投射阴影的物体和接受阴影的物体。
接下来将尝试将其与Projector结合,注意之前已经讨论过,使用projector意味着额外的drawcall,尤其是场景中物件很多且全部都是分离的物体时,不建议使用这种方式。当然如果场景中只有极少部分物体需要接受阴影,比如只有主要地形,则不妨可以尝试使用这种方式,因为使用projector,你可以很方便的在shader中加入IgnoreProjector标签来忽略投影机的作用,或者直接在projector上修改projector影响的层,并且不需要为接受阴影的物体单独编写shader或脚本,只需要一个projector即可。
从unity标准资源包中的projector shader我们大致可以了解,projector shader中需要两个4阶矩阵, 分别为_Projector和_ProjectorClip,其中后者主要用于近远裁面的淡入淡出,并不是必须的。而前者的_Projector,注意这个矩阵应该区别于摄像机的projection矩阵(尽管摄像机和projector在很多参数上很相似),原因是官方的projector shader中直接通过:o.uvShadow = mul (_Projector, vertex);计算得到投影纹理坐标,这意味着_Projector矩阵应该同时实现将vertex转换到世界空间,再转换到projector的局部空间,最后转换到projector的投影空间的功能,所以其性质应该类似UNITY_MATRIX_MVP矩阵,所以使用projector实现投射rendertexture的效果,只需在projector位置创建一个摄像机,并复制projector的参数,并将这个摄像机的rendertexture传递给projector的material,具体shader实现如下:
其中_FadeTex是一张表示阴影衰减的贴图,其r、g通道效果如下:需要注意第二个通道的纹理使用_ProjectorClip,用于实现近裁面到远裁面的衰减效果,而第一个通道的纹理则用于将rt周围进行遮罩,如果不使用则当投射阴影的物体刚好到达或超出projector摄像机时,由于我的rt的wrapmode为clamp,会产生这种效果:
最后实现的阴影如下:
另外由于投射的是带Alpha通道的Rendertexture,意味着可以方便的对其使用模糊shader完成模糊效果,这里是我自己编写的模糊脚本效果图:
最后附上工程
链接:http://pan.baidu.com/s/1mgTkDhY 密码:ggh8
之前的简书文章地址:http://www.jianshu.com/p/1fade1e8c309
原创文章,如需转载请注明出处。