Skip to content

Commit 4291a73

Browse files
AreaLight Sampler Reduction (#239)
1 parent d50ccb1 commit 4291a73

5 files changed

Lines changed: 92 additions & 89 deletions

File tree

com.microsoft.mrtk.graphicstools.unity/CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,18 @@ All notable changes to this package will be documented in this file.
44

55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
66

7+
## [0.8.6] - 2025-04-21
8+
9+
### Changed
10+
11+
- Reduce the number of samplers AreaLights use.
12+
13+
## [0.8.5] - 2025-04-18
14+
15+
### Changed
16+
17+
- Fix corrupt .meta file.
18+
719
## [0.8.4] - 2025-04-03
820

921
### Changed

com.microsoft.mrtk.graphicstools.unity/Runtime/Experimental/AreaLight/AreaLight.cs

Lines changed: 43 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,7 @@ public partial class AreaLight : BaseLight, IComparable<AreaLight>
2323
private const int lutMatrixDim = 3;
2424
private static readonly Matrix4x4 rotation180Up = Matrix4x4.Rotate(Quaternion.AngleAxis(180.0f, Vector3.up));
2525

26-
private static Texture2D transformInvTextureSpecular;
27-
private static Texture2D transformInvTextureDiffuse;
28-
private static Texture2D ampDiffAmpSpecFresnel;
26+
private static Texture2D areaLightLUTAtlas;
2927

3028
private static int lastAreaLightUpdate = -1;
3129
private static List<AreaLight> activeAreaLights = new(maxAreaLights);
@@ -258,10 +256,16 @@ protected override void Initialize()
258256
areaLightCookiesIDs[i] = Shader.PropertyToID($"_AreaLightCookie{i}");
259257
}
260258

261-
facingID = Shader.PropertyToID("_facing");
262-
uvStartsAtTopID = Shader.PropertyToID("_uvStartsAtTop");
259+
facingID = Shader.PropertyToID("_Facing");
260+
uvStartsAtTopID = Shader.PropertyToID("_UVStartsAtTop");
261+
262+
if (areaLightLUTAtlas == null)
263+
{
264+
areaLightLUTAtlas = CreateLUTAtlas();
265+
}
266+
267+
Shader.SetGlobalTexture("_AreaLightLUTAtlas", areaLightLUTAtlas);
263268

264-
CreateLUTs();
265269
UpdateLightSourceVisual();
266270

267271
// Only create a culling group when playing since we can't consistently dispose of them in edit mode.
@@ -522,28 +526,6 @@ private static Vector3 TransformVertex(int index, Vector2 size, Matrix4x4 localT
522526
return localToWorld.MultiplyPoint(vertex);
523527
}
524528

525-
private static void CreateLUTs()
526-
{
527-
if (transformInvTextureDiffuse == null)
528-
{
529-
transformInvTextureDiffuse = LoadLUT(LUTType.TransformInv_DisneyDiffuse);
530-
}
531-
532-
if (transformInvTextureSpecular == null)
533-
{
534-
transformInvTextureSpecular = LoadLUT(LUTType.TransformInv_GGX);
535-
}
536-
537-
if (ampDiffAmpSpecFresnel == null)
538-
{
539-
ampDiffAmpSpecFresnel = LoadLUT(LUTType.AmpDiffAmpSpecFresnel);
540-
}
541-
542-
Shader.SetGlobalTexture("_TransformInvDiffuse", transformInvTextureDiffuse);
543-
Shader.SetGlobalTexture("_TransformInvSpecular", transformInvTextureSpecular);
544-
Shader.SetGlobalTexture("_AmpDiffAmpSpecFresnel", ampDiffAmpSpecFresnel);
545-
}
546-
547529
private void UpdateLightSourceVisual()
548530
{
549531
if (drawLightSource && enabled)
@@ -598,46 +580,44 @@ private void DestroyLightVisual(bool destroyMaterial = true)
598580
}
599581
}
600582

601-
private enum LUTType
583+
private static Texture2D CreateLUTAtlas()
602584
{
603-
TransformInv_DisneyDiffuse,
604-
TransformInv_GGX,
605-
AmpDiffAmpSpecFresnel
606-
}
585+
var diffusePixels = LoadLUT(s_LUTTransformInv_DisneyDiffuse);
586+
var specularPixels = LoadLUT(s_LUTTransformInv_GGX);
587+
var fresnelPixels = LoadLUT(s_LUTAmplitude_DisneyDiffuse, s_LUTAmplitude_GGX, s_LUTFresnel_GGX);
607588

608-
private static Texture2D LoadLUT(LUTType type)
609-
{
610-
switch (type)
589+
// Combine all three LUTs into a single texture. Each LUT side by side.
590+
// Diffuse Specular Fresnel
591+
// +----------+----------+----------+
592+
// | | | |
593+
// | XX | XX | XX |
594+
// | | | |
595+
// +----------+----------+----------+
596+
int combinedWidth = lutResolution * 3;
597+
int combinedHeight = lutResolution;
598+
Texture2D combinedLUT = new Texture2D(combinedWidth, combinedHeight, TextureFormat.RGBAHalf, false, true);
599+
combinedLUT.hideFlags = HideFlags.HideAndDontSave;
600+
combinedLUT.wrapMode = TextureWrapMode.Clamp;
601+
602+
var combinedPixels = new Color[combinedWidth * combinedHeight];
603+
604+
for (int y = 0; y < lutResolution; y++)
611605
{
612-
case LUTType.TransformInv_DisneyDiffuse:
613-
{
614-
return LoadLUT(s_LUTTransformInv_DisneyDiffuse);
615-
}
616-
case LUTType.TransformInv_GGX:
617-
{
618-
return LoadLUT(s_LUTTransformInv_GGX);
619-
}
620-
case LUTType.AmpDiffAmpSpecFresnel:
621-
{
622-
return LoadLUT(s_LUTAmplitude_DisneyDiffuse, s_LUTAmplitude_GGX, s_LUTFresnel_GGX);
623-
}
606+
for (int x = 0; x < lutResolution; x++)
607+
{
608+
combinedPixels[y * combinedWidth + x] = diffusePixels[y * lutResolution + x];
609+
combinedPixels[y * combinedWidth + x + lutResolution] = specularPixels[y * lutResolution + x];
610+
combinedPixels[y * combinedWidth + x + 2 * lutResolution] = fresnelPixels[y * lutResolution + x];
611+
}
624612
}
625613

626-
return null;
627-
}
628-
629-
private static Texture2D CreateLUT(TextureFormat format, Color[] pixels)
630-
{
631-
var texture = new Texture2D(lutResolution, lutResolution, format, false /*mipmap*/, true /*linear*/);
632-
texture.hideFlags = HideFlags.HideAndDontSave;
633-
texture.wrapMode = TextureWrapMode.Clamp;
634-
texture.SetPixels(pixels);
635-
texture.Apply();
614+
combinedLUT.SetPixels(combinedPixels);
615+
combinedLUT.Apply();
636616

637-
return texture;
617+
return combinedLUT;
638618
}
639619

640-
private static Texture2D LoadLUT(double[,] LUTTransformInv)
620+
private static Color[] LoadLUT(double[,] LUTTransformInv)
641621
{
642622
const int count = lutResolution * lutResolution;
643623
Color[] pixels = new Color[count];
@@ -651,10 +631,10 @@ private static Texture2D LoadLUT(double[,] LUTTransformInv)
651631
(float)LUTTransformInv[i, 6]);
652632
}
653633

654-
return CreateLUT(TextureFormat.RGBAHalf, pixels);
634+
return pixels;
655635
}
656636

657-
private static Texture2D LoadLUT(float[] LUTScalar0, float[] LUTScalar1, float[] LUTScalar2)
637+
private static Color[] LoadLUT(float[] LUTScalar0, float[] LUTScalar1, float[] LUTScalar2)
658638
{
659639
const int count = lutResolution * lutResolution;
660640
Color[] pixels = new Color[count];
@@ -665,7 +645,7 @@ private static Texture2D LoadLUT(float[] LUTScalar0, float[] LUTScalar1, float[]
665645
pixels[i] = new Color(LUTScalar0[i], LUTScalar1[i], LUTScalar2[i], 0);
666646
}
667647

668-
return CreateLUT(TextureFormat.RGBAHalf, pixels);
648+
return pixels;
669649
}
670650
}
671651
}

com.microsoft.mrtk.graphicstools.unity/Runtime/Experimental/AreaLight/Shaders/AreaLight.hlsl

Lines changed: 31 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66

77
// Based off work from: https://github.com/Unity-Technologies/VolumetricLighting
88

9+
#include "HLSLSupport.cginc"
10+
911
#pragma multi_compile _ _AREA_LIGHT_ACTIVE _AREA_LIGHTS_ACTIVE
1012

1113
#define AREA_LIGHT_COUNT 2
@@ -16,15 +18,18 @@
1618
/// Global properties.
1719
/// </summary>
1820

19-
#if defined(AREA_LIGHT_ENABLE_DIFFUSE)
20-
sampler2D _TransformInvDiffuse;
21-
#endif // AREA_LIGHT_ENABLE_DIFFUSE
22-
sampler2D _TransformInvSpecular;
23-
sampler2D _AmpDiffAmpSpecFresnel;
21+
// LUTs are atlased in the following order:
22+
// Diffuse Specular Fresnel
23+
// +----------+----------+----------+
24+
// | | | |
25+
// | XX | XX | XX |
26+
// | | | |
27+
// +----------+----------+----------+
28+
UNITY_DECLARE_TEX2D(_AreaLightLUTAtlas);
2429

2530
// Shader.SetGlobalTexture…Array(…) does not exist, so this is the best we can do.
26-
sampler2D _AreaLightCookie0;
27-
sampler2D _AreaLightCookie1;
31+
UNITY_DECLARE_TEX2D(_AreaLightCookie0);
32+
UNITY_DECLARE_TEX2D_NOSAMPLER(_AreaLightCookie1); // Reuse _AreaLightCookie0's sampler.
2833

2934
float4 _AreaLightData[AREA_LIGHT_COUNT * AREA_LIGHT_DATA_SIZE];
3035
float4x4 _AreaLightVerts[AREA_LIGHT_COUNT];
@@ -46,14 +51,14 @@ half4 SampleAreaLightCookie(in int lightIndex, in float2 uv)
4651
switch (lightIndex)
4752
{
4853
case 0:
49-
return tex2D(_AreaLightCookie0, uv);
54+
return UNITY_SAMPLE_TEX2D(_AreaLightCookie0, uv);
5055
case 1:
51-
return tex2D(_AreaLightCookie1, uv);
56+
return UNITY_SAMPLE_TEX2D_SAMPLER(_AreaLightCookie1, _AreaLightCookie0, uv);
5257
default:
5358
return half4(1, 1, 1, 1);
5459
}
5560
#else
56-
return tex2D(_AreaLightCookie0, uv);
61+
return UNITY_SAMPLE_TEX2D(_AreaLightCookie0, uv);
5762
#endif // _AREA_LIGHTS_ACTIVE
5863
}
5964

@@ -101,7 +106,7 @@ float3 IntegrateEdge(in float3 v1, in float3 v2)
101106
return cross(v1, v2) * theta_sintheta;
102107
}
103108

104-
float4 PolygonRadiance(in int lightIndex, in float4x3 L)
109+
float3 PolygonRadiance(in int lightIndex, in float4x3 L)
105110
{
106111
// Baum's equation
107112
// Expects non-normalized vertex positions
@@ -260,25 +265,31 @@ float4 PolygonRadiance(in int lightIndex, in float4x3 L)
260265
}
261266

262267
float3 direction = normalize(sum);
263-
return float4(max(0, sum.z * 0.15915) * SampleDiffuseFilteredTexture(lightIndex, unclippedL, direction), direction.z);
268+
sum.z = max(0, sum.z * 0.15915); // 1 / (2 * Pi).
269+
return SampleDiffuseFilteredTexture(lightIndex, unclippedL, direction) * sum.z;
270+
}
271+
272+
half4 SampleAtlas(in float2 uv, in half index)
273+
{
274+
return UNITY_SAMPLE_TEX2D(_AreaLightLUTAtlas, float2(uv.x / 3.0 + (index / 3.0), uv.y));
264275
}
265276

266-
half4 TransformedPolygonRadiance(in int lightIndex,
277+
half3 TransformedPolygonRadiance(in int lightIndex,
267278
in float4x3 L,
268279
in float2 uv,
269-
in sampler2D transformInv,
280+
in half transformIndex,
270281
in float amplitude)
271282
{
272283
// Get the inverse LTC matrix M.
273284
float3x3 Minv = 0;
274285
Minv._m22 = 1;
275-
Minv._m00_m02_m11_m20 = tex2D(transformInv, uv);
286+
Minv._m00_m02_m11_m20 = SampleAtlas(uv, transformIndex);
276287

277288
// Transform light vertices into diffuse configuration.
278289
float4x3 LTransformed = mul(L, Minv);
279290

280291
// Polygon radiance in transformed configuration - specular.
281-
return PolygonRadiance(lightIndex, LTransformed) * float4(amplitude.xxx, 1);
292+
return PolygonRadiance(lightIndex, LTransformed) * amplitude.xxx;
282293
}
283294

284295
void CalculateAreaLight(in float3 worldPosition,
@@ -308,17 +319,17 @@ void CalculateAreaLight(in float3 worldPosition,
308319

309320
// UVs for sampling the LUTs.
310321
float theta = acos(dot(V, worldNormal));
311-
half2 uv = half2(roughness, theta / 1.57);
322+
half2 uv = half2(roughness, theta / 1.57); // Half Pi.
312323

313-
half3 AmpDiffAmpSpecFresnel = tex2D(_AmpDiffAmpSpecFresnel, uv).rgb;
324+
half3 AmpDiffAmpSpecFresnel = SampleAtlas(uv, 2).rgb;
314325

315326
half3 result = 0;
316327
#if defined(AREA_LIGHT_ENABLE_DIFFUSE)
317-
half3 diffuseTerm = TransformedPolygonRadiance(lightIndex, L, uv, _TransformInvDiffuse, AmpDiffAmpSpecFresnel.x);
328+
half3 diffuseTerm = TransformedPolygonRadiance(lightIndex, L, uv, 0, AmpDiffAmpSpecFresnel.x);
318329
result = diffuseTerm * baseColor;
319330
#endif // AREA_LIGHT_ENABLE_DIFFUSE
320331

321-
half3 specularTerm = TransformedPolygonRadiance(lightIndex, L, uv, _TransformInvSpecular, AmpDiffAmpSpecFresnel.y);
332+
half3 specularTerm = TransformedPolygonRadiance(lightIndex, L, uv, 1, AmpDiffAmpSpecFresnel.y);
322333
half3 fresnelTerm = (half) (specularColor + (1.0 - specularColor) * AmpDiffAmpSpecFresnel.z);
323334
result += specularTerm * fresnelTerm * 3.14159265359; // Pi.
324335

com.microsoft.mrtk.graphicstools.unity/Runtime/Experimental/AreaLight/Shaders/AreaLightVisualize.shader

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,8 @@ Shader "Hidden/Graphics Tools/Experimental/Area Light Visualize"
4141
CBUFFER_START(UnityPerMaterial)
4242
fixed4 _Color;
4343
float4 _MainTex_ST;
44-
float _facing;
45-
float _uvStartsAtTop;
44+
float _Facing;
45+
float _UVStartsAtTop;
4646
CBUFFER_END
4747

4848
v2f vert (appdata_t input)
@@ -57,10 +57,10 @@ Shader "Hidden/Graphics Tools/Experimental/Area Light Visualize"
5757

5858
fixed4 frag (v2f input, bool facing : SV_IsFrontFace) : SV_Target
5959
{
60-
facing = _facing ? !facing : facing;
60+
facing = _Facing ? !facing : facing;
6161
float2 texcoord = input.texcoord;
62-
texcoord.x = abs(_facing - texcoord.x);
63-
texcoord.y = abs(_uvStartsAtTop - texcoord.y);
62+
texcoord.x = abs(_Facing - texcoord.x);
63+
texcoord.y = abs(_UVStartsAtTop - texcoord.y);
6464
fixed4 output = facing ? tex2D(_MainTex, texcoord) : fixed4(0.05, 0.05, 0.05, 1.0);
6565
UNITY_OPAQUE_ALPHA(output.a);
6666
return output * _Color;

com.microsoft.mrtk.graphicstools.unity/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "com.microsoft.mrtk.graphicstools.unity",
3-
"version": "0.8.4",
3+
"version": "0.8.6",
44
"displayName": "MRTK Graphics Tools",
55
"description": "Graphics tools and components for developing Mixed Reality applications in Unity.",
66
"documentationUrl": "https://aka.ms/mrtk3graphics",

0 commit comments

Comments
 (0)