yum/3ner

A toon shader for Unity's BIRP.

git clone https://git.yummers.dev/yum/3ner

yumUpdate light volumes169fe5e

master
37.2 KiB907 linesraw
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