UE4_SpeedTree Shader源码分析

目录

1.UMaterialExpressionSpeedTree::Compile

2.Compiler->SpeedTree

3.GetSpeedTreeVertexOffset

4.Scene中的SpeedTree应用 (Scene->UpdateSpeedTreeWind)

5.GetSpeedTreeVertexOffsetInner 


由于之后做风场,也需要在SpeedTree中加入风力场的交互,因此这里先整理分析一下原本UE4里面SpeedTree的逻辑,方便在之后修改。

今天的主角就是它了

可以看到这个节点有四个输入,在MaterialExpressionSpeedTree.h里也可以看到,分别是

GeometryInput,类型有

UENUM()
enum ESpeedTreeGeometryType
{
	STG_Branch		UMETA(DisplayName="Branch"),
	STG_Frond		UMETA(DisplayName="Frond"),
	STG_Leaf		UMETA(DisplayName="Leaf"),
	STG_FacingLeaf	UMETA(DisplayName="Facing Leaf"),
	STG_Billboard	UMETA(DisplayName="Billboard")
};

WindInput,类型有

UENUM()
enum ESpeedTreeWindType
{
	STW_None		UMETA(DisplayName="None"),
	STW_Fastest		UMETA(DisplayName="Fastest"),
	STW_Fast		UMETA(DisplayName="Fast"),
	STW_Better		UMETA(DisplayName="Better"),
	STW_Best		UMETA(DisplayName="Best"),
	STW_Palm		UMETA(DisplayName="Palm"),
	STW_BestPlus	UMETA(DisplayName="BestPlus"),
};

LODInput,类型有

UENUM()
enum ESpeedTreeLODType
{
	STLOD_Pop		UMETA(DisplayName="Pop"),
	STLOD_Smooth	UMETA(DisplayName="Smooth")
};

以及额外的弯曲偏移 ExtraBendWS。

整个Shader过程也就是在处理这些type的不同的偏移计算方式。

好的,就从节点的Compiler开始吧。

1.UMaterialExpressionSpeedTree::Compile

在MaterialExpressions.cpp里

很简单,就是Compile 4中Input的输入,没有Input就用默认值,之后,调用到实际的Compiler->SpeedTree

2.Compiler->SpeedTree

在HLSLMaterialTranslator.h里,即材质的模板翻译,会把节点信息翻译成HLSL,基本逻辑如下:

  • 检查FeatureLevel是否支持
  • 检查不允许应用到SkeletalMesh上
  • 检查不是VS以外的Shader阶段
  • 好,之后会把 bUsesSpeedTree 设为true,用于之后宏定义 USES_SPEEDTREE 设为 1,设置纹理采样坐标NumUserVertexTexCoords
  • 设置 bEnablePreviousFrameInformation ,这个会用于shader设置 bUsePreviousFrame 参数,判断SpeedTreeData的来源
  • 最终把 GetSpeedTreeVertexOffset(Parameters, %s, %s, %s, %g, %s, %s, %s)加入Shader,参数就是上面介绍的那些

3.GetSpeedTreeVertexOffset

这个就到shader里了,主要两个文件 MaterialTemplate.ush ,这就是一般材质最终翻译的模板文件,SpeedTreeCommon.ush,定义了很多SpeedTree有关的函数

这里主要调用 GetSpeedTreeVertexOffsetInner 方法(最重要的方法),唯一逻辑是根据上一步的传参 bUsePreviousFrame ,填充不同的FSpeedTreeData。

如果为true,调用 GetPreviousSpeedTreeData,在里,使用SpeedTreeData 的 Prev前缀的数据

否则,调用 GetCurrentSpeedTreeData,使用SpeedTreeData 的 默认的数据填充。

4.Scene中的SpeedTree应用 (Scene->UpdateSpeedTreeWind)

在介绍最后的算法之前,先介绍一下场景中是怎么应用和填充SpeedTree的,就是上面的TreeData到底是怎么来的,这样后面Shader里用到这些变量才能知道这些东西都是从哪里计算得来的。

在Scene中,跟SpeedTree有关的接口主要是

virtual void AddSpeedTreeWind(class FVertexFactory* VertexFactory, const class UStaticMesh* StaticMesh) override {}
virtual void RemoveSpeedTreeWind_RenderThread(class FVertexFactory* VertexFactory, const class UStaticMesh* StaticMesh) override {}
virtual void UpdateSpeedTreeWind(double CurrentTime) override {}
virtual FUniformBufferRHIParamRef GetSpeedTreeUniformBuffer(const FVertexFactory* VertexFactory) const override { return FUniformBufferRHIParamRef(); }

首先有类似之前WindField的 Add/RemoveSpeedTreeWind 的逻辑,每有一种SpeedTreeWind的StaticMesh,Scene会Add一个SpeedTreeWindComputation,然后保存在SpeedTreeWindComputationMap里, 然后就是场景主要的模拟更新的逻辑了,主要就一个方法UpdateSpeedTreeWind。

它的执行在 void UWorld::Tick里,每帧更新

	if( Scene )
	{
		// Update SpeedTree wind objects.
		Scene->UpdateSpeedTreeWind(TimeSeconds);
	}

里面的调用主要就是遍历循环 SpeedTreeWindComputationMap ,更新每一个Wind的数据了,主要逻辑如下,这里都在渲染线程执行

  1. 调用Scene->GetDirectionalWindParameters(WindDirection, WindSpeed, WindMinGustAmt, WindMaxGustAmt);获取当前Scene的全局WindDirection, WindSpeed, WindMinGustAmt, WindMaxGustAmt. 这个方法内部就是之前说到的,遍历场景所有的 WindDirectionalSources ,然后通过计算权重,得到一个全局的结果,哈哈,写到这,有种之后会慢慢的全局都串起来的感觉
  2. 得到全局数据之后,就是依次遍历所有SpeedTreeWindComputation
  3. 检查Computation是否合法
  4. 开始更新数据,依次调用
    WindComputation->Wind.SetDirection(WindDirection);
    WindComputation->Wind.SetStrength(WindSpeed);
    WindComputation->Wind.SetGustMin(WindMinGustAmt);
    WindComputation->Wind.SetGustMax(WindMaxGustAmt);
    WindComputation->Wind.Advance(true, CurrentTime);

    首先设置四个全局数据,最后调用Advance更新,这个方法代码挺长不再细说,主要目标就是根据更新的各种Target值,做插值Interpolate,这里会更新到各种SpeedTree不同类型的数据。

  5. 计算好了之后,就是各种Set了,以及更新UniformBuffer,这里Set的就是之前说到的传入TreeData的数据,这里Set 和声明的地方的一一对应,这样可以在之后Shader应用的地方找到。

    SET_SPEEDTREE_TABLE_FLOAT4V(WindVector, SH_WIND_DIR_X);
    SET_SPEEDTREE_TABLE_FLOAT4V(WindGlobal, SH_GLOBAL_TIME);
    SET_SPEEDTREE_TABLE_FLOAT4V(WindBranch, SH_BRANCH_1_TIME);
    SET_SPEEDTREE_TABLE_FLOAT4V(WindBranchTwitch, SH_BRANCH_1_TWITCH);
    SET_SPEEDTREE_TABLE_FLOAT4V(WindBranchWhip, SH_BRANCH_1_WHIP);
    SET_SPEEDTREE_TABLE_FLOAT4V(WindBranchAnchor, SH_WIND_ANCHOR_X);
    SET_SPEEDTREE_TABLE_FLOAT4V(WindBranchAdherences, SH_GLOBAL_DIRECTION_ADHERENCE);
    SET_SPEEDTREE_TABLE_FLOAT4V(WindTurbulences, SH_BRANCH_1_TURBULENCE);
    SET_SPEEDTREE_TABLE_FLOAT4V(WindLeaf1Ripple, SH_LEAF_1_RIPPLE_TIME);
    SET_SPEEDTREE_TABLE_FLOAT4V(WindLeaf1Tumble, SH_LEAF_1_TUMBLE_TIME);
    SET_SPEEDTREE_TABLE_FLOAT4V(WindLeaf1Twitch, SH_LEAF_1_TWITCH_THROW);
    SET_SPEEDTREE_TABLE_FLOAT4V(WindLeaf2Ripple, SH_LEAF_2_RIPPLE_TIME);
    SET_SPEEDTREE_TABLE_FLOAT4V(WindLeaf2Tumble, SH_LEAF_2_TUMBLE_TIME);
    SET_SPEEDTREE_TABLE_FLOAT4V(WindLeaf2Twitch, SH_LEAF_2_TWITCH_THROW);
    SET_SPEEDTREE_TABLE_FLOAT4V(WindFrondRipple, SH_FROND_RIPPLE_TIME);
    SET_SPEEDTREE_TABLE_FLOAT4V(WindRollingBranch, SH_ROLLING_BRANCH_FIELD_MIN);
    SET_SPEEDTREE_TABLE_FLOAT4V(WindRollingLeafAndDirection, SH_ROLLING_LEAF_RIPPLE_MIN);
    SET_SPEEDTREE_TABLE_FLOAT4V(WindRollingNoise, SH_ROLLING_NOISE_PERIOD);

    声明的地方呢,在SpeedTreeWind.h 里,

    enum EShaderValues
    {
    		// g_vWindVector
    		SH_WIND_DIR_X, SH_WIND_DIR_Y, SH_WIND_DIR_Z, SH_GENERAL_STRENGTH,
    
    		// g_vWindGlobal
    		SH_GLOBAL_TIME, SH_GLOBAL_DISTANCE, SH_GLOBAL_HEIGHT, SH_GLOBAL_HEIGHT_EXPONENT,
    
    		// g_vWindBranch
    		SH_BRANCH_1_TIME, SH_BRANCH_1_DISTANCE, SH_BRANCH_2_TIME, SH_BRANCH_2_DISTANCE,
    
    		// g_vWindBranchTwitch
    		SH_BRANCH_1_TWITCH, SH_BRANCH_1_TWITCH_FREQ_SCALE, SH_BRANCH_2_TWITCH, SH_BRANCH_2_TWITCH_FREQ_SCALE,
    
    		// g_vWindBranchWhip
    		SH_BRANCH_1_WHIP, SH_BRANCH_2_WHIP, SH_WIND_PACK0, SH_WIND_PACK1,
    
    		// g_vWindBranchAnchor
    		SH_WIND_ANCHOR_X, SH_WIND_ANCHOR_Y, SH_WIND_ANCHOR_Z, SH_WIND_PACK2,
    ...
    }

    变量对应。

5.GetSpeedTreeVertexOffsetInner 

到了最后最重要的算法部分了

完整形参是

float3 GetSpeedTreeVertexOffsetInner (FMaterialVertexParameters Parameters, int GeometryType, int WindType, int LODType, float BillboardThreshold, bool bExtraBend, float3 ExtraBend, FSpeedTreeData STData

ps: WIND_EFFECT_FROND_RIPPLE_ADJUST_LIGHTING 由于目前(UE4.22.3)都没有用到,暂不考虑

流程如下

(1)计算LocalToWorld矩阵以及LocalPosition,如果是instance的,从Pamaters中拿取,否则通过GetPrimitiveData计算

(2)如果非Instance,并且不是勾选USE_DITHERED_LOD_TRANSITION,那如果LODType == SPEEDTREE_LOD_TYPE_SMOOTH,会计算一下 LodInterp,用于之后计算 得到的 LODPos和FinalPosition 中间插值

(3)TreeScale ,默认只计算uniform的,使用LocalToWorld的第三列,并计算长度,如果有必要不用uniform的,官方这里也提供了代码,只不过注释掉了,可以手动修改

(4)mul(LocalPosition, LocalToWorld) / TreeScale 得到 OriginalPosition,并缓存为FinalPosition

(5)如果是GeometryType 是 BILLBOARD,且 BillboardThreshold <1.0 , ViewForward 视口Forward 和 .TangentToWorld[2]即 VertexNormal (由于是billboard,只取了xy) 做点乘,夹角 < (-1.0 + BillboardThreshold * 0.25)),就把FinalPosition置为0

(6)如果是 Branch ||  Frond

a.非Instance且为SPEEDTREE_LOD_TYPE_SMOOTH,做FinalPosition的lerp

b.如果是Frond,且 WindType 是SPEEDTREE_WIND_TYPE_PALM,从定好的uv下标读取数据后,调用RippleFrond

RippleFrond

float3 RippleFrond(FSpeedTreeData STData,
	float3 vPos, 
	inout float3 vDirection,
	float fU, 
	float fV, 
	float fPackedRippleDir, 
	float fRippleScale, 
	float fLenghtPercent)
  • 内部有两种实现 RippleFrondOneSided 和 RippleFrondTwoSided,默认调用 RippleFrondOneSided
  • TrigApproximate 传入 SH_FROND_RIPPLE_TIME和 SH_FROND_RIPPLE_TILE 得到 vOscillations,

  • 计算 vOffset =  fRippleScale * vOscillations.x * SH_FROND_RIPPLE_DISTANCE * vDirection

  • 返回 FinalPosition += vOffset

(7)如果是FacingLeaf ||(Leaf && (LOD_SMOOTH || WindType不是 FASTEST 以及 PALM))

a.从纹理中缓存Anchor位置

b.如果是FacingLeaf,FinalPosition = LocalPosition - Anchor,然后根据视口朝向ResolvedView 校正 相对偏移

c.用LocationToworld,把Anchor转到世界空间

d.如果是Leaf,此时再计算FinalPosition -= Anchor,之后用相对偏移做旋转

e.LOD Lerp

f.计算LeafWind,

g.加回Anchor到FinalPosition

LeafWind 

float3 LeafWind(FSpeedTreeData STData,
	bool bLeaf2,
	float3 vPos, 
	inout float3 vDirection, 
	float fScale, 
	float3 vAnchor, 
	float fPackedGrowthDir, 
	float fPackedRippleDir,
	float fRippleTrigOffset,
	int WindType)
  • 如果WindType是最高级的 SPEEDTREE_WIND_TYPE_BEST_PLUS,则计算LeafRipple
  • LeafRipple

    • fMoveAmount = fAmount* (fTime + fTrigOffset)(TrigApproximate)

    • 如果 bDirectional , vPos +=vDirection.xyz * fMoveAmount * fScale;

    • 否则,vPos += vRippleDir * fMoveAmount * fScale

  • 如果WindType 高于 BestType,调用LeafTumble

  • LeafTumble

    • 这里有一部分代码我也不是很明白具体的原因,如果有比较厉害的人恰巧看到这里,欢迎分享一下

    • float3 vFracs = frac((vAnchor + fTrigOffset) * 30.3);
      float fOffset = vFracs.x + vFracs.y + vFracs.z;
      float4 vOscillations = TrigApproximate(float4(fTime + fOffset, fTime * 0.75 - fOffset, fTime * 0.01 + fOffset, fTime * 1.0 + fOffset));

      计算得到vOscillations ??

    • fLength 记录 vPos 到 Anhor的长度

    • float fOsc = vOscillations.x + vOscillations.y * vOscillations.y; ??

    • 计算 Tumble矩阵 matTumble = 绕 vGrowthDir 旋转 fScale * fTwist * fOsc

    • 如果 WindType 是BestPlus,

      • cross(vGrowthDir ,WindVector)得到旋转轴 vAxis

      • dot(vGrowthDir ,WindVector)得到角度的cos值 fDot

      • fAngle = acos(fDot);

      • fOsc = vOscillations.y - vOscillations.x * vOscillations.x;??

      • 计算fTwitch = Twitch(vAnchor.xyz, vTwitch.x, vTwitch.y, vTwitch.z + fOffset); ??

      • 在上面 matTumble 的基础上,再乘以旋转矩阵 绕vAxis 旋转 fScale * (fAngle * fAdherence * fAdherenceScale + fOsc * fFlip + fTwitch) ??

    • matTumble 旋转 vDirection 和 vOriginPos,最终输出 normalize(vOriginPos)*fLength+vAnchor

(8)如果WindType不是Fastest以及Fast,会应用 BrandWind到各种Geometry类型

BranchWind

float3 BranchWind(FSpeedTreeData STData, float3 vPos, float3 vInstancePos, float4 vWindData, int WindType)
  • 如果WindType是Palm类型,调用DirectionalBranchWindFrondStyle
    • fWeight 和 foffset 记录在 TexCoords[2]里,UnpackNormalFromFloat(fOffset) * fWeight 得到 vWindVector
    • fTime 使用InstancePos 给time加一些随机扰动
    • Oscillate 计算 vOscillations 和 fOsc
    • vPos.xyz += vWindVector * fOsc * fDistance;
    • Turbulence 计算 fAdherenceScale
    • 如果是whip,fAdherenceScale += vOscillations.w * STData.WindVector.w * fWhip;
    • 得到最后 vPos.xyz += STData.WindVector.xyz * fAdherence * fAdherenceScale * fWeight;
  • 否则调用SimpleBranchWind,就是比上面少了最后三步

(9)应用 GlobalWind 以及 传入的 ExtraBendWS,调用GlobalWind

GlobalWind

float3 GlobalWind(FSpeedTreeData STData, float3 vPos, float3 vInstancePos, bool bPreserveShape, bool bGlobalWind, bool bExtraBend, float3 ExtraBend)
  • 由于之前调用的地方 bPreserveShape 为true,计算 fLength = length(vPos.xyz) 为实际length
  • 根据SH_GLOBAL_HEIGHT 和 up轴向 计算 fAdjust = vPos.z * SH_GLOBAL_HEIGHT -0.25
  • fAdjust 计算指数,pow(fAdjust, SH_GLOBAL_HEIGHT_EXPONENT)
  • 如果有 GlobalWind
    • TrigApproximate (pos.x * SH_GLOBAL_TIME + pos.y * SH_GLOBAL_TIME *0.8f)得到vOscillations
    • 同样 fOsc = vOscillations.x + (vOscillations.y * vOscillations.y)
    • fMoveAmount = fOsc *SH_GLOBAL_DISTANCE
    • 基于 direction adherence 计算move的最小值 fMinMove
    • fMinMove = SH_GLOBAL_DIRECTION_ADHERENCE/SH_GLOBAL_HEIGHT
    • fMoveAmount += fMinMove;
    • fMoveAmount *= fAdjust;
    • 叠加Global的direction 到非up轴向,vPos.xy += (SH_WIND_DIR_X,SH_WIND_DIR_Y) * fMoveAmount;,这里Global Wind 不会叠加 up轴向的偏移
  • 如果有 ExtraBend
    • 叠加ExtraBend到非up轴向,vPos.xy += ExtraBend.xy * fAdjust;
  • 如果PreserveShape
    • vPos.xyz = normalize(vPos.xyz) * fLength;,归一朝向*长度
  • 返回 vPos

(10) 最后,返回世界空间偏移PositionOffset = (FinalPosition - OriginalPosition) * TreeScale; 

好了,基本整体的流程就到这里啦,先这样~

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值