You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
438 lines
20 KiB
Plaintext
438 lines
20 KiB
Plaintext
/*
|
|
* Modifications Copyright (c) Contributors to the Open 3D Engine Project.
|
|
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
|
*
|
|
* SPDX-License-Identifier: (Apache-2.0 OR MIT) AND MIT
|
|
*
|
|
*/
|
|
|
|
//---------------------------------------------------------------------------------------
|
|
// Shader code related to simulating hair strands in compute.
|
|
//-------------------------------------------------------------------------------------
|
|
//
|
|
// Copyright (c) 2019 Advanced Micro Devices, Inc. All rights reserved.
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
// of this software and associated documentation files (the "Software"), to deal
|
|
// in the Software without restriction, including without limitation the rights
|
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
// copies of the Software, and to permit persons to whom the Software is
|
|
// furnished to do so, subject to the following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be included in
|
|
// all copies or substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
// THE SOFTWARE.
|
|
//
|
|
//--------------------------------------------------------------------------------------
|
|
#include <HairSimulationComputeSrgs.azsli>
|
|
#include <HairSimulationCommon.azsli>
|
|
|
|
//--------------------------------------------------------------------------------------
|
|
//
|
|
// IntegrationAndGlobalShapeConstraints
|
|
//
|
|
// Compute shader to simulate the gravitational force with integration and to maintain the
|
|
// global shape constraints.
|
|
//
|
|
// One thread computes one vertex.
|
|
//
|
|
//--------------------------------------------------------------------------------------
|
|
[numthreads(THREAD_GROUP_SIZE, 1, 1)]
|
|
void IntegrationAndGlobalShapeConstraints(
|
|
uint GIndex : SV_GroupIndex,
|
|
uint3 GId : SV_GroupID,
|
|
uint3 DTid : SV_DispatchThreadID)
|
|
{
|
|
uint globalStrandIndex, localStrandIndex, globalVertexIndex, localVertexIndex, numVerticesInTheStrand, indexForSharedMem, strandType;
|
|
CalcIndicesInVertexLevelMaster(GIndex, GId.x, globalStrandIndex, localStrandIndex, globalVertexIndex, localVertexIndex, numVerticesInTheStrand, indexForSharedMem, strandType);
|
|
|
|
// Copy data from init rest data to be used to set updated shared memory
|
|
float4 initialPos = float4(CM_TO_METERS,CM_TO_METERS,CM_TO_METERS,1.0) * g_InitialHairPositions[globalVertexIndex]; // rest position
|
|
|
|
// Apply bone skinning to initial position
|
|
BoneSkinningData skinningData = g_BoneSkinningData[globalStrandIndex];
|
|
float4 bone_quat;
|
|
initialPos.xyz = ApplyVertexBoneSkinning(initialPos.xyz, skinningData, bone_quat);
|
|
|
|
// position when this step starts. In other words, a position from the last step.
|
|
sharedPos[indexForSharedMem] = GetSharedPosition(globalVertexIndex);
|
|
float4 currentPos = sharedPos[indexForSharedMem];
|
|
// float4 currentPos = sharedPos[indexForSharedMem] = g_HairVertexPositions[globalVertexIndex];
|
|
|
|
GroupMemoryBarrierWithGroupSync();
|
|
|
|
// Integrate
|
|
float dampingCoeff = GetDamping(strandType);
|
|
float4 oldPos = g_HairVertexPositionsPrev[globalVertexIndex];
|
|
|
|
// reset if we got teleported
|
|
if (g_ResetPositions != 0.0f)
|
|
{ // Originally part of the data here was NaN as the original TressFX code wrote number
|
|
// vertices including the follow hair although the shader accounts for that, hence
|
|
// memory was overwriten. In our implementation the memory resides all within
|
|
// a single buffer and this would actively overwrite the rest of the buffer hence
|
|
// destroying the original contexnt.
|
|
currentPos = initialPos;
|
|
g_HairVertexPositions[globalVertexIndex] = initialPos;
|
|
g_HairVertexPositionsPrev[globalVertexIndex] = initialPos;
|
|
g_HairVertexPositionsPrevPrev[globalVertexIndex] = initialPos;
|
|
oldPos = initialPos;
|
|
}
|
|
|
|
// skipping all the physics simulation in between
|
|
if ( IsMovable(currentPos) )
|
|
sharedPos[indexForSharedMem].xyz = Integrate(currentPos.xyz, oldPos.xyz, initialPos.xyz, dampingCoeff);
|
|
else
|
|
sharedPos[indexForSharedMem] = initialPos;
|
|
|
|
// Global Shape Constraints
|
|
float stiffnessForGlobalShapeMatching = GetGlobalStiffness(strandType);
|
|
float globalShapeMatchingEffectiveRange = GetGlobalRange(strandType);
|
|
|
|
if ( stiffnessForGlobalShapeMatching > 0 && globalShapeMatchingEffectiveRange )
|
|
{
|
|
if ( IsMovable(sharedPos[indexForSharedMem]) )
|
|
{
|
|
if ( (float)localVertexIndex < globalShapeMatchingEffectiveRange * (float)numVerticesInTheStrand )
|
|
{
|
|
float factor = stiffnessForGlobalShapeMatching;
|
|
float3 del = factor * (initialPos - sharedPos[indexForSharedMem]).xyz;
|
|
sharedPos[indexForSharedMem].xyz += del;
|
|
}
|
|
}
|
|
}
|
|
|
|
// update global position buffers
|
|
UpdateFinalVertexPositions(currentPos, sharedPos[indexForSharedMem], globalVertexIndex);
|
|
}
|
|
|
|
|
|
//--------------------------------------------------------------------------------------
|
|
//
|
|
// Calculate Strand Level Data
|
|
//
|
|
// Propagate velocity shock resulted by attached based mesh
|
|
//
|
|
// One thread computes two vertices within a strand.
|
|
//
|
|
//--------------------------------------------------------------------------------------
|
|
[numthreads(THREAD_GROUP_SIZE, 1, 1)]
|
|
void CalculateStrandLevelData(
|
|
uint GIndex : SV_GroupIndex,
|
|
uint3 GId : SV_GroupID,
|
|
uint3 DTid : SV_DispatchThreadID)
|
|
{
|
|
uint local_id, group_id, globalStrandIndex, numVerticesInTheStrand, globalRootVertexIndex, strandType;
|
|
CalcIndicesInStrandLevelMaster(GIndex, GId.x, globalStrandIndex, numVerticesInTheStrand, globalRootVertexIndex, strandType);
|
|
|
|
// Accounting for the right and left side of the strand.
|
|
float4 pos_old_old[2]; // previous previous positions for vertex 0 (root) and vertex 1.
|
|
float4 pos_old[2]; // previous positions for vertex 0 (root) and vertex 1.
|
|
float4 pos_new[2]; // current positions for vertex 0 (root) and vertex 1.
|
|
|
|
pos_old_old[0] = g_HairVertexPositionsPrevPrev[globalRootVertexIndex];
|
|
pos_old_old[1] = g_HairVertexPositionsPrevPrev[globalRootVertexIndex + 1];
|
|
|
|
pos_old[0] = g_HairVertexPositionsPrev[globalRootVertexIndex];
|
|
pos_old[1] = g_HairVertexPositionsPrev[globalRootVertexIndex + 1];
|
|
|
|
pos_new[0] = g_HairVertexPositions[globalRootVertexIndex];
|
|
pos_new[1] = g_HairVertexPositions[globalRootVertexIndex + 1];
|
|
|
|
float3 u = normalize(pos_old[1].xyz - pos_old[0].xyz);
|
|
float3 v = normalize(pos_new[1].xyz - pos_new[0].xyz);
|
|
|
|
// Compute rotation and translation which transform pos_old to pos_new.
|
|
// Since the first two vertices are immovable, we can assume that there is no scaling during tranform.
|
|
float4 rot = QuatFromTwoUnitVectors(u, v);
|
|
float3 trans = pos_new[0].xyz - MultQuaternionAndVector(rot, pos_old[0].xyz);
|
|
|
|
float vspCoeff = GetVelocityShockPropogation();
|
|
float restLength0 = g_HairRestLengthSRV[globalRootVertexIndex];
|
|
float vspAccelThreshold = GetVSPAccelThreshold();
|
|
|
|
// Increase the VSP coefficient by checking pseudo-acceleration to handle over-stretching when the character moves very fast
|
|
float accel = length(pos_new[1] - 2.0 * pos_old[1] + pos_old_old[1]);
|
|
|
|
if (accel > vspAccelThreshold)
|
|
vspCoeff = 1.0f;
|
|
g_StrandLevelData[globalStrandIndex].vspQuat = rot;
|
|
g_StrandLevelData[globalStrandIndex].vspTranslation = float4(trans, vspCoeff);
|
|
|
|
// Skinning
|
|
|
|
// Copy data from init rest data to be used to set updated shared memory
|
|
float4 initialPos = float4(CM_TO_METERS,CM_TO_METERS,CM_TO_METERS,1.0) * g_InitialHairPositions[globalRootVertexIndex]; // rest position
|
|
|
|
// Apply bone skinning to initial position
|
|
BoneSkinningData skinningData = g_BoneSkinningData[globalStrandIndex];
|
|
float4 bone_quat;
|
|
initialPos.xyz = ApplyVertexBoneSkinning(initialPos.xyz, skinningData, bone_quat);
|
|
g_StrandLevelData[globalStrandIndex].skinningQuat = bone_quat;
|
|
}
|
|
|
|
|
|
//--------------------------------------------------------------------------------------
|
|
//
|
|
// VelocityShockPropagation
|
|
//
|
|
// Propagate velocity shock resulted by attached based mesh
|
|
//
|
|
// One thread computes a vertex in a strand.
|
|
//
|
|
//--------------------------------------------------------------------------------------
|
|
[numthreads(THREAD_GROUP_SIZE, 1, 1)]
|
|
void VelocityShockPropagation(
|
|
uint GIndex : SV_GroupIndex,
|
|
uint3 GId : SV_GroupID,
|
|
uint3 DTid : SV_DispatchThreadID)
|
|
{
|
|
uint globalStrandIndex, localStrandIndex, globalVertexIndex, localVertexIndex, numVerticesInTheStrand, indexForSharedMem, strandType;
|
|
CalcIndicesInVertexLevelMaster(GIndex, GId.x, globalStrandIndex, localStrandIndex, globalVertexIndex, localVertexIndex, numVerticesInTheStrand, indexForSharedMem, strandType);
|
|
|
|
// The first two vertices are the ones attached to the skin
|
|
if (localVertexIndex < 2)
|
|
return;
|
|
|
|
float4 vspQuat = g_StrandLevelData[globalStrandIndex].vspQuat;
|
|
float4 vspTrans = g_StrandLevelData[globalStrandIndex].vspTranslation;
|
|
float vspCoeff = vspTrans.w;
|
|
|
|
float4 pos_new_n = g_HairVertexPositions[globalVertexIndex];
|
|
float4 pos_old_n = g_HairVertexPositionsPrev[globalVertexIndex];
|
|
|
|
pos_new_n.xyz = (1.f - vspCoeff) * pos_new_n.xyz + vspCoeff * (MultQuaternionAndVector(vspQuat, pos_new_n.xyz) + vspTrans.xyz);
|
|
pos_old_n.xyz = (1.f - vspCoeff) * pos_old_n.xyz + vspCoeff * (MultQuaternionAndVector(vspQuat, pos_old_n.xyz) + vspTrans.xyz);
|
|
|
|
g_HairVertexPositions[globalVertexIndex].xyz = pos_new_n.xyz;
|
|
g_HairVertexPositionsPrev[globalVertexIndex].xyz = pos_old_n.xyz;
|
|
}
|
|
|
|
|
|
//--------------------------------------------------------------------------------------
|
|
//
|
|
// LocalShapeConstraints
|
|
//
|
|
// Compute shader to maintain the local shape constraints.
|
|
//
|
|
// One thread computes one strand.
|
|
//
|
|
//--------------------------------------------------------------------------------------
|
|
[numthreads(THREAD_GROUP_SIZE, 1, 1)]
|
|
void LocalShapeConstraints(
|
|
uint GIndex : SV_GroupIndex,
|
|
uint3 GId : SV_GroupID,
|
|
uint3 DTid : SV_DispatchThreadID)
|
|
{
|
|
uint local_id, group_id, globalStrandIndex, numVerticesInTheStrand, globalRootVertexIndex, strandType;
|
|
CalcIndicesInStrandLevelMaster(GIndex, GId.x, globalStrandIndex, numVerticesInTheStrand, globalRootVertexIndex, strandType);
|
|
|
|
// stiffness for local shape constraints
|
|
float stiffnessForLocalShapeMatching = GetLocalStiffness(strandType);
|
|
|
|
// Going beyond the TH will create less stability in convergence
|
|
const float stabilityTH = 0.95f;
|
|
stiffnessForLocalShapeMatching = 0.5f * min(stiffnessForLocalShapeMatching, stabilityTH);
|
|
|
|
//--------------------------------------------
|
|
// Local shape constraint for bending/twisting
|
|
//--------------------------------------------
|
|
{
|
|
float4 boneQuat = g_StrandLevelData[globalStrandIndex].skinningQuat;
|
|
|
|
// vertex 1 through n-1
|
|
for (uint localVertexIndex = 1; localVertexIndex < numVerticesInTheStrand - 1; localVertexIndex++)
|
|
{
|
|
uint globalVertexIndex = globalRootVertexIndex + localVertexIndex;
|
|
|
|
float4 pos = g_HairVertexPositions[globalVertexIndex];
|
|
float4 pos_plus_one = g_HairVertexPositions[globalVertexIndex + 1];
|
|
float4 pos_minus_one = g_HairVertexPositions[globalVertexIndex - 1];
|
|
|
|
float3 bindPos = MultQuaternionAndVector(boneQuat, g_InitialHairPositions[globalVertexIndex].xyz * CM_TO_METERS);
|
|
float3 bindPos_plus_one = MultQuaternionAndVector(boneQuat, g_InitialHairPositions[globalVertexIndex + 1].xyz * CM_TO_METERS);
|
|
float3 bindPos_minus_one = MultQuaternionAndVector(boneQuat, g_InitialHairPositions[globalVertexIndex - 1].xyz * CM_TO_METERS);
|
|
|
|
float3 lastVec = pos.xyz - pos_minus_one.xyz;
|
|
|
|
float3 vecBindPose = bindPos_plus_one - bindPos;
|
|
float3 lastVecBindPose = bindPos - bindPos_minus_one;
|
|
float4 rotGlobal = QuatFromTwoUnitVectors(normalize(lastVecBindPose), normalize(lastVec));
|
|
|
|
float3 orgPos_i_plus_1_InGlobalFrame = MultQuaternionAndVector(rotGlobal, vecBindPose) + pos.xyz;
|
|
float3 del = stiffnessForLocalShapeMatching * (orgPos_i_plus_1_InGlobalFrame - pos_plus_one.xyz);
|
|
|
|
if (IsMovable(pos))
|
|
pos.xyz -= del.xyz;
|
|
|
|
if (IsMovable(pos_plus_one))
|
|
pos_plus_one.xyz += del.xyz;
|
|
|
|
|
|
g_HairVertexPositions[globalVertexIndex].xyz = pos.xyz;
|
|
g_HairVertexPositions[globalVertexIndex + 1].xyz = pos_plus_one.xyz;
|
|
}
|
|
}
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------
|
|
//
|
|
// LengthConstriantsWindAndCollision
|
|
//
|
|
// Compute shader to move the vertex position based on wind, maintain the lenght constraints
|
|
// and handles collisions.
|
|
//
|
|
// One thread computes one vertex.
|
|
//
|
|
//--------------------------------------------------------------------------------------
|
|
[numthreads(THREAD_GROUP_SIZE, 1, 1)]
|
|
void LengthConstriantsWindAndCollision(uint GIndex : SV_GroupIndex,
|
|
uint3 GId : SV_GroupID,
|
|
uint3 DTid : SV_DispatchThreadID)
|
|
{
|
|
uint globalStrandIndex, localStrandIndex, globalVertexIndex, localVertexIndex, numVerticesInTheStrand, indexForSharedMem, strandType;
|
|
CalcIndicesInVertexLevelMaster(GIndex, GId.x, globalStrandIndex, localStrandIndex, globalVertexIndex, localVertexIndex, numVerticesInTheStrand, indexForSharedMem, strandType);
|
|
|
|
uint numOfStrandsPerThreadGroup = g_NumOfStrandsPerThreadGroup;
|
|
|
|
//------------------------------
|
|
// Copy data into shared memory
|
|
//------------------------------
|
|
sharedPos[indexForSharedMem] = g_HairVertexPositions[globalVertexIndex];
|
|
sharedLength[indexForSharedMem] = g_HairRestLengthSRV[globalVertexIndex] * CM_TO_METERS;
|
|
|
|
GroupMemoryBarrierWithGroupSync();
|
|
|
|
/*
|
|
//------------
|
|
// Wind - does not work yet and requires some LTC
|
|
//------------
|
|
if (any(g_Wind.xyz)) // g_Wind.w is the current frame
|
|
{
|
|
float4 force = float4(0, 0, 0, 0);
|
|
|
|
if ( localVertexIndex >= 2 && localVertexIndex < numVerticesInTheStrand-1 )
|
|
{
|
|
// combining four winds.
|
|
float a = ((float)(globalStrandIndex % 20))/20.0f;
|
|
float3 w = a* g_Wind.xyz + (1.0f - a) * g_Wind1.xyz + a * g_Wind2.xyz + (1.0f - a) * g_Wind3.xyz;
|
|
// float3 w = float3(5.2, 0, 0);
|
|
|
|
uint sharedIndex = localVertexIndex * numOfStrandsPerThreadGroup + localStrandIndex;
|
|
|
|
float3 v = sharedPos[sharedIndex].xyz - sharedPos[sharedIndex+numOfStrandsPerThreadGroup].xyz;
|
|
float3 force = -cross(cross(v, w), v);
|
|
sharedPos[sharedIndex].xyz += force*g_TimeStep*g_TimeStep;
|
|
}
|
|
}
|
|
|
|
GroupMemoryBarrierWithGroupSync();
|
|
*/
|
|
//----------------------------
|
|
// Enforce length constraints
|
|
//----------------------------
|
|
uint a = numVerticesInTheStrand/2.0f;
|
|
uint b = (numVerticesInTheStrand-1)/2.0f;
|
|
|
|
int lengthContraintIterations = GetLengthConstraintIterations();
|
|
|
|
for ( int iterationE=0; iterationE < lengthContraintIterations; iterationE++ )
|
|
{
|
|
uint sharedIndex = 2 * localVertexIndex * numOfStrandsPerThreadGroup + localStrandIndex;
|
|
|
|
// Notice that the following is based on the fact that we are dealing here with two vertices
|
|
// one at each side of the central control point and each should extend towards its side only.
|
|
if( localVertexIndex < a )
|
|
ApplyDistanceConstraint(sharedPos[sharedIndex], sharedPos[sharedIndex+numOfStrandsPerThreadGroup], sharedLength[sharedIndex].x);
|
|
|
|
GroupMemoryBarrierWithGroupSync();
|
|
|
|
if( localVertexIndex < b )
|
|
ApplyDistanceConstraint(sharedPos[sharedIndex+numOfStrandsPerThreadGroup], sharedPos[sharedIndex+numOfStrandsPerThreadGroup*2], sharedLength[sharedIndex+numOfStrandsPerThreadGroup].x);
|
|
|
|
GroupMemoryBarrierWithGroupSync();
|
|
}
|
|
|
|
//------------------------------------------
|
|
// Collision handling with capsule objects
|
|
//------------------------------------------
|
|
float4 oldPos = g_HairVertexPositionsPrev[globalVertexIndex];
|
|
bool bAnyColDetected = false; // Adi
|
|
// bool bAnyColDetected = ResolveCapsuleCollisions(sharedPos[indexForSharedMem], oldPos);
|
|
GroupMemoryBarrierWithGroupSync();
|
|
|
|
//-------------------
|
|
// Compute tangent
|
|
//-------------------
|
|
// If this is the last vertex in the strand, we can't get tangent from subtracting from the next vertex, need to use last vertex to current
|
|
uint indexForTangent = (localVertexIndex == numVerticesInTheStrand - 1) ? indexForSharedMem - numOfStrandsPerThreadGroup : indexForSharedMem;
|
|
float3 tangent = sharedPos[indexForTangent + numOfStrandsPerThreadGroup].xyz - sharedPos[indexForTangent].xyz;
|
|
g_HairVertexTangents[globalVertexIndex].xyz = normalize(tangent);
|
|
|
|
//---------------------------------------
|
|
// clamp velocities, rewrite history
|
|
//---------------------------------------
|
|
float3 positionDelta = sharedPos[indexForSharedMem].xyz - oldPos;
|
|
float speedSqr = dot(positionDelta, positionDelta);
|
|
if (speedSqr > g_ClampPositionDelta * g_ClampPositionDelta) {
|
|
positionDelta *= g_ClampPositionDelta * g_ClampPositionDelta / speedSqr;
|
|
g_HairVertexPositionsPrev[globalVertexIndex].xyz = sharedPos[indexForSharedMem].xyz - positionDelta;
|
|
}
|
|
|
|
//---------------------------------------
|
|
// update global position buffers
|
|
//---------------------------------------
|
|
g_HairVertexPositions[globalVertexIndex] = sharedPos[indexForSharedMem];
|
|
|
|
if (bAnyColDetected)
|
|
g_HairVertexPositionsPrev[globalVertexIndex] = sharedPos[indexForSharedMem];
|
|
|
|
return;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------
|
|
//
|
|
// UpdateFollowHairVertices
|
|
//
|
|
// Last stage update of the follow hair to follow their guide hair
|
|
//
|
|
// One thread computes one vertex.
|
|
//
|
|
//--------------------------------------------------------------------------------------
|
|
[numthreads(THREAD_GROUP_SIZE, 1, 1)]
|
|
void UpdateFollowHairVertices(
|
|
uint GIndex : SV_GroupIndex,
|
|
uint3 GId : SV_GroupID,
|
|
uint3 DTid : SV_DispatchThreadID)
|
|
{
|
|
uint globalStrandIndex, localStrandIndex, globalVertexIndex, localVertexIndex, numVerticesInTheStrand, indexForSharedMem, strandType;
|
|
CalcIndicesInVertexLevelMaster(GIndex, GId.x, globalStrandIndex, localStrandIndex, globalVertexIndex, localVertexIndex, numVerticesInTheStrand, indexForSharedMem, strandType);
|
|
|
|
sharedPos[indexForSharedMem] = GetSharedPosition(globalVertexIndex); // g_HairVertexPositions[globalVertexIndex];
|
|
sharedTangent[indexForSharedMem].xyz = GetSharedTangent(globalVertexIndex); // g_HairVertexTangents[globalVertexIndex];
|
|
GroupMemoryBarrierWithGroupSync();
|
|
|
|
for ( uint i = 0; i < g_NumFollowHairsPerGuideHair; i++ )
|
|
{
|
|
int globalFollowVertexIndex = globalVertexIndex + numVerticesInTheStrand * (i + 1);
|
|
int globalFollowStrandIndex = globalStrandIndex + i + 1;
|
|
float factor = g_TipSeparationFactor*((float)localVertexIndex / (float)numVerticesInTheStrand) + 1.0f;
|
|
float3 followPos = sharedPos[indexForSharedMem].xyz + factor * CM_TO_METERS * g_FollowHairRootOffset[globalFollowStrandIndex].xyz;
|
|
|
|
SetSharedPosition3(globalFollowVertexIndex, followPos);
|
|
// g_HairVertexPositions[globalFollowVertexIndex].xyz = followPos;
|
|
//-----------------------
|
|
// SetSharedTangent(globalFollowVertexIndex, sharedTangent[indexForSharedMem]);
|
|
g_HairVertexTangents[globalFollowVertexIndex] = sharedTangent[indexForSharedMem];
|
|
}
|
|
|
|
return;
|
|
}
|