avatar

Hika

A blog focus on GameDev

  • 首页
  • 分类
  • 标签
  • 归档
  • 关于
首页 UE实现圆锥体ConeTrace
文章

UE实现圆锥体ConeTrace

发表于 2023/07/27
作者 lynx 6 分钟阅读

有时候实现Gameplay逻辑的时候,会要求判断屏幕的圆是否与场景的碰撞体相交(如瞄准辅助)。屏幕的圆与场景碰撞相交本质是视锥体与场景物体是否相交。此时ConeTrace能解决此问题,然而UE并不支持,Physx貌似也没有此类基本几何体。本文意在实现快速的ConeTrace。

ConeTrace

ConeTrace在摄像机内的横截面图示

构造凸包碰撞体方案

动态生成凸包是最万金油的做法,基本上大部分三维形状的Sweep都能做到。不过由于圆锥有弧度,生成的凸包会有一定的顶点数,最终也没有SphereTrace(内部实现是CapsuleTrace)的性能好。不过好处是能实现各种通用形状的Trace,如扇形体,平截头体等。由于暂时多考虑性能而不是通用性,暂不采用此方案。

基本几何体的组合方案

使用SphereTrace或者BoxTrace通过迭代多次,组合成近似的圆锥体是一种简单有效的实现方案。基本几何的Trace性能可控,做法调试也比较简单。

而组合Trace的方法又有两个:网格划分和切面划分。

网格划分指的是将屏幕圆划分成数个圆或者矩形,以圆或矩形发射SphereTrace或者BoxTrace。BoxTrace比SphereTrace的空隙更少,Trace长度长的比短的空隙更少,Trace分割(迭代)次数多的空隙比分割次数少的空隙更少。

image-20230803162823639

切面划分的方法指的是把圆锥垂直切分成数分,用SphereTrace捕获对应的Actor。此方案不会有空隙的情况,但是需要排除几何体以外的碰撞。考虑到点在圆锥内的判断算法消耗不高,用此方案更为合适。

image-20230803162745861

切面划分

对于敌人高密度分布均匀的场景,切面划分次数越多,性能相对来说会越好。否则,一般三五个划分足矣。这个取决于游戏类型。另外对于UE物理引擎,默认Trace会遇到Block会立刻停止,如果此碰撞位置位于圆锥外面,需要忽略掉此物理体重新发出Trace,这样会增加物理消耗。此处可以利用碰撞通道反馈的特点,把Trace的FCollisionResponseParams.CollisionResponse降级为Overlap,此时Trace始终不会停止。具体可参考官方博客

Filter Table

图来自官方博客,通道会根据最小通道反馈自动降级

经过切面划分的方式,结果可能有不在圆锥内的结果,因此还需要排除。排除方法也很简单,判断Trace方向与结果方向的夹角,是否小于圆锥半角即可。可以概括为下面公式:

\[\arccos(\hat{V_t}\cdot\hat{V_d}) \leq \frac{\theta_c}{2}\]

其中$\hat{V_t}$是圆锥顶点到搜索目标的单位向量,$\hat{V_d}$是Trace方向的单位向量,$\theta_c$为圆锥角大小。

实现代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
void ConeTraceSingle(const UObject* WorldContextObject, const FVector Start, const FVector End, 
                     float ConeAngle, ETraceTypeQuery TraceChannel, bool bTraceComplex,
                     const TArray<AActor*>& ActorsToIgnore, EDrawDebugTrace::Type 		
                     DrawDebugType, FHitResult& OutHit, bool bIgnoreSelf,
                     int IterateNum, FLinearColor TraceColor, FLinearColor TraceHitColor, float DrawTime)
{
	UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull);
	FVector Direction = (End - Start).GetSafeNormal();
	float Length = (End - Start).Size();
	float StepLength = Length / IterateNum;
	float ConeHalfAngle = ConeAngle / 2;
	FHitResult FinalResult;

	if (IterateNum <= 1)
	{
		UKismetSystemLibrary::SphereTraceSingle(WorldContextObject, Start, End, 0, TraceChannel, bTraceComplex,
			ActorsToIgnore, DrawDebugType, OutHit, bIgnoreSelf, TraceColor, TraceHitColor, DrawTime);
	}
	else
	{
		for (int i = 0; i < IterateNum; i++)
		{
			float ConeBottomRadius = (i + 1) * StepLength * FMath::Tan(FMath::DegreesToRadians(ConeHalfAngle));
			FVector StepStart = Start + Direction * i * StepLength;
			FVector StepEnd = Start + Direction * (i + 1) * StepLength;

			TArray<FHitResult> StepHit;
			SphereTraceMultiWithoutBlock(WorldContextObject, StepStart, StepEnd, ConeBottomRadius, TraceChannel, 
                                         bTraceComplex, ActorsToIgnore, DrawDebugType, StepHit, bIgnoreSelf,
                                         TraceColor,FLinearColor::Black, DrawTime);

			if (StepHit.Num() > 0)
			{
				StepHit.Sort([StepStart](const FHitResult& A, const FHitResult& B)
				{
					return (A.ImpactPoint - StepStart).SizeSquared() < (B.ImpactPoint - StepStart).SizeSquared(); 
				});

				for (auto Hit : StepHit)
				{
					FVector ImpactVec = Hit.ImpactPoint - Start;
					float HitAngle = AngleBetweenVectors(ImpactVec, Direction);
					if (Hit.Actor != nullptr && HitAngle <= ConeHalfAngle)
					{
						FinalResult = Hit;
						break;
					}
				}
			}

			if (FinalResult.Actor != nullptr)
				break;
		}
	}

	OutHit = FinalResult;
}

其中SphereTraceMultiWithoutBlock函数是把Block降级为Overlap的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void SphereTraceMultiWithoutBlock(const UObject* WorldContextObject, const FVector Start, 
                                  const FVector End, float Radius,
                                  ETraceTypeQuery TraceChannel, bool bTraceComplex, 
                                  const TArray<AActor*>& ActorsToIgnore,
                                  EDrawDebugTrace::Type DrawDebugType, 
                                  TArray<FHitResult>& OutHits, bool bIgnoreSelf, FLinearColor
                                  TraceColor, FLinearColor TraceHitColor, float DrawTime)
{
	SCOPE_CYCLE_COUNTER(STAT_SMGSphereTrace);
	
	ECollisionChannel CollisionChannel = UEngineTypes::ConvertToCollisionChannel(TraceChannel);

	static const FName SphereTraceMultiName(TEXT("SphereTraceMulti"));
	FCollisionQueryParams Params = ConfigureCollisionParams(SphereTraceMultiName, bTraceComplex, 
                                                            ActorsToIgnore, bIgnoreSelf, WorldContextObject);
    
    FCollisionResponseParams ResponseParams;
	ResponseParams.CollisionResponse.SetAllChannels(ECR_Overlap);
	
	UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull);
	bool const bHit = World ? World->SweepMultiByChannel(OutHits, Start, End, FQuat::Identity, CollisionChannel,
                                                         FCollisionShape::MakeSphere(Radius), 
                                                         Params, ResponseParams) : false;
}
游戏, 物理
游戏 物理
本文由作者按照 CC BY 4.0 进行授权
分享

热门标签

游戏 动画 渲染 物理 网络
推荐博客
  •  iquilezles
  •  timlly cnblog
  •  catlikecoding
  •  therealmjp
  •  realtime-rendering-blog

文章内容

相关文章

2023/09/10

叠加动画原理细节

叠加动画常用于战斗受击、射击抖动、AO等。其作用是能同时两个动画的表现,如角色在跑步的时候受击,跑动动画是不能停的同时也要有被击中的抽搐表现;又如角色瞄准的时候射击,射击会因为武器的后坐力让角色躯体发生抖动,而角色也会下意识地维持着瞄准动作……叠加动画几乎占据角色动画逻辑的一大部分,不过似乎很少人讨论有关叠加动画的技术细节。本以UE实现方法为例,意在分析叠加动画的原理和一些细节。 基本原理...

2023/09/13

MotionWarping

MotionWarping算是一项老生常谈的动画技术了。技术本身原理并不复杂,不过UE自己却有不少的变种实现方案,对这最基础的实现思想有不断的进化和补充,目的也是为了提高算法的最终表现效果,做3A游戏真是对细节真是孜孜不倦啊。本文主要是解释MotionWarping的基础原理和UE的SkewWarping的实现原理。 DeltaCorrection和AnimationWarping Mo...

2020/12/24

前向渲染、延迟渲染和Forward+

项目最近计划要在移动平台上添加大量的动态光,计划使用Cluster Forward Plus,由于项目使用URP,别说CFP,延迟渲染现在还没被支持(据说之后Unity有支持的计划),所以需要我们自己实现。本文是对网络论文的整理,有部分是自己的理解,但大部分还是作者的论述。 简介 前端渲染就是通过栅格化场景的多边形对象来工作,迭代场景的灯光列表以确定几何对象如何被照亮。这意味着每个灯光并...

掩体系统(下)

叠加动画原理细节

© 2023 lynx. 保留部分权利。

本站采用 Jekyll 主题 Chirpy

热门标签

游戏 动画 渲染 物理 网络

发现新版本的内容。