Skip to content

Instantly share code, notes, and snippets.

@AYANO-TFT
Last active July 11, 2025 14:43
Show Gist options
  • Select an option

  • Save AYANO-TFT/7b4818f024c797e03b10db2cadf20111 to your computer and use it in GitHub Desktop.

Select an option

Save AYANO-TFT/7b4818f024c797e03b10db2cadf20111 to your computer and use it in GitHub Desktop.
LightVolumesCustom.cginc
#ifndef VRC_LIGHT_VOLUMES_INCLUDED
uniform float _UdonLightVolumeEnabled;
uniform float _UdonLightVolumeCount;
uniform float _UdonLightVolumeAdditiveMaxOverdraw;
uniform float _UdonLightVolumeAdditiveCount;
uniform float _UdonLightVolumeProbesBlend;
uniform float _UdonLightVolumeSharpBounds;
uniform sampler3D _UdonLightVolume;
uniform float4x4 _UdonLightVolumeInvWorldMatrix[32];
uniform float3 _UdonLightVolumeRotation[64];
uniform float3 _UdonLightVolumeInvLocalEdgeSmooth[32];
uniform float3 _UdonLightVolumeUvw[192];
uniform float4 _UdonLightVolumeColor[32];
#endif
// https://huwahuwa2017.hatenablog.com/entry/2024/12/05/021616
float4x4 Inverse(float4x4 m)
{
float4 det1 = mad(m._33_31_13_11, m._44_42_24_22, -m._34_32_14_12 * m._43_41_23_21);
float4 det2 = mad(m._23_21_13_11, m._44_42_34_32, -m._24_22_14_12 * m._43_41_33_31);
float4 det3 = mad(m._23_21_13_11, m._34_32_44_42, -m._24_22_14_12 * m._33_31_43_41);
float4x4 im;
im._11_21_31_41 = mad(m._22_21_24_23, det1.xxyy, mad(-m._32_31_34_33, det2.xxyy, m._42_41_44_43 * det3.xxyy));
im._12_22_32_42 = mad(m._12_11_14_13, det1.xxyy, mad(-m._32_31_34_33, det3.zzww, m._42_41_44_43 * det2.zzww));
im._13_23_33_43 = mad(m._12_11_14_13, det2.xxyy, mad(-m._22_21_24_23, det3.zzww, m._42_41_44_43 * det1.zzww));
im._14_24_34_44 = mad(m._12_11_14_13, det3.xxyy, mad(-m._22_21_24_23, det2.zzww, m._32_31_34_33 * det1.zzww));
im._21_41 = -im._21_41;
im._12_32 = -im._12_32;
im._23_43 = -im._23_43;
im._14_34 = -im._14_34;
float invDet = rcp(dot(m[0], im._11_21_31_41));
im._11_21_31_41 *= invDet;
im._12_22_32_42 *= invDet;
im._13_23_33_43 *= invDet;
im._14_24_34_44 *= invDet;
return im;
}
// LV_LocalFromVolumeの逆変換
// volumeIDはSH計算のマテリアルで0など決め打ちする想定
float3 LV_VolumeFromLocal(uint volumeID, float3 localUVW) {
return mul(Inverse(_UdonLightVolumeInvWorldMatrix[volumeID]), float4(localUVW, 1.0)).xyz;
}
// LV_LocalToIslandの逆変換
// islandUVW(atlasUVW)からlocalUVWとtexIDを返す
// volumeIDはSH計算のマテリアルで0など決め打ちする想定
float3 LV_IslandToLocal(uint volumeID, float3 islandUVW, out uint outTexID)
{
const float3 eps = 1e-6.xxx; // 0 除算回避
const float bound = 0.5001; // 判定マージン
[unroll]
for (uint i = 0; i < 3; ++i) // texID = 0–2
{
uint uvwID = volumeID * 6 + i * 2;
float3 uvwMin = _UdonLightVolumeUvw[uvwID ].xyz;
float3 uvwMax = _UdonLightVolumeUvw[uvwID + 1].xyz;
float3 range = max(uvwMax - uvwMin, eps);
float3 localUVW = (islandUVW - uvwMin) / range - 0.5;
// 全成分が [-0.5,0.5] に収まっているか
if (all(abs(localUVW) <= bound.xxx))
{
outTexID = i;
return localUVW;
}
}
// 失敗
outTexID = 0xFFFFFFFFu;
return 0.0.xxx;
}
// IDが不要なケース
float3 LV_IslandToLocalNoID(uint volumeID, float3 islandUVW)
{
const float3 eps = 1e-6.xxx; // 0 除算回避
const float bound = 0.5001; // 判定マージン
[unroll]
for (uint i = 0; i < 3; ++i) // texID = 0–2
{
uint uvwID = volumeID * 6 + i * 2;
float3 uvwMin = _UdonLightVolumeUvw[uvwID ].xyz;
float3 uvwMax = _UdonLightVolumeUvw[uvwID + 1].xyz;
float3 range = max(uvwMax - uvwMin, eps);
float3 localUVW = (islandUVW - uvwMin) / range - 0.5;
// 全成分が [-0.5,0.5] に収まっているか
if (all(abs(localUVW) <= bound.xxx))
{
return localUVW;
}
}
// 失敗
return 0.0.xxx;
}
// SH書き込み用Geometry Shaderとそれの読み取りで使った
float4 LV_IslandToClipPos(float3 islandUVW, float3 atlasResolution)
{
float y = floor(islandUVW.y * atlasResolution.y + 0.5) / atlasResolution.y;
float2 uv = float2(islandUVW.x / atlasResolution.y + y,
islandUVW.z);
return float4(uv * 2.0 - 1.0, 0.0, 1.0);
}
float4 ComputeSH1_PointLight(
float3 lightPos, float3 color, float intensity,
float3 worldPos, uint texID)
{
const float k0 = 0.282095; // 1/(2√π)
const float k1 = 0.488603; // √3/(2√π)
// ベクトルと距離・減衰
float3 toLight = lightPos - worldPos;
float distSq = max(dot(toLight, toLight), 1e-1); // 0 除算回避、ちょっと大きめ
float atten = intensity / distSq; // 逆二乗減衰
float3 dir = toLight * rsqrt(distSq); // normalize(toLight)
// 減衰
float3 c = color * atten;
// L0
float3 L0 = k0 * c;
// L1
float3 B1 = k1 * dir;
float3 L1r = B1 * c.r;
float3 L1g = B1 * c.g;
float3 L1b = B1 * c.b;
if(texID == 0) return float4(L0, L1r.z);
if(texID == 1) return float4(L1r.x, L1g.x, L1b.x, L1g.z);
if(texID == 2) return float4(L1r.y, L1g.y, L1b.y, L1b.z);
return float4(0,0,0,0);
}
@AYANO-TFT
Copy link
Author

AYANO-TFT commented May 25, 2025

VRC Light Volume : https://github.com/REDSIM/VRCLightVolumes
のリアルタイム化拡張に使っている関数群です。

LightVolumeAtlasのUVWからworldPosを復元できるとライティング情報の計算がしやすいと思います。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment