半影方案
之前用来生成lightMesh的端点扫描的方案并不适合生成ShadowMesh,主要原因是光源体积边缘的点和光源中心点的端点顺序可能不同。虽然端点排序很快,但也不可能每个半影区域都排一次,即使有优化方案,代码的复杂度也会很高。
使用Shader绘制阴影(包括半影)比较简单,而且效率很高。个人觉得它不能完全替代生成lightMesh的方案。使用Shader实现的阴影仅仅是视觉效果,很难将受影或受光区域反馈给Unity。比如说角色在光照区域下有一些Buff之类的效果,优化好的lightMesh可以比用射线检测的效率高很多。
目前我所知的半影方案有两种:
绘制ShadowMesh(Mesh为所有阴影区域),明确区分出半影区域,然后使用半影贴图绘制半影区域。参考:dynamic 2d soft shadows
绘制ShadowMesh(Mesh为所有阴影区域),在片元着色器里计算遮挡值来绘制半影区域。参考:如何在unity实现足够快的2d动态光照
看了SF soft Shadow 2d的阴影实现源码,发现与方案2比较类似。它的ShadowMesh是在顶点作色器里用一个非常巧妙的方法计算的。遮挡值计算比较复杂,虽然搞明白了它怎么实现的,但是不清楚原理来源,属于知其然而不知其所以然。
采用方案:使用sf shadow中的方法来实现ShadowMesh,遮挡值计算使用方案2中方法,大部分计算都在着色器里。
ShadowMesh
单个线段投影区域计算方法
在顶点着色器里需要将模型空间顶点转化为裁剪空间顶点。
o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
// unit 会自动转化为
o.vertex = UnityObjectToClipPos(v.vertex);
顶点和uv都为(0,0) (1,0) (0,1) (1,1)的正方形:
如果修改顶点的W值
// UnityObjectToClipPos会将W修改为1,所以替换成以下代码
o.vertex = mul(UNITY_MATRIX_VP, mul(unity_ObjectToWorld, float4(v.vertex.rgb, 0.5)));
结果是变大了。
实际变化是以原点到顶点的向量方向除以W的值。
仅uv.y=1的时候修改w值。
if(i.uv.y == 1){
o.vertex = mul(UNITY_MATRIX_VP, mul(unity_ObjectToWorld, float4(v.vertex.rgb, 0.5)));
} else {
o.vertex = UnityObjectToClipPos(v.vertex);
}
当AB与CD相同且w趋近于0,那么AB则无限远,结果ABCD形状就是原点对线段CD的投影。
// 简写,效率更高更合适。
// o.vertex = mul(UNITY_MATRIX_VP, mul(unity_ObjectToWorld, float4(v.vertex.rgb, 1 - uv.y)));
// 为了方便理解我都是用的条件语句
if(i.uv.y == 1){
// 让w趋近为0,直接为0会出错,应该是之后的齐次除法导致的。
o.vertex = mul(UNITY_MATRIX_VP, mul(unity_ObjectToWorld, float4(v.vertex.rgb, 0.0001)));
} else {
o.vertex = UnityObjectToClipPos(v.vertex);
}