Files
UnrealEngineUWP/Engine/Shaders/ParticleSpriteVertexFactory.usf
Guillaume Abadie cbeddee7e1 Changes some variable names in the view uniform buffer.
ViewWorldOrigin -> WorldViewOrigin
CameraWorldOrigin -> WorldCameraOrigin
CameraTranslatedWorldOrigin -> TranslatedWorldCameraOrigin
CameraWorldOriginDelta -> WorldCameraMovementSinceLastFrame
PrevCameraWorldOrigin -> PrevWorldCameraOrigin
PrevViewWorldOrigin -> PrevWorldViewOrigin

#code_review: Martin.Mittring

[CL 2666624 by Guillaume Abadie in Main branch]
2015-08-24 16:27:34 -04:00

562 lines
19 KiB
Plaintext

// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.
/*=============================================================================
ParticleSpriteVertexFactory.hlsl: Particle vertex factory shader code.
Shared by standard sprite particles and SubUV sprite particles.
=============================================================================*/
#include "VertexFactoryCommon.usf"
#include "ParticleVertexFactoryCommon.usf"
// These are only enabled for SM5 due to limited interpolators elsewhere
#define USE_PARTICLE_LIGHTING_OFFSET (FEATURE_LEVEL >= FEATURE_LEVEL_SM5 && !MATERIAL_SHADINGMODEL_UNLIT)
#define USE_PARTICLE_POSITION (FEATURE_LEVEL >= FEATURE_LEVEL_SM5 && NEEDS_PARTICLE_POSITION)
#define USE_PARTICLE_VELOCITY (FEATURE_LEVEL >= FEATURE_LEVEL_SM5 && NEEDS_PARTICLE_VELOCITY)
#define USE_PARTICLE_TIME (FEATURE_LEVEL >= FEATURE_LEVEL_SM5 && NEEDS_PARTICLE_TIME)
#define USE_PARTICLE_SIZE (FEATURE_LEVEL >= FEATURE_LEVEL_SM5 && NEEDS_PARTICLE_SIZE)
// We don't compute a tangent basis in ES2 to save shader instructions.
#define NEEDS_TANGENT_BASIS (FEATURE_LEVEL >= FEATURE_LEVEL_SM4)
struct FVertexFactoryInput
{
float4 Position : ATTRIBUTE0;
float4 OldPosition : ATTRIBUTE1;
float4 SizeRotSubImage : ATTRIBUTE2;
float4 Color : ATTRIBUTE3;
#if USE_DYNAMIC_PARAMETERS
float4 DynamicParameter : ATTRIBUTE5;
#endif //USE_DYNAMIC_PARAMETERS
float2 TexCoord : ATTRIBUTE4;
};
struct FVertexFactoryInterpolantsVSToPS
{
#if NEEDS_TANGENT_BASIS
// First row of the tangent to world matrix, Interp_Sizer used by SUBUV_PARTICLES in w
float4 TangentToWorld0AndInterp_Sizer : TANGENTTOWORLD0;
// Last row of the tangent to world matrix in xyz
float4 TangentToWorld2 : TANGENTTOWORLD2;
#else
float SubImageLerp : TEXCOORD0;
#endif
#if USE_DYNAMIC_PARAMETERS
float4 DynamicParameter : TEXCOORD1;
#endif //USE_DYNAMIC_PARAMETERS
#if NEEDS_PARTICLE_COLOR
float4 Color : TEXCOORD2;
#endif
#if NUM_MATERIAL_TEXCOORDS
float4 TexCoords[(NUM_MATERIAL_TEXCOORDS + 1) / 2] : TEXCOORD3;
#endif
//Not sure this is actually being used now and it's awkward to slot in now we're supporting custom UVs so I'm just giving this its own interpolant.
#if LIGHTMAP_UV_ACCESS
float2 LightMapUVs : LIGHTMAP_UVS;
#endif
#if USE_PARTICLE_SUBUVS
float4 ParticleSubUVs : PARTICLE_SUBUVS;
#endif
#if USE_PARTICLE_POSITION
float4 ParticlePositionAndSize : PARTICLE_POSITION;
#endif
#if USE_PARTICLE_VELOCITY
float4 ParticleVelocity : PARTICLE_VELOCITY;
#endif
#if USE_PARTICLE_TIME
float ParticleTime : PARTICLE_TIME;
#endif
#if USE_PARTICLE_LIGHTING_OFFSET
float3 LightingPositionOffset : PARTICLE_LIGHTING_OFFSET;
#endif
#if USE_PARTICLE_SIZE
float2 ParticleSize : PARTICLE_SIZE;
#endif
};
struct FVertexFactoryIntermediates
{
/** The position of the particle in non-translated world space. */
float3 ParticleWorldPosition;
/** The position of the vertex in translated world space. */
float3 VertexWorldPosition;
/** Particle (non-translated) world space position and size. */
float4 PositionAndSize;
#if USE_PARTICLE_LIGHTING_OFFSET
float3 LightingPositionOffset;
#endif
/** The texture coordinate at this vertex. */
float4 TexCoord;
/** The sprite tangent in world space (+V). */
float3 TangentUp;
/** The sprite tangent in world space (+U). */
float3 TangentRight;
/** The color of the sprite. */
float4 Color;
/** The velocity of the particle, XYZ: direction, W: speed. */
float4 ParticleVelocity;
/** Dynamic parameter. */
float4 DynamicParameter;
/** The sub-image lerp. */
float SubImageLerp;
/** Relative time. */
float RelativeTime;
/** Transform from tangent space to local space. */
float3x3 TangentToLocal;
/** Size of the particle */
float2 ParticleSize;
};
#if NUM_MATERIAL_TEXCOORDS
bool UVIndexUseZW(int UVIndex)
{
#if COMPILER_GLSL_ES2
return (frac((float)UVIndex / 2.0f) == 0.5f);
#else
return (UVIndex % 2) != 0;
#endif
}
float2 GetUV(FVertexFactoryInterpolantsVSToPS Interpolants, int UVIndex)
{
float4 UVVector = Interpolants.TexCoords[UVIndex / 2];
return UVIndexUseZW(UVIndex) ? UVVector.zw : UVVector.xy;
}
void SetUV(inout FVertexFactoryInterpolantsVSToPS Interpolants, int UVIndex, float2 InValue)
{
FLATTEN
if (UVIndexUseZW(UVIndex))
{
Interpolants.TexCoords[UVIndex / 2].zw = InValue;
}
else
{
Interpolants.TexCoords[UVIndex / 2].xy = InValue;
}
}
#endif
/** Converts from vertex factory specific interpolants to a FMaterialPixelParameters, which is used by material inputs. */
FMaterialPixelParameters GetMaterialPixelParameters(FVertexFactoryInterpolantsVSToPS Interpolants, float4 PixelPosition)
{
// GetMaterialPixelParameters is responsible for fully initializing the result
FMaterialPixelParameters Result = MakeInitializedMaterialPixelParameters();
#if NUM_MATERIAL_TEXCOORDS
UNROLL
for (int CoordinateIndex = 0; CoordinateIndex < NUM_MATERIAL_TEXCOORDS; CoordinateIndex++)
{
Result.TexCoords[CoordinateIndex] = GetUV(Interpolants, CoordinateIndex);
}
#endif // NUM_MATERIAL_TEXCOORDS
Result.VertexColor = 1;
#if NEEDS_TANGENT_BASIS
half4 TangentToWorld0 = Interpolants.TangentToWorld0AndInterp_Sizer;
half4 TangentToWorld2 = Interpolants.TangentToWorld2;
float SubImageLerp = Interpolants.TangentToWorld0AndInterp_Sizer.w;
#if GENERATE_SPHERICAL_PARTICLE_NORMALS && USE_PARTICLE_POSITION
Result.TangentToWorld = GetSphericalParticleNormal(PixelPosition.xyz + View.WorldCameraOrigin, Interpolants.ParticlePositionAndSize.xyz, Interpolants.ParticlePositionAndSize.w);
#else
Result.TangentToWorld = AssembleTangentToWorld(TangentToWorld0.xyz, TangentToWorld2);
#endif
#else
float SubImageLerp = Interpolants.SubImageLerp;
#endif
Result.UnMirrored = 1;
Result.Particle.MacroUV = SpriteVF.MacroUVParameters;
#if NEEDS_PARTICLE_COLOR
Result.Particle.Color = Interpolants.Color;
#endif
#if USE_DYNAMIC_PARAMETERS
Result.Particle.DynamicParameter = Interpolants.DynamicParameter;
#endif //USE_DYNAMIC_PARAMETERS
#if USE_PARTICLE_POSITION
Result.Particle.PositionAndSize = Interpolants.ParticlePositionAndSize;
#endif
#if USE_PARTICLE_VELOCITY
Result.Particle.Velocity = Interpolants.ParticleVelocity;
#endif
#if USE_PARTICLE_TIME
Result.Particle.RelativeTime = Interpolants.ParticleTime;
#endif
Result.Particle.MotionBlurFade = 1.0f;
#if USE_PARTICLE_LIGHTING_OFFSET
Result.LightingPositionOffset = Interpolants.LightingPositionOffset;
#endif
#if USE_PARTICLE_SUBUVS
Result.Particle.SubUVCoords[0] = Interpolants.ParticleSubUVs.xy;
Result.Particle.SubUVCoords[1] = Interpolants.ParticleSubUVs.zw;
Result.Particle.SubUVLerp = SubImageLerp;
#endif
#if LIGHTMAP_UV_ACCESS
Result.LightmapUVs = Interpolants.LightMapUVs;
#endif
#if USE_PARTICLE_SIZE
Result.Particle.Size = Interpolants.ParticleSize;
#endif
Result.TwoSidedSign = 1;
return Result;
}
/** Converts from vertex factory specific input to a FMaterialVertexParameters, which is used by vertex shader material inputs. */
FMaterialVertexParameters GetMaterialVertexParameters(FVertexFactoryInput Input, FVertexFactoryIntermediates Intermediates, float3 WorldPosition, float3x3 TangentToLocal)
{
FMaterialVertexParameters Result = (FMaterialVertexParameters)0;
Result.WorldPosition = WorldPosition;
Result.VertexColor = Input.Color;
Result.TangentToWorld = mul(TangentToLocal, GetLocalToWorld3x3());
Result.Particle.MacroUV = SpriteVF.MacroUVParameters;
Result.Particle.Color = Intermediates.Color;
Result.Particle.MotionBlurFade = 1.0f;
#if USE_DYNAMIC_PARAMETERS
Result.Particle.DynamicParameter = Intermediates.DynamicParameter;
#endif //USE_DYNAMIC_PARAMETERS
#if USE_PARTICLE_POSITION
Result.Particle.PositionAndSize = Intermediates.PositionAndSize;
#endif
#if USE_PARTICLE_VELOCITY
Result.Particle.Velocity = Intermediates.ParticleVelocity;
#endif
#if USE_PARTICLE_TIME
Result.Particle.RelativeTime = Intermediates.RelativeTime;
#endif
#if NUM_MATERIAL_TEXCOORDS_VERTEX
#if NUM_MATERIAL_TEXCOORDS_VERTEX >= 1
Result.TexCoords[0] = Intermediates.TexCoord.xy;
#if NUM_MATERIAL_TEXCOORDS_VERTEX >= 2
Result.TexCoords[1] = Intermediates.TexCoord.zw;
#endif // >= 2
#endif // >= 1
#endif //NUM_MATERIAL_TEXCOORDS_VERTEX
#if USE_PARTICLE_SUBUVS
Result.Particle.SubUVCoords[0] = Intermediates.TexCoord.xy;
Result.Particle.SubUVCoords[1] = Intermediates.TexCoord.zw;
#endif
#if USE_PARTICLE_SIZE
Result.Particle.Size = Intermediates.ParticleSize;
#endif
return Result;
}
void GetLightMapCoordinates(FVertexFactoryInterpolantsVSToPS Interpolants, out float2 LightmapUV0, out float2 LightmapUV1)
{
LightmapUV0 = LightmapUV1 = 0;
}
half2 GetShadowMapCoordinate(FVertexFactoryInterpolantsVSToPS Interpolants)
{
return 0;
}
float3 SafeNormalize(float3 V)
{
return V * rsqrt(max(dot(V,V),0.01));
}
void GetTangents(FVertexFactoryInput Input, float3 WorldPosition, float3 OldWorldPosition, out float3 OutRight, out float3 OutUp)
{
// Select camera up/right vectors.
float3 CameraRight = lerp(View.ViewRight, SpriteVF.AxisLockRight.xyz, SpriteVF.AxisLockRight.w);
float3 CameraUp = lerp(-View.ViewUp, SpriteVF.AxisLockUp.xyz, SpriteVF.AxisLockUp.w);
// Determine the vector from the particle to the camera and the particle's movement direction.
float3 CameraDirection = SafeNormalize(View.WorldCameraOrigin - WorldPosition.xyz);
float3 ParticleDirection = SafeNormalize(WorldPosition.xyz - OldWorldPosition.xyz);
// Tangent vectors for PSA_Velocity.
float3 Right_Velocity = SafeNormalize(cross(CameraDirection, ParticleDirection));
float3 Up_Velocity = -ParticleDirection;
// Tangent vectors for rotation locked about an axis.
float3 Right_AxisRot = SpriteVF.AxisLockRight.xyz;
float3 Up_AxisRot = -SafeNormalize(cross(Right_AxisRot,CameraDirection));
// Tangent vectors for camera facing position.
float3 Right_FacingCameraPosition = SafeNormalize(cross(CameraDirection,float3(0,0,1)));
float3 Up_FacingCameraPosition = cross(CameraDirection, Right_FacingCameraPosition);
// Select the appropriate tangents.
// The TangentSelector values are as follows:
// x - multiplied by the standard camera right/up values
// y - multiplied by the velocity right/up values
// z - multiplied by the axis rotation right/up values
// The multiplies are summed together to give the final right/up
float3 Right_Square = (CameraRight.xyz * SpriteVF.TangentSelector.x) +
(Right_Velocity * SpriteVF.TangentSelector.y) +
(Right_AxisRot * SpriteVF.TangentSelector.z) +
(Right_FacingCameraPosition * SpriteVF.TangentSelector.w);
float3 Up_Square = (CameraUp.xyz * SpriteVF.TangentSelector.x) +
(Up_Velocity * SpriteVF.TangentSelector.y) +
(Up_AxisRot * SpriteVF.TangentSelector.z) +
(Up_FacingCameraPosition * SpriteVF.TangentSelector.w);
// Determine the angle of rotation.
float SinRotation;
float CosRotation;
const float RotationScale = SpriteVF.RotationScale;
const float RotationBias = SpriteVF.RotationBias;
const float SpriteRotation = Input.SizeRotSubImage.z * RotationScale + RotationBias;
sincos(SpriteRotation, SinRotation, CosRotation);
// Rotate the sprite to determine final tangents.
OutRight = SinRotation * Up_Square + CosRotation * Right_Square;
OutUp = CosRotation * Up_Square - SinRotation * Right_Square;
}
/** derive basis vectors */
float3x3 CalcTangentBasis(FVertexFactoryIntermediates Intermediates)
{
#if NEEDS_TANGENT_BASIS
// Using camera facing TangentX and TangentY. The resulting tangent basis is not orthonormal with anything other than ENM_CameraFacing,
// So there are artifacts with tangent space calculations like the TransformVector node,
// But this allows lighting based on a world space shape via the normal while still having normal maps camera aligned.
float3x3 Result;
Result[0] = Intermediates.TangentRight;
Result[1] = Intermediates.TangentUp;
// ENM_CameraFacing
//@todo - use static branching
if (SpriteVF.NormalsType < .5f)
{
Result[2] = normalize(cross(Result[0],Result[1]));
}
// ENM_Spherical
else if (SpriteVF.NormalsType < 1.5f)
{
float3 TangentZ = normalize(Intermediates.ParticleWorldPosition.xyz - SpriteVF.NormalsSphereCenter.xyz);
Result[2] = TangentZ;
}
// ENM_Cylindrical
else
{
float3 ClosestPointOnCylinder = SpriteVF.NormalsSphereCenter.xyz + dot(SpriteVF.NormalsCylinderUnitDirection.xyz, Intermediates.ParticleWorldPosition.xyz - SpriteVF.NormalsSphereCenter.xyz) * SpriteVF.NormalsCylinderUnitDirection.xyz;
float3 TangentZ = normalize(Intermediates.ParticleWorldPosition.xyz - ClosestPointOnCylinder);
Result[2] = TangentZ;
}
return Result;
#else
// Return the identity matrix.
return float3x3(1,0,0,0,1,0,0,0,1);
#endif
}
FVertexFactoryIntermediates GetVertexFactoryIntermediates(FVertexFactoryInput Input)
{
FVertexFactoryIntermediates Intermediates = (FVertexFactoryIntermediates)0;
// World position.
float4 ParticleWorldPosition = mul(float4(Input.Position.xyz,1), Primitive.LocalToWorld);
float4 ParticleOldWorldPosition = mul(float4(Input.OldPosition.xyz,1), Primitive.LocalToWorld);
Intermediates.ParticleWorldPosition = ParticleWorldPosition.xyz;
// Tangents.
float3 Right,Up;
GetTangents(Input,ParticleWorldPosition.xyz,ParticleOldWorldPosition.xyz,Right,Up);
Intermediates.TangentUp = Up;
Intermediates.TangentRight = Right;
// Vertex position.
float4 VertexWorldPosition = float4(ParticleWorldPosition.xyz + View.PreViewTranslation,1);
float2 Size = abs(Input.SizeRotSubImage.xy);
VertexWorldPosition += Size.x * (Input.TexCoord.x + SpriteVF.PivotOffset.x) * float4(Right,0);
VertexWorldPosition += Size.y * (Input.TexCoord.y + SpriteVF.PivotOffset.y) * float4(Up,0);
Intermediates.VertexWorldPosition = VertexWorldPosition.xyz;
//We encode the UV flip in the sign of the size data.
float2 UVFlip = sign(Input.SizeRotSubImage.xy);
float2 FlippedUV = Input.TexCoord.xy;
FlippedUV.x = UVFlip.x < 0.0 ? 1.0 - FlippedUV.x : FlippedUV.x;
FlippedUV.y = UVFlip.y < 0.0 ? 1.0 - FlippedUV.y : FlippedUV.y;
// SubUV.
float SubImageIndex = Input.SizeRotSubImage.w;
float SubImageLerp = frac(SubImageIndex);
float SubImageA = SubImageIndex - SubImageLerp;
float SubImageB = SubImageA + 1;
float SubImageAH = fmod( SubImageA, SpriteVF.SubImageSize.x );
float SubImageBH = fmod( SubImageB, SpriteVF.SubImageSize.x );
float SubImageAV = floor( SubImageA * SpriteVF.SubImageSize.z );
float SubImageBV = floor( SubImageB * SpriteVF.SubImageSize.z );
Intermediates.TexCoord.xy = (float2( SubImageAH, SubImageAV ) + FlippedUV) * SpriteVF.SubImageSize.zw ;
Intermediates.TexCoord.zw = (float2( SubImageBH, SubImageBV ) + FlippedUV) * SpriteVF.SubImageSize.zw ;
Intermediates.SubImageLerp = SubImageLerp;
Intermediates.Color = Input.Color;
#if USE_DYNAMIC_PARAMETERS
Intermediates.DynamicParameter = Input.DynamicParameter;
#endif //USE_DYNAMIC_PARAMETERS
#if USE_PARTICLE_POSITION
float ParticleRadius = .5f * min(Input.SizeRotSubImage.x, Input.SizeRotSubImage.y);
Intermediates.PositionAndSize = float4(ParticleWorldPosition.xyz, ParticleRadius);
#endif
#if USE_PARTICLE_VELOCITY
float3 ParticleVelocity = (ParticleWorldPosition - ParticleOldWorldPosition) * SpriteVF.InvDeltaSeconds;
Intermediates.ParticleVelocity.xyz = normalize(ParticleVelocity);
Intermediates.ParticleVelocity.w = length(ParticleVelocity);
#endif
#if USE_PARTICLE_TIME
Intermediates.RelativeTime = Input.Position.w;
#endif
Intermediates.TangentToLocal = CalcTangentBasis(Intermediates);
#if USE_PARTICLE_LIGHTING_OFFSET
// Hash function based on the particle ID to generate a uniformly distributed 3d offset
float3 RandomParticleOffset = frac(Square(Input.OldPosition.w + 10) * float3(1361.456345, 2333.578, 3623.983)) * 2 - 1;
Intermediates.LightingPositionOffset = .5f * View.TranslucencyLightingVolumeInvSize[0].w * RandomParticleOffset;
#endif
#if USE_PARTICLE_SIZE
Intermediates.ParticleSize = Size;
#endif
return Intermediates;
}
float4 VertexFactoryGetWorldPosition(FVertexFactoryInput Input, FVertexFactoryIntermediates Intermediates)
{
return float4(Intermediates.VertexWorldPosition,1);
}
float4 VertexFactoryGetRasterizedWorldPosition(FVertexFactoryInput Input, FVertexFactoryIntermediates Intermediates, float4 TranslatedWorldPosition)
{
#if SPHERICAL_PARTICLE_OPACITY
// For particles using spherical opacity, move the quad toward the viewer so that it lies in front of the sphere defined by the particle
// This avoids opaque objects intersecting the particle from causing clipping artifacts due to depth testing
// The downside is that the particle will clip the near plane sooner
float Radius = .5f * min(Input.SizeRotSubImage.x, Input.SizeRotSubImage.y);
return ReprojectPosition(TranslatedWorldPosition, Radius);
#else
return TranslatedWorldPosition;
#endif
}
FVertexFactoryInterpolantsVSToPS VertexFactoryGetInterpolantsVSToPS(FVertexFactoryInput Input, FVertexFactoryIntermediates Intermediates, FMaterialVertexParameters VertexParameters)
{
FVertexFactoryInterpolantsVSToPS Interpolants;
// Initialize the whole struct to 0
Interpolants = (FVertexFactoryInterpolantsVSToPS)0;
#if NUM_MATERIAL_TEXCOORDS
float2 CustomizedUVs[NUM_MATERIAL_TEXCOORDS];
GetMaterialCustomizedUVs(VertexParameters, CustomizedUVs);
UNROLL
for (int CoordinateIndex = 0; CoordinateIndex < NUM_MATERIAL_TEXCOORDS; CoordinateIndex++)
{
SetUV(Interpolants, CoordinateIndex, CustomizedUVs[CoordinateIndex]);
}
#endif
#if LIGHTMAP_UV_ACCESS
Interpolants.LightMapUVs = Intermediates.TexCoord.xy;
#endif
#if USE_PARTICLE_SUBUVS
Interpolants.ParticleSubUVs.xy = VertexParameters.Particle.SubUVCoords[0];
Interpolants.ParticleSubUVs.zw = VertexParameters.Particle.SubUVCoords[1];
#endif
#if NEEDS_TANGENT_BASIS
// Calculate the transform from tangent to world space.
// Note that "local" space for particles is actually oriented in world space! Therefore no rotation is needed.
float3x3 TangentToWorld = Intermediates.TangentToLocal;
Interpolants.TangentToWorld0AndInterp_Sizer.xyz = TangentToWorld[0];
Interpolants.TangentToWorld0AndInterp_Sizer.w = Intermediates.SubImageLerp;
Interpolants.TangentToWorld2 = float4(TangentToWorld[2], sign(determinant(Intermediates.TangentToLocal)));
#else
Interpolants.SubImageLerp = Intermediates.SubImageLerp;
#endif
#if NEEDS_PARTICLE_COLOR
Interpolants.Color = Intermediates.Color;
#endif
#if USE_DYNAMIC_PARAMETERS
Interpolants.DynamicParameter = Intermediates.DynamicParameter;
#endif //USE_DYNAMIC_PARAMETERS
#if USE_PARTICLE_POSITION
Interpolants.ParticlePositionAndSize = Intermediates.PositionAndSize;
#endif
#if USE_PARTICLE_VELOCITY
Interpolants.ParticleVelocity = Intermediates.ParticleVelocity;
#endif
#if USE_PARTICLE_TIME
Interpolants.ParticleTime = Intermediates.RelativeTime;
#endif
#if USE_PARTICLE_LIGHTING_OFFSET
Interpolants.LightingPositionOffset = Intermediates.LightingPositionOffset;
#endif
#if USE_PARTICLE_SIZE
Interpolants.ParticleSize = Intermediates.ParticleSize;
#endif
return Interpolants;
}
float4 VertexFactoryGetPreviousWorldPosition(FVertexFactoryInput Input, FVertexFactoryIntermediates Intermediates)
{
return VertexFactoryGetWorldPosition(Input, Intermediates);
}
/**
* Get the 3x3 tangent basis vectors for this vertex factory
*
* @param Input - vertex input stream structure
* @return 3x3 matrix
*/
float3x3 VertexFactoryGetTangentToLocal(FVertexFactoryInput Input, FVertexFactoryIntermediates Intermediates)
{
return Intermediates.TangentToLocal;
}