本文的鱼眼相机代码源自github上Carla仓库的Fisheye sensor #3755(https://github.com/carla-simulator/carla/pull/3755),正文中作为示例的是本文作者基于此改编的适配最新版Carla"Unreal Engine 5.5 version of CARLA"的Fisheye sensor代码。文末会附上改编后的代码。
在Carla的文档中,有如何建立新传感器模型的教程:how-to-add-a-new-sensor,建议首先阅读链接中的文档以熟悉这一过程。
和文档中一样,我们首先从新建立的Sensor Actor开始。AFisheyeSensor::GetSensorDefinition(),AFisheyeSensor::GetImageWidth(),AFisheyeSensor::GetImageHeight(),AFisheyeSensor::GetFOV()获取一些如图像分辨率、相机内参等参数,AFisheyeSensor::Set(const FActorDescription &Description)会将这些参数赋值给类内部的成员。
在构造函数中,定义了一个Fisheye对象:
Fisheye = CreateDefaultSubobject<USceneCaptureComponentCube>(
FName(*FString::Printf(TEXT("SceneCaptureComponentCube_%d"), SCENE_CAPTURE_COUNTER_CUBE)));
USceneCaptureComponentCube 的作用动态捕获立方体贴图:USceneCaptureComponentCube 是用于从场景的某个位置捕获 立方体贴图(Cubemap) 的组件。它会实时渲染场景的六个面(前、后、左、右、上、下),生成一个立方体贴图。这种贴图常用于动态环境反射(如金属、水面)、天空盒效果,或需要全方向视角的场景捕捉(如VR,和本文所述的鱼眼相机)。
应用场景:
例如,在角色周围动态生成环境反射时,可以将 USceneCaptureComponentCube 附加到角色身上,实时捕捉周围环境,并将结果传递给材质系统,实现动态反射效果。
SCENE_CAPTURE_COUNTER_CUBE 的作用唯一命名标识:SCENE_CAPTURE_COUNTER_CUBE 是一个无符号整型计数器(0u 表示初始值为0),用于为每个 USceneCaptureComponentCube 实例生成唯一名称。通过 FString::Printf 将其嵌入名称字符串(如 "SceneCaptureComponentCube_0"),避免多个组件因同名导致冲突。
计数器的递增:
通常,在每次创建组件后,计数器会递增(例如 SCENE_CAPTURE_COUNTER_CUBE++),确保后续实例的名称后缀依次为 _1、_2 等。这是Unreal中管理动态生成组件命名的常见做法。
在Unreal Engine中,**USceneCaptureComponentCube::CaptureScene()** 是一个关键方法,用于 手动触发 立方体贴图(Cubemap)的场景捕获操作。以下是关于该方法的详细说明:
CaptureScene() 会立即从 USceneCaptureComponentCube 的当前位置和方向,渲染场景的六个面(前、后、左、右、上、下),生成一个完整的立方体贴图,并将结果写入关联的渲染目标(TextureRenderTargetCube)。下面进入FisheyeSensor在仿真开始时会运行的方法:BeginPlay()
void AFisheyeSensor::BeginPlay()
{
// 省略
}
这是FisheyeSensor中的第一个重要方法。以下是逐行解释:
CaptureRenderTarget->Init(GetImageWidth(), PF_B8G8R8A8);
UTextureRenderTarget2D),设置其宽度为GetImageWidth()返回的值,像素格式为PF_B8G8R8A8(32位BGRA格式,适合存储颜色数据)。Fisheye->Deactivate();
Fisheye->TextureTarget = CaptureRenderTarget;
Fisheye->CaptureScene();
USceneCaptureComponent2D),避免未配置时自动渲染。然后将渲染目标绑定到组件,并立即触发一次场景捕捉。CaptureScene()可能在组件未激活时仍可手动调用,确保首次捕捉完成。Fisheye->CaptureSource = ESceneCaptureSource::SCS_FinalColorLDR;
Fisheye->UpdateContent();
Fisheye->Activate();
UpdateContent()确保参数更改生效,Activate()启用组件,使其后续可以自动更新(如每帧捕捉)。UKis metSystemLibrary::ExecuteConsoleCommand(
GetWorld(),
FString("g.TimeoutForBlockOnRenderFence 300000"));
Weather->NotifyWeather(this);
GetEpisode()可能指向当前游戏情景,与项目逻辑相关。Super::BeginPlay();
AActor)的BeginPlay(),完成继承链的初始化。那么,在上面的代码中,我们为什么让Fisheye先调用了Deactivate()停用,再调用CaptureScene(),再调用Activate()呢?其行为目的如下:
即使调用了Deactivate(),手动调用CaptureScene()仍然有效。Deactivate()的作用是 关闭组件的自动更新逻辑(如Tick或依赖引擎自动触发的渲染),但不会阻止显式调用CaptureScene()这种主动请求的捕获操作。
关键区别:
bCaptureEveryFrame=true):依赖组件的激活状态(IsActive()),停用后自动捕获停止。CaptureScene()):直接执行,与激活状态无关。因此,代码中Deactivate()的目的是 避免自动更新干扰,而非禁用所有功能。
Fisheye->Deactivate(); // 步骤1:停用自动逻辑
Fisheye->TextureTarget = CaptureRenderTarget; // 步骤2:绑定渲染目标
Fisheye->CaptureScene(); // 步骤3:手动触发捕获
Fisheye->CaptureSource = ESceneCaptureSource::SCS_FinalColorLDR; // 步骤4:设置捕获源
Fisheye->UpdateContent(); // 步骤5:强制更新配置
Fisheye->Activate(); // 步骤6:重新激活组件
Deactivate()的作用bCaptureEveryFrame=true或其他引擎机制自动触发捕获。TextureTarget、CaptureSource)的过程中,组件不会意外执行未完全初始化的捕获操作。CaptureScene()的调用TextureTarget。TextureTarget,因此捕获结果会正确写入目标资源。UpdateContent()的必要性CaptureSource)的修改可能需要通过UpdateContent()强制更新渲染代理(Proxy),确保后续操作使用最新参数。Activate()的恢复bCaptureEveryFrame=true),需重新激活。bCaptureEveryFrame=true,直接修改参数可能导致捕获过程中途被自动更新打断,产生不完整或冲突的结果。Deactivate()冻结组件的自动行为,确保参数修改(TextureTarget、CaptureSource)和手动捕获(CaptureScene())是一个完整且不可中断的流程。TextureTarget,可避免渲染线程正在使用该资源时发生冲突(如前一帧未完成的捕获仍在写入纹理)。Activate(),确保组件后续可以正常参与引擎的更新循环(如需要持续自动捕获)。TextureTarget并立即捕获一次,但不希望自动更新持续写入新目标。CaptureSource、后期处理参数),并要求这些参数在捕获时完全生效。在AFisheyeSensor中,每一步鱼眼图像渲染的执行是在AFisheyeSensor::PrePhysTick()方法中实现的,其中最核心的方法就是以下函数:
template <typename TSensor>
void AFisheyeSensor::SendPixelsInRenderThread(TSensor &Sensor, float MaxAngle, float SizeX, float SizeY, float Fx, float Fy, float Cx, float Cy, float D1, float D2, float D3, float D4)
{
// 省略
}
以下是代码的详细分步解释:
其核心功能是:
在渲染线程中将立方体贴图(Cubemap)转换为鱼眼图像,并通过Carla传感器数据流发送出去。
check(Sensor.CaptureRenderTarget != nullptr);
UTextureRenderTargetCube *RenderTarget = Sensor.CaptureRenderTarget;
UTextureRenderTargetCube**:Unreal中存储立方体贴图的资源,通常由USceneCaptureComponentCube捕获生成。TArray64<uint8> RawData; // 存储鱼眼图像原始像素数据
FIntPoint Size; // 图像尺寸(输出参数)
EPixelFormat Format; // 像素格式(输出参数)
TArray64<uint8>**:支持大尺寸(64位)的字节数组,用于容纳高分辨率图像数据。CubemapHelpersFisheye::FFisheyeParams FisheyeParams;
FisheyeParams.ImageSize = FIntPoint(SizeX, SizeY); // 鱼眼图像分辨率
FisheyeParams.CameraMatrix = FVector4(Fx, Fy, Cx, Cy); // 相机内参矩阵
FisheyeParams.DistortionCoeffs = FVector4(D1, D2, D3, D4); // 畸变系数(k1, k2, k3, k4)
FisheyeParams.MaxAngle = MaxAngle; // 鱼眼最大视场角(FOV)
bool bUnwrapSuccess = CubemapHelpersFisheye::GenerateLongLatUnwrapFisheye(
RenderTarget, RawData, FisheyeParams, Size, Format
);
RawData:存储鱼眼图像的像素数据(RGB或灰度)。Size:图像实际尺寸(可能与输入SizeX/Y不同,取决于算法)。Format:像素格式(如PF_R8G8B8A8)。auto Stream = Sensor.GetDataStream(Sensor);
GetDataStream**:Carla框架中获取传感器数据流的方法,用于将数据发送到客户端或日志系统。auto Buffer = Stream.PopBufferFromPool(); // 从缓冲池获取内存块
// 将鱼眼数据拷贝到缓冲区
Buffer.copy_from(carla::sensor::SensorRegistry::get<TSensor *>::type::header_offset, RawData.GetData(), RawData.Num());
// 序列化数据
carla::Buffer BufferReady(std::move(carla::sensor::SensorRegistry::Serialize(Sensor, std::move(Buffer))));
carla::SharedBufferView BufView = carla::BufferView::CreateFrom(std::move(BufferReady));
SCOPE_CYCLE_COUNTER(STAT_CarlaSensorStreamSend);
TRACE_CPUPROFILER_EVENT_SCOPE_STR("Stream Send");
// 发送数据
Stream.Send(Sensor, BufView);
PopBufferFromPool 避免频繁内存分配,提升性能。header_offset)。Stream.Send将数据发送到Carla客户端或日志。SendPixelsInRenderThread 指示其在渲染线程调用,确保对渲染资源(如UTextureRenderTargetCube)的安全访问。PopBufferFromPool 减少内存分配开销。RawData.GetData() 直接访问内存,避免额外复 制。Stream.Send 可能非阻塞,避免阻塞渲染线程。template <typename TSensor> 支持多种传感器类型复用相同逻辑。在AFisheyeSensor::SendPixelsInRenderThread()这个函数中,最关键的是对于CubemapHelpersFisheye::GenerateLongLatUnwrapFisheye方法的调用。CubemapHelpersFisheye::GenerateLongLatUnwrapFisheye来自UnrealEngine5_carla的Engine\Source\Runtime\Engine\Private\CubemapUnwrapUtils.cpp,是根据原有的CubemapHelpers编写的针对鱼眼相机仿真的新方法。
我们首先来看核心函数GenerateLongLatUnwrapFisheye的解析:
GenerateLongLatUnwrapFisheye 是Unreal引擎中用于将 立方体贴图(Cubemap)转换为鱼眼图像 的核心函数。其通过 GPU渲染管线 实现以下功能:
// 创建自定义的渲染参数(鱼眼变形逻辑)
TRefCountPtr<FBatchedElementParameters> BatchedElementParameters;
BatchedElementParameters = new FMipLevelBatchedElementParametersFisheye(
(float)0,
FisheyeParams.DistortionCoeffs,
FisheyeParams.CameraMatrix,
FisheyeParams.ImageSize,
FisheyeParams.MaxAngle,
true);
// 选择目标像素格式(LDR或HDR)
const EPixelFormat TargetPixelFormat = CalculateImageBytes(1, 1, 0, SourcePixelFormat) <= 4 ? PF_B8G8R8A8 : PF_FloatRGBA;
FMipLevelBatchedElementParametersFisheye**:DistortionCoeffs:镜头畸变系数(k1, k2, k3, k4)。CameraMatrix:相机内参(fx, fy, cx, cy)。MaxAngle:最大视场角(控制鱼眼范围)。PF_B8G8R8A8,HDR用PF_FloatRGBA)。// 创建2D渲染目标(用于存储鱼眼图像)
UTextureRenderTarget2D* RenderTargetLongLat = NewObject<UTextureRenderTarget2D>();
RenderTargetLongLat->AddToRoot(); // 防止被GC回收
RenderTargetLongLat->ClearColor = FLinearColor(0.0f, 0.0f, 0.0f, 0.0f);
RenderTargetLongLat->InitCustomFormat(FisheyeParams.ImageSize.X, FisheyeParams.ImageSize.Y, TargetPixelFormat, false);
RenderTargetLongLat->TargetGamma = 0;
FRenderTarget* RenderTarget = RenderTargetLongLat->GameThread_GetRenderTargetResource();
// 创建画布并绑定渲染目标
FCanvas* Canvas = new FCanvas(RenderTarget, NULL, FGameTime(), GMaxRHIFeatureLevel);
Canvas->SetRenderTarget_GameThread(RenderTarget);
Canvas->Clear(FLinearColor(0, 0, 0, 0)); // 清空为透明
UTextureRenderTarget2D**:2D渲染目标,用于存储中间渲染结果。AddToRoot**:防止Unreal垃圾回收器(GC)误删资源。FCanvas**:Unreal的2D画布工具,允许在游戏线程操作GPU渲染。// 将立方体贴图绘制到渲染目标,并应用鱼眼参数
FCanvasTileItem TileItem(FVector2D(0.0f, 0.0f), TextureResource, FVector2D(FisheyeParams.ImageSize.X, FisheyeParams.ImageSize.Y), FLinearColor::White);
TileItem.BatchedElementParameters = BatchedElementParameters; // 注入鱼眼逻辑
TileItem.BlendMode = SE_BLEND_Opaque;
Canvas->DrawItem(TileItem);
// 提交渲染命令并等待完成
Canvas->Flush_GameThread();
FlushRenderingCommands();
FCanvasTileItem**:将立方体贴图作为纹理平铺到整个渲染目标。BatchedElementParameters**:关键参数,绑定自定义的鱼眼变形逻辑(通过Unreal的FBatchedElementParameters机制注入到着色器)。FlushRenderingCommands**:强制同步渲染线程,确保所有GPU操作完成。// 分配内存并读取渲染结果
BitsOUT.AddUninitialized(ImageBytes);
bool bReadSuccess = false;
switch (TargetPixelFormat)
{
case PF_B8G8R8A8: // LDR格式
bReadSuccess = RenderTarget->ReadPixelsPtr((FColor*)BitsOUT.GetData());
break;
case PF_FloatRGBA: // HDR格式
{
TArray<FFloat16Color> FloatColors;
bReadSuccess = RenderTarget->ReadFloat16Pixels(FloatColors);
FMemory::Memcpy(BitsOUT.GetData(), FloatColors.GetData(), ImageBytes);
}
break;
}
ReadPixelsPtr / ReadFloat16Pixels**:从GPU读取像素数据到CPU内存。FFloat16Color数组转换为字节流。// 释放资源
RenderTargetLongLat->ReleaseResource();
RenderTargetLongLat->RemoveFromRoot();
RenderTargetLongLat = NULL;
delete Canvas;
// 设置输出参数
if (!bReadSuccess) BitsOUT.Empty();
return bReadSuccess;
SizeOUT = FisheyeParams.ImageSize;
FormatOUT = TargetPixelFormat;
if (bReadSuccess == false)
{
// 失败时清空数据
BitsOUT.Empty();
}
return bReadSuccess;
FMipLevelBatchedElementParametersFisheye向Unreal的渲染管线注入自定义的顶点/像素着色器逻辑。FCanvas和UTextureRenderTarget2D操作均在游戏线程执行,通过FlushRenderingCommands同步渲染线程,确保GPU操作完成。AddToRoot/RemoveFromRoot显式控制渲染目标的GC生命周期,防止异步渲染过程中资源被释放。TArray64<uint8>& BitsOUT复用内存,减少分配开销。下面我们来看FMipLevelBatchedElementParametersFisheye绑定着色器shader的方法:
template<typename TPixelShader>
void FMipLevelBatchedElementParametersFisheye::BindShaders(FRHICommandList& RHICmdList, FGraphicsPipelineStateInitializer& GraphicsPSOInit, ERHIFeatureLevel::Type InFeatureLevel, const FMatrix& InTransform, const float InGamma, const FMatrix& ColorWeights, const FTexture* Texture)
{
// 省略
}
此函数是 具体执行着色器绑定和参数传递 的核心实现,负责将 顶点着色器(VS)和像素着色器(PS) 绑定到图形管线,并传递渲染所需的参数(如畸变系数、相机矩阵等)。它是在代码中前文出现的BindShaders函数的具体实现模板。
template<typename TPixelShader>
void FMipLevelBatchedElementParametersFisheye::BindShaders(...)
{
// 1. 设置混合状态(不混合,直接覆盖)
GraphicsPSOInit.BlendState = TStaticBlendState<>::GetRHI();
// 2. 获取着色器实例
TShaderMapRef<FCubemapTexturePropertiesVSFisheye> VertexShader(...); // 顶点着色器
TShaderMapRef<TPixelShader> PixelShader(...); // 模板化的像素着色器
// 3. 配置管线状态
GraphicsPSOInit.BoundShaderState.VertexDeclarationRHI = GSimpleElementVertexDeclaration.VertexDeclarationRHI; // 顶点声明
GraphicsPSOInit.BoundShaderState.VertexShaderRHI = VertexShader.GetVertexShader(); // 绑定VS
GraphicsPSOInit.BoundShaderState.PixelShaderRHI = PixelShader.GetPixelShader(); // 绑定PS
GraphicsPSOInit.PrimitiveType = PT_TriangleList; // 图元类型为三角形列表
// 4. 提交管线状态到GPU
RHICmdList.ApplyCachedRenderTargets(GraphicsPSOInit);
SetGraphicsPipelineState(RHICmdList, GraphicsPSOInit, 0);
// 5. 传递参数到着色器
SetShaderParametersLegacyVS(RHICmdList, VertexShader, InTransform); // VS参数(变换矩阵)
SetShaderParametersLegacyPS(RHICmdList, PixelShader, Texture,
ColorWeights,
MipLevel,
InGamma,
DistortionCoeffs,
CameraMatrix,
ImageSize,
MaxAngle
); // PS参数(包含鱼眼关键参数)
}
TPixelShader**:FCubemapTexturePropertiesPSFisheye<true>和<false>),在 编译时生成多份代码,避免运行时分支判断。HDR_OUTPUT),实现 同一份HLSL代码的不同编译版本。FCubemapTexturePropertiesVSFisheye**:InTransform | FMatrix) | |
DistortionCoeffs | DistortionCoeffs | |
CameraMatrix | CameraMatrix | |
MaxAngle | MaxAngle |
GraphicsPSOInit.BlendState = TStaticBlendState<>::GetRHI();
TStaticBlendState<>**:BlendOp=Add, SrcBlend=One, DestBlend=Zero),直接覆盖目标颜色,适合不透明渲染。GraphicsPSOInit.PrimitiveType = PT_TriangleList;
GSimpleElementVertexDeclaration.VertexDeclarationRHI
POSITION)和纹理坐标(TEXCOORD0),匹配全屏四边形的输入结构。VertexShader->SetParameters(RHICmdList, InTransform);
对应HLSL中的顶点变换矩阵:
float4x4 TransformMatrix;
PixelShader->SetParameters(RHICmdList, DistortionCoeffs, ...);
对应鱼眼着色器参数:
float4 DistortionCoeffs;
float4 CameraMatrix;
float MaxAngle;
在代码中,我们使用的顶点着色器是UE在SimpleElementVertexShader.usf中原有的顶点着色器:
IMPLEMENT_SHADER_TYPE(,FCubemapTexturePropertiesVSFisheye,TEXT("/Engine/Private/SimpleElementVertexShader.usf"),TEXT("Main"),SF_Vertex);
而鱼眼图像的像素转换过程则依靠在UE的SimpleElementPixelShader.usf中新添加的像素着色器。首先解释下什么是前面提到的HLSL,这是在usf文件中使用的编程语言:
HLSL(High-Level Shader Language,高级着色器语言)是微软为 DirectX 图形API设计的一种编程语言,主要用于编写在GPU上运行的 着色器程序(Shader)。它是实现复杂图形效果的核心工具,广泛应用于游戏开发、影视渲染和实时图形处理。
通过编写不同类型的着色器,控制GPU如何处理几何体、光照、纹理和像素:
与C语言类似,支持函数、条件语句、循环等,但包含图形专用类型和语义:
// 示例:简单的像素着色器
float4 MainPS(float2 UV : TEXCOORD0) : SV_Target
{
float4 Color = Texture2D.Sample(TextureSampler, UV);
return Color * LightIntensity;
}
float half | float3 position; | |
int uint | uint vertexID; | |
float2 float3x3 | float4 color = {1,0,0,1}; | |
Texture2D | Texture2D DiffuseMap; | |
SamplerState | SamplerState LinearWrap; |
通过语义标签将变量与渲染管线数据关联:
// 顶点着色器输入
struct VSInput
{
float3 Position : POSITION; // 顶点位置
float2 UV : TEXCOORD0; // 纹理坐标
};
// 像素着色器输出
float4 MainPS() : SV_Target // 输出到渲染目标
{
return float4(1,0,0,1);
}
Unreal的 材质编辑器 底层通过HLSL代码生成最终的着色器。用户可以通过节点图可视化编程,或直接编写 Custom Node 注入HLSL代码。
开发者可以直接编写.usf(Unreal Shader File)文件,实现复杂效果(如我们分析的鱼眼投影函数):
// 示例:鱼眼畸变计算
void CubemapTexturePropertiesFisheye(float2 UV)
{
float2 NormCoord = (UV - CameraMatrix.zw) / CameraMatrix.xy;
// 迭代求解畸变模型...
}
通过修改引擎的 Shader 代码,可以自定义延迟渲染、光线追踪等高级管线。
最后我们终于来到了本文最核心的部分:shader文件SimpleElementPixelShader.usf,该Shader文件是Unreal简单元素渲染系统的核心组件,通过高度优化的HLSL代码实现了从基础纹理混合到复杂鱼眼投影的多功能渲染。其中的鱼眼投影函数CubemapTexturePropertiesFisheye是本文讨论的重点:
DistortionCoeffs | float4 | ||
CameraMatrix | float4 | ||
MaxAngle | float |
以下是函数内部关键计算步骤与参数的对应关系:
TextureCoordinate.x = (TextureCoordinate.x - c_x) / f_x;
TextureCoordinate.y = (TextureCoordinate.y - c_y) / f_y;
c_x, c_y(来自CameraMatrix):将像素坐标从图像坐标系(以左上角为原点)转换到以光心为中心的归一化坐标系。f_x, f_y(来自CameraMatrix):将像素坐标缩放为无单位的归一化坐标(类似于OpenCV的“理想像素”坐标系)。
float r = sqrt(x*x + y*y);
float theta = r;
for (int j = 0; j < 10; j++) {
float th2 = theta * theta;
float th4 = th2 * th2;
float th6 = th4 * th2;
float th8 = th6 * th2;
theta = r / (1.0 + d1*th2 + d2*th4 + d3*th6 + d4*th8);
}
d1, d2, d3, d4(来自DistortionCoeffs):定义畸变多项式权重,通过牛顿迭代法反向求解无畸变半径。
通过10次迭代反向求解:

float lambda = atan2(y, x);
float phi = PI/2 - theta;
float3 Direction = float3(cos(phi)*cos(lambda), -cos(phi)*sin(lambda), sin(phi));
theta(来自迭代结果):无畸变归一化半径,转换为极角(Polar Angle)。lambda:方位角(Azimuth Angle),从x轴逆时针旋转。phi:天顶角(Zenith Angle),从z轴向下倾斜。float theta_f = MaxAngle * PI / 360.0f; // 转换为半角弧度
BaseColor *= (1 - s moothstep(theta_f - 0.2, theta_f, theta));
MaxAngle:定义有效成像的最大半角(例如180°对应theta_f = PI/2)。s moothstep在[theta_f-0.2, theta_f]范围内创建渐变透明度,避免硬截断导致的锯齿。增大MaxAngle | ||
增加d1(k₁) | ||
调整f_x, f_y | f_x减小 → 水平视场角增大;f_x增大 → 水平视场角减小。 | |
偏移c_x, c_y |
flowchart TD
C++[C++侧参数设置] --> |DistortionCoeffs| HLSL(d1, d2, d3, d4)
C++ --> |CameraMatrix| HLSL(f_x, f_y, c_x, c_y)
C++ --> |MaxAngle| HLSL(theta_f)
HLSL --> 像素坐标校正["归一化坐标 (x, y)"]
像素坐标校正 --> 畸变迭代["迭代求解 theta"]
畸变迭代 --> 球面映射["方向向量 Direction"]
球面映射 --> 立方体采样["采样 Cubemap"]
立方体采样 --> 边缘柔化["应用 MaxAngle 渐变"]
边缘柔化 --> 输出颜色["OutColor"]
通过以上这种CubemapTexture展开做鱼眼相机投影的方法产生鱼眼相机图像,仍然没有解决的问题是,六面体Cubemap展开时,面与面的接缝有时会出现在图像中,如下图所示:

根据文档how-to-add-a-new-sensor,接下来是编制Sensor data serializer,Sensor data object,在SensorRegistry.h中注册你的新传感器等环节,本文提供的代码在这些部分中基本都是参照Carla原有传感器的代码进行编写的,读者可自行对照文末提供的代码进行分析。
在CarlaUE5\PythonAPI\examples提供的示例py文件cam_test_0313.py中,有调用这种Fisheye Sensor的例子。
本文适配的CarlaUE5和UnrealEngine5_carla的确切版本是:
CarlaUE5:
commit 0bfb05bfdde8435bfecfccdef8a0e05041b22aef (origin/ue5-dev, origin/HEAD)
Author: MarcelPiNacy-CVC <169088301+MarcelPiNacy-CVC@users.noreply.github.com>
Date: Mon Mar 10 14:24:37 2025 +0100
Remove MakeFile. (#8739)
UnrealEngine5_carla:
commit eea6a8e677e3509a3e56122d69e3d380bb624bbf (HEAD -> ue5-dev-carla, origin/ue5-dev-carla, origin/HEAD)
Author: Marcel Pi <169088301+MarcelPiNacy-CVC@users.noreply.github.com>
Date: Fri Feb 28 12:53:51 2025 +0100
Add missing chrono include.
编译CarlaUE5和UnrealEngine5_carla的方法在官方文档中很清晰了。在CarlaUE的编译脚本中,会下载Unreal\CarlaUnreal\Content目录下的大文件(约70G),见文末百度云链接。
读者在编译前可提前下载,并放置在对应位置。
首次编译成功,可以运行Carla后,如果对UE和Carla的代码进行了修改,需要对UnrealEngine5_carla和CarlaUE5分别再次进行编译:
# UE5:
./Setup.sh
./GenerateProjectFiles.sh
make
# CarlaUE5:
./CarlaSetup.sh --interactive --python-root=/home/vipuser/.conda/envs/carla/bin
最新版本的CarlaUE5可以通过--python-root参数指定需要的python虚拟环境。CarlaSetup.sh中的参数launch修改为1可以在运行脚本后打开UE窗口。
点击UE窗口中的Run后,运行PythonAPI\examples\cam_test_0313.py,会将Fisheye生成的鱼眼图像保存在py脚本中写出的保存路径(请先按需修改)。


