unity场景初始化_【渲染流程】Cluster_Unity实现详解(一)预计算AABB

紧接上篇

未名客:【渲染流程】Cluster_Unity实现概述​zhuanlan.zhihu.com
6558a10a1c99b33624868f5ccfc6c713.png

上篇文章,是对Unity 实现Cluster 灯光裁剪的一个概述,从这篇文章开始,我们开始结合代码详细展开,实现每一个流程。强烈建议大家先看上篇文章,很多推导,总结都在上篇文章里,这里及以后的文章不重复相关内容。

用Unity 实现ClusterBasedLighting,一开始考虑的便是Unity 的Srp,不过一来,自己对SRP 不熟,目前也没有足够的时间学习相关的东西,其二,本文的重点是梳理Cluster的流程,想更纯粹一些。最后受MaxwellGeng 兄弟的启发,决定,决定从零开始,完全自己手写。未来某一天如果对SRP 比较熟,会移植一下~

其实这里所谓的完全自定义,是自己调用一些Unity 较底层的绘制函数,绘制到自己创建的RT上,最后使用一个Blit 操作,把我们自己创建的RT 拷贝到摄像机的RT 上,由Unity 提交,最后在屏幕上显示。

一、准备环境

这一步比较简单,首先在一个空场景中,新建脚本Script_ClusterBasedLighting.cs, 并把它挂在Camera 上面,当你的Scene 视图,背景被清空成灰色,环境准备完成~

6e0faa6f9b9a4e783288d68bb46293c5.png
clear rt: gray color
[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

从这一阶段开始,我们进入正题,如标题所言,我们将实现如下内容:

  1. 预计算Cluster AABB
  2. 绘制供调试的Cluster

完成以后,就能得到本篇文章标题的画面啦~

2.1 预计算Cluster AABB

主相机的视锥体被按照一定规则,切分成一定数量的小锥体,为了能更快的完成后面Cluster 与光源的求交,我们用AABB BoundBox 包围盒这种方式来表示Cluster,且此包围盒需要完整包含小锥体。这一步,我们的目标就是求出View空间下一系列Cluster AABB 的数组。

810db0b387b4ac95202bea4b777aa643.png

之前有提到我们最终实现的cluster, 是类似cube 的aabb,尽量做到均匀分布,实现方法就是在view 空间,做一种类似指数型的切分,具体推导过程见上篇文章,最终我们得到了如下公式:

(1)

(2)

这两个公式非常重要,是我们后面做各种变化的基础。简单解释一下:

第一个公式:根据View 空间下的z ,计算当前z 所处的 cluster z 方向的Index;

第二个公式:根据cluster z 方向的Index ,反推 View 空间下 z坐标。

设想,假设我们知道屏幕上的一个Tile(即,Cluster 的xy 坐标),配合相机近裁面的z,便可得出一个view 空间下的3d 坐标,这个坐标与 摄像机的位置,便可形成一条线。

同时我知道了view 空间下,第k 个 cluster 的z坐标,根据这个z 构建一个平行于相机近、远裁面的面,那么便可以求出 这条线与k 平面的交点。 这个交点其实 就是 小cluster 锥体的某个点啦~

e69049388771d025813bf94829196dab.png
屏幕上的Tile,对应Cluster,及AABB

有了这个思路就可以写代码了~

为了方便,我们构建如下结构体:

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-direction.
        float fieldOfViewY = cam.fieldOfView * Mathf.Deg2Rad * 0.5f;//Degree 2 Radiance:  Param.CameraInfo.Property.Perspective.fFovAngleY * 0.5f;
        float zNear = cam.nearClipPlane;// Param.CameraInfo.Property.Perspective.fMinVisibleDistance;
        float zFar = cam.farClipPlane;// Param.CameraInfo.Property.Perspective.fMaxVisibleDistance;

        // Number of clusters in the screen X direction.
        int clusterDimX = Mathf.CeilToInt(Screen.width / (float)m_ClusterGridBlockSize);
        // Number of clusters in the screen Y direction.
        int clusterDimY = Mathf.CeilToInt(Screen.height / (float)m_ClusterGridBlockSize);

        // The depth of the cluster grid during clustered rendering is dependent on the 
        // number of clusters subdivisions in the screen Y direction.
        // Source: Clustered Deferred and Forward Shading (2012) (Ola Olsson, Markus Billeter, Ulf Assarsson).
        float sD = 2.0f * Mathf.Tan(fieldOfViewY) / (float)clusterDimY;
        float logDimY = 1.0f / Mathf.Log(1.0f + sD);

        float logDepth = Mathf.Log(zFar / zNear);
        int clusterDimZ = Mathf.FloorToInt(logDepth * logDimY);

        m_DimData.zNear = zNear;
        m_DimData.zFar = zFar;
        m_DimData.sD = sD;
        m_DimData.fieldOfViewY = fieldOfViewY;
        m_DimData.logDepth = logDepth;
        m_DimData.logDimY = logDimY;
        m_DimData.clusterDimX = clusterDimX;
        m_DimData.clusterDimY = clusterDimY;
        m_DimData.clusterDimZ = clusterDimZ;
        m_DimData.clusterDimXYZ = clusterDimX * clusterDimY * clusterDimZ;
    }

其中,clusterDimX/Y/Z 就是Cluster 的三维个数, clusterDimXYZ 就是总个数。

我们根据这个clusterDimXYZ,来创建一个AABB数组

    private ComputeBuffer cb_ClusterAABBs;

    void Start()
    {
        //...
        CalculateMDim(_camera);

        int stride = Marshal.SizeOf(typeof(AABB));
        cb_ClusterAABBs = new ComputeBuffer(m_DimData.clusterDimXYZ, stride);
        //...
    }

完成以后,就可以调用CS 数组里面填数据了。

2.1.1 先说shader

CS 计算的过程,就是我们刚刚分析的过程,完整的cs代码,如下:

// Each #kernel tells which function to compile; you can have many kernels
#pragma kernel CSMain
#pragma enable_d3d11_debug_symbols

//Cluster Data
uint3 ClusterCB_GridDim;      // The 3D dimensions of the cluster grid.
float ClusterCB_ViewNear;     // The distance to the near clipping plane. (Used for computing the index in the cluster grid)
uint2 ClusterCB_Size;         // The size of a cluster in screen space (pixels).
float ClusterCB_NearK;        // ( 1 + ( 2 * tan( fov * 0.5 ) / ClusterGridDim.y ) ) // Used to compute the near plane for clusters at depth k.
float ClusterCB_LogGridDimY;  // 1.0f / log( 1 + ( tan( fov * 0.5 ) / ClusterGridDim.y )
float4 ClusterCB_ScreenDimensions;

struct Plane
{
	float3 N;   // Plane normal.
	float  d;   // Distance to origin.
};

/**
 * Convert a 1D cluster index into a 3D cluster index.
 */
uint3 ComputeClusterIndex3D(uint clusterIndex1D)
{
	uint i = clusterIndex1D % ClusterCB_GridDim.x;
	uint j = clusterIndex1D % (ClusterCB_GridDim.x * ClusterCB_GridDim.y) / ClusterCB_GridDim.x;
	uint k = clusterIndex1D / (ClusterCB_GridDim.x * ClusterCB_GridDim.y);

	return uint3(i, j, k);
}

/**
 * Convert the 3D cluster index into a 1D cluster index.
 */
uint ComputeClusterIndex1D(uint3 clusterIndex3D)
{
	return clusterIndex3D.x + (ClusterCB_GridDim.x * (clusterIndex3D.y + ClusterCB_GridDim.y * clusterIndex3D.z));
}

/**
* Compute the 3D cluster index from a 2D screen position and Z depth in view space.
* source: Clustered deferred and forward shading (Olsson, Billeter, Assarsson, 2012)
*/
uint3 ComputeClusterIndex3D(float2 screenPos, float viewZ)
{
	uint i = screenPos.x / ClusterCB_Size.x;
	uint j = screenPos.y / ClusterCB_Size.y;
	// It is assumed that view space z is negative (right-handed coordinate system)
	// so the view-space z coordinate needs to be negated to make it positive.
	uint k = log(viewZ / ClusterCB_ViewNear) * ClusterCB_LogGridDimY;

	return uint3(i, j, k);
}
/**
 * Find the intersection of a line segment with a plane.
 * This function will return true if an intersection point
 * was found or false if no intersection could be found.
 * Source: Real-time collision detection, Christer Ericson (2005)
 */
bool IntersectLinePlane(float3 a, float3 b, Plane p, out float3 q)
{
	float3 ab = b - a;

	float t = (p.d - dot(p.N, a)) / dot(p.N, ab);

	bool intersect = (t >= 0.0f && t <= 1.0f);

	q = float3(0, 0, 0);
	if (intersect)
	{
		q = a + t * ab;
	}

	return intersect;
}

/// Functions.hlsli
// Convert clip space coordinates to view space
float4 ClipToView(float4 clip)
{
	// View space position.
	//float4 view = mul(clip, g_Com.Camera.CameraProjectInv);
	float4 view = mul(_InverseProjectionMatrix, clip);
	// Perspecitive projection.
	view = view / view.w;

	return view;
}

// Convert screen space coordinates to view space.
float4 ScreenToView(float4 screen)
{
	// Convert to normalized texture coordinates in the range [0 .. 1].
	float2 texCoord = screen.xy * ClusterCB_ScreenDimensions.zw;

	// Convert to clip space
	float4 clip = float4(texCoord * 2.0f - 1.0f, screen.z, screen.w);

	return ClipToView(clip);
}

#ifndef BLOCK_SIZE
#define BLOCK_SIZE 1024
#endif

struct ComputeShaderInput
{
	uint3 GroupID           : SV_GroupID;           // 3D index of the thread group in the dispatch.
	uint3 GroupThreadID     : SV_GroupThreadID;     // 3D index of local thread ID in a thread group.
	uint3 DispatchThreadID  : SV_DispatchThreadID;  // 3D index of global thread ID in the dispatch.
	uint  GroupIndex        : SV_GroupIndex;        // Flattened local index of the thread within a thread group.
};

struct AABB
{
	float4 Min;
	float4 Max;
};
RWStructuredBuffer<AABB> RWClusterAABBs;

[numthreads(BLOCK_SIZE, 1, 1)]
void CSMain(ComputeShaderInput cs_IDs)
{
	uint clusterIndex1D = cs_IDs.DispatchThreadID.x;

	// Convert the 1D cluster index into a 3D index in the cluster grid.
	uint3 clusterIndex3D = ComputeClusterIndex3D(clusterIndex1D);

	// Compute the near and far planes for cluster K.
	Plane nearPlane = { 0.0f, 0.0f, 1.0f, ClusterCB_ViewNear * pow(abs(ClusterCB_NearK), clusterIndex3D.z) };
	Plane farPlane = { 0.0f, 0.0f, 1.0f, ClusterCB_ViewNear * pow(abs(ClusterCB_NearK), clusterIndex3D.z + 1) };

	// The top-left point of cluster K in screen space.
	float4 pMin = float4(clusterIndex3D.xy * ClusterCB_Size.xy, 0.0f, 1.0f);
	// The bottom-right point of cluster K in screen space.
	float4 pMax = float4((clusterIndex3D.xy + 1) * ClusterCB_Size.xy, 0.0f, 1.0f);

	// Transform the screen space points to view space.
	pMin = ScreenToView(pMin);
	pMax = ScreenToView(pMax);

	pMin.z *= -1;
	pMax.z *= -1;

	// Find the min and max points on the near and far planes.
	float3 nearMin, nearMax, farMin, farMax;
	// Origin (camera eye position)
	float3 eye = float3(0, 0, 0);
	IntersectLinePlane(eye, (float3)pMin, nearPlane, nearMin);
	IntersectLinePlane(eye, (float3)pMax, nearPlane, nearMax);
	IntersectLinePlane(eye, (float3)pMin, farPlane, farMin);
	IntersectLinePlane(eye, (float3)pMax, farPlane, farMax);

	float3 aabbMin = min(nearMin, min(nearMax, min(farMin, farMax)));
	float3 aabbMax = max(nearMin, max(nearMax, max(farMin, farMax)));

	AABB aabb = { float4(aabbMin, 1.0f), float4(aabbMax, 1.0f) };
	
	RWClusterAABBs[clusterIndex1D] = aabb;
}

ComputeShader 分析:

1、每一个cluster 就是一个线程,若一个线程组有1024个线程,所以,需要dispath m_DimData.clusterDimXYZ / 1024.0 个线程组

2、为了方便填充我们的一维数组结果,这里我们分配的线程组是一维的。虽然cluster index 是3维的,但是不虚,因为index 3维到一维是非常方便的。所以在shader 的一开始,我们写了如下一些用于转换的工具函数,这些函数后面会被反复用到。

/**
 * Convert a 1D cluster index into a 3D cluster index.
 */
uint3 ComputeClusterIndex3D(uint clusterIndex1D)
{
	uint i = clusterIndex1D % ClusterCB_GridDim.x;
	uint j = clusterIndex1D % (ClusterCB_GridDim.x * ClusterCB_GridDim.y) / ClusterCB_GridDim.x;
	uint k = clusterIndex1D / (ClusterCB_GridDim.x * ClusterCB_GridDim.y);

	return uint3(i, j, k);
}

/**
 * Convert the 3D cluster index into a 1D cluster index.
 */
uint ComputeClusterIndex1D(uint3 clusterIndex3D)
{
	return clusterIndex3D.x + (ClusterCB_GridDim.x * (clusterIndex3D.y + ClusterCB_GridDim.y * clusterIndex3D.z));
}

/**
* Compute the 3D cluster index from a 2D screen position and Z depth in view space.
* source: Clustered deferred and forward shading (Olsson, Billeter, Assarsson, 2012)
*/
uint3 ComputeClusterIndex3D(float2 screenPos, float viewZ)
{
	uint i = screenPos.x / ClusterCB_Size.x;
	uint j = screenPos.y / ClusterCB_Size.y;
	uint k = log(viewZ / ClusterCB_ViewNear) * ClusterCB_LogGridDimY;

	return uint3(i, j, k);
}

前两个转换非常简单,第三个有一点意思,用到了上面提到的公式1, 即,根据屏幕像素坐标,和 view 空间的z 计算cluster 的3维index。

3、接下来是一个线面求交的函数,这个等下将用来计算 cluster 锥体两个平面的的左上角,和右下角,四个顶点。

b5bf2957773aa225c75fba9144bf1ac2.png
线面求交,简单图示

4、再下来就是常规的空间转化,不多解释。

5、CSMain 的内容简单介绍:

1)DispatchThreadID 即对应cluster 的1维索引。

2)这里要注意,Unity 的View 空间是右手坐标系,我们所有view 空间下的z 值都是负数,同理,在构建纵向k,k+1 两个平面的时候,他们的法向量是,( 0,0,1)。

3)下面的代码是,计算view 空间的z, 用到了公式1

ClusterCB_ViewNear * pow(abs(ClusterCB_NearK), clusterIndex3D.z) 

4)我们将屏幕tile (即cluster 的xy) 左上角,右下角转换到view空间,求出view 空间的坐标,pMin, pMax。注意view z 是负数。

5)最后就可以做线面求交了,从四个点中,拼出最小最大的,就是我们cluster aabb 的左上,右下角的点啦~

cluster AABB 的计算,到了这一步,就已经完成了。在实际游戏运行过程中,因为视锥体常规情况下是不会发生变化的,所以可以预先把数据准备好,在Init 的时候就可以Dispatch 执行CS了。

2.1.2 c# 分析

最后简单看一眼c# 这边情况

    void UpdateClusterCBuffer(ComputeShader cs)
    {
        int[] gridDims = { m_DimData.clusterDimX, m_DimData.clusterDimY, m_DimData.clusterDimZ };
        int[] sizes = { m_ClusterGridBlockSize, m_ClusterGridBlockSize };
        Vector4 screenDim = new Vector4((float)Screen.width, (float)Screen.height, 1.0f / Screen.width, 1.0f / Screen.height);
        float viewNear = m_DimData.zNear;

        cs.SetInts("ClusterCB_GridDim", gridDims);
        cs.SetFloat("ClusterCB_ViewNear", viewNear);
        cs.SetInts("ClusterCB_Size", sizes);
        cs.SetFloat("ClusterCB_NearK", 1.0f + m_DimData.sD);
        cs.SetFloat("ClusterCB_LogGridDimY", m_DimData.logDimY);
        cs.SetVector("ClusterCB_ScreenDimensions", screenDim);
    }
     void Pass_ComputeClusterAABB()
    {
        var projectionMatrix = GL.GetGPUProjectionMatrix(_camera.projectionMatrix, false);
        var projectionMatrixInvers = projectionMatrix.inverse;
        cs_ComputeClusterAABB.SetMatrix("_InverseProjectionMatrix", projectionMatrixInvers);

        UpdateClusterCBuffer(cs_ComputeClusterAABB);

        int threadGroups = Mathf.CeilToInt(m_DimData.clusterDimXYZ / 1024.0f);

        int kernel = cs_ComputeClusterAABB.FindKernel("CSMain");
        cs_ComputeClusterAABB.SetBuffer(kernel, "RWClusterAABBs", cb_ClusterAABBs);
        cs_ComputeClusterAABB.Dispatch(kernel, threadGroups, 1, 1);
    }

这里有一点要注意的是,Unity InvProjectMatrix 的用法。Dx 系列和OpenGl 系类的投影是不一样的。DX是以左上角为(0,0) OpenGL是以右下角为(0,0) ,DX的Z范围是(0,1) GL是(-1,1)。为了跨平台,Unity 使用GetGPUProjectionMatrix 函数,做转换,友情提示,这里处理不好,出来的结果很有可能是 Y 方向相反哦。

这个我当时也被坑了,最后翻了翻Untiy PostProcess Steck 里面SSR 代码,才明白过来.....

2.2 绘制供调试的Cluster

ClusterAABB 计算完以后,心里还是没底,也为了后面调试的方便,我们首先把计算出来的AABB 画出来,先看个大概~

0151429e4f8c07212c231b20601aea52.png
cluster AABB 的调试界面

绘制的话,我们使用GS的方式,提交clusterDimXYZ 这个点,shader 里面根据点的id, 访问我们刚刚完成的数组,就能拿到当前点对应的cluster aabb 啦,然后在GS里面,生成8个点,表示cube。

简单看一下shader :

Shader "ClusterBasedLightingGit/Shader_DebugCluster"
{
	Properties
	{
	}
	SubShader
	{
		Tags { "RenderType"="Opaque" }
		LOD 100

		Pass
		{
			CGPROGRAM

			#pragma vertex main_VS
			#pragma fragment main_PS
			#pragma geometry main_GS
			#pragma target 5.0
			#pragma enable_d3d11_debug_symbols

			#include "UnityCG.cginc"

			struct VertexShaderOutput
			{
				float4 Min          : AABB_MIN;  // Min vertex position in view space.
				float4 Max          : AABB_MAX;  // Max vertex position in view space.
				float4 Color        : COLOR;     // Cluster color.
			};

			struct GeometryShaderOutput
			{
				float4 Color        : COLOR;
				float4 Position     : SV_POSITION;          // Clip space position.
			};

			struct AABB
			{
				float4 Min;
				float4 Max;
			};

			StructuredBuffer<AABB> ClusterAABBs;// : register(t1);

			bool CMin(float3 a, float3 b)
			{
				if (a.x < b.x && a.y < b.y && a.z < b.z)
					return true;
				else
					return false;
			}

			bool CMax(float3 a, float3 b)
			{
				if (a.x > b.x && a.y > b.y && a.z > b.z)
				{
					return true;
				}
				else
				{
					return false;
				}
			}

			float4 WorldToProject(float4 posWorld)
			{	
				float4 posVP0 = UnityObjectToClipPos(posWorld);
				return posVP0;
			}

			VertexShaderOutput main_VS(uint VertexID : SV_VertexID)
			{
				uint clusterID = VertexID; ;// UniqueClusters[VertexID];// VertexID;

				VertexShaderOutput vsOutput = (VertexShaderOutput)0;

				AABB aabb = ClusterAABBs[clusterID];// ClusterAABBs[VertexID];

				vsOutput.Min = aabb.Min;
				vsOutput.Max = aabb.Max;

				float4 factor = aabb.Max - aabb.Min;
				//factor *= 0.2;
				vsOutput.Max = aabb.Min + factor;
				vsOutput.Color = float4(1,1,1,1);

				return vsOutput;
			}


			// Geometry shader to convert AABB to cube.
			[maxvertexcount(16)]
			void main_GS(point VertexShaderOutput IN[1], inout TriangleStream<GeometryShaderOutput> OutputStream)
			{
				float4 min = IN[0].Min;
				float4 max = IN[0].Max;

				// Clip space position
				GeometryShaderOutput OUT = (GeometryShaderOutput)0;

				// AABB vertices
				const float4 Pos[8] = {
					float4(min.x, min.y, min.z, 1.0f),    // 0
					float4(min.x, min.y, max.z, 1.0f),    // 1
					float4(min.x, max.y, min.z, 1.0f),    // 2

					float4(min.x, max.y, max.z, 1.0f),    // 3
					float4(max.x, min.y, min.z, 1.0f),    // 4
					float4(max.x, min.y, max.z, 1.0f),    // 5
					float4(max.x, max.y, min.z, 1.0f),    // 6
					float4(max.x, max.y, max.z, 1.0f)     // 7
				};

				// Colors (to test correctness of AABB vertices)
				const float4 Col[8] = {
					float4(0.0f, 0.0f, 0.0f, 1.0f),       // Black
					float4(0.0f, 0.0f, 1.0f, 1.0f),       // Blue
					float4(0.0f, 1.0f, 0.0f, 1.0f),       // Green
					float4(0.0f, 1.0f, 1.0f, 1.0f),       // Cyan
					float4(1.0f, 0.0f, 0.0f, 1.0f),       // Red
					float4(1.0f, 0.0f, 1.0f, 1.0f),       // Magenta
					float4(1.0f, 1.0f, 0.0f, 1.0f),       // Yellow
					float4(1.01, 1.0f, 1.0f, 1.0f)        // White
				};

				const uint Index[18] = {
					0, 1, 2,
					3, 6, 7,
					4, 5, -1,
					2, 6, 0,
					4, 1, 5,
					3, 7, -1
				};

				[unroll]
				for (uint i = 0; i < 18; ++i)
				{
					if (Index[i] == (uint) - 1)
					{
						OutputStream.RestartStrip();
					}
					else
					{
						OUT.Position = WorldToProject(Pos[Index[i]]);
						OUT.Color = IN[0].Color;
						OutputStream.Append(OUT);
					}
				}
			}

			float4 main_PS(GeometryShaderOutput IN) : SV_Target
			{
				return IN.Color;
			}

			ENDCG
		}
	}
}

GS 的用法和DX11, 很常规,这里就不多介绍了,不了解的小伙伴,可以搜一下,GS的具体用法,功能很灵活,但是慎用哦,效率是一个很大的问题。

可以从上面的图里看出,Cluster 的AABB 确实把小锥体完全包围住了, 因为很多AABB在边缘的地方都有重叠,这是符合我们的需求的。为了方便查看,我把aabb 的左上角做了保留,实际大小做了一下缩放,看上去,清晰明了~ 标题图,get!

b0b1f225c20516a4c7823eb69c439071.png

c# 脚本简单看一眼

    void Pass_DebugCluster()
    {
        GL.wireframe = true;

        mtlDebugCluster.SetBuffer("ClusterAABBs", cb_ClusterAABBs);

        mtlDebugCluster.SetPass(0);
        Graphics.DrawProceduralNow(MeshTopology.Points, m_DimData.clusterDimXYZ);
        
        GL.wireframe = false;
    }
    void OnRenderImage(RenderTexture sourceTexture, RenderTexture destTexture)
    {
        Graphics.SetRenderTarget(_rtColor.colorBuffer, _rtDepth.depthBuffer);
        GL.Clear(true, true, Color.gray);

        Pass_DebugCluster();

        Graphics.Blit(_rtColor, destTexture);
    }

没什么好说的=-=

因为白天还要上班,晚上回家抽了一点时间写了这么多,回过神儿来,不知不觉,已经凌晨0.27分了,由于时间问题,就只能先总结到了,下一篇再见~

d2b68396f0e387d89a646e000bb8882c.png

版权声明:本文为weixin_35318343原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。