UE4 中射線檢測的簡單探索

XTG111發表於2024-09-01

透過原始碼瞭解實現簡單的實現原理。非常粗淺,只涉及一些基本的呼叫路徑。
LineTrace...這個函式實際上是呼叫的TSceneCastCommon這個模板函式

template <typename Traits, typename TGeomInputs>
bool TSceneCastCommon(const UWorld* World, typename Traits::TOutHits& OutHits, const TGeomInputs& GeomInputs, const FVector Start, const FVector End, ECollisionChannel TraceChannel, const struct FCollisionQueryParams& Params, const struct FCollisionResponseParams& ResponseParams, const struct FCollisionObjectQueryParams& ObjectParams)

首先它是呼叫的RaycastSingle函式

using TCastTraits = TSQTraits<FHitRaycast, ESweepOrRay::Raycast, ESingleMultiOrTest::Single>;
return TSceneCastCommon<TCastTraits>(World, OutHit, FRaycastSQAdditionalInputs(), Start, End, TraceChannel, Params, ResponseParams, ObjectParams);

TSQTraits結構體

該結構體會儲存或者設定我們檢測中需要用到的變數,利用上方的using進行初始賦值
其中最主要的就是

using THitBuffer = typename TChooseClass<InSingleMultiOrTest == ESingleMultiOrTest::Multi, FDynamicHitBuffer<InHitType>, typename TChooseClass<InGeometryQuery == ESweepOrRay::Sweep, FSingleHitBuffer<FHitSweep>, FSingleHitBuffer<FHitRaycast>>::Result >::Result;

後續會出現的函式

IsRay() 檢測當前的掃描型別

constexpr static bool IsRay() { return GeometryQuery == ESweepOrRay::Raycast;  }

GetNumHits() 檢測命中的數量

針對multi和single有不同的檢測返回

// GetNumHits - multi
template <ESingleMultiOrTest T = SingleMultiOrTest>
static typename TEnableIf<T == ESingleMultiOrTest::Multi, int32>::Type GetNumHits(const THitBuffer& HitBuffer)
{
	return HitBuffer.GetNumHits();
}

// GetNumHits - single/test
template <ESingleMultiOrTest T = SingleMultiOrTest>
static typename TEnableIf<T != ESingleMultiOrTest::Multi, int32>::Type GetNumHits(const THitBuffer& HitBuffer)
{
	return GetHasBlock(HitBuffer) ? 1 : 0;
}

HitBuffer.GetNumHits()

其呼叫的是根據TChooseClass選擇出來的掃描型別的命中結果中的函式

	FORCEINLINE int32 GetNumHits() const
	{
		return Hits.Num();
	}

而這些實際的HitBuffer類裡面存在著一個Hits成員變數

//Engine\Source\Runtime\PhysicsCore\Public\PhysXInterfaceWrapperCore.h
/** Hits encountered. Can be larger than HIT_BUFFER_SIZE */
TArray<TTypeCompatibleBytes<HitType>, TInlineAllocator<HIT_BUFFER_SIZE>> Hits;

//當發生碰撞時會執行該回撥函式,向Hits陣列裡面加入資料
virtual PxAgain processTouches(const HitType* buffer, PxU32 nbHits) override
{
	Hits.Append((TTypeCompatibleBytes<HitType>*)buffer, nbHits);
	return true;
}

TSceneCastCommon函式

在該函式中,首先會進行射線檢測距離的計算

	FVector Delta = End - Start;
	float DeltaSize = Delta.Size();
	float DeltaMag = FMath::IsNearlyZero(DeltaSize) ? 0.f : DeltaSize;
	float MinBlockingDistance = DeltaMag;

然後如果有新增了Ignore的物件,會進行處理。
接著就會開始進行檢測了,Traits就是TSQTraits結構體

typename Traits::THitBuffer HitBufferSync; //HitBufferSync用來儲存命中的快取結果

bool bBlockingHit = false;
const FVector Dir = DeltaMag > 0.f ? (Delta / DeltaMag) : FVector(1, 0, 0);
const FTransform StartTM = Traits::IsRay() ? FTransform(Start) : FTransform(*GeomInputs.GetGeometryOrientation(), Start); //透過判斷檢測型別

接著會對當前的物理場景進行凍結,避免其他因素的影響

// Enable scene locks, in case they are required
FPhysScene& PhysScene = *World->GetPhysicsScene();

FScopedSceneReadLock SceneLocks(PhysScene);
{
	FScopedSQHitchRepeater<decltype(HitBufferSync)> HitchRepeater(HitBufferSync, QueryCallback, FHitchDetectionInfo(Start, End, TraceChannel, Params));
	do
	{
		Traits::SceneTrace(PhysScene, GeomInputs, Dir, DeltaMag, StartTM, HitchRepeater.GetBuffer(), Traits::GetHitFlags(), Traits::GetQueryFlags(), Filter, Params, &QueryCallback);
	} while (HitchRepeater.RepeatOnHitch());
}

凍結之後會進行命中檢測

const int32 NumHits = Traits::GetNumHits(HitBufferSync);

透過獲得的NumHits個數來判斷是否成功命中,然後返回最近的命中結果

if(NumHits > 0 && GetHasBlock(HitBufferSync))
{
	bBlockingHit = true;
	MinBlockingDistance = GetDistance(Traits::GetHits(HitBufferSync)[NumHits - 1]);
}

相關文章