522 lines
22 KiB
HLSL
522 lines
22 KiB
HLSL
#ifndef HOUDINI_VAT_INCLUDED
|
||
#define HOUDINI_VAT_INCLUDED
|
||
|
||
// ─────────────────────────────────────────────────────────────────────
|
||
// Houdini VAT 3.0 — 统一实现(SoftBody / RigidBody / DynamicRemeshing / ParticleSprite)
|
||
// 集成于 ParticleBase.shader 的粒子着色器管线
|
||
// ─────────────────────────────────────────────────────────────────────
|
||
|
||
// ── 纹理声明 ──────────────────────────────────────────────────────────
|
||
TEXTURE2D(_posTexture); SAMPLER(sampler_posTexture);
|
||
TEXTURE2D(_posTexture2); SAMPLER(sampler_posTexture2);
|
||
TEXTURE2D(_rotTexture); SAMPLER(sampler_rotTexture);
|
||
TEXTURE2D(_colTexture); SAMPLER(sampler_colTexture);
|
||
TEXTURE2D(_lookupTable); SAMPLER(sampler_lookupTable);
|
||
|
||
#if defined(_VAT) && defined(_VAT_HOUDINI) && \
|
||
!defined(_HOUDINI_VAT_SOFTBODY) && \
|
||
!defined(_HOUDINI_VAT_RIGIDBODY) && \
|
||
!defined(_HOUDINI_VAT_DYNAMIC_REMESH) && \
|
||
!defined(_HOUDINI_VAT_PARTICLE_SPRITE)
|
||
#define _HOUDINI_VAT_SOFTBODY
|
||
#endif
|
||
|
||
// ── extern 材质属性(Unity 自动从材质中查找同名值) ───────────────────
|
||
|
||
// Playback
|
||
extern float _B_autoPlayback;
|
||
extern float _gameTimeAtFirstFrame;
|
||
extern float _playbackSpeed;
|
||
extern float _houdiniFPS;
|
||
extern float _displayFrame;
|
||
extern float _B_interpolate;
|
||
extern float _animateFirstFrame;
|
||
extern float _frameCount;
|
||
|
||
// Bounds
|
||
extern float _boundMinX, _boundMinY, _boundMinZ;
|
||
extern float _boundMaxX, _boundMaxY, _boundMaxZ;
|
||
|
||
// Scale
|
||
extern float _globalPscaleMul;
|
||
extern float _B_pscaleAreInPosA;
|
||
|
||
// Particle Sprite
|
||
extern float _widthBaseScale;
|
||
extern float _heightBaseScale;
|
||
extern float _B_hideOverlappingOrigin;
|
||
extern float _originRadius;
|
||
extern float _B_CAN_SPIN;
|
||
extern float _B_spinFromHeading;
|
||
extern float _spinPhase;
|
||
extern float _scaleByVelAmount;
|
||
extern float _particleTexUScale;
|
||
extern float _particleTexVScale;
|
||
|
||
// Flags
|
||
extern float _B_LOAD_POS_TWO_TEX;
|
||
extern float _B_UNLOAD_ROT_TEX;
|
||
extern float _B_LOAD_COL_TEX;
|
||
extern float _B_LOAD_LOOKUP_TABLE;
|
||
|
||
// ─────────────────────────────────────────────────────────────────────
|
||
// 工具函数
|
||
// ─────────────────────────────────────────────────────────────────────
|
||
|
||
// 用单位四元数旋转向量
|
||
// 公式: v' = v + 2 * cross(q.xyz, q.w*v + cross(q.xyz, v))
|
||
float3 HVAT_RotateByQuat(float3 v, float4 q)
|
||
{
|
||
float3 t = cross(q.xyz, v);
|
||
return v + cross(q.xyz, t + v * q.w) * 2.0;
|
||
}
|
||
|
||
// 从 3 个最小组件重建单位四元数(RigidBody 专用)
|
||
// maxComp (0-3) 标识被省略的分量
|
||
float4 HVAT_DecodeQuaternion(float3 xyz, float maxComp)
|
||
{
|
||
float w = sqrt(saturate(1.0 - dot(xyz, xyz)));
|
||
float4 q = float4(xyz.x, xyz.y, xyz.z, w);
|
||
int mc = (int)maxComp;
|
||
if (mc == 1) q = float4( w, xyz.y, xyz.z, xyz.x);
|
||
else if (mc == 2) q = float4(xyz.x, -w, xyz.z, -xyz.y);
|
||
else if (mc == 3) q = float4(xyz.x, xyz.y, -w, -xyz.z);
|
||
return q;
|
||
}
|
||
|
||
// 从 posTexture.a 解码 5-bit spheremap 压缩法线
|
||
float3 HVAT_DecodeCompressedNormal(float posA)
|
||
{
|
||
float scaledA = posA * 1024.0;
|
||
float xIdx = floor(scaledA / 32.0);
|
||
float yRaw = scaledA - xIdx * 32.0;
|
||
float xNorm = xIdx / 31.5;
|
||
float yNorm = yRaw / 31.5;
|
||
float2 xy = float2(xNorm, yNorm) * 4.0 - 2.0;
|
||
float d = dot(xy, xy);
|
||
float sqrtF = sqrt(saturate(1.0 - d * 0.25));
|
||
return float3(-sqrtF * xy.x, 1.0 - d * 0.5, sqrtF * xy.y);
|
||
}
|
||
|
||
// Lookup table 解码:从 RGBA 得到高精度采样 UV
|
||
float2 HVAT_DecodeLookupUV(float4 lookupSample, float boundMinX)
|
||
{
|
||
float lookupHDR = (frac(-boundMinX * 10.0) >= 0.5) ? 1.0 : 0.0;
|
||
float divisor = lookupHDR ? 2048.0 : 255.0;
|
||
float lookupX = lookupSample.r + lookupSample.g / divisor;
|
||
float lookupY = 1.0 - (lookupSample.b + lookupSample.a / divisor);
|
||
return float2(lookupX, lookupY);
|
||
}
|
||
|
||
// 计算 VAT 采样 UV
|
||
float2 HVAT_VatUV(float selectedFrame, float uv_r, float uv_g,
|
||
float oneMinusBoundMaxR, float multiplyBoundMinB, float totalFrames)
|
||
{
|
||
float wrapped = fmod(selectedFrame - 1.0, totalFrames);
|
||
float vBase = (1.0 - uv_g) * oneMinusBoundMaxR
|
||
+ (wrapped / totalFrames) * oneMinusBoundMaxR;
|
||
return float2(multiplyBoundMinB, 1.0 - vBase);
|
||
}
|
||
|
||
// 2D hash 随机 [0, 1]
|
||
float HVAT_HashRandom2D(float2 seed)
|
||
{
|
||
return frac(sin(dot(seed, float2(12.9898, 78.233))) * 43758.5453);
|
||
}
|
||
|
||
// ─────────────────────────────────────────────────────────────────────
|
||
// 帧选择
|
||
// ─────────────────────────────────────────────────────────────────────
|
||
|
||
float2 HVAT_GetVatUV1(AttributesParticle input)
|
||
{
|
||
return CheckLocalFlags1(FLAG_BIT_PARTICLE_1_IS_PARTICLE_SYSTEM)
|
||
? input.texcoords.zw
|
||
: input.Custom1.xy;
|
||
}
|
||
|
||
void HVAT_ComputeFrameSelection(AttributesParticle input, out float selectedFrame, out float frameAlpha)
|
||
{
|
||
float totalFrames = _frameCount;
|
||
float animTime = (_Time.y - _gameTimeAtFirstFrame)
|
||
* (_houdiniFPS / (totalFrames - 0.01))
|
||
* _playbackSpeed;
|
||
float frameFloat = frac(animTime) * totalFrames;
|
||
|
||
selectedFrame = _B_autoPlayback
|
||
? floor(frameFloat) + 1.0
|
||
: floor(_displayFrame);
|
||
frameAlpha = frac(_B_autoPlayback ? frameFloat : _displayFrame);
|
||
|
||
if (CheckLocalFlags1(FLAG_BIT_PARTICLE_1_IS_PARTICLE_SYSTEM))
|
||
{
|
||
float frameCustomData = GetCustomData(NB_CUSTOM_DATA_FLAG_2, FLAGBIT_POS_2_CUSTOMDATA_VAT_FRAME, -1.0, input.Custom1, input.Custom2);
|
||
if (frameCustomData >= 0.0)
|
||
{
|
||
float customFrame = saturate(frameCustomData) * max(totalFrames - 1.0, 0.0) + 1.0;
|
||
selectedFrame = floor(customFrame);
|
||
frameAlpha = frac(customFrame);
|
||
}
|
||
}
|
||
}
|
||
|
||
// ─────────────────────────────────────────────────────────────────────
|
||
// 主函数:ApplyHoudiniVAT
|
||
// ─────────────────────────────────────────────────────────────────────
|
||
|
||
void ApplyHoudiniVAT(AttributesParticle input, inout float4 positionOS, inout float3 normalOS)
|
||
{
|
||
// ── 共享 Bounds 常量(不依赖 UV 的部分) ──
|
||
float comparisonBoundMaxb = (frac(_boundMaxZ * 10.0) >= 0.5) ? 1.0 : 0.0;
|
||
float oneMinusBoundMaxR = 1.0 - frac(_boundMaxX * (-10.0));
|
||
float boundMinMul10z = _boundMinZ * 10.0;
|
||
float oneMinusBoundMinB = 1.0 - (ceil(boundMinMul10z) - boundMinMul10z);
|
||
float pscaleDenom = max(1.0 - frac(_boundMaxY * 10.0), 1e-5);
|
||
|
||
// ── 帧选择 ──
|
||
float selectedFrame, frameAlpha;
|
||
HVAT_ComputeFrameSelection(input, selectedFrame, frameAlpha);
|
||
float totalFrames = _frameCount;
|
||
|
||
float3 boundsMax = float3(_boundMaxX, _boundMaxY, _boundMaxZ);
|
||
float3 boundsMin = float3(_boundMinX, _boundMinY, _boundMinZ);
|
||
|
||
// ─────────────────────────────────────────────────────────────────
|
||
// Sub Mode 0: SoftBody — 加法位移 + 四元数法线 / 压缩法线
|
||
// ─────────────────────────────────────────────────────────────────
|
||
#if defined(_HOUDINI_VAT_SOFTBODY)
|
||
{
|
||
float2 vatUV1 = HVAT_GetVatUV1(input);
|
||
float uv1r = vatUV1.r;
|
||
float uv1g = vatUV1.g;
|
||
float multiplyBoundMinB = uv1r * oneMinusBoundMinB;
|
||
|
||
float2 texUV = HVAT_VatUV(selectedFrame, uv1r, uv1g,
|
||
oneMinusBoundMaxR, multiplyBoundMinB, totalFrames);
|
||
|
||
// 采样位置
|
||
float4 posSample = SAMPLE_TEXTURE2D_LOD(_posTexture, sampler_posTexture, texUV, 0);
|
||
float3 posRGB = posSample.rgb;
|
||
float posA = posSample.a;
|
||
|
||
// 双纹理高精度位置
|
||
if (_B_LOAD_POS_TWO_TEX > 0.5)
|
||
{
|
||
float4 pos2 = SAMPLE_TEXTURE2D_LOD(_posTexture2, sampler_posTexture2, texUV, 0);
|
||
posRGB += pos2.rgb * 0.01;
|
||
}
|
||
|
||
// 解码位移
|
||
float3 posDecoded = posRGB * (boundsMax - boundsMin) + boundsMin;
|
||
float3 displacement = comparisonBoundMaxb ? posRGB : posDecoded;
|
||
|
||
// SoftBody: 原始位置 + 位移
|
||
positionOS.xyz += displacement;
|
||
|
||
// 法线
|
||
if (_B_UNLOAD_ROT_TEX > 0.5)
|
||
{
|
||
normalOS = normalize(HVAT_DecodeCompressedNormal(posA));
|
||
}
|
||
else
|
||
{
|
||
float4 rotSample = SAMPLE_TEXTURE2D_LOD(_rotTexture, sampler_rotTexture, texUV, 0);
|
||
float4 rotFinal = comparisonBoundMaxb ? rotSample : (rotSample - 0.5) * 2.0;
|
||
normalOS = normalize(HVAT_RotateByQuat(float3(0.0, 1.0, 0.0), rotFinal));
|
||
}
|
||
|
||
return;
|
||
}
|
||
|
||
// ─────────────────────────────────────────────────────────────────
|
||
// Sub Mode 1: RigidBody — Pivot 旋转 + Pscale + 帧间插值
|
||
// ─────────────────────────────────────────────────────────────────
|
||
#elif defined(_HOUDINI_VAT_RIGIDBODY)
|
||
{
|
||
if (CheckLocalFlags1(FLAG_BIT_PARTICLE_1_IS_PARTICLE_SYSTEM))
|
||
{
|
||
return;
|
||
}
|
||
|
||
float2 vatUV1 = HVAT_GetVatUV1(input);
|
||
float uv1r = vatUV1.r;
|
||
float uv1g = vatUV1.g;
|
||
float multiplyBoundMinB = uv1r * oneMinusBoundMinB;
|
||
|
||
// 当前帧和下一帧 UV
|
||
float2 texUV = HVAT_VatUV(selectedFrame, uv1r, uv1g,
|
||
oneMinusBoundMaxR, multiplyBoundMinB, totalFrames);
|
||
float2 texUV_next = HVAT_VatUV(selectedFrame + 1.0, uv1r, uv1g,
|
||
oneMinusBoundMaxR, multiplyBoundMinB, totalFrames);
|
||
|
||
// 采样旋转
|
||
float4 rotSample = SAMPLE_TEXTURE2D_LOD(_rotTexture, sampler_rotTexture, texUV, 0);
|
||
float4 rotRemapped = (rotSample - 0.5) * 2.0;
|
||
float4 rotFinal = comparisonBoundMaxb ? rotSample : rotRemapped;
|
||
|
||
// 采样位置
|
||
float4 posSample = SAMPLE_TEXTURE2D_LOD(_posTexture, sampler_posTexture, texUV, 0);
|
||
float3 posRGB = posSample.rgb;
|
||
float posA = posSample.a;
|
||
|
||
// 四元数 maxComp 索引(从 posA 整数部分)
|
||
float quatMaxIdxScaled = posA * 4.0;
|
||
float quatMaxIdx = floor(quatMaxIdxScaled);
|
||
|
||
// 解码四元数
|
||
float4 q = HVAT_DecodeQuaternion(rotFinal.rgb, quatMaxIdx);
|
||
|
||
// 解码位置
|
||
float3 posRawForDecode = posRGB;
|
||
if (_B_LOAD_POS_TWO_TEX > 0.5)
|
||
{
|
||
float4 pos2 = SAMPLE_TEXTURE2D_LOD(_posTexture2, sampler_posTexture2, texUV, 0);
|
||
posRawForDecode = posRGB + pos2.rgb * 0.01;
|
||
}
|
||
|
||
float3 posDecoded = posRawForDecode * (boundsMax - boundsMin) + boundsMin;
|
||
float3 piecePos = comparisonBoundMaxb ? posRawForDecode : posDecoded;
|
||
|
||
// Pscale
|
||
float pscaleFromPosA = (1.0 - frac(quatMaxIdxScaled)) / pscaleDenom;
|
||
float pscale = (_B_pscaleAreInPosA > 0.5) ? pscaleFromPosA : 1.0;
|
||
float totalScale = _globalPscaleMul * pscale;
|
||
|
||
// Rest pivot 从 UV2/UV3
|
||
// Custom2 = TEXCOORD2 (uv2), vatTexcoord5 = TEXCOORD4 (uv3)
|
||
float3 restPivot = float3(-input.Custom2.r, input.vatTexcoord5.r, 1.0 - input.vatTexcoord5.g);
|
||
|
||
// 旋转局部偏移
|
||
float3 localOffset = positionOS.xyz - restPivot;
|
||
float3 rotatedLocal = HVAT_RotateByQuat(localOffset, q);
|
||
float3 scaledLocal = rotatedLocal * totalScale;
|
||
|
||
// 帧间插值
|
||
float3 finalPiecePos = piecePos;
|
||
if (_B_interpolate > 0.5)
|
||
{
|
||
float4 posSampleNext = SAMPLE_TEXTURE2D_LOD(_posTexture, sampler_posTexture, texUV_next, 0);
|
||
float3 posRGBNext = posSampleNext.rgb;
|
||
if (_B_LOAD_POS_TWO_TEX > 0.5)
|
||
{
|
||
float4 pos2Next = SAMPLE_TEXTURE2D_LOD(_posTexture2, sampler_posTexture2, texUV_next, 0);
|
||
posRGBNext += pos2Next.rgb * 0.01;
|
||
}
|
||
float3 posDecodedNext = posRGBNext * (boundsMax - boundsMin) + boundsMin;
|
||
float3 piecePosNext = comparisonBoundMaxb ? posRGBNext : posDecodedNext;
|
||
finalPiecePos = lerp(piecePos, piecePosNext, frameAlpha);
|
||
}
|
||
|
||
// 组装最终位置
|
||
float3 animatedPos = scaledLocal + finalPiecePos;
|
||
|
||
// 首帧复位
|
||
float wrappedForCheck = fmod(selectedFrame - 1.0, totalFrames);
|
||
bool isRestFrame = (wrappedForCheck < 0.5) && !(_animateFirstFrame > 0.5);
|
||
float3 finalPosOS = isRestFrame ? positionOS.xyz : animatedPos;
|
||
|
||
// 崩塌:无 piece 关联
|
||
finalPosOS = (uv1g <= 0.1) ? float3(0, 0, 0) : finalPosOS;
|
||
|
||
// 法线
|
||
float3 rotatedNormalOS = isRestFrame
|
||
? normalOS
|
||
: normalize(HVAT_RotateByQuat(normalOS, q));
|
||
normalOS = rotatedNormalOS;
|
||
|
||
positionOS.xyz = finalPosOS;
|
||
return;
|
||
}
|
||
|
||
// ─────────────────────────────────────────────────────────────────
|
||
// Sub Mode 2: DynamicRemeshing — Lookup Table → 绝对位置
|
||
// ─────────────────────────────────────────────────────────────────
|
||
#elif defined(_HOUDINI_VAT_DYNAMIC_REMESH)
|
||
{
|
||
// 使用 uv0 (texcoords.r/g) 作为 piece UV
|
||
float uv0r = input.texcoords.r;
|
||
float uv0g = input.texcoords.g;
|
||
float multiplyBoundMinB = uv0r * oneMinusBoundMinB;
|
||
|
||
float2 vatUV = HVAT_VatUV(selectedFrame, uv0r, uv0g,
|
||
oneMinusBoundMaxR, multiplyBoundMinB, totalFrames);
|
||
|
||
// Lookup Table
|
||
float4 lookupSample = SAMPLE_TEXTURE2D_LOD(_lookupTable, sampler_lookupTable, vatUV, 0);
|
||
float2 lookupUV = HVAT_DecodeLookupUV(lookupSample, _boundMinX);
|
||
|
||
// 采样位置(绝对坐标)
|
||
float4 posSample = SAMPLE_TEXTURE2D_LOD(_posTexture, sampler_posTexture, lookupUV, 0);
|
||
float3 posRGB = posSample.rgb;
|
||
float posA = posSample.a;
|
||
|
||
// 双纹理高精度位置
|
||
if (_B_LOAD_POS_TWO_TEX > 0.5)
|
||
{
|
||
float4 pos2 = SAMPLE_TEXTURE2D_LOD(_posTexture2, sampler_posTexture2, lookupUV, 0);
|
||
posRGB += pos2.rgb * 0.01;
|
||
}
|
||
|
||
// 解码绝对位置
|
||
float3 posDecoded = posRGB * (boundsMax - boundsMin) + boundsMin;
|
||
float3 finalPosOS = comparisonBoundMaxb ? posRGB : posDecoded;
|
||
|
||
// 无有效 piece 塌陷
|
||
finalPosOS = (uv0g <= 0.1) ? float3(0, 0, 0) : finalPosOS;
|
||
|
||
positionOS.xyz = finalPosOS;
|
||
|
||
// 法线
|
||
if (_B_UNLOAD_ROT_TEX > 0.5)
|
||
{
|
||
normalOS = normalize(HVAT_DecodeCompressedNormal(posA));
|
||
}
|
||
else
|
||
{
|
||
float4 rotSample = SAMPLE_TEXTURE2D_LOD(_rotTexture, sampler_rotTexture, lookupUV, 0);
|
||
float4 rotFinal = comparisonBoundMaxb ? rotSample : (rotSample - 0.5) * 2.0;
|
||
normalOS = normalize(HVAT_RotateByQuat(float3(0.0, 1.0, 0.0), rotFinal));
|
||
}
|
||
|
||
return;
|
||
}
|
||
|
||
// ─────────────────────────────────────────────────────────────────
|
||
#elif defined(_HOUDINI_VAT_PARTICLE_SPRITE)
|
||
// Sub Mode 3: ParticleSprite — Billboard + Spin + Origin Mask
|
||
// ─────────────────────────────────────────────────────────────────
|
||
{
|
||
float2 vatUV1 = HVAT_GetVatUV1(input);
|
||
float uv1r = vatUV1.r;
|
||
float uv1g = vatUV1.g;
|
||
float multiplyBoundMinB = uv1r * oneMinusBoundMinB;
|
||
|
||
// 当前帧 + 下一帧 UV
|
||
float2 vatUV = HVAT_VatUV(selectedFrame, uv1r, uv1g,
|
||
oneMinusBoundMaxR, multiplyBoundMinB, totalFrames);
|
||
float2 vatUV_next = HVAT_VatUV(selectedFrame + 1.0, uv1r, uv1g,
|
||
oneMinusBoundMaxR, multiplyBoundMinB, totalFrames);
|
||
|
||
// 采样位置
|
||
float4 posSample = SAMPLE_TEXTURE2D_LOD(_posTexture, sampler_posTexture, vatUV, 0);
|
||
float4 posSample_next = SAMPLE_TEXTURE2D_LOD(_posTexture, sampler_posTexture, vatUV_next, 0);
|
||
|
||
float3 posRGB = posSample.rgb;
|
||
float3 posRGB_next = posSample_next.rgb;
|
||
float posA = posSample.a;
|
||
float posA_next = posSample_next.a;
|
||
|
||
// 双纹理高精度位置
|
||
if (_B_LOAD_POS_TWO_TEX > 0.5)
|
||
{
|
||
float4 pos2 = SAMPLE_TEXTURE2D_LOD(_posTexture2, sampler_posTexture2, vatUV, 0);
|
||
float4 pos2_next = SAMPLE_TEXTURE2D_LOD(_posTexture2, sampler_posTexture2, vatUV_next, 0);
|
||
posRGB += pos2.rgb * 0.01;
|
||
posRGB_next += pos2_next.rgb * 0.01;
|
||
}
|
||
|
||
// 解码粒子中心
|
||
float3 posDecoded = posRGB * (boundsMax - boundsMin) + boundsMin;
|
||
float3 posDecoded_next = posRGB_next * (boundsMax - boundsMin) + boundsMin;
|
||
|
||
float3 particlePos = comparisonBoundMaxb ? posRGB : posDecoded;
|
||
float3 particlePos_next = comparisonBoundMaxb ? posRGB_next : posDecoded_next;
|
||
|
||
// 帧间插值
|
||
float3 particlePosF = (_B_interpolate > 0.5)
|
||
? lerp(particlePos, particlePos_next, frameAlpha)
|
||
: particlePos;
|
||
|
||
// Pscale
|
||
float posA_f = (_B_interpolate > 0.5) ? lerp(posA, posA_next, frameAlpha) : posA;
|
||
float pscaleRaw = posA_f / pscaleDenom;
|
||
|
||
// 每粒子随机缩放
|
||
float perParticleRandom = HVAT_HashRandom2D(float2(uv1r, uv1g));
|
||
float additionalPscaleMul = 1.0 + perParticleRandom;
|
||
|
||
// 原点遮挡 mask
|
||
float distThis = distance(particlePos, float3(0, 0, 0));
|
||
float distNext = distance(particlePos_next, float3(0, 0, 0));
|
||
float maskThis = saturate(sign(distThis - _originRadius));
|
||
float maskNext = saturate(sign(distNext - _originRadius));
|
||
float maskF = (_B_interpolate > 0.5) ? lerp(maskThis, maskNext, frameAlpha) : maskThis;
|
||
|
||
float pscaleFinal;
|
||
if (_B_pscaleAreInPosA > 0.5)
|
||
{
|
||
pscaleFinal = (_B_hideOverlappingOrigin > 0.5)
|
||
? pscaleRaw * _globalPscaleMul * additionalPscaleMul * maskF
|
||
: pscaleRaw * _globalPscaleMul * additionalPscaleMul;
|
||
}
|
||
else
|
||
{
|
||
pscaleFinal = (_B_hideOverlappingOrigin > 0.5)
|
||
? _globalPscaleMul * additionalPscaleMul * maskF
|
||
: _globalPscaleMul * additionalPscaleMul;
|
||
}
|
||
|
||
// Billboard 方向轴
|
||
float3 viewRight = float3(1, 0, 0);
|
||
float3 viewUp = float3(0, 1, 0);
|
||
float velStretch = 1.0;
|
||
float3 headingViewDir = float3(0, 0, 0);
|
||
|
||
if (_B_CAN_SPIN > 0.5)
|
||
{
|
||
// 从颜色通道读取 heading
|
||
if (_B_LOAD_COL_TEX > 0.5)
|
||
{
|
||
float4 colThis = SAMPLE_TEXTURE2D_LOD(_colTexture, sampler_colTexture, vatUV, 0);
|
||
headingViewDir = float3(-colThis.r, colThis.g, colThis.b);
|
||
}
|
||
|
||
if (_B_spinFromHeading > 0.5)
|
||
{
|
||
float2 hXY = headingViewDir.xy;
|
||
float hLen = length(hXY);
|
||
float2 hDir = (hLen > 1e-5) ? hXY / hLen : float2(1, 0);
|
||
viewRight = float3(hDir.x, hDir.y, 0);
|
||
viewUp = cross(viewRight, float3(0, 0, -1));
|
||
velStretch = _scaleByVelAmount * hLen;
|
||
}
|
||
else
|
||
{
|
||
float angle = frac(_spinPhase) * 6.283185;
|
||
float c = cos(angle);
|
||
float s = sin(angle);
|
||
viewRight = float3(c, s, 0);
|
||
viewUp = cross(viewRight, float3(0, 0, -1));
|
||
}
|
||
}
|
||
|
||
// 视图空间 → 世界空间 → 物体空间
|
||
float3 worldRight = mul((float3x3)UNITY_MATRIX_I_V, viewRight);
|
||
float3 worldUp = mul((float3x3)UNITY_MATRIX_I_V, viewUp);
|
||
float3 worldFwd = mul((float3x3)UNITY_MATRIX_I_V, float3(0, 0, 1));
|
||
|
||
float3 rightOS = normalize(TransformWorldToObjectDir(worldRight));
|
||
float3 upOS = normalize(TransformWorldToObjectDir(worldUp));
|
||
float3 normalOS_new = normalize(TransformWorldToObjectDir(worldFwd));
|
||
|
||
// Billboard 顶点偏移
|
||
float cornerX = input.texcoords.r - 0.5;
|
||
float cornerY = input.texcoords.g - 0.5;
|
||
|
||
float3 offsetX = rightOS * cornerX * _widthBaseScale * pscaleFinal;
|
||
float3 offsetY = upOS * cornerY * _heightBaseScale * pscaleFinal * velStretch;
|
||
|
||
float3 finalPosOS = particlePosF + offsetX + offsetY;
|
||
|
||
// 崩塌
|
||
finalPosOS = (uv1g <= 0.1) ? float3(0, 0, 0) : finalPosOS;
|
||
|
||
positionOS.xyz = finalPosOS;
|
||
normalOS = normalOS_new;
|
||
return;
|
||
}
|
||
#endif
|
||
}
|
||
|
||
#endif // HOUDINI_VAT_INCLUDED
|