紧接上篇
未名客:【渲染流程】Cluster_Unity实现概述zhuanlan.zhihu.com上篇文章,是对Unity 实现Cluster 灯光裁剪的一个概述,从这篇文章开始,我们开始结合代码详细展开,实现每一个流程。强烈建议大家先看上篇文章,很多推导,总结都在上篇文章里,这里及以后的文章不重复相关内容。
用Unity 实现ClusterBasedLighting,一开始考虑的便是Unity 的Srp,不过一来,自己对SRP 不熟,目前也没有足够的时间学习相关的东西,其二,本文的重点是梳理Cluster的流程,想更纯粹一些。最后受MaxwellGeng 兄弟的启发,决定,决定从零开始,完全自己手写。未来某一天如果对SRP 比较熟,会移植一下~
其实这里所谓的完全自定义,是自己调用一些Unity 较底层的绘制函数,绘制到自己创建的RT上,最后使用一个Blit 操作,把我们自己创建的RT 拷贝到摄像机的RT 上,由Unity 提交,最后在屏幕上显示。
一、准备环境
这一步比较简单,首先在一个空场景中,新建脚本Script_ClusterBasedLighting.cs, 并把它挂在Camera 上面,当你的Scene 视图,背景被清空成灰色,环境准备完成~
[ExecuteInEditMode]
#if UNITY_5_4_OR_NEWER
[ImageEffectAllowedInSceneView]
#endif
public class Script_ClusterBasedLighting : MonoBehaviour
{
private RenderTexture _rtColor;
private RenderTexture _rtDepth;
void Start()
{
_rtColor = new RenderTexture(Screen.width, Screen.height, 24);
_rtDepth = new RenderTexture(Screen.width, Screen.height, 24, RenderTextureFormat.Depth, RenderTextureReadWrite.Linear);
}
void OnRenderImage(RenderTexture sourceTexture, RenderTexture destTexture)
{
Graphics.SetRenderTarget(_rtColor.colorBuffer, _rtDepth.depthBuffer);
GL.Clear(true, true, Color.gray);
Graphics.Blit(_rtColor, destTexture);
}
}
我们在_rtColor, _rtDepth 上面绘制内容,最后blit 到 camera 的rt 上。
使用OnRenderImage() 函数,配合【ExecuteInEditMode】,【ImageEffectAllowedInSceneView】 是为了能在Scene视图窗口 预览结果。
这部分内容MaxwellGeng 已经解释的很清楚了,这里不做赘述,有兴趣或者不太明白的小伙伴可以点一下链接过去学习一下。
二、预计算ClusterAABB, 绘制调试Cluster
从这一阶段开始,我们进入正题,如标题所言,我们将实现如下内容:
- 预计算Cluster AABB
- 绘制供调试的Cluster
完成以后,就能得到本篇文章标题的画面啦~
2.1 预计算Cluster AABB
主相机的视锥体被按照一定规则,切分成一定数量的小锥体,为了能更快的完成后面Cluster 与光源的求交,我们用AABB BoundBox 包围盒这种方式来表示Cluster,且此包围盒需要完整包含小锥体。这一步,我们的目标就是求出View空间下一系列Cluster AABB 的数组。
之前有提到我们最终实现的cluster, 是类似cube 的aabb,尽量做到均匀分布,实现方法就是在view 空间,做一种类似指数型的切分,具体推导过程见上篇文章,最终我们得到了如下公式:
这两个公式非常重要,是我们后面做各种变化的基础。简单解释一下:
第一个公式:根据View 空间下的z ,计算当前z 所处的 cluster z 方向的Index;
第二个公式:根据cluster z 方向的Index ,反推 View 空间下 z坐标。
设想,假设我们知道屏幕上的一个Tile(即,Cluster 的xy 坐标),配合相机近裁面的z,便可得出一个view 空间下的3d 坐标,这个坐标与 摄像机的位置,便可形成一条线。
同时我知道了view 空间下,第k 个 cluster 的z坐标,根据这个z 构建一个平行于相机近、远裁面的面,那么便可以求出 这条线与k 平面的交点。 这个交点其实 就是 小cluster 锥体的某个点啦~
有了这个思路就可以写代码了~
为了方便,我们构建如下结构体:
struct CD_DIM
{
public float fieldOfViewY;
public float zNear;
public float zFar;
public float sD;
public float logDimY;
public float logDepth;
public int clusterDimX;
public int clusterDimY;
public int clusterDimZ;
public int clusterDimXYZ;
};
用来表示cluster 和相机的一些信息。在相机视锥体发生改变时(初始化需要调用一次),计算这个结构:
void CalculateMDim(Camera cam)
{
// The half-angle of the field of view in the Y-directio