距離上一篇博客已經有點久了,中間忙的飛起,突然發現好久沒寫了,這樣很差,寫一篇和工做無關的吧。git
一直想搞清UE4距離場的原理,網上有幾乎找不到任何有關UE4距離場實現的內容,加上上篇末說要寫一個徹底的Rendering過程,而UE4下有個距離場的渲染,恰好用來追蹤理解UE4距離場,並順便理下距離場的Rendering相關。github
先說下我如今對UE4模型距離場比較淺顯的認識,就是咱們把場景裏的全部不透明模型信息移植到GPU中,不一樣於咱們直接看到的場景,是按照現實中的擺放,在距離場中,聲明瞭一個3D紋理,咱們把這個3D紋理能夠看作是一個房子,房子裏填滿了不少個長方體,長方體之間不穿插。而在場景中的每個不透明模型,對應房子裏的一個長方體,注意,他們之間的位置並不要緊,可能在場景中模型屬於中間,在房子裏,可能放在左上角的長方體中。每一個長方體裏保存的就是當前模型的距離場數據(簡單來講,模型內是負數,模型外是正數),這樣就把場景裏不透明模型的整個信息所有保存到一張3D紋理中,能夠說信息量很是集中。而3D紋理佔用的顯存由於多了一個深度的維度,很是高,因此這個3D紋理默認分辨率並非特別高,在4.14裏只有512*512*1024*f16=512M。windows
在這,用UE4裏的模型距離場來對比一下深度圖,咱們知道深度圖其實只是至關於對應一個特定角度的攝像機所獲得的最近的不透明像素的距離,像Shadow mapping這種只須要比較個二個位置下的深度的大小的結果,用來處理很不錯,而須要知道模型與場景非特定角度信息,如AO這些深度度則知足不了要求,只有距離場才能知足。數組
在開始講UE4模型距離場的渲染前,咱們還要來看下,UE4裏的Compute Shader,這種類型的着色器比較特殊,不一樣於常見的頂點與片段着色器,他並非渲染管線的一部分,通常來講和CUDA/OpenCL相似,用來作GPU通用計算,一樣,也要調度GPU劃分線程組,線程組劃分線程,主要有以下部分要理解。app
SV_GroupID是線程塊的三維ID,SV_GroupThreadID對應線程塊裏的線程組ID,SV_DispatchThreadID對應全部線程裏的ID,SV_GroupIndex如固定維度的三維數組和一維數組能夠相互轉化同樣,這個指的是在當前線程組中一維索引。以下是引用 https://msdn.microsoft.com/en-us/library/windows/desktop/ff471568(v=vs.85).aspx 裏的圖來講明。ide
這裏爲何要理解Compute Shader的這些概念了,由於距離場的構成與使用大部分都是Compute Shader,Compute Shader的基本概念也很容易理清,大部分代碼和咱們日常寫的沒什麼區別,參數,邏輯,同步。函數
UE4還有一個概念,叫全局距離場,和模型距離場相似,可是其實只能算是模型距離場的衍生物,能夠算是對模型場的一種優化使用方式,不要被這個名字騙了,覺得他纔是主要的,沒有模型距離場,就不可能有全局距離場。優化
接下來,咱們按照UE4中的模型距離場可視化渲染流程來講明過程,主要有以下幾個部分,如何建立距離場,對應的CPU與GPU的數據有那些。先看下UE裏的模型距離可視化渲染是什麼樣子的圖片。ui
如上面所說,模型距離場是一個裝着格子的房子,如何組裝這個房子,更新房子的類就是FDistanceFieldVolumeTextureAtlas,對應的對象是GDistanceFieldVolumeTextureAtlas,每一個格子對應一個靜態模型的FDistanceFieldVolumeTexture,有個很重要的值就是Size,表示這個長方體格子的三維大小。this
首先UStaticMesh在加載時,就自動被GDistanceFieldVolumeTextureAtlas收錄了。
而後會調用MeshUtilities->GenerateSignedDistanceFieldVolumeData生成對應FStaticMesh的距離場數據,這段代碼不貼了,簡單說明下。
計算Mesh格子的大小DistanceFieldVolumeBounds,這個對應模型的FDistanceFieldVolumeTexture數據裏的3D紋理的各維大小,從代碼上來看,比模型的MeshBounds要大一圈,這樣網格就包含在FDistanceFieldVolumeTexture之中,而且還要包含邊緣的數據。
然後是VolumeDimensions,對應FDistanceFieldVolumeTexture裏的3D紋理的各維索引長度,對應各個像素點,各個維度最小8個像素,否則會穿幫。
最後根據索引解析成三角,同Unity相似的Mesh-SubMesh相似,UE4裏FStaticMeshLODResources->FStaticMeshSection結構也是FStaticMeshLODResources包含頂點數據buffer,頂點buffer,而FStaticMeshSection對應材質索引,頂點區塊,頂點索引發點等。根據頂點索引查找區塊對應材質,若是是不透明的,就添加進距離場運算,而後使用K樹分割成多維空間,創建搜索索引,而後生成一個上下密度大約在384,平面密度在600左右的點空間,每一個上下對應點生成一條射線,這射線與對應模型前面分解的三角形計算獲得距離場數據,在物體內爲負,表面接近0,物體外一段距離爲正值(主要邏輯在FMeshDistanceFieldAsyncTask::DoWork)。
這樣各個UStaticMesh的FDistanceFieldVolumeTexture都有值了,size就是上面的VolumeDimensions,LocalBoundingBox就是DistanceFieldVolumeBounds,對應的DistanceFieldVolume初始化VolumeDimensions個零,CompressedDistanceFieldVolume就是上面最後生成的距離場數據。
嗯,終於到渲染這步了,在FDeferredShadingSceneRenderer::Render中,咱們能夠看到,在prez-pass以前,就會調用GDistanceFieldVolumeTextureAtlas->UpdateAllocations(),這個方法很簡單,就是把如上的全部UStaticMesh的FDistanceFieldVolumeTexture數據,提交到對應的GPU中的3D紋理DistanceFieldTexture中。
接着上面立刻調用FDeferredShadingSceneRenderer::UpdateGlobalDistanceFieldObjectBuffers,這個也很簡單,上面所說的部分,只是提供給GPU一個距離場,而場景中模型與距離場中的格子對應關係並無,如格子從GPU距離場到世界空間的互相轉化的矩陣,對應UVAdd,UVScale,模型的box bounds等信息,這些信息都會存在Scene->DistanceFieldSceneData.ObjectBuffers裏,對應的GPU裏的ObjectData,ObjectBounds。ObjectData如上所說,每個節點,包含格子從GPU距離場到世界空間的互相轉化的矩陣,對應UVAdd,UVScale,模型的box 等的全部顯存信息。
這裏有一個Compute Shader就是FUploadObjectsToBufferCS生成的臨時數據提交到RWObjectBounds/RWObjectData的,這裏就不分析了,很容易理解,每一個Compute Shader的類,如上篇文章所說,每一個shader,直接定位到相應位置,類後必定有個宏顯示他是在那個usf文件裏,對應的入口函數是那個。
在接着以下,能夠看到針對每一個view生成一個全局距離場的3維紋理,限於本文篇幅,只分析模型距離場,全局距離場只是大體提下,在這,每一個View生成四個底密度的3維紋理,大小同樣,密度不同,分割成多個Grid,規劃每一個grid的大小,檢測每一個view對應的clipmap須要更新的區塊,有4.17裏邏輯在GlobalDistanceField.usf裏的函數CompositeObjectDistanceFieldsCS中,用的數據就是上面的ObjectData,ObjectBounds裏的,固然還有一些GPU計算攝像計Cull的過程,在這先不說,由於這個邏輯在下面渲染模型距離場可視化時還用再見到,咱們等到那個位置來仔細分析。這裏填充了全局距離場的GPU數據,這也是我在上面所說,沒有模型距離場,就沒有全局距離場的緣由,模型距離場的精度比全局距離場也要更高。
在這先介紹一個usf文件,DistanceFieldLightingShared.usf 能夠看到不少Load開頭的函數,這些函數大都是取ObjectData裏的數據,咱們知道ObjectData包含了不少信息,如上面所說從GPU距離場到世界空間的互相轉化的矩陣,對應UVAdd,UVScale等,這裏主要用於單獨取這些數據,而對應的咱們能夠看到還有如CulledObjectData/CulledObjectBounds等加了Culled前綴的GPU信息,這些信息就是經過計算當前攝像機對應ObjectData的Cull經過後的模型,減小計算量。
接上面更新全局距離場後,作了一些延遲渲染應該作的事,如預渲染深度,渲染GBuffer,渲染燈光,渲染透明物體等等後,能夠看到RenderMeshDistanceFieldVisualization 渲染模型距離場可視化了,咱們來看下如何如何使用模型距離場的一個例子。
在RenderMeshDistanceFieldVisualization函數中,咱們開始就調用一個函數,CullObjectsToView(這裏版本可能有點變化,我記的4.14是直接在函數裏,沒有單獨拉出來,如今是4.17發現單獨拉出來了),這個函數主要是使用GPU來進行攝像機的cull過程,這個過程仍是比較有意思的,咱們來分析下,shader是FCullObjectsForVolumeCS,usf文件是GlobalDistanceField.usf,入口是CullObjectsForVolumeCS函數,咱們先來看下代碼。
DispatchComputeShader(RHICmdList, *ComputeShader, FMath::DivideAndRoundUp<uint32>(Scene->DistanceFieldSceneData.NumObjectsInBuffer, UpdateObjectsGroupSize), 1, 1); class FCullObjectsForViewCS : public FGlobalShader { DECLARE_SHADER_TYPE(FCullObjectsForViewCS,Global) public: static bool ShouldCache(EShaderPlatform Platform) { return IsFeatureLevelSupported(Platform, ERHIFeatureLevel::SM5) && DoesPlatformSupportDistanceFieldAO(Platform); } static void ModifyCompilationEnvironment(EShaderPlatform Platform, FShaderCompilerEnvironment& OutEnvironment) { FGlobalShader::ModifyCompilationEnvironment(Platform,OutEnvironment); OutEnvironment.SetDefine(TEXT("UPDATEOBJECTS_THREADGROUP_SIZE"), UpdateObjectsGroupSize); } FCullObjectsForViewCS(const ShaderMetaType::CompiledShaderInitializerType& Initializer) : FGlobalShader(Initializer) { ObjectBufferParameters.Bind(Initializer.ParameterMap); CulledObjectParameters.Bind(Initializer.ParameterMap); AOParameters.Bind(Initializer.ParameterMap); NumConvexHullPlanes.Bind(Initializer.ParameterMap, TEXT("NumConvexHullPlanes")); ViewFrustumConvexHull.Bind(Initializer.ParameterMap, TEXT("ViewFrustumConvexHull")); ObjectBoundingGeometryIndexCount.Bind(Initializer.ParameterMap, TEXT("ObjectBoundingGeometryIndexCount")); } FCullObjectsForViewCS() { } void SetParameters(FRHICommandList& RHICmdList, const FScene* Scene, const FSceneView& View, const FDistanceFieldAOParameters& Parameters) { FUnorderedAccessViewRHIParamRef OutUAVs[6]; OutUAVs[0] = GAOCulledObjectBuffers.Buffers.ObjectIndirectArguments.UAV; OutUAVs[1] = GAOCulledObjectBuffers.Buffers.Bounds.UAV; OutUAVs[2] = GAOCulledObjectBuffers.Buffers.Data.UAV; OutUAVs[3] = GAOCulledObjectBuffers.Buffers.BoxBounds.UAV; OutUAVs[4] = Scene->DistanceFieldSceneData.ObjectBuffers->Data.UAV; OutUAVs[5] = Scene->DistanceFieldSceneData.ObjectBuffers->Bounds.UAV; RHICmdList.TransitionResources(EResourceTransitionAccess::ERWBarrier, EResourceTransitionPipeline::EComputeToCompute, OutUAVs, ARRAY_COUNT(OutUAVs)); FComputeShaderRHIParamRef ShaderRHI = GetComputeShader(); FGlobalShader::SetParameters<FViewUniformShaderParameters>(RHICmdList, ShaderRHI, View.ViewUniformBuffer); ObjectBufferParameters.Set(RHICmdList, ShaderRHI, *(Scene->DistanceFieldSceneData.ObjectBuffers), Scene->DistanceFieldSceneData.NumObjectsInBuffer); CulledObjectParameters.Set(RHICmdList, ShaderRHI, GAOCulledObjectBuffers.Buffers); AOParameters.Set(RHICmdList, ShaderRHI, Parameters); // Shader assumes max 6 check(View.ViewFrustum.Planes.Num() <= 6); SetShaderValue(RHICmdList, ShaderRHI, NumConvexHullPlanes, View.ViewFrustum.Planes.Num()); SetShaderValueArray(RHICmdList, ShaderRHI, ViewFrustumConvexHull, View.ViewFrustum.Planes.GetData(), View.ViewFrustum.Planes.Num()); SetShaderValue(RHICmdList, ShaderRHI, ObjectBoundingGeometryIndexCount, StencilingGeometry::GLowPolyStencilSphereIndexBuffer.GetIndexCount()); } void UnsetParameters(FRHICommandList& RHICmdList, const FScene* Scene) { ObjectBufferParameters.UnsetParameters(RHICmdList, GetComputeShader(), *(Scene->DistanceFieldSceneData.ObjectBuffers)); CulledObjectParameters.UnsetParameters(RHICmdList, GetComputeShader()); FUnorderedAccessViewRHIParamRef OutUAVs[6]; OutUAVs[0] = GAOCulledObjectBuffers.Buffers.ObjectIndirectArguments.UAV; OutUAVs[1] = GAOCulledObjectBuffers.Buffers.Bounds.UAV; OutUAVs[2] = GAOCulledObjectBuffers.Buffers.Data.UAV; OutUAVs[3] = GAOCulledObjectBuffers.Buffers.BoxBounds.UAV; OutUAVs[4] = Scene->DistanceFieldSceneData.ObjectBuffers->Data.UAV; OutUAVs[5] = Scene->DistanceFieldSceneData.ObjectBuffers->Bounds.UAV; RHICmdList.TransitionResources(EResourceTransitionAccess::ERWBarrier, EResourceTransitionPipeline::EComputeToCompute, OutUAVs, ARRAY_COUNT(OutUAVs)); } virtual bool Serialize(FArchive& Ar) { bool bShaderHasOutdatedParameters = FGlobalShader::Serialize(Ar); Ar << ObjectBufferParameters; Ar << CulledObjectParameters; Ar << AOParameters; Ar << NumConvexHullPlanes; Ar << ViewFrustumConvexHull; Ar << ObjectBoundingGeometryIndexCount; return bShaderHasOutdatedParameters; } private: FDistanceFieldObjectBufferParameters ObjectBufferParameters; FDistanceFieldCulledObjectBufferParameters CulledObjectParameters; FAOParameters AOParameters; FShaderParameter NumConvexHullPlanes; FShaderParameter ViewFrustumConvexHull; FShaderParameter ObjectBoundingGeometryIndexCount; }; IMPLEMENT_SHADER_TYPE(,FCullObjectsForViewCS,TEXT("/Engine/Private/DistanceFieldObjectCulling.usf"),TEXT("CullObjectsForViewCS"),SF_Compute); [numthreads(UPDATEOBJECTS_THREADGROUP_SIZE, 1, 1)] void CullObjectsForViewCS( uint3 GroupId : SV_GroupID, uint3 DispatchThreadId : SV_DispatchThreadID, uint3 GroupThreadId : SV_GroupThreadID) { uint ObjectIndex = DispatchThreadId.x; #define USE_FRUSTUM_CULLING 1 #if USE_FRUSTUM_CULLING if (DispatchThreadId.x == 0) { // RWObjectIndirectArguments is zeroed by a clear before this shader, only need to set things that are non-zero (and are not read by this shader as that would be a race condition) // IndexCount, NumInstances, StartIndex, BaseVertexIndex, FirstInstance RWObjectIndirectArguments[0] = ObjectBoundingGeometryIndexCount; } if (GroupThreadId.x == 0) { NumGroupObjects = 0; } GroupMemoryBarrierWithGroupSync(); if (ObjectIndex < NumSceneObjects) { uint SourceIndex = ObjectIndex; float4 ObjectBoundingSphere = float4(ObjectBounds[4 * SourceIndex + 0], ObjectBounds[4 * SourceIndex + 1], ObjectBounds[4 * SourceIndex + 2], ObjectBounds[4 * SourceIndex + 3]); float DistanceToViewSq = dot(View.WorldCameraOrigin - ObjectBoundingSphere.xyz, View.WorldCameraOrigin - ObjectBoundingSphere.xyz); if (DistanceToViewSq < Square(AOMaxViewDistance + ObjectBoundingSphere.w) && ViewFrustumIntersectSphere(ObjectBoundingSphere.xyz, ObjectBoundingSphere.w + AOObjectMaxDistance)) { uint DestIndex; InterlockedAdd(NumGroupObjects, 1U, DestIndex); GroupObjectIndices[DestIndex] = SourceIndex; } } GroupMemoryBarrierWithGroupSync(); if (GroupThreadId.x == 0) { InterlockedAdd(RWObjectIndirectArguments[1], NumGroupObjects, GroupBaseIndex); } GroupMemoryBarrierWithGroupSync(); if (GroupThreadId.x < NumGroupObjects) { uint SourceIndex = GroupObjectIndices[GroupThreadId.x]; uint DestIndex = GroupBaseIndex + GroupThreadId.x; CopyCulledObjectData(DestIndex, SourceIndex); } #else if (DispatchThreadId.x == 0) { // IndexCount, NumInstances, StartIndex, BaseVertexIndex, FirstInstance RWObjectIndirectArguments[0] = ObjectBoundingGeometryIndexCount; RWObjectIndirectArguments[1] = NumSceneObjects; } GroupMemoryBarrierWithGroupSync(); if (ObjectIndex < NumSceneObjects) { uint SourceIndex = ObjectIndex; uint DestIndex = ObjectIndex; CopyCulledObjectData(DestIndex, SourceIndex); } #endif }
這段代碼分三部分,開始是調用,二是Shader通常寫法,如bind對應GPU參數,三是Compute Shader自己邏輯。
第一個部分,咱們須要注意DispatchComputeShader這個函數,這個函數的第三以後的三個參數,表明GPU調度的線程組,也就是如最上圖的Dispatch(x/64,1,1),對應的SV_GroupID指向的就是Dispatch相應的三維空間,這裏的n,1,1表示把三維數組轉化成一維數組了。
第二部分,你們能夠看到UE4裏Shader的寫法都是如此,在構造函數,綁定GPU與CPU的參數,在SetParameters方法裏設置對應參數的值,如在這裏,上面對應ObjectData,CullObjectData都是在這傳入的,在這主要是攝像機的參數與原來的ObjectData的綁定,輸出的Cull數據綁定,其中FRWShaderParameter參數會分別綁定UVA與SRV資源,如上一個只讀的ObjectData,還有一個可讀寫的RwObjectData.
第三部分能夠看到每一個Compute Shader的入口函數都有一個numthreads,其對應參數通常在相應的Shader代碼的ModifyCompilationEnvironment裏指定,numthreads對應的每一個線程組如何分線程,這裏是(64,1,1),簡單來講,線程裏的線程也被分紅一維數組,這樣達到一個啥效果了,簡單來看SV_DispatchThreadID.x就是全部線程相應的索引,而後咱們來看代碼。
前面objectIndex是對應DispatchThreadId.x這個好理解,前面調度就是objectNum/64,線程組是64個,簡單來講,每一個線程組裏有64個模型,而DispatchThreadId表示整個線程中的索引。
在第一個GroupMemoryBarrierWithGroupSync以前,初始化RWObjectIndirectArguments與NumGroupObjects(每一個線程組)。
第二個GroupMemoryBarrierWithGroupSync以前,每一個線程組裏記錄當前在攝像機Cull範圍下的DestIndex,而DestIndex經過同步方法InterlockedAdd獲得的NumGroupObjects的索引。
在第三個GroupMemoryBarrierWithGroupSync以前,把全部的線程組裏的Cull之和添加到RWObjectIndirectArguments[1]中。
在第四個GroupMemoryBarrierWithGroupSync以前,每一個線程組裏把對應的索引取的ObjectData/bounds放入CullObjectData/bounds裏,GPU的相應Cull過程就完了。
關於GPU的攝像機CULL過程就到這了,接着上面,咱們在RTT池裏找一個PF_FloatRGBA,UAV的2維RTT叫VisualizeDistanceField,而後經過TVisualizeMeshDistanceFieldCS這個Compute Shader把CullObjectData/bounds的數據渲染到當前攝像機的平面VisualizeDistanceField上,咱們能夠看到TVisualizeMeshDistanceFieldCS這個使用個泛型化的bool參數,這樣能夠去掉一次bool判斷,可是多生成一份代碼。
TVisualizeMeshDistanceFieldCS如前面所說,能夠看到對應的是DistanceFieldVisualization文件,入口是VisualizeMeshDistanceFieldCS,調度是(viewsize.xy/32,1),線程組是(16,16,1).對應VisualizeMeshDistanceFieldCS裏邏輯主要以下。
[numthreads(THREADGROUP_SIZEX, THREADGROUP_SIZEY, 1)] void VisualizeMeshDistanceFieldCS( uint3 GroupId : SV_GroupID, uint3 DispatchThreadId : SV_DispatchThreadID, uint3 GroupThreadId : SV_GroupThreadID) { uint ThreadIndex = GroupThreadId.y * THREADGROUP_SIZEX + GroupThreadId.x; float2 ScreenUV = float2((DispatchThreadId.xy * DOWNSAMPLE_FACTOR + View.ViewRectMin.xy + .5f) * View.BufferSizeAndInvSize.zw); float2 ScreenPosition = (ScreenUV.xy - View.ScreenPositionScaleBias.wz) / View.ScreenPositionScaleBias.xy; float SceneDepth = CalcSceneDepth(ScreenUV); float4 HomogeneousWorldPosition = mul(float4(ScreenPosition * SceneDepth, SceneDepth, 1), View.ScreenToWorld); float3 OpaqueWorldPosition = HomogeneousWorldPosition.xyz / HomogeneousWorldPosition.w; float TraceDistance = 40000; float3 WorldRayStart = View.WorldCameraOrigin; float3 WorldRayEnd = WorldRayStart + normalize(OpaqueWorldPosition - View.WorldCameraOrigin) * TraceDistance; float3 WorldRayDirection = WorldRayEnd - WorldRayStart; float3 UnitWorldRayDirection = normalize(WorldRayDirection); #if USE_GLOBAL_DISTANCE_FIELD float TotalStepsTaken = 0; float MaxRayTime0; float IntersectRayTime; float StepsTaken; RayTraceThroughGlobalDistanceField((uint)0, WorldRayStart, WorldRayEnd, TraceDistance, 0, MaxRayTime0, IntersectRayTime, StepsTaken); TotalStepsTaken += StepsTaken; if (IntersectRayTime >= TraceDistance) { float MaxRayTime1; RayTraceThroughGlobalDistanceField((uint)1, WorldRayStart, WorldRayEnd, TraceDistance, MaxRayTime0, MaxRayTime1, IntersectRayTime, StepsTaken); TotalStepsTaken += StepsTaken; if (IntersectRayTime >= TraceDistance) { float MaxRayTime2; RayTraceThroughGlobalDistanceField((uint)2, WorldRayStart, WorldRayEnd, TraceDistance, MaxRayTime1, MaxRayTime2, IntersectRayTime, StepsTaken); TotalStepsTaken += StepsTaken; if (IntersectRayTime >= TraceDistance) { float MaxRayTime3; RayTraceThroughGlobalDistanceField((uint)3, WorldRayStart, WorldRayEnd, TraceDistance, MaxRayTime2, MaxRayTime3, IntersectRayTime, StepsTaken); TotalStepsTaken += StepsTaken; } } } float3 Result = saturate(TotalStepsTaken / 400.0f); #else if (ThreadIndex == 0) { NumIntersectingObjects = 0; } GroupMemoryBarrierWithGroupSync(); float3 TileConeVertex; float3 TileConeAxis; float TileConeAngleCos; float TileConeAngleSin; { float2 ViewSize = float2(1 / View.ViewToClip[0][0], 1 / View.ViewToClip[1][1]); float3 TileCorner00 = normalize(float3((GroupId.x + 0) / NumGroups.x * ViewSize.x * 2 - ViewSize.x, ViewSize.y - (GroupId.y + 0) / NumGroups.y * ViewSize.y * 2, 1)); float3 TileCorner10 = normalize(float3((GroupId.x + 1) / NumGroups.x * ViewSize.x * 2 - ViewSize.x, ViewSize.y - (GroupId.y + 0) / NumGroups.y * ViewSize.y * 2, 1)); float3 TileCorner01 = normalize(float3((GroupId.x + 0) / NumGroups.x * ViewSize.x * 2 - ViewSize.x, ViewSize.y - (GroupId.y + 1) / NumGroups.y * ViewSize.y * 2, 1)); float3 TileCorner11 = normalize(float3((GroupId.x + 1) / NumGroups.x * ViewSize.x * 2 - ViewSize.x, ViewSize.y - (GroupId.y + 1) / NumGroups.y * ViewSize.y * 2, 1)); float3 ViewSpaceTileConeAxis = normalize(TileCorner00 + TileCorner10 + TileCorner01 + TileCorner11); TileConeAxis = mul(ViewSpaceTileConeAxis, (float3x3)View.ViewToTranslatedWorld); TileConeAngleCos = dot(ViewSpaceTileConeAxis, TileCorner00); TileConeAngleSin = sqrt(1 - TileConeAngleCos * TileConeAngleCos); TileConeVertex = View.WorldCameraOrigin; } uint NumCulledObjects = GetCulledNumObjects(); LOOP for (uint ObjectIndex = ThreadIndex; ObjectIndex < NumCulledObjects; ObjectIndex += THREADGROUP_TOTALSIZE) { float4 SphereCenterAndRadius = LoadObjectPositionAndRadius(ObjectIndex); BRANCH if (SphereIntersectCone(SphereCenterAndRadius, TileConeVertex, TileConeAxis, TileConeAngleCos, TileConeAngleSin)) { uint ListIndex; InterlockedAdd(NumIntersectingObjects, 1U, ListIndex); if (ListIndex < MAX_INTERSECTING_OBJECTS) { IntersectingObjectIndices[ListIndex] = ObjectIndex; } } } GroupMemoryBarrierWithGroupSync(); float MinRayTime; float TotalStepsTaken; // Trace once to find the distance to first intersection RayTraceThroughTileCulledDistanceFields(WorldRayStart, WorldRayEnd, TraceDistance, MinRayTime, TotalStepsTaken); float TempMinRayTime; // Recompute the ray end point WorldRayEnd = WorldRayStart + UnitWorldRayDirection * MinRayTime; // Trace a second time to only accumulate steps taken before the first intersection, improves visualization RayTraceThroughTileCulledDistanceFields(WorldRayStart, WorldRayEnd, MinRayTime, TempMinRayTime, TotalStepsTaken); float3 Result = saturate(TotalStepsTaken / 200.0f); if (MinRayTime < TraceDistance) { Result += .1f; } #endif RWVisualizeMeshDistanceFields[DispatchThreadId.xy] = float4(Result, 0); }
首先根據線程全局索引DispatchThreadId查找對應uv,在Compute Shader裏,根據調度與線程組的分配來生成對應貼圖uv好像是一種常見技巧,我記的 Unity有份簡單的講解光線追蹤 也是這樣,還挻有意思的,你們能夠看看。回到上面,獲得UV後,根據攝像機的機能夠獲得攝像機座標下的平面值,加上以前的深度圖,就能夠求得在當前攝像機下,無透明的最近點的三維座標,而後以攝像機爲原點,攝像機向對應uv的三維座標下很遠的值爲終點,咱們使用模型距離場,就不考慮上面那段根據全局距離場的代碼,而後調用RayTraceThroughTileCulledDistanceFields把當前原點,終點傳入模型距離場中計算。
在第一次RayTraceThroughTileCulledDistanceFields中,根據上面的原點終點生成一條射線,而後查找每一個模型對應的距離場信息,如box,radius,世界座標到對應的距離場矩陣,uvscale/uvadd等,首先把上面的原點和終點轉到對應的模型距離中去計算,這樣方便計算,咱們首先獲得這條射線是否與這個距離場相交(請注意,原來起點在眼睛位置,終點在很遠地方,這樣就算轉入到模型距離場中,也同樣是起點和終點同樣在模型距離場bound的外面),若是相交,LineBoxIntersect返回xy,x是近交點,y是遠交點,從近交點不斷向遠交點慢慢移動,比較模型距離場中對應3d的uv取到的距離場值(DistanceField),能夠看到DistanceField<0後中斷循環,咱們知道,DistanceField<0表示已經在物體裏面了,SampleRayTime表示遇到物體的距離,TotalStepsTaken表示前進了多少步,而後在第二次RayTraceThroughTileCulledDistanceFields中,把SampleRayTime,TotalStepsTaken傳入計算,這樣只計算這一段,在這一段裏再次精確多段,多段裏取更精確的值出來,而後把TotalStepsTaken這個值放入VisualizeDistanceField中。
在這裏結果應該也出來了,最後調用一個DrawRectangle其實就是把VisualizeDistanceField貼到對應的View全屏上,整個過程就到這個,若是有不清楚或是錯誤的地方,歡迎你們指出。