This commit is contained in:
SoulliesOfficial
2026-07-17 17:46:16 -04:00
parent b00ac27e3a
commit 40fa80cd70
1178 changed files with 51090 additions and 0 deletions

View File

@@ -0,0 +1,521 @@
#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