距离上一篇博客已经有点久了,中间忙的飞起,突然发现好久没写了,这样很差,写一篇和工做无关的吧。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全屏上,整个过程就到这个,若是有不清楚或是错误的地方,欢迎你们指出。