Files
ichni_Official/Packages/NB_FX/XuanXuanRenderUtility/Shader/HLSL/HoudiniVAT.hlsl
SoulliesOfficial 40fa80cd70 NB_FX
2026-07-17 17:46:16 -04:00

522 lines
22 KiB
HLSL
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#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