|
97 | 97 |
|
98 | 98 | float4 _UdonPointLightVolumeDirection[128]; |
99 | 99 |
|
100 | | - float2 _UdonPointLightVolumeCustomID[128]; |
| 100 | + float3 _UdonPointLightVolumeCustomID[128]; |
101 | 101 |
|
102 | | - float _UdonAreaLightBrightnessCutoff; |
| 102 | + float _UdonLightBrightnessCutoff; |
103 | 103 |
|
104 | 104 | float _UdonLightVolumeOcclusionCount; |
105 | 105 |
|
|
261 | 261 | return LV_SAMPLE(_UdonPointLightVolumeTexture, uvid); |
262 | 262 | } |
263 | 263 |
|
264 | | - float LV_ComputeAreaLightSquaredBoundingSphere(float width, float height, float minSolidAngle) { |
265 | | - float A = width * height; |
266 | | - float w2 = width * width; |
267 | | - float h2 = height * height; |
268 | | - float B = 0.25 * (w2 + h2); |
269 | | - float t = tan(0.25 * minSolidAngle); |
270 | | - float T = t * t; |
271 | | - float TB = T * B; |
272 | | - float discriminant = sqrt(TB * TB + 4.0 * T * A * A); |
273 | | - float d2 = (discriminant - TB) * 0.125 / T; |
274 | | - return d2; |
275 | | - } |
276 | | - |
277 | 264 | float4 LV_ProjectQuadLightIrradianceSH(float3 shadingPosition, float3 lightVertices[4]) { |
278 | 265 | // Transform the vertices into local space centered on the shading position, |
279 | 266 | // project, the polygon onto the unit sphere. |
|
355 | 342 | return float4(l1x, l1y, l1z, l0); |
356 | 343 | } |
357 | 344 |
|
358 | | - void LV_QuadLight(float3 worldPos, float3 centroidPos, float4 rotationQuat, float2 size, float3 color, float occlusion, inout float3 L0, inout float3 L1r, inout float3 L1g, inout float3 L1b, inout uint count) { |
| 345 | + void 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) { |
359 | 346 |
|
360 | | - float2 halfSize = size * 0.5f; |
361 | 347 | float3 lightToWorldPos = worldPos - centroidPos; |
362 | 348 |
|
363 | | - // Get normal to cull the light early |
| 349 | + // Normal culling |
364 | 350 | float3 normal = LV_MultiplyVectorByQuaternion(float3(0, 0, 1), rotationQuat); |
365 | 351 | [branch] if (dot(normal, lightToWorldPos) < 0.0) return; |
366 | | - |
367 | | - // Calculate the bounding sphere of the area light given the cutoff irradiance |
368 | | - // The irradiance of an emitter at a point is assuming normal incidence is irradiance over radiance. |
369 | | - float minSolidAngle = min(abs(_UdonAreaLightBrightnessCutoff * rcp(max(color.r, max(color.g, color.b)))), LV_PI2); |
370 | | - |
371 | | - float sqMaxDist = LV_ComputeAreaLightSquaredBoundingSphere(size.x, size.y, minSolidAngle); |
372 | | - float sqCutoffDist = sqMaxDist - dot(lightToWorldPos, lightToWorldPos); |
373 | | - [branch] if (sqCutoffDist < 0) return; |
374 | 352 |
|
375 | 353 | // Attenuate the light based on distance to the bounding sphere, so we don't get hard seam at the edge. |
376 | | - color.rgb *= saturate(sqCutoffDist / sqMaxDist); |
377 | | - |
| 354 | + float sqCutoffDist = sqMaxDist - dot(lightToWorldPos, lightToWorldPos); |
| 355 | + color.rgb *= saturate(sqCutoffDist / sqMaxDist) * LV_PI; |
| 356 | + |
378 | 357 | // Compute the vertices of the quad |
| 358 | + float2 halfSize = size * 0.5f; |
379 | 359 | float3 xAxis = LV_MultiplyVectorByQuaternion(float3(1, 0, 0), rotationQuat); |
380 | 360 | float3 yAxis = cross(normal, xAxis); |
381 | 361 | float3 verts[4]; |
|
392 | 372 | float lenL1 = length(areaLightSH.xyz); |
393 | 373 | if (lenL1 > areaLightSH.w) |
394 | 374 | areaLightSH.xyz *= areaLightSH.w / lenL1; |
395 | | - |
396 | | - // Accumulate SH coefficients |
397 | | - //float3 l0 = areaLightSH.w * color.rgb * occlusion; |
398 | | - //float3 l1 = areaLightSH.xyz * occlusion; |
399 | | - //float3 stp = step(l0, 0); |
400 | | - |
401 | | - //L0 = lerp(L0 + l0, L0 * saturate(1 + l0), stp); |
402 | | - //L1r = lerp(L1r + l1 * color.r, L1r * saturate(1 + l0), stp); |
403 | | - //L1g = lerp(L1g + l1 * color.g, L1g * saturate(1 + l0), stp); |
404 | | - //L1b = lerp(L1b + l1 * color.b, L1b * saturate(1 + l0), stp); |
405 | 375 |
|
406 | 376 | L0 += areaLightSH.w * color.rgb * occlusion; |
407 | 377 | L1r += areaLightSH.xyz * color.r * occlusion; |
|
411 | 381 | count++; |
412 | 382 | } |
413 | 383 |
|
414 | | - void LV_PointLight(uint id, float3 worldPos, float occlusion, inout float3 L0, inout float3 L1r, inout float3 L1g, inout float3 L1b, inout uint count) { |
415 | | - |
416 | | - // Light position and inversed squared range |
417 | | - float4 pos = _UdonPointLightVolumePosition[id]; |
418 | | - float invSqRange = abs(pos.w); // Sign of range defines if it's point light (positive) or a spot light (negative) |
| 384 | + float3 LV_PointLightAttenuation(float sqdist, float sqlightSize, float3 color, float brightnessCutoff, float sqMaxDist) { |
| 385 | + float mask = saturate(1 - sqdist / sqMaxDist); |
| 386 | + return mask * mask * color * sqlightSize / (sqdist + sqlightSize); |
| 387 | + } |
| 388 | + |
| 389 | + float LV_PointLightSolidAngle(float sqdist, float sqlightSize) { |
| 390 | + return saturate(sqrt(sqdist / (sqlightSize + sqdist))); |
| 391 | + } |
| 392 | + |
| 393 | + void LV_SphereLight(float3 worldPos, float3 centerPos, float sqlightSize, float3 color, float occlusion, float sqMaxDist, inout float3 L0, inout float3 L1r, inout float3 L1g, inout float3 L1b, inout uint count) { |
419 | 394 |
|
420 | | - float3 dir = pos.xyz - worldPos; |
421 | | - float sqlen = max(dot(dir, dir), 1e-6); |
422 | | - float invSqLen = rcp(sqlen); |
| 395 | + float3 dir = centerPos - worldPos; |
| 396 | + float sqdist = max(dot(dir, dir), 1e-6); |
| 397 | + float3 att = LV_PointLightAttenuation(sqdist, sqlightSize, color, _UdonLightBrightnessCutoff, sqMaxDist); |
423 | 398 |
|
424 | | - float4 color = _UdonPointLightVolumeColor[id]; // Color, angle |
| 399 | + float3 l0 = att * occlusion; |
| 400 | + float3 l1 = normalize(dir) * LV_PointLightSolidAngle(sqdist, sqlightSize); |
| 401 | + |
| 402 | + L0 += l0; |
| 403 | + L1r += l0.r * l1; |
| 404 | + L1g += l0.g * l1; |
| 405 | + L1b += l0.b * l1; |
| 406 | + count++; |
| 407 | + |
| 408 | + } |
| 409 | + |
| 410 | + void LV_SphereSpotLight(float3 worldPos, float3 centerPos, float sqlightSize, float3 color, float3 lightDir, float cosAngle, float coneFalloff, float occlusion, float sqMaxDist, inout float3 L0, inout float3 L1r, inout float3 L1g, inout float3 L1b, inout uint count) { |
| 411 | + |
| 412 | + float3 dir = centerPos - worldPos; |
| 413 | + float sqdist = max(dot(dir, dir), 1e-6); |
| 414 | + float3 dirN = normalize(dir); |
| 415 | + |
| 416 | + float spotMask = dot(lightDir, -dirN) - cosAngle; |
| 417 | + if (spotMask < 0) return; // Culling by spot angle |
425 | 418 |
|
426 | | - bool isSpotLight = pos.w < 0; |
427 | | - bool isPointLight = !isSpotLight && color.w <= 1.5f; |
| 419 | + float3 att = LV_PointLightAttenuation(sqdist, sqlightSize, color, _UdonLightBrightnessCutoff, sqMaxDist); |
| 420 | + |
| 421 | + float smoothedCone = LV_Smoothstep01(saturate(spotMask * coneFalloff)); |
| 422 | + float3 l0 = att * occlusion * smoothedCone; |
| 423 | + float3 l1 = dirN * LV_PointLightSolidAngle(sqdist, sqlightSize * saturate(1 - cosAngle)); |
| 424 | + |
| 425 | + L0 += l0; |
| 426 | + L1r += l0.r * l1; |
| 427 | + L1g += l0.g * l1; |
| 428 | + L1b += l0.b * l1; |
| 429 | + count++; |
| 430 | + |
| 431 | + } |
| 432 | + |
| 433 | + void LV_SphereSpotLightCookie(float3 worldPos, float3 centerPos, float sqlightSize, float3 color, float4 lightRot, float tanAngle, uint customId, float occlusion, float sqMaxDist, inout float3 L0, inout float3 L1r, inout float3 L1g, inout float3 L1b, inout uint count) { |
428 | 434 |
|
429 | | - // Culling spotlight by radius |
430 | | - if ((isSpotLight || isPointLight) && invSqLen < invSqRange ) return; |
| 435 | + float3 dir = centerPos - worldPos; |
| 436 | + float sqdist = max(dot(dir, dir), 1e-6); |
| 437 | + float3 dirN = normalize(dir); |
431 | 438 |
|
432 | | - float angle = color.w; |
433 | | - float4 ldir = _UdonPointLightVolumeDirection[id]; // Dir + falloff or Rotation |
434 | | - float coneFalloff = ldir.w; |
435 | | - int customId = (int) _UdonPointLightVolumeCustomID[id].x; // Custom Texture ID |
| 439 | + float3 localDir = LV_MultiplyVectorByQuaternion(-dirN, lightRot); |
| 440 | + if (localDir.z <= 0.0) return; // Culling by direction |
436 | 441 |
|
437 | | - float3 dirN = dir * rsqrt(sqlen); |
438 | | - float dirRadius = sqlen * invSqRange; |
| 442 | + float2 uv = localDir.xy * rcp(localDir.z * tanAngle); |
| 443 | + if (abs(uv.x) > 1.0 || abs(uv.y) > 1.0) return; // Culling by UV |
439 | 444 |
|
440 | | - float3 att = color.rgb; // Light attenuation |
| 445 | + float3 att = LV_PointLightAttenuation(sqdist, sqlightSize, color, _UdonLightBrightnessCutoff, sqMaxDist); |
| 446 | + |
| 447 | + uint id = (uint) _UdonPointLightVolumeCubeCount * 5 - customId - 1; |
| 448 | + float3 uvid = float3(uv * 0.5 + 0.5, id); |
| 449 | + float angleSize = saturate(rsqrt(1 + tanAngle * tanAngle)); |
| 450 | + float4 cookie = LV_SAMPLE(_UdonPointLightVolumeTexture, uvid); |
| 451 | + |
| 452 | + float3 l0 = att * occlusion * cookie.rgb * cookie.a; |
| 453 | + float3 l1 = dirN * LV_PointLightSolidAngle(sqdist, sqlightSize * (1 - angleSize)); |
| 454 | + |
| 455 | + L0 += l0; |
| 456 | + L1r += l0.r * l1; |
| 457 | + L1g += l0.g * l1; |
| 458 | + L1b += l0.b * l1; |
| 459 | + count++; |
| 460 | + |
| 461 | + } |
| 462 | + |
| 463 | + void LV_PointLight(uint id, float3 worldPos, float4 occlusion, inout float3 L0, inout float3 L1r, inout float3 L1g, inout float3 L1b, inout uint count) { |
| 464 | + |
| 465 | + // IDs and range data |
| 466 | + float3 customID_data = _UdonPointLightVolumeCustomID[id]; |
| 467 | + int shadowId = (int) customID_data.y; // Shadowmask id |
| 468 | + int customId = (int) customID_data.x; // Custom Texture ID |
| 469 | + float sqrRange = customID_data.z; // Squared culling distance |
| 470 | + |
| 471 | + float4 pos = _UdonPointLightVolumePosition[id]; // Light position and inversed squared range |
| 472 | + float3 dir = pos.xyz - worldPos; |
| 473 | + float sqlen = max(dot(dir, dir), 1e-6); |
| 474 | + [branch] // Early distance based culling |
| 475 | + if (sqlen > sqrRange) return; |
| 476 | + |
| 477 | + // Processing lights occlusion |
| 478 | + float lightOcclusion = 1; |
| 479 | + [branch] |
| 480 | + if (_UdonLightVolumeOcclusionCount != 0 && shadowId >= 0) { |
| 481 | + lightOcclusion = dot(1, float4(shadowId == 0, shadowId == 1, shadowId == 2, shadowId == 3) * occlusion); |
| 482 | + } |
| 483 | + |
| 484 | + float4 color = _UdonPointLightVolumeColor[id]; // Color, angle |
441 | 485 |
|
442 | | - if (isSpotLight) { // It is a spot light |
| 486 | + if (pos.w < 0) { // It is a spot light |
| 487 | + |
| 488 | + float angle = color.w; |
| 489 | + float4 ldir = _UdonPointLightVolumeDirection[id]; // Dir + falloff or Rotation |
443 | 490 |
|
444 | 491 | if (customId > 0) { // If it uses Attenuation LUT |
445 | 492 |
|
| 493 | + float invSqRange = abs(pos.w); // Sign of range defines if it's point light (positive) or a spot light (negative) |
| 494 | + float3 dirN = dir * rsqrt(sqlen); |
| 495 | + float dirRadius = sqlen * invSqRange; |
446 | 496 | float spotMask = dot(ldir.xyz, -dirN) - angle; |
447 | | - if(spotMask < 0) return; |
| 497 | + if(spotMask < 0) return; // Spot cone based culling |
448 | 498 | float spot = 1 - saturate(spotMask * rcp(1 - angle)); |
449 | 499 | uint id = (uint) _UdonPointLightVolumeCubeCount * 5 + customId - 1; |
450 | 500 | float3 uvid = float3(sqrt(float2(spot, dirRadius)), id); |
451 | | - att *= LV_SAMPLE(_UdonPointLightVolumeTexture, uvid).xyz; |
| 501 | + float3 att = color.rgb * LV_SAMPLE(_UdonPointLightVolumeTexture, uvid).xyz; |
| 502 | + |
| 503 | + L0 += att * lightOcclusion; |
| 504 | + L1r += dirN * att.r * lightOcclusion; |
| 505 | + L1g += dirN * att.g * lightOcclusion; |
| 506 | + L1b += dirN * att.b * lightOcclusion; |
| 507 | + |
| 508 | + count++; |
452 | 509 |
|
453 | 510 | } else if (customId < 0) { // If uses cookie |
454 | 511 |
|
455 | | - float3 localDir = LV_MultiplyVectorByQuaternion(-dirN, ldir); |
456 | | - if (localDir.z <= 0.0) return; |
457 | | - float2 uv = localDir.xy * rcp(localDir.z * angle); // Here angle is tan(angle) |
458 | | - if (abs(uv.x) > 1.0 || abs(uv.y) > 1.0) return; |
459 | | - uint id = (uint) _UdonPointLightVolumeCubeCount * 5 - customId - 1; |
460 | | - float3 uvid = float3(uv * 0.5 + 0.5, id); |
461 | | - att *= saturate((1 - dirRadius) * rcp(dirRadius * 60 + 1.732f)) * LV_SAMPLE(_UdonPointLightVolumeTexture, uvid).xyz; |
| 512 | + LV_SphereSpotLightCookie(worldPos, pos.xyz, -pos.w, color.rgb, ldir, angle, customId, lightOcclusion, sqrRange, L0, L1r, L1g, L1b, count); |
462 | 513 |
|
463 | 514 | } else { // If it uses default parametric attenuation |
464 | 515 |
|
465 | | - float spotMask = dot(ldir.xyz, -dirN) - angle; |
466 | | - if(spotMask < 0) return; |
467 | | - att *= saturate((1 - dirRadius) * rcp(dirRadius * 60 + 1.732f)) * LV_Smoothstep01(saturate(spotMask * coneFalloff)); |
| 516 | + LV_SphereSpotLight(worldPos, pos.xyz, -pos.w, color.rgb, ldir.xyz, angle, ldir.w, lightOcclusion, sqrRange, L0, L1r, L1g, L1b, count); |
468 | 517 |
|
469 | 518 | } |
470 | 519 |
|
471 | | - } else if (isPointLight) { // It is a point light |
| 520 | + } else if (color.w <= 1.5f) { // It is a point light |
472 | 521 |
|
473 | 522 | if (customId < 0) { // If it uses a cubemap |
474 | 523 |
|
| 524 | + float4 ldir = _UdonPointLightVolumeDirection[id]; // Dir + falloff or Rotation |
| 525 | + float3 dirN = dir * rsqrt(sqlen); |
475 | 526 | uint id = -customId - 1; // Cubemap ID starts from zero and should not take in count texture array slices count. |
476 | | - att *= saturate((1 - dirRadius) * rcp(dirRadius * 60 + 1.732f)) * LV_SampleCubemapArray(id, LV_MultiplyVectorByQuaternion(dirN, ldir)).xyz; |
477 | | - |
| 527 | + float3 cubeColor = LV_SampleCubemapArray(id, LV_MultiplyVectorByQuaternion(dirN, ldir)).xyz; |
| 528 | + float3 l0 = 0, l1r = 0, l1g = 0, l1b = 0; |
| 529 | + LV_SphereLight(worldPos, pos.xyz, pos.w, color.rgb, lightOcclusion, sqrRange, l0, l1r, l1g, l1b, count); |
| 530 | + L0 += l0 * cubeColor; |
| 531 | + L1r += l1r * cubeColor.r; |
| 532 | + L1g += l1g * cubeColor.g; |
| 533 | + L1b += l1b * cubeColor.b; |
| 534 | + |
478 | 535 | } else if (customId > 0) { // Using LUT |
479 | 536 |
|
| 537 | + float invSqRange = abs(pos.w); // Sign of range defines if it's point light (positive) or a spot light (negative) |
| 538 | + float3 dirN = dir * rsqrt(sqlen); |
| 539 | + float dirRadius = sqlen * invSqRange; |
480 | 540 | uint id = (uint) _UdonPointLightVolumeCubeCount * 5 + customId; |
481 | 541 | float3 uvid = float3(sqrt(float2(0, dirRadius)), id); |
482 | | - att *= LV_SAMPLE(_UdonPointLightVolumeTexture, uvid).xyz; |
| 542 | + float3 att = color.rgb * LV_SAMPLE(_UdonPointLightVolumeTexture, uvid).xyz; |
| 543 | + |
| 544 | + L0 += att * lightOcclusion; |
| 545 | + L1r += dirN * att.r * lightOcclusion; |
| 546 | + L1g += dirN * att.g * lightOcclusion; |
| 547 | + L1b += dirN * att.b * lightOcclusion; |
| 548 | + |
| 549 | + count++; |
483 | 550 |
|
484 | 551 | } else { // If it uses default parametric attenuation |
485 | 552 |
|
486 | | - att *= saturate((1 - dirRadius) * rcp(dirRadius * 60 + 1.732f)); |
| 553 | + LV_SphereLight(worldPos, pos.xyz, pos.w, color.rgb, lightOcclusion, sqrRange, L0, L1r, L1g, L1b, count); |
487 | 554 |
|
488 | 555 | } |
489 | 556 |
|
490 | 557 | } else { // It is an area light |
491 | | - |
492 | | - // Area light is defined by centroid, rotation and size |
493 | | - float3 centroidPos = pos.xyz; |
494 | | - float4 rotationQuat = ldir; |
495 | | - float2 size = float2(pos.w, color.w - 2.0f); |
496 | 558 |
|
497 | | - LV_QuadLight(worldPos, centroidPos, rotationQuat, size, color.rgb, occlusion, L0, L1r, L1g, L1b, count); |
498 | | - return; |
| 559 | + float4 ldir = _UdonPointLightVolumeDirection[id]; // Dir + falloff or Rotation |
| 560 | + LV_QuadLight(worldPos, pos.xyz, ldir, float2(pos.w, color.w - 2.0f), color.rgb, sqrRange, lightOcclusion, L0, L1r, L1g, L1b, count); |
499 | 561 |
|
500 | 562 | } |
501 | 563 |
|
502 | | - // Accumulate SH coefficients |
503 | | - //float3 l0 = att * occlusion; |
504 | | - //float3 l1 = dirN * occlusion; |
505 | | - //float3 stp = step(l0, 0); |
506 | | - |
507 | | - //L0 = lerp(L0 + l0, L0 * saturate(1 + l0), stp); |
508 | | - //L1r = lerp(L1r + l1 * att.r, L1r * saturate(1 + l0), stp); |
509 | | - //L1g = lerp(L1g + l1 * att.g, L1g * saturate(1 + l0), stp); |
510 | | - //L1b = lerp(L1b + l1 * att.b, L1b * saturate(1 + l0), stp); |
511 | | - |
512 | | - L0 += att * occlusion; |
513 | | - L1r += dirN * att.r * occlusion; |
514 | | - L1g += dirN * att.g * occlusion; |
515 | | - L1b += dirN * att.b * occlusion; |
516 | | - |
517 | | - count++; |
518 | | - |
519 | 564 | } |
520 | 565 |
|
521 | 566 | void LV_SampleLightVolumeTex(float3 uvw0, float3 uvw1, float3 uvw2, out float3 L0, out float3 L1r, out float3 L1g, out float3 L1b) { |
|
648 | 693 |
|
649 | 694 | [loop] |
650 | 695 | for (uint pid = 0; pid < pointCount && pcount < maxOverdraw; pid++) { |
651 | | - float lightOcclusion = 1; |
652 | | - float shadowId = _UdonPointLightVolumeCustomID[pid].y; |
653 | | - [branch] |
654 | | - if (_UdonLightVolumeOcclusionCount != 0 && shadowId >= 0) { |
655 | | - lightOcclusion = dot(1, float4(shadowId == 0, shadowId == 1, shadowId == 2, shadowId == 3) * occlusion); |
656 | | - } |
657 | | - LV_PointLight(pid, worldPos, lightOcclusion, L0, L1r, L1g, L1b, pcount); |
| 696 | + LV_PointLight(pid, worldPos, occlusion, L0, L1r, L1g, L1b, pcount); |
658 | 697 | } |
659 | 698 |
|
660 | 699 | } |
|
878 | 917 | float3 a = coloredSpecs + specs * L0; |
879 | 918 | float3 b = coloredSpecs * 3; |
880 | 919 |
|
881 | | - return max(lerp(a, b, smoothness), 0.0); |
| 920 | + return max(lerp(a, b, smoothness) * 0.5f, 0.0); |
882 | 921 |
|
883 | 922 | } |
884 | 923 |
|
|
898 | 937 |
|
899 | 938 | float spec = LV_DistributionGGX(nh, roughExp); |
900 | 939 |
|
901 | | - return max(spec * L0 * f0, 0.0) * 3; |
| 940 | + return max(spec * L0 * f0, 0.0) * 1.5f; |
902 | 941 |
|
903 | 942 | } |
904 | 943 |
|
|
0 commit comments