实验比较材质节点中“ActorPosition”和“ObjectPosition”的区别,并观察其值是如何在代码中设置的

本文通过实验对比了Unreal Engine 4中材质节点“ActorPosition”与“ObjectPosition”的区别,详细介绍了这两种节点如何获取对象位置及中心点,并进一步探讨了它们在C++层面的实现原理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目标

我注意到材质编辑器中有两个节点从名字上看似乎不好分辨:
在这里插入图片描述
本篇的目标是:

  • 实验比较材质节点中“ActorPosition”和“ObjectPosition”的区别
  • 作为知识上的扩展,我想观察其值是如何从代码上得到的。

实验

为了更有目的性地实验,我在实验前查阅了官方文档《坐标表达式 | Unreal Engine Documentation》,得到对二者如下的描述:

  • “ActorPosition” 输出矢量3(RGB)数据,该数据表示对象在世界场景空间中的位置以及其上的材质。
  • “ObjectPosition” 表达式输出对象边界的世界场景空间中心位置。

另外,测试时我还加上了“AbsoluteWorldPosition”节点:

  • “AbsoluteWorldPosition”表达式输出当前像素在全局空间中的位置

当然,文字描述毕竟是抽象的,进行实验才能有具体的认识。

下面使用地形进行实验,因为他的Actor位置和bound中心的位置并不一样。
在这里插入图片描述

1. Absolute World Position

在这里插入图片描述
在这里插入图片描述

2. ActorPosition

在这里插入图片描述
在这里插入图片描述
可以看到,它是整体变化的,并且是以Actor坐标为基准。

3. Object Position

在这里插入图片描述
在这里插入图片描述
可以看到,它也是整体变化的,不过区别是它是以整体的中心为基准。

4. Object Position(测试多个地形组件)

对于地形,当地形有多个组件时:
在这里插入图片描述
在这里插入图片描述
可以看到每个地形组件是独立变化的。这或许暗示着“Object Position”的值计算的单位。

观察

关于材质节点对应的C++类UMaterialExpression,在之前的博客《用C++扩展一个UE4材质节点》中有讨论。

“ActorPosition”和“ObjectPosition”看起来有很多相似性,这里选择“ActorPosition”进行观察。

1. 观察对应的 UMaterialExpression

“ActorPosition”节点的类是UMaterialExpressionActorPositionWS

(我注意到在上一部分测试的三个节点都是红色且带有 “Input Data”字样 ,我观察到这是bShaderInputData成员决定的。)

Compile函数中可以看到:

int32 UMaterialExpressionActorPositionWS::Compile(class FMaterialCompiler* Compiler, int32 OutputIndex)
{
	if (Material != nullptr && (Material->MaterialDomain != MD_Surface) && (Material->MaterialDomain != MD_DeferredDecal) && (Material->MaterialDomain != MD_RuntimeVirtualTexture) && (Material->MaterialDomain != MD_Volume))
	{
		return CompilerError(Compiler, TEXT("Expression only available in the Surface and Deferred Decal material domains."));
	}

	return Compiler->ActorWorldPosition();
}

它直接使用了FMaterialCompiler* CompilerActorWorldPosition方法。

2. FHLSLMaterialTranslator::ActorWorldPosition

virtual int32 ActorWorldPosition() override
{
	if (bCompilingPreviousFrame && ShaderFrequency == SF_Vertex)
	{
		// Decal VS doesn't have material code so FMaterialVertexParameters
		// and primitve uniform buffer are guaranteed to exist if ActorPosition
		// material node is used in VS
		return AddInlinedCodeChunk(
			MCT_Float3,
			TEXT("mul(mul(float4(GetActorWorldPosition(Parameters.PrimitiveId), 1), GetPrimitiveData(Parameters.PrimitiveId).WorldToLocal), Parameters.PrevFrameLocalToWorld)"));
	}
	else
	{
		return AddInlinedCodeChunk(MCT_Float3, TEXT("GetActorWorldPosition(Parameters.PrimitiveId)"));
	}
}

可以看到,有一个分支,但最终都是使用AddInlinedCodeChunk来直接向shader代码中加入字符串。根据注释所解释,上面的分支应该是属于少数的情况,在大部分情况下应该走下面的分支,即加入:
GetActorWorldPosition(Parameters.PrimitiveId)

3. MaterialTemplate.ush

\Engine\Shaders\路径下搜索GetActorWorldPosition可以在\Engine\Shaders\Private\MaterialTemplate.ush中找到这个函数的定义:

#if DECAL_PRIMITIVE
	/*
	 * Deferred decal don't have a Primitive uniform buffer, because we don't know on which primitive the decal
	 * is being projected to. But the user may still need to get the decal's actor world position.
	 * So instead of setting up a primitive buffer that may cost to much CPU effort to be almost never used,
	 * we directly fetch this value from the DeferredDecal.usf specific uniform variable DecalToWorld.
	 */
	float3 GetActorWorldPosition(uint PrimitiveId)
	{
		return DecalToWorld[3].xyz;
	}

#else
	float3 GetActorWorldPosition(uint PrimitiveId)
	{
		return GetPrimitiveData(PrimitiveId).ActorWorldPosition;
	}

类似上述可以看到一个分支,同样根据注释所解释,上面的分支应该是属于少数的情况,在大部分情况下应该走下面的分支,即:
GetPrimitiveData(PrimitiveId).ActorWorldPosition


此外,在\Engine\Source\Runtime\RenderCore\Private\ShaderCore.cpp可以看到,MaterialTemplate.ush在编译shader文件的时候是会自动加入的:

// Always add ShaderVersion.ush, so if shader forgets to include it, it will still won't break DDC.
AddShaderSourceFileEntry(OutVirtualFilePaths, FString(TEXT("/Engine/Public/ShaderVersion.ush")), ShaderPlatform);
AddShaderSourceFileEntry(OutVirtualFilePaths, FString(TEXT("/Engine/Private/MaterialTemplate.ush")), ShaderPlatform);
AddShaderSourceFileEntry(OutVirtualFilePaths, FString(TEXT("/Engine/Private/Common.ush")), ShaderPlatform);
AddShaderSourceFileEntry(OutVirtualFilePaths, FString(TEXT("/Engine/Private/Definitions.usf")), ShaderPlatform);

4. shader中的FPrimitiveSceneData

SceneData.ush可以看到GetPrimitiveData的定义:

// Fetch from scene primitive buffer
FPrimitiveSceneData GetPrimitiveData(uint PrimitiveId)
{
	// Note: layout must match FPrimitiveSceneShaderData in C++
	...

它返回一个FPrimitiveSceneData类型的结构体

// Must match FPrimitiveUniformShaderParameters in C++
struct FPrimitiveSceneData
{
	float4x4 LocalToWorld;
	float4 InvNonUniformScaleAndDeterminantSign;
	float4 ObjectWorldPositionAndRadius;
	float4x4 WorldToLocal;
	float4x4 PreviousLocalToWorld;
	float4x4 PreviousWorldToLocal;
	float3 ActorWorldPosition;
	float UseSingleSampleShadowFromStationaryLights;
	float3 ObjectBounds;
	float LpvBiasMultiplier;
	float DecalReceiverMask;
	float PerObjectGBufferData;
	float UseVolumetricLightmapShadowFromStationaryLights;
	float DrawsVelocity;
	float4 ObjectOrientation;
	float4 NonUniformScale;
	float3 LocalObjectBoundsMin;
	uint LightingChannelMask;
	float3 LocalObjectBoundsMax;
	uint LightmapDataIndex;
	float3 PreSkinnedLocalBoundsMin;
	int SingleCaptureIndex;
	float3 PreSkinnedLocalBoundsMax;
    uint OutputVelocity;
	float4 CustomPrimitiveData[NUM_CUSTOM_PRIMITIVE_DATA];
};

正如其提到的它需要和C++中的FPrimitiveUniformShaderParameters相匹配。看来,它是一个UniformBuffer,对应于C++中这个结构体。

5. C++中的FPrimitiveUniformShaderParameters

\Engine\Source\Runtime\Engine\Public\PrimitiveUniformShaderParameters.h中可以看到FPrimitiveUniformShaderParameters的定义:
在这里插入图片描述


而在这个定义下面,有个GetPrimitiveUniformShaderParameters函数,返回一个FPrimitiveUniformShaderParameters对象,加上其函数名字的描述,我相信这就是设定这个UniformBuffer数据的地方,其中可以发现我现在感兴趣的“ActorPosition”和“ObjectPosition”。
在这里插入图片描述
在此断点并观察堆栈就可知道这些值是如何设置的。

6. 观察值是如何设置的

我创建一个空场景来观察,并且隐藏了网格:
在这里插入图片描述
然后观察到堆栈:
在这里插入图片描述

最终结论

“ActorPosition”和“ObjectPosition”都是UniformBuffer中的数据,不过区别是:

  • “ActorPosition”是Actor的位置。具体来说,是调用FPrimitiveSceneProxyGetActorPosition()
  • “ObjectPosition”是bound的中心。具体来说,是调用FPrimitiveSceneProxyGetBounds()来获得一个FBoxSphereBounds对象,中心即为它的Origin
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值