// Copyright Epic Games, Inc. All Rights Reserved. #include "/Engine/Private/Common.ush" #include "/Engine/Private/BoneTransform.ush" #include "/Engine/Shared/SkinningDefinitions.h" #include "/Engine/Private/ComputeShaderUtils.ush" #include "/Engine/Private/Nanite/NaniteDataDecode.ush" #include "Shared/DynamicWindCommon.ush" // Enable to debug always outputting identity bones #define FORCE_IDENTITY_OUTPUT 0 StructuredBuffer SkeletonBuffer; StructuredBuffer HeaderBuffer; ByteAddressBuffer BoneDataBuffer; RWByteAddressBuffer TransformBuffer; Texture2D WindTexture; SamplerState WindSampler; float Time; float DeltaTime; float WindSpeed; float WindAmplitude; float3 WindDirection; float4 Debug; // === WIND PARAMETERS =============================================================================================================================== // Wind speed and amplitude modulations #define DYNAMIC_WIND_SPEED_MOD 1.25f #define DYNAMIC_WIND_AMPLITUDE_MOD 3.0f // Flat value added to the final amplitude #define DYNAMIC_WIND_BEND_OFFSET 0.1f // Attenuation modifier based on the wind direction #define DYNAMIC_WIND_ALIGN_ATTENUATION 0.5f // SPECIFIC FOR BRANCHES // Attenuation modifier based on the bone height (0-1 normalized on skeleton height) #define DYNAMIC_WIND_HEIGHT_ATTENUATION 0.1f // Modulation of the angle of the torsion rotation of a branch #define DYNAMIC_WIND_TORSION_MOD 0.5f // Attenuation by branch length #define DYNAMIC_WIND_LONG_BRANCH_SPEED_MOD 0.5f #define DYNAMIC_WIND_LONG_BRANCH_MAX 500.0f #define DYNAMIC_WIND_LONG_BRANCH_MIN 100.0f // Gust speed #define DYNAMIC_WIND_GUST_SPEED_MIN 0.05f #define DYNAMIC_WIND_GUST_SPEED_MAX 0.1f uint GetBoneDataAddress(uint BoneDataOffset, uint BoneIndex) { return (BoneDataOffset + BoneIndex) * uint(sizeof(FDynamicWindBoneData)); } float RemapValue(float Value, float InLow, float InHigh, float OutLow, float OutHigh) { return OutLow + (Value - InLow) * (OutHigh - OutLow) / (InHigh - InLow); } FQuat QuatAttenuate(FQuat Q1, half Attenuation) { const FQuat Q0 = QuatIdentity(); const half C = dot(Q0, Q1); return normalize(lerp(Q0, C < 0.0f ? -Q1 : Q1, Attenuation)); } float WindFlow(const float RandomValue, const float Distance = 0.0f, const float BlendByGroup = 0.0f, const float ChainLength = 0.0f, float TrunkBlend = 0.0f) { // Reducing the speed on lower branches to simulate wood thickness and weight const float RemappedBranchLength = RemapValue(ChainLength, DYNAMIC_WIND_LONG_BRANCH_MAX, DYNAMIC_WIND_LONG_BRANCH_MIN, DYNAMIC_WIND_LONG_BRANCH_SPEED_MOD, 1.0f); const float SpeedByLengthModulation = max(TrunkBlend, clamp(RemappedBranchLength, DYNAMIC_WIND_LONG_BRANCH_SPEED_MOD, 1.0f)); // 100.0f is a manual modulation to keep the speed inside human-readable range const float BiasedWindSpeed = WindSpeed / 100.0f; const float FlowSpeed = Time * BiasedWindSpeed * SpeedByLengthModulation * DYNAMIC_WIND_SPEED_MOD; const float2 UV = float2(RandomValue, frac(Distance * RandomValue)) - float2(frac(FlowSpeed), 0.0f); const float4 Sample = WindTexture.SampleLevel(WindSampler, UV, 0); // Fractal Brownian noise approximation float FinalIntensity = Sample.x + lerp(0.0f, Sample.w * 0.5f, BlendByGroup); FinalIntensity = (DYNAMIC_WIND_BEND_OFFSET + FinalIntensity) * DYNAMIC_WIND_AMPLITUDE_MOD * WindSpeed / 100.0f; return FinalIntensity; } float WindWave(float T) { return cos(PI * T) * cos(3.0f * PI * T) * cos(5.0f * PI * T) * cos(7.0f * PI * T); } float RampStrength(float X) { return X / (X * X + 1.0f); } [numthreads(BONES_PER_GROUP, 1, 1)] void WindEvalCS(uint3 GroupId : SV_GroupID, uint GroupThreadIndex : SV_GroupIndex) { const uint GlobalBlockIndex = GroupId.x; const uint BoneIndexInBlock = GroupThreadIndex; const FDynamicWindBlockHeader Header = HeaderBuffer[GlobalBlockIndex]; if (BoneIndexInBlock >= Header.BlockNumBones) { return; } if (Header.SkeletonDataIndex == ~uint(0) || FORCE_IDENTITY_OUTPUT) { const float4x3 Identity = { float3(1, 0, 0), float3(0, 1, 0), float3(0, 0, 1), float3(0, 0, 0) }; StoreCompressedBoneTransform(TransformBuffer, Header.BlockDstTransformOffset, BoneIndexInBlock, Identity); StoreCompressedBoneTransform(TransformBuffer, Header.BlockDstTransformOffset, BoneIndexInBlock + Header.TotalTransformCount, Identity); return; } FDynamicWindSkeletonData SkeletonData = SkeletonBuffer[Header.SkeletonDataIndex]; const uint SkeletonBoneIndex = Header.BlockBoneOffset + BoneIndexInBlock; const uint BoneDataAddress = GetBoneDataAddress(SkeletonData.BoneDataOffset, SkeletonBoneIndex); const FDynamicWindBoneData BoneData = BoneDataBuffer.Load(BoneDataAddress); #if USE_SIN_WIND const float3 WindRotationVector = cross(float3(0.0f, 0.0f, 1.0f), WindDirection); FBoneTransform BoneTransform = MakeBoneTransformIdentity(); BoneTransform.Rotation = QuatNormalize(QuatFromAxisAngle(WindRotationVector, sin(Time * 0.25f) * 0.005f * WindSpeed)); #else FBoneTransform BoneTransform = MakeBoneTransformIdentity(); float3 PositionAccumulation = float3(0.0f, 0.0f, 0.0f); float3 LastBoneBindPosition = BoneData.BindPosePosition; float SliceAngle = 2.0f * PI / float(DYNAMIC_WIND_DIRECTIONALITY_SLICES); for (uint CurrentBoneDataAddress = BoneDataAddress, ParentBoneIndex = SkeletonBoneIndex; ParentBoneIndex != DYNAMIC_WIND_INVALID_BONE_INDEX;) { const FDynamicWindBoneData CurrentBoneData = BoneDataBuffer.Load(CurrentBoneDataAddress); // === ROTATION AXIS CALCULATIONS ======================================================================================================== const float3 SectionWindDirection = QuatRotateVector(QuatConjugate(QuatFromAxisAngle(float3(0.0f, 0.0f, 1.0f), Header.BlockDirectionalityIndex * SliceAngle)), WindDirection); const float3 AdjustedBoneForward = QuatRotateVector(CurrentBoneData.BindPoseRotation, float3(1.0f, 0.0f, 0.0f)); // Prevent error when the bone forward and wind direction are collinear const float3 WindHorRotVector = abs(dot(AdjustedBoneForward, SectionWindDirection)) > 0.999f ? normalize(cross(AdjustedBoneForward, float3(0.0f, 0.0f, 1.0f))) : normalize(cross(AdjustedBoneForward, SectionWindDirection)); const float3 WindVerRotVector = normalize(cross(AdjustedBoneForward, float3(0.0f, 0.0f, 1.0f))); const float WindDirectionAlignment = 1.0f - saturate(dot(AdjustedBoneForward, SectionWindDirection)) * DYNAMIC_WIND_ALIGN_ATTENUATION; const float WindDirectionOrthogonality = 1.0f - abs(dot(AdjustedBoneForward, SectionWindDirection)); const float HeightGrad = saturate(CurrentBoneData.BindPosePosition.z / SkeletonData.SkeletonHeight); // RandBBSfloat(0) returns 0, which result in an uniform rotation for all branches // SkeletonDataIndex is 0 for the first registered skeleton, hence the +1 const float RandomValue = RandBBSfloat(((Header.SkeletonDataIndex << 8) | Header.BlockDirectionalityIndex) + 1); float GustSpeed = lerp(DYNAMIC_WIND_GUST_SPEED_MIN, DYNAMIC_WIND_GUST_SPEED_MAX, WindSpeed / 100.0f); float GustStrength = RampStrength(WindSpeed / 100.0f); FQuat PartialRotation; if (CurrentBoneData.bIsTrunkBone) { float WindX = WindWave(GustSpeed * Time + RandomValue * 17) + 0.75f; WindX *= lerp(0.3f, 0.6f, HeightGrad); WindX *= 0.06f * GustStrength; WindX += 0.02f * Pow4(WindSpeed / 100.0f); float WindY = WindWave(GustSpeed * Time + RandomValue * 17 + 23); WindY *= lerp(0.5f, 0.4f, HeightGrad); WindY *= 0.03f * GustStrength; WindY *= 0.25f; float3 BendX = cross(float3(0.0f, 0.0f, 1.0f), SectionWindDirection); float3 BendY = cross(float3(0.0f, 0.0f, 1.0f), BendX); FQuat RotateX = QuatFromAxisAngle(BendX, WindX); FQuat RotateY = QuatFromAxisAngle(BendY, WindY); PartialRotation = QuatMultiply(RotateX, RotateY); PartialRotation = QuatAttenuate(PartialRotation, (1.0f - SkeletonData.GustAttenuation) * WindAmplitude); } else // Branches { const float DistanceFromOrigin = distance(CurrentBoneData.BindPosePosition, float3(0.0f, 0.0f, 0.0f)); const float BlendByGroup = max(CurrentBoneData.SimulationGroupIndex, 0) / (SkeletonData.MaxSimulationGroupIndex + 1); const float TrunkBlend = (float)CurrentBoneData.bIsTrunkBone; float Gust = GustStrength * max(0, WindWave(GustSpeed * (Time + 0.5f) + RandomValue * 17)); float WindFlowIntensity = WindFlow(RandomValue, DistanceFromOrigin, BlendByGroup, CurrentBoneData.BoneChainLength, TrunkBlend); WindFlowIntensity *= lerp(0.2f, 0.4f, Pow2(HeightGrad)); WindFlowIntensity *= Gust * (4 + 8 * cos(0.4f * Time + RandomValue * 17 + frac(DistanceFromOrigin * RandomValue) * 1777)) + 1.0f; // Soft clip WindFlowIntensity /= PI; WindFlowIntensity /= WindFlowIntensity + 1; WindFlowIntensity *= PI; PartialRotation = QuatFromAxisAngle(WindHorRotVector, WindFlowIntensity * CurrentBoneData.Influence * WindDirectionAlignment); // Add torsion to branches positioned orthogonally to wind direction const FQuat TorsionRotation = QuatFromAxisAngle(AdjustedBoneForward, DYNAMIC_WIND_TORSION_MOD * WindFlowIntensity * CurrentBoneData.Influence * WindDirectionOrthogonality); PartialRotation = QuatMultiply(PartialRotation, TorsionRotation); float WindBounce = WindWave(0.2f * Time + RandomValue * 13 + frac(DistanceFromOrigin * RandomValue) * 1777); WindBounce *= 0.25f * RampStrength(WindSpeed / 100.0f); WindBounce *= Gust * 8 + 1; FQuat VerticalRotation = QuatFromAxisAngle(WindVerRotVector, WindBounce * CurrentBoneData.Influence); PartialRotation = QuatMultiply(VerticalRotation, PartialRotation); // Attenuate branch final rotation on lower branches // It's here and not on the branch section because I want to influence all the rotation on the branch const float RawHeightBlend = CurrentBoneData.BindPosePosition.z / SkeletonData.SkeletonHeight; const float FinalHeightBlend = max(SkeletonData.bIsGroundCover, RemapValue(RawHeightBlend, 0.0f, 1.0f, DYNAMIC_WIND_HEIGHT_ATTENUATION, 1.0f)); PartialRotation = QuatAttenuate(PartialRotation, FinalHeightBlend * WindAmplitude); } const FQuat CurrentRotation = QuatMultiply(BoneTransform.Rotation, PartialRotation); PositionAccumulation = QuatRotateVector(PartialRotation, PositionAccumulation + LastBoneBindPosition - CurrentBoneData.BindPosePosition); BoneTransform.Rotation = CurrentRotation; LastBoneBindPosition = CurrentBoneData.BindPosePosition; ParentBoneIndex = CurrentBoneData.ParentBoneIndex; CurrentBoneDataAddress = GetBoneDataAddress(SkeletonData.BoneDataOffset, CurrentBoneData.ParentBoneIndex); } BoneTransform.Location = PositionAccumulation - QuatRotateVector(BoneTransform.Rotation, BoneData.BindPosePosition); #endif const float4x3 CurrentTransform = (float4x3)MatrixFromBoneTransform(BoneTransform); const float4x3 PreviousTransform = LoadCompressedBoneTransform(TransformBuffer, Header.BlockDstTransformOffset, BoneIndexInBlock); // TODO: Handle first frame by writing CurrentTransform for both StoreCompressedBoneTransform(TransformBuffer, Header.BlockDstTransformOffset, BoneIndexInBlock, CurrentTransform); StoreCompressedBoneTransform(TransformBuffer, Header.BlockDstTransformOffset, BoneIndexInBlock + Header.TotalTransformCount, PreviousTransform); }