yum/3ner
A toon shader for Unity's BIRP.
git clone https://git.yummers.dev/yum/3ner
169fe5e
master
1#ifndef VRC_LIGHT_VOLUMES_INCLUDED 2#define VRC_LIGHT_VOLUMES_INCLUDED 3#define VRCLV_VERSION 2 4#define VRCLV_MAX_VOLUMES_COUNT 32 5#define VRCLV_MAX_LIGHTS_COUNT 128 6 7 8#ifndef SHADER_TARGET_SURFACE_ANALYSIS 9cbuffer LightVolumeUniforms { 10#endif 11 12// Are Light Volumes enabled on scene? can be 0 or 1 13uniform float _UdonLightVolumeEnabled; 14 15// Rreturns 1, 2 or other number if there are light volumes on the scene. Number represents the light volumes system internal version number. 16uniform float _UdonLightVolumeVersion; 17 18// All volumes count in scene 19uniform float _UdonLightVolumeCount; 20 21// Additive volumes max overdraw count 22uniform float _UdonLightVolumeAdditiveMaxOverdraw; 23 24// Additive volumes count 25uniform float _UdonLightVolumeAdditiveCount; 26 27// Should volumes be blended with lightprobes? 28uniform float _UdonLightVolumeProbesBlend; 29 30// Should volumes be with sharp edges when not blending with each other 31uniform float _UdonLightVolumeSharpBounds; 32 33// World to Local (-0.5, 0.5) UVW Matrix 4x4 34uniform float4x4 _UdonLightVolumeInvWorldMatrix[VRCLV_MAX_VOLUMES_COUNT]; 35 36// L1 SH quaternion rotation (relative to baked rotation) 37//uniform float4 _UdonLightVolumeRotationQuaternion[32]; 38uniform float4 _UdonLightVolumeRotation[VRCLV_MAX_VOLUMES_COUNT * 2]; // Legacy! Used in this version to have back compatibility with older worlds. Array commented above will be used in future releases! Legacy! 39 40// Value that is needed to smoothly blend volumes ( BoundsScale / edgeSmooth ) 41uniform float3 _UdonLightVolumeInvLocalEdgeSmooth[VRCLV_MAX_VOLUMES_COUNT]; 42 43// AABB Bounds of islands on the 3D Texture atlas. XYZ: UvwMin, W: Scale per axis 44// uniform float4 _UdonLightVolumeUvwScale[96]; 45uniform float3 _UdonLightVolumeUvw[VRCLV_MAX_VOLUMES_COUNT * 6]; // Legacy! AABB Bounds of islands on the 3D Texture atlas. Array commented above will be used in future releases! Legacy! 46 47// XYZ: AABB Bounds of islands on the 3D Texture atlas storing occlusion. W: Scale factor for the occlusion volume UVW 48// This is optional data. If the volume has no occlusion, the value will be (-1, -1, -1, -1). 49uniform float4 _UdonLightVolumeOcclusionUvw[VRCLV_MAX_VOLUMES_COUNT]; 50 51// Color multiplier (RGB) | If we actually need to rotate L1 components at all (A) 52uniform float4 _UdonLightVolumeColor[VRCLV_MAX_VOLUMES_COUNT]; 53 54// Point Lights count 55uniform float _UdonPointLightVolumeCount; 56 57// Cubemaps count in the custom textures array 58uniform float _UdonPointLightVolumeCubeCount; 59 60// For point light: XYZ = Position, W = Inverse squared range 61// For spot light: XYZ = Position, W = Inverse squared range, negated 62// For area light: XYZ = Position, W = Width 63uniform float4 _UdonPointLightVolumePosition[VRCLV_MAX_LIGHTS_COUNT]; 64 65// For point light: XYZ = Color, W = Cos of angle (for LUT) 66// For spot light: XYZ = Color, W = Cos of outer angle if no custom texture, tan of outer angle otherwise 67// For area light: XYZ = Color, W = 2 + Height 68uniform float4 _UdonPointLightVolumeColor[VRCLV_MAX_LIGHTS_COUNT]; 69 70// For point light: XYZW = Rotation quaternion 71// For spot light: XYZ = Direction, W = Cone falloff 72// For area light: XYZW = Rotation quaternion 73uniform float4 _UdonPointLightVolumeDirection[VRCLV_MAX_LIGHTS_COUNT]; 74 75// X = Custom ID: 76// If parametric: X stores 0 77// If uses custom lut: X stores LUT ID with positive sign 78// If uses custom texture: X stores texture ID with negative sign 79// Y = Shadowmask index. If light doesn't use shadowmask, the index will be negative. 80// Z = Squared Culling Range. Just a precalculated culling range to not recalculate in in shader. 81uniform float3 _UdonPointLightVolumeCustomID[VRCLV_MAX_LIGHTS_COUNT]; 82 83// If we are far enough from a light that the irradiance 84// is guaranteed lower than the threshold defined by this value, 85// we cull the light. 86uniform float _UdonLightBrightnessCutoff; 87 88// The number of volumes that provide occlusion data. 89// We use this to take faster paths when no occlusion is needed. 90uniform float _UdonLightVolumeOcclusionCount; 91 92#ifndef SHADER_TARGET_SURFACE_ANALYSIS 93} 94#endif 95 96#ifndef SHADER_TARGET_SURFACE_ANALYSIS 97 98// Main 3D Texture atlas 99uniform Texture3D _UdonLightVolume; 100uniform SamplerState sampler_UdonLightVolume; 101// First elements must be cubemap faces (6 face textures per cubemap). Then goes other textures 102uniform Texture2DArray _UdonPointLightVolumeTexture; 103// Samples a texture using mip 0, and reusing a single sampler 104#define LV_SAMPLE(tex, uvw) tex.SampleLevel(sampler_UdonLightVolume, uvw, 0) 105 106#else 107 108// Dummy macro definition to satisfy MojoShader (surface shaders). 109#define LV_SAMPLE(tex, uvw) float4(0,0,0,0) 110 111#endif 112 113#define LV_PI 3.141592653589793f 114#define LV_PI2 6.283185307179586f 115 116// Smoothstep to 0, 1 but cheaper 117float LV_Smoothstep01(float x) { 118 return x * x * (3 - 2 * x); 119} 120 121// Rotates vector by Quaternion 122float3 LV_MultiplyVectorByQuaternion(float3 v, float4 q) { 123 float3 t = 2.0 * cross(q.xyz, v); 124 return v + q.w * t + cross(q.xyz, t); 125} 126 127// Builds orthonormal axes from a normalized quaternion. 128void LV_QuaternionAxes(float4 q, out float3 xAxis, out float3 yAxis, out float3 zAxis) { 129 float x2 = q.x + q.x; 130 float y2 = q.y + q.y; 131 float z2 = q.z + q.z; 132 float xx = q.x * x2; 133 float yy = q.y * y2; 134 float zz = q.z * z2; 135 float xy = q.x * y2; 136 float xz = q.x * z2; 137 float yz = q.y * z2; 138 float wx = q.w * x2; 139 float wy = q.w * y2; 140 float wz = q.w * z2; 141 142 xAxis = float3(1.0f - yy - zz, xy + wz, xz - wy); 143 yAxis = float3(xy - wz, 1.0f - xx - zz, yz + wx); 144 zAxis = float3(xz + wy, yz - wx, 1.0f - xx - yy); 145} 146 147// Rotates vector by Matrix 3x3 with precomputed third axis 148float3 LV_MultiplyVectorByMatrix3x3(float3 v, float3 r0, float3 r1, float3 r2) { 149 return float3(dot(v, r0), dot(v, r1), dot(v, r2)); 150} 151 152// Fast approximate arctangent for positive values. Max error is small enough for area light attenuation. 153float LV_FastAtanPositive(float x) { 154 float x2 = x * x; 155 float atanSmall = x * rcp(1.0f + 0.280872f * x2); 156 float invX = rcp(max(x, 1e-6f)); 157 float atanLarge = LV_PI * 0.5f - invX * rcp(1.0f + 0.280872f * invX * invX); 158 return x <= 1.0f ? atanSmall : atanLarge; 159} 160 161// Forms specular based on roughness 162float LV_DistributionGGX(float NoH, float roughness) { 163 float f = (roughness - 1) * ((roughness + 1) * (NoH * NoH)) + 1; 164 return (roughness * roughness) / ((float) LV_PI * f * f); 165} 166 167// Checks if local UVW point is in bounds from -0.5 to +0.5 168bool LV_PointLocalAABB(float3 localUVW) { 169 return all(abs(localUVW) <= 0.5); 170} 171 172// Calculates local UVW using volume ID 173float3 LV_LocalFromVolume(uint volumeID, float3 worldPos) { 174 return mul(_UdonLightVolumeInvWorldMatrix[volumeID], float4(worldPos, 1.0)).xyz; 175} 176 177// Linear single SH L1 channel evaluation 178float LV_EvaluateSH(float L0, float3 L1, float3 n) { 179 return L0 + dot(L1, n); 180} 181 182// Samples a cubemap from _UdonPointLightVolumeTexture array 183float4 LV_SampleCubemapArray(uint id, float3 dir) { 184 float3 absDir = abs(dir); 185 float2 uv; 186 uint face; 187 if (absDir.x >= absDir.y && absDir.x >= absDir.z) { 188 face = dir.x > 0 ? 0 : 1; 189 uv = float2((dir.x > 0 ? -dir.z : dir.z), -dir.y) * rcp(absDir.x); 190 } else if (absDir.y >= absDir.z) { 191 face = dir.y > 0 ? 2 : 3; 192 uv = float2(dir.x, (dir.y > 0 ? dir.z : -dir.z)) * rcp(absDir.y); 193 } else { 194 face = dir.z > 0 ? 4 : 5; 195 uv = float2((dir.z > 0 ? dir.x : -dir.x), -dir.y) * rcp(absDir.z); 196 } 197 float3 uvid = float3(uv * 0.5 + 0.5, id * 6 + face); 198 return LV_SAMPLE(_UdonPointLightVolumeTexture, uvid); 199} 200 201// Projects a quad light into L1 SH using a cheap solid-angle approximation. 202// The axis-aligned case follows the same attenuation law as ComputeAreaLightSquaredBoundingSphere(). 203float4 LV_ProjectFastQuadLightIrradianceSH(float3 lightToWorldPos, float4 rotationQuat, float2 size) { 204 float3 xAxis; 205 float3 yAxis; 206 float3 normal; 207 LV_QuaternionAxes(rotationQuat, xAxis, yAxis, normal); 208 209 float3 localPos = float3(dot(lightToWorldPos, xAxis), dot(lightToWorldPos, yAxis), dot(lightToWorldPos, normal)); 210 [branch] if (localPos.z <= 0.0f) return float4(0.0f, 0.0f, 0.0f, 0.0f); 211 212 float2 halfSize = size * 0.5f; 213 float area = max(size.x * size.y, 1e-6f); 214 float extentSq = max(dot(halfSize, halfSize), 1e-6f); 215 216 float2 closestXY = clamp(localPos.xy, -halfSize, halfSize); 217 float2 rectDelta = localPos.xy - closestXY; 218 float rectDeltaSq = dot(rectDelta, rectDelta); 219 float planeSq = localPos.z * localPos.z; 220 float closestSqDist = max(rectDeltaSq + planeSq, 1e-6f); 221 float centerSqDist = max(dot(localPos, localPos), 1e-6f); 222 223 float distanceBlend = (rectDeltaSq + planeSq) * rcp(rectDeltaSq + planeSq + extentSq); 224 float solidSqDist = lerp(closestSqDist, centerSqDist, distanceBlend); 225 float invSolidDist = rsqrt(solidSqDist); 226 float invExtendedDist = rsqrt(solidSqDist + extentSq); 227 228 float atanArg = area * localPos.z * invSolidDist * invSolidDist * invExtendedDist * 0.25f; 229 float solidAngle = 4.0f * LV_FastAtanPositive(atanArg); 230 float l0 = solidAngle * (0.25f / LV_PI); 231 232 float2 representativeXY = lerp(closestXY, float2(0.0f, 0.0f), distanceBlend); 233 float3 worldDir = xAxis * representativeXY.x + yAxis * representativeXY.y - lightToWorldPos; 234 float3 dir = worldDir * rsqrt(max(dot(worldDir, worldDir), 1e-6f)); 235 float directionality = saturate(1.0f - solidAngle * (0.25f / LV_PI)); 236 237 return float4(dir * (l0 * directionality), l0); 238} 239 240// Samples a quad light, including culling 241void LV_QuadLight(float3 worldPos, float3 centroidPos, float4 rotationQuat, float2 size, float3 color, float sqMaxDist, float occlusion, inout float3 L0, inout float3 L1r, inout float3 L1g, inout float3 L1b, inout uint count) { 242 243 float3 lightToWorldPos = worldPos - centroidPos; 244 245 float4 areaLightSH = LV_ProjectFastQuadLightIrradianceSH(lightToWorldPos, rotationQuat, size); 246 [branch] if (areaLightSH.w <= 0.0f) return; 247 248 // Attenuate the light based on distance to the bounding sphere, so we don't get hard seam at the edge. 249 float sqCutoffDist = sqMaxDist - dot(lightToWorldPos, lightToWorldPos); 250 color.rgb *= saturate(sqCutoffDist / sqMaxDist) * LV_PI * occlusion; 251 252 L0 += areaLightSH.w * color.rgb; 253 L1r += areaLightSH.xyz * color.r; 254 L1g += areaLightSH.xyz * color.g; 255 L1b += areaLightSH.xyz * color.b; 256 257 count++; 258} 259 260// Calculates point light attenuation. Returns false if it's culled 261float3 LV_PointLightAttenuation(float sqdist, float sqlightSize, float3 color, float brightnessCutoff, float sqMaxDist) { 262 float mask = saturate(1 - sqdist / sqMaxDist); 263 return mask * mask * color * sqlightSize / (sqdist + sqlightSize); 264} 265 266// Calculates point light solid angle coefficient 267float LV_PointLightSolidAngle(float sqdist, float sqlightSize) { 268 return saturate(sqrt(sqdist / (sqlightSize + sqdist))); 269} 270 271// Calculares a spherical light source 272void LV_SphereLight(float sqdist, float3 dirN, float sqlightSize, float3 color, float occlusion, float sqMaxDist, inout float3 L0, inout float3 L1r, inout float3 L1g, inout float3 L1b, inout uint count) { 273 274 float3 att = LV_PointLightAttenuation(sqdist, sqlightSize, color, _UdonLightBrightnessCutoff, sqMaxDist); 275 276 float3 l0 = att * occlusion; 277 float3 l1 = dirN * LV_PointLightSolidAngle(sqdist, sqlightSize); 278 279 L0 += l0; 280 L1r += l0.r * l1; 281 L1g += l0.g * l1; 282 L1b += l0.b * l1; 283 count++; 284 285} 286 287// Calculares a spherical spot light source 288void LV_SphereSpotLight(float sqdist, float3 dirN, float sqlightSize, float3 att, float spotMask, float cosAngle, float coneFalloff, float occlusion, inout float3 L0, inout float3 L1r, inout float3 L1g, inout float3 L1b, inout uint count) { 289 290 float smoothedCone = LV_Smoothstep01(saturate(spotMask * coneFalloff)); 291 float3 l0 = att * (occlusion * smoothedCone); 292 float3 l1 = dirN * LV_PointLightSolidAngle(sqdist, sqlightSize * saturate(1 - cosAngle)); 293 294 L0 += l0; 295 L1r += l0.r * l1; 296 L1g += l0.g * l1; 297 L1b += l0.b * l1; 298 count++; 299 300} 301 302// Calculares a spherical spot light source 303void LV_SphereSpotLightCookie(float sqdist, float3 dirN, float sqlightSize, float3 att, float4 lightRot, float tanAngle, uint customId, float occlusion, inout float3 L0, inout float3 L1r, inout float3 L1g, inout float3 L1b, inout uint count) { 304 305 float3 localDir = LV_MultiplyVectorByQuaternion(-dirN, lightRot); 306 float2 uv = localDir.xy * rcp(localDir.z * tanAngle); 307 [branch] if ( 308 localDir.z <= 0.0 || // Culling by direction 309 abs(uv.x) > 1.0 || abs(uv.y) > 1.0 // Culling by UV 310 ) return; 311 312 uint id = (uint) _UdonPointLightVolumeCubeCount * 5 - customId - 1; 313 float3 uvid = float3(uv * 0.5 + 0.5, id); 314 float angleSize = saturate(rsqrt(1 + tanAngle * tanAngle)); 315 float4 cookie = LV_SAMPLE(_UdonPointLightVolumeTexture, uvid); 316 317 float3 l0 = att * cookie.rgb * (cookie.a * occlusion); 318 float3 l1 = dirN * LV_PointLightSolidAngle(sqdist, sqlightSize * (1 - angleSize)); 319 320 L0 += l0; 321 L1r += l0.r * l1; 322 L1g += l0.g * l1; 323 L1b += l0.b * l1; 324 count++; 325 326} 327 328// Calculares a spherical spot light source 329void LV_SphereSpotLightAttenuationLUT(float sqdist, float3 dirN, float sqlightSize, float3 color, float spotMask, float cosAngle, uint customId, float occlusion, inout float3 L0, inout float3 L1r, inout float3 L1g, inout float3 L1b, inout uint count) { 330 331 float dirRadius = sqdist * abs(sqlightSize); 332 float spot = 1 - saturate(spotMask * rcp(1 - cosAngle)); 333 uint id = (uint) _UdonPointLightVolumeCubeCount * 5 + customId - 1; 334 float3 uvid = float3(sqrt(float2(spot, dirRadius)), id); 335 float3 att = color.rgb * LV_SAMPLE(_UdonPointLightVolumeTexture, uvid).xyz * occlusion; 336 337 L0 += att; 338 L1r += dirN * att.r; 339 L1g += dirN * att.g; 340 L1b += dirN * att.b; 341 342 count++; 343 344} 345 346// Samples a spot light, point light or quad/area light 347void LV_PointLight(uint id, float3 worldPos, float4 occlusion, inout float3 L0, inout float3 L1r, inout float3 L1g, inout float3 L1b, inout uint count) { 348 349 // IDs and range data 350 float3 customID_data = _UdonPointLightVolumeCustomID[id]; 351 int shadowId = (int) customID_data.y; // Shadowmask id 352 int customId = (int) customID_data.x; // Custom Texture ID 353 float sqrRange = customID_data.z; // Squared culling distance 354 355 float4 pos = _UdonPointLightVolumePosition[id]; // Light position and inversed squared range 356 float3 dir = pos.xyz - worldPos; 357 float sqlen = max(dot(dir, dir), 1e-6); 358 [branch] if (sqlen > sqrRange) return; // Early distance based culling 359 360 // Processing lights occlusion 361 float lightOcclusion = 1; 362 if (_UdonLightVolumeOcclusionCount != 0 && shadowId >= 0) { 363 lightOcclusion = dot(occlusion, float4(shadowId == 0, shadowId == 1, shadowId == 2, shadowId == 3)); 364 } 365 366 float4 color = _UdonPointLightVolumeColor[id]; // Color, angle 367 float4 ldir = _UdonPointLightVolumeDirection[id]; // Dir + falloff or Rotation 368 369 [branch] if (pos.w < 0) { // It is a spot light 370 371 float3 dirN = dir * rsqrt(sqlen); 372 float angle = color.w; 373 float spotMask = dot(ldir.xyz, -dirN) - angle; 374 [branch] if(customId >= 0 && spotMask < 0) return; // Spot cone based culling 375 376 [branch] if (customId > 0) { // If it uses Attenuation LUT 377 378 LV_SphereSpotLightAttenuationLUT(sqlen, dirN, -pos.w, color.rgb, spotMask, angle, customId, lightOcclusion, L0, L1r, L1g, L1b, count); 379 380 } else { // If it uses default parametric attenuation 381 382 float3 att = LV_PointLightAttenuation(sqlen, -pos.w, color.rgb, _UdonLightBrightnessCutoff, sqrRange); 383 384 [branch] if (customId < 0) { // If uses cookie 385 386 LV_SphereSpotLightCookie(sqlen, dirN, -pos.w, att, ldir, angle, customId, lightOcclusion, L0, L1r, L1g, L1b, count); 387 388 } else { // If it uses default parametric attenuation 389 390 LV_SphereSpotLight(sqlen, dirN, -pos.w, att, spotMask, angle, ldir.w, lightOcclusion, L0, L1r, L1g, L1b, count); 391 392 } 393 394 } 395 396 } else if (color.w <= 1.5f) { // It is a point light 397 398 float3 dirN = dir * rsqrt(sqlen); 399 [branch] if (customId > 0) { // Using LUT 400 401 float invSqRange = abs(pos.w); // Sign of range defines if it's point light (positive) or a spot light (negative) 402 float dirRadius = sqlen * invSqRange; 403 uint id = (uint) _UdonPointLightVolumeCubeCount * 5 + customId; 404 float3 uvid = float3(sqrt(float2(0, dirRadius)), id); 405 float3 att = color.rgb * LV_SAMPLE(_UdonPointLightVolumeTexture, uvid).xyz * lightOcclusion; 406 407 L0 += att; 408 L1r += dirN * att.r; 409 L1g += dirN * att.g; 410 L1b += dirN * att.b; 411 412 count++; 413 414 } else { // If it uses default parametric attenuation 415 416 float3 l0 = 0, l1r = 0, l1g = 0, l1b = 0; 417 LV_SphereLight(sqlen, dirN, pos.w, color.rgb, lightOcclusion, sqrRange, l0, l1r, l1g, l1b, count); 418 419 float3 cubeColor = 1; 420 [branch] if (customId < 0) { // If it uses a cubemap 421 uint id = -customId - 1; // Cubemap ID starts from zero and should not take in count texture array slices count. 422 cubeColor = LV_SampleCubemapArray(id, LV_MultiplyVectorByQuaternion(dirN, ldir)).xyz; 423 } 424 425 L0 += l0 * cubeColor; 426 L1r += l1r * cubeColor.r; 427 L1g += l1g * cubeColor.g; 428 L1b += l1b * cubeColor.b; 429 } 430 431 } else { // It is an area light 432 433 LV_QuadLight(worldPos, pos.xyz, ldir, float2(pos.w, color.w - 2.0f), color.rgb, sqrRange, lightOcclusion, L0, L1r, L1g, L1b, count); 434 435 } 436 437} 438 439// Samples 3 SH textures and packing them into L1 channels 440void LV_SampleLightVolumeTex(float3 uvw0, float3 uvw1, float3 uvw2, out float3 L0, out float3 L1r, out float3 L1g, out float3 L1b) { 441 // Sampling 3D Atlas 442 float4 tex0 = LV_SAMPLE(_UdonLightVolume, uvw0); 443 float4 tex1 = LV_SAMPLE(_UdonLightVolume, uvw1); 444 float4 tex2 = LV_SAMPLE(_UdonLightVolume, uvw2); 445 // Packing final data 446 L0 = tex0.rgb; 447 L1r = float3(tex1.r, tex2.r, tex0.a); 448 L1g = float3(tex1.g, tex2.g, tex1.a); 449 L1b = float3(tex1.b, tex2.b, tex2.a); 450} 451 452// Bounds mask for a volume rotated in world space, using local UVW 453float LV_BoundsMask(float3 localUVW, float3 invLocalEdgeSmooth) { 454 float3 distToMin = (localUVW + 0.5) * invLocalEdgeSmooth; 455 float3 distToMax = (0.5 - localUVW) * invLocalEdgeSmooth; 456 float3 fade = saturate(min(distToMin, distToMax)); 457 return fade.x * fade.y * fade.z; 458} 459 460// Default light probes SH components 461void LV_SampleLightProbe(inout float3 L0, inout float3 L1r, inout float3 L1g, inout float3 L1b) { 462 L0 += float3(unity_SHAr.w, unity_SHAg.w, unity_SHAb.w); 463 L1r += unity_SHAr.xyz; 464 L1g += unity_SHAg.xyz; 465 L1b += unity_SHAb.xyz; 466} 467 468// Applies deringing to light probes. Useful if they baked with Bakery L1 469void LV_SampleLightProbeDering(inout float3 L0, inout float3 L1r, inout float3 L1g, inout float3 L1b) { 470 L0 += float3(unity_SHAr.w, unity_SHAg.w, unity_SHAb.w); 471 L1r += unity_SHAr.xyz * 0.565f; 472 L1g += unity_SHAg.xyz * 0.565f; 473 L1b += unity_SHAb.xyz * 0.565f; 474} 475 476// Samples a Volume with ID and Local UVW 477void LV_SampleVolume(uint id, float3 localUVW, inout float3 L0, inout float3 L1r, inout float3 L1g, inout float3 L1b, out float4 occlusion) { 478 479 // Additive UVW 480 //uint uvwID = id * 3; 481 //float4 uvwPos0 = _UdonLightVolumeUvwScale[uvwID]; 482 //float4 uvwPos1 = _UdonLightVolumeUvwScale[uvwID + 1]; 483 //float4 uvwPos2 = _UdonLightVolumeUvwScale[uvwID + 2]; 484 //float3 uvwScale = float3(uvwPos0.w, uvwPos1.w, uvwPos2.w); 485 486 //float3 uvwScaled = saturate(localUVW + 0.5) * uvwScale; 487 //float3 uvw0 = uvwPos0.xyz + uvwScaled; 488 //float3 uvw1 = uvwPos1.xyz + uvwScaled; 489 //float3 uvw2 = uvwPos2.xyz + uvwScaled; 490 491 // Legacy! Commented code above will be used in future releases! Legacy! 492 uint uvwID = id * 6; 493 float3 uvwScaled = saturate(localUVW + 0.5) * (_UdonLightVolumeUvw[uvwID + 1].xyz - _UdonLightVolumeUvw[uvwID].xyz); 494 float3 uvw0 = uvwScaled + _UdonLightVolumeUvw[uvwID].xyz; 495 float3 uvw1 = uvwScaled + _UdonLightVolumeUvw[uvwID + 2].xyz; 496 float3 uvw2 = uvwScaled + _UdonLightVolumeUvw[uvwID + 4].xyz; 497 498 // Sample additive 499 float3 l0, l1r, l1g, l1b; 500 LV_SampleLightVolumeTex(uvw0, uvw1, uvw2, l0, l1r, l1g, l1b); 501 502 // Sample occlusion 503 float4 uvwOcclusion = _UdonLightVolumeOcclusionUvw[id]; 504 [branch] if (uvwOcclusion.x >= 0) { 505 occlusion = 1.0f - LV_SAMPLE(_UdonLightVolume, uvwOcclusion.xyz + uvwScaled * uvwOcclusion.w); 506 } else { 507 occlusion = 1; 508 } 509 510 // Color correction 511 float4 color = _UdonLightVolumeColor[id]; 512 L0 += l0 * color.rgb; 513 l1r *= color.r; 514 l1g *= color.g; 515 l1b *= color.b; 516 517 // Rotate if needed 518 if (color.a != 0) { 519 //float4 r = _UdonLightVolumeRotationQuaternion[id]; 520 //L1r = LV_MultiplyVectorByQuaternion(L1r, r); 521 //L1g = LV_MultiplyVectorByQuaternion(L1g, r); 522 //L1b = LV_MultiplyVectorByQuaternion(L1b, r); 523 524 // Legacy to support older light volumes worlds! Commented code above will be used in future releases! Legacy! 525 float3 r0 = _UdonLightVolumeRotation[id * 2].xyz; 526 float3 r1 = _UdonLightVolumeRotation[id * 2 + 1].xyz; 527 float3 r2 = cross(r0, r1); 528 L1r += LV_MultiplyVectorByMatrix3x3(l1r, r0, r1, r2); 529 L1g += LV_MultiplyVectorByMatrix3x3(l1g, r0, r1, r2); 530 L1b += LV_MultiplyVectorByMatrix3x3(l1b, r0, r1, r2); 531 } else { 532 L1r += l1r; 533 L1g += l1g; 534 L1b += l1b; 535 } 536 537} 538 539float4 LV_SampleVolumeOcclusion(uint id, float3 localUVW) { 540 541 // Sample occlusion 542 float4 uvwOcclusion = _UdonLightVolumeOcclusionUvw[id]; 543 544 [branch] if (uvwOcclusion.x >= 0) { 545 //uint uvwID = id * 3; 546 //float4 uvwPos0 = _UdonLightVolumeUvwScale[uvwID]; 547 //float4 uvwPos1 = _UdonLightVolumeUvwScale[uvwID + 1]; 548 //float4 uvwPos2 = _UdonLightVolumeUvwScale[uvwID + 2]; 549 //float3 uvwScale = float3(uvwPos0.w, uvwPos1.w, uvwPos2.w); 550 //float3 uvwScaled = saturate(localUVW + 0.5) * uvwScale; 551 552 // Legacy to support older light volumes worlds! Commented code above will be used in future releases! Legacy! 553 uint uvwID = id * 6; 554 float3 uvwScaled = saturate(localUVW + 0.5) * (_UdonLightVolumeUvw[uvwID + 1].xyz - _UdonLightVolumeUvw[uvwID].xyz); 555 556 return 1.0f - LV_SAMPLE(_UdonLightVolume, uvwOcclusion.xyz + uvwScaled * uvwOcclusion.w); 557 } else { 558 return 1; 559 } 560 561} 562 563// Calculates L1 SH based on the world position and occlusion factor. Only samples point lights, not light volumes. 564void LV_PointLightVolumeSH(float3 worldPos, float4 occlusion, inout float3 L0, inout float3 L1r, inout float3 L1g, inout float3 L1b) { 565 566 uint pointCount = min((uint) _UdonPointLightVolumeCount, VRCLV_MAX_LIGHTS_COUNT); 567 [branch] if (pointCount == 0) return; 568 569 uint maxOverdraw = min((uint) _UdonLightVolumeAdditiveMaxOverdraw, VRCLV_MAX_LIGHTS_COUNT); 570 uint pcount = 0; // Point lights counter 571 572 [loop] for (uint pid = 0; pid < pointCount && pcount < maxOverdraw; pid++) { 573 LV_PointLight(pid, worldPos, occlusion, L0, L1r, L1g, L1b, pcount); 574 } 575 576} 577 578// Calculates L1 SH and occlusion based on the world position. Only samples light volumes, not point lights. 579void LV_LightVolumeSH(float3 worldPos, inout float3 L0, inout float3 L1r, inout float3 L1g, inout float3 L1b, out float4 occlusion) { 580 581 // Initializing output variables 582 occlusion = 1; 583 float4 mOcclusion = 1; // Multiplicative occlusion. Applies on top of regular occlusion 584 585 // Clamping gloabal iteration counts 586 uint volumesCount = min((uint) _UdonLightVolumeCount, VRCLV_MAX_VOLUMES_COUNT); 587 588 //if (_UdonLightVolumeVersion < VRCLV_VERSION || volumesCount == 0 ) { // Fallback to default light probes if Light Volume are not enabled or a version is too old to have a support 589 [branch] if (volumesCount == 0) { // Legacy! Fallback to default light probes if Light Volume are not enabled or a version is too old to have a support. Legacy! 590 LV_SampleLightProbe(L0, L1r, L1g, L1b); 591 return; 592 } 593 594 uint maxOverdraw = min((uint) _UdonLightVolumeAdditiveMaxOverdraw, VRCLV_MAX_VOLUMES_COUNT); 595 uint additiveCount = min((uint) _UdonLightVolumeAdditiveCount, VRCLV_MAX_VOLUMES_COUNT); 596 bool lightProbesBlend = _UdonLightVolumeProbesBlend; 597 598 uint volumeID_A = -1; // Main, dominant volume ID 599 uint volumeID_B = -1; // Secondary volume ID to blend main with 600 601 float3 localUVW = 0; // Last local UVW to use in disabled Light Probes mode 602 float3 localUVW_A = 0; // Main local UVW 603 float3 localUVW_B = 0; // Secondary local UVW 604 605 // Are A and B volumes NOT found? 606 bool isNoA = true; 607 bool isNoB = true; 608 609 // Additive volumes variables 610 uint addVolumesCount = 0; 611 612 // Iterating through all light volumes with simplified algorithm requiring Light Volumes to be sorted by weight in descending order 613 [loop] for (uint id = 0; id < volumesCount; id++) { 614 localUVW = LV_LocalFromVolume(id, worldPos); 615 [branch] if (LV_PointLocalAABB(localUVW)) { // Intersection test 616 [branch] if (id < additiveCount) { // Sampling additive volumes 617 [branch] if (addVolumesCount < maxOverdraw) { 618 float4 occ; // Multiplicative occlusion 619 LV_SampleVolume(id, localUVW, L0, L1r, L1g, L1b, occ); 620 mOcclusion *= occ; 621 addVolumesCount++; 622 } 623 } else if (isNoA) { // First, searching for volume A 624 volumeID_A = id; 625 localUVW_A = localUVW; 626 isNoA = false; 627 } else { // Next, searching for volume B if A found 628 volumeID_B = id; 629 localUVW_B = localUVW; 630 isNoB = false; 631 break; 632 } 633 } 634 } 635 636 // If no volumes found, using Light Probes as fallback 637 [branch] if (isNoA && lightProbesBlend) { 638 LV_SampleLightProbe(L0, L1r, L1g, L1b); 639 occlusion *= mOcclusion; 640 return; 641 } 642 643 // Fallback to lowest weight light volume if outside of every volume 644 localUVW_A = isNoA ? localUVW : localUVW_A; 645 volumeID_A = isNoA ? volumesCount - 1 : volumeID_A; 646 647 // Volume A SH components, occlusion, and mask to blend volume sides 648 float3 L0_A = 0; 649 float3 L1r_A = 0; 650 float3 L1g_A = 0; 651 float3 L1b_A = 0; 652 float4 occlusion_A = 1; 653 654 // Sampling Light Volume A 655 LV_SampleVolume(volumeID_A, localUVW_A, L0_A, L1r_A, L1g_A, L1b_A, occlusion_A); 656 657 float mask = LV_BoundsMask(localUVW_A, _UdonLightVolumeInvLocalEdgeSmooth[volumeID_A]); 658 [branch] if (mask == 1 || isNoA || (_UdonLightVolumeSharpBounds && isNoB)) { // Returning SH A result if it's the center of mask or out of bounds 659 L0 += L0_A; 660 L1r += L1r_A; 661 L1g += L1g_A; 662 L1b += L1b_A; 663 occlusion = occlusion_A; 664 occlusion *= mOcclusion; 665 return; 666 } 667 668 // Volume B SH components and occlusion 669 float3 L0_B = 0; 670 float3 L1r_B = 0; 671 float3 L1g_B = 0; 672 float3 L1b_B = 0; 673 float4 occlusion_B = 1; 674 675 [branch] if (isNoB && lightProbesBlend) { // No Volume found and light volumes blending enabled 676 677 // Sample Light Probes B 678 LV_SampleLightProbe(L0_B, L1r_B, L1g_B, L1b_B); 679 680 } else { // Blending Volume A and Volume B 681 682 // If no volume b found, use last one found to fallback 683 localUVW_B = isNoB ? localUVW : localUVW_B; 684 volumeID_B = isNoB ? volumesCount - 1 : volumeID_B; 685 686 // Sampling Light Volume B 687 LV_SampleVolume(volumeID_B, localUVW_B, L0_B, L1r_B, L1g_B, L1b_B, occlusion_B); 688 689 } 690 691 // Lerping occlusion 692 occlusion = lerp(occlusion_B, occlusion_A, mask); 693 occlusion *= mOcclusion; 694 695 // Lerping SH components 696 L0 += lerp(L0_B, L0_A, mask); 697 L1r += lerp(L1r_B, L1r_A, mask); 698 L1g += lerp(L1g_B, L1g_A, mask); 699 L1b += lerp(L1b_B, L1b_A, mask); 700 701} 702 703// Calculates L1 SH based on the world position from additive volumes only. Only samples light volumes, not point lights. 704// Also returns an occlusion factor, which may be used for point light shadows. 705void LV_LightVolumeAdditiveSH(float3 worldPos, inout float3 L0, inout float3 L1r, inout float3 L1g, inout float3 L1b, out float4 occlusion) { 706 707 // Initializing output variables 708 occlusion = 1; 709 float4 mOcclusion = 1; // Multiplicative occlusion. Applies on top of regular occlusion 710 711 // Clamping gloabal iteration counts 712 uint additiveCount = min((uint) _UdonLightVolumeAdditiveCount, VRCLV_MAX_VOLUMES_COUNT); 713 //if (_UdonLightVolumeVersion < VRCLV_VERSION || (additiveCount == 0 && pointCount == 0)) return; 714 [branch] if (additiveCount == 0 && (uint) _UdonPointLightVolumeCount == 0) return; // Legacy! 715 716 uint volumesCount = min((uint) _UdonLightVolumeCount, VRCLV_MAX_VOLUMES_COUNT); 717 uint maxOverdraw = min((uint) _UdonLightVolumeAdditiveMaxOverdraw, VRCLV_MAX_VOLUMES_COUNT); 718 719 uint volumeID_A = -1; // Main, dominant volume ID 720 uint volumeID_B = -1; // Secondary volume ID to blend main with 721 722 float3 localUVW = 0; // Last local UVW to use in disabled Light Probes mode 723 float3 localUVW_A = 0; // Main local UVW for Y Axis and Free rotations 724 float3 localUVW_B = 0; // Secondary local UVW 725 726 // Are A and B volumes NOT found? 727 bool isNoA = true; 728 bool isNoB = true; 729 730 // Additive volumes variables 731 uint addVolumesCount = 0; 732 733 // Iterating through all light volumes with simplified algorithm requiring Light Volumes to be sorted by weight in descending order 734 uint count = min(_UdonLightVolumeOcclusionCount == 0 ? additiveCount : volumesCount, VRCLV_MAX_VOLUMES_COUNT); // Only use all volumes if occlusion volumes are enabled 735 [loop] for (uint id = 0; id < count; id++) { 736 localUVW = LV_LocalFromVolume(id, worldPos); 737 [branch] if (LV_PointLocalAABB(localUVW)) { // Intersection test 738 [branch] if (id < additiveCount) { // Sampling additive volumes 739 [branch] if (addVolumesCount < maxOverdraw) { 740 float4 occ; // Multiplicative occlusion 741 LV_SampleVolume(id, localUVW, L0, L1r, L1g, L1b, occ); 742 mOcclusion *= occ; 743 addVolumesCount++; 744 } 745 } else if (isNoA) { // First, searching for volume A 746 volumeID_A = id; 747 localUVW_A = localUVW; 748 isNoA = false; 749 } else { // Next, searching for volume B if A found 750 volumeID_B = id; 751 localUVW_B = localUVW; 752 isNoB = false; 753 break; 754 } 755 } 756 } 757 758 // If no volumes found, or we don't need the occlusion data, we are done 759 [branch] if (isNoA || _UdonLightVolumeOcclusionCount == 0) { 760 occlusion *= mOcclusion; 761 return; 762 } 763 764 // Fallback to lowest weight light volume if outside of every volume 765 localUVW_A = isNoA ? localUVW : localUVW_A; 766 volumeID_A = isNoA ? volumesCount - 1 : volumeID_A; 767 768 // Sampling Light Volume A 769 occlusion = LV_SampleVolumeOcclusion(volumeID_A, localUVW_A); 770 float mask = LV_BoundsMask(localUVW_A, _UdonLightVolumeInvLocalEdgeSmooth[volumeID_A]); 771 772 [branch] if (mask == 1 || (_UdonLightVolumeSharpBounds && isNoB)) { 773 occlusion *= mOcclusion; 774 return; // Returning A result if it's the center of mask or out of bounds 775 } 776 777 // Blending Volume A and Volume B 778 [branch] if (isNoB) occlusion = lerp(1, occlusion, mask); 779 else occlusion = lerp(LV_SampleVolumeOcclusion(volumeID_B, localUVW_B), occlusion, mask); 780 781 occlusion *= mOcclusion; 782 783} 784 785// Calculates speculars for light volumes or any SH L1 data with privided f0 786float3 LightVolumeSpecular(float3 f0, float smoothness, float3 worldNormal, float3 viewDir, float3 L0, float3 L1r, float3 L1g, float3 L1b) { 787 788 float3 specColor = max(float3(dot(reflect(-L1r, worldNormal), viewDir), dot(reflect(-L1g, worldNormal), viewDir), dot(reflect(-L1b, worldNormal), viewDir)), 0); 789 790 float3 rDir = normalize(normalize(L1r) + viewDir); 791 float3 gDir = normalize(normalize(L1g) + viewDir); 792 float3 bDir = normalize(normalize(L1b) + viewDir); 793 794 float rNh = saturate(dot(worldNormal, rDir)); 795 float gNh = saturate(dot(worldNormal, gDir)); 796 float bNh = saturate(dot(worldNormal, bDir)); 797 798 float roughness = 1 - smoothness * 0.9f; 799 float roughExp = roughness * roughness; 800 801 float rSpec = LV_DistributionGGX(rNh, roughExp); 802 float gSpec = LV_DistributionGGX(gNh, roughExp); 803 float bSpec = LV_DistributionGGX(bNh, roughExp); 804 805 float3 specs = (rSpec + gSpec + bSpec) * f0; 806 float3 coloredSpecs = specs * specColor; 807 808 float3 a = coloredSpecs + specs * L0; 809 float3 b = coloredSpecs * 3; 810 811 return max(lerp(a, b, smoothness) * 0.5f, 0.0); 812 813} 814 815// Calculates speculars for light volumes or any SH L1 data 816float3 LightVolumeSpecular(float3 albedo, float smoothness, float metallic, float3 worldNormal, float3 viewDir, float3 L0, float3 L1r, float3 L1g, float3 L1b) { 817 float3 specularf0 = lerp(0.04f, albedo, metallic); 818 return LightVolumeSpecular(specularf0, smoothness, worldNormal, viewDir, L0, L1r, L1g, L1b); 819} 820 821// Calculates speculars for light volumes or any SH L1 data, but simplified, with only one dominant direction with provided f0 822float3 LightVolumeSpecularDominant(float3 f0, float smoothness, float3 worldNormal, float3 viewDir, float3 L0, float3 L1r, float3 L1g, float3 L1b) { 823 824 float3 dominantDir = L1r + L1g + L1b; 825 float3 dir = normalize(normalize(dominantDir) + viewDir); 826 float nh = saturate(dot(worldNormal, dir)); 827 828 float roughness = 1 - smoothness * 0.9f; 829 float roughExp = roughness * roughness; 830 831 float spec = LV_DistributionGGX(nh, roughExp); 832 833 return max(spec * L0 * f0, 0.0) * 1.5f; 834 835} 836 837// Calculates speculars for light volumes or any SH L1 data, but simplified, with only one dominant direction 838float3 LightVolumeSpecularDominant(float3 albedo, float smoothness, float metallic, float3 worldNormal, float3 viewDir, float3 L0, float3 L1r, float3 L1g, float3 L1b) { 839 float3 specularf0 = lerp(0.04f, albedo, metallic); 840 return LightVolumeSpecularDominant(specularf0, smoothness, worldNormal, viewDir, L0, L1r, L1g, L1b); 841} 842 843// Calculate Light Volume Color based on all SH components provided and the world normal 844float3 LightVolumeEvaluate(float3 worldNormal, float3 L0, float3 L1r, float3 L1g, float3 L1b) { 845 return float3(LV_EvaluateSH(L0.r, L1r, worldNormal), LV_EvaluateSH(L0.g, L1g, worldNormal), LV_EvaluateSH(L0.b, L1b, worldNormal)); 846} 847 848// Calculates L1 SH based on the world position. Samples both light volumes and point lights. 849void LightVolumeSH(float3 worldPos, out float3 L0, out float3 L1r, out float3 L1g, out float3 L1b, float3 worldPosOffset = 0) { 850 L0 = 0; L1r = 0; L1g = 0; L1b = 0; 851 if (_UdonLightVolumeEnabled == 0) { 852 LV_SampleLightProbeDering(L0, L1r, L1g, L1b); 853 } else { 854 float4 occlusion = 1; 855 LV_LightVolumeSH(worldPos + worldPosOffset, L0, L1r, L1g, L1b, occlusion); 856 LV_PointLightVolumeSH(worldPos, occlusion, L0, L1r, L1g, L1b); 857 } 858} 859 860// Calculates L1 SH based on the world position from additive volumes only. Samples both light volumes and point lights. 861void LightVolumeAdditiveSH(float3 worldPos, out float3 L0, out float3 L1r, out float3 L1g, out float3 L1b, float3 worldPosOffset = 0) { 862 L0 = 0; L1r = 0; L1g = 0; L1b = 0; 863 if (_UdonLightVolumeEnabled != 0) { 864 float4 occlusion = 1; 865 LV_LightVolumeAdditiveSH(worldPos + worldPosOffset, L0, L1r, L1g, L1b, occlusion); 866 LV_PointLightVolumeSH(worldPos, occlusion, L0, L1r, L1g, L1b); 867 } 868} 869 870// Calculates L0 SH based on the world position. Samples both light volumes and point lights. 871float3 LightVolumeSH_L0(float3 worldPos, float3 worldPosOffset = 0) { 872 if (_UdonLightVolumeEnabled == 0) { 873 return float3(unity_SHAr.w, unity_SHAg.w, unity_SHAb.w); 874 } else { 875 float3 L0 = 0; float4 occlusion = 1; 876 float3 unused_L1; // Let's just pray that compiler will strip everything x.x 877 LV_LightVolumeSH(worldPos + worldPosOffset, L0, unused_L1, unused_L1, unused_L1, occlusion); 878 LV_PointLightVolumeSH(worldPos, occlusion, L0, unused_L1, unused_L1, unused_L1); 879 return L0; 880 } 881} 882 883// Calculates L0 SH based on the world position from additive volumes only. Samples both light volumes and point lights. 884float3 LightVolumeAdditiveSH_L0(float3 worldPos, float3 worldPosOffset = 0) { 885 if (_UdonLightVolumeEnabled == 0) { 886 return 0; 887 } else { 888 float3 L0 = 0; float4 occlusion = 1; 889 float3 unused_L1; // Let's just pray that compiler will strip everything x.x 890 LV_LightVolumeAdditiveSH(worldPos + worldPosOffset, L0, unused_L1, unused_L1, unused_L1, occlusion); 891 LV_PointLightVolumeSH(worldPos, occlusion, L0, unused_L1, unused_L1, unused_L1); 892 return L0; 893 } 894} 895 896// Checks if Light Volumes are used in this scene. Returns 0 if not, returns 1 if enabled 897float LightVolumesEnabled() { 898 return _UdonLightVolumeEnabled; 899} 900 901// Returns the light volumes version 902float LightVolumesVersion() { 903 return _UdonLightVolumeVersion == 0 ? _UdonLightVolumeEnabled : _UdonLightVolumeVersion; 904} 905 906#endif 907