2026-04-16 오브젝트 그림자

This commit is contained in:
skrwns304@gmail.com
2026-04-16 04:58:10 +09:00
parent 0fe8b18872
commit 42646a636f
303 changed files with 54374 additions and 20 deletions

View File

@@ -0,0 +1,138 @@
using UnityEngine;
using UnityEditor;
namespace BadDog.Rendering.AreaLight
{
/// <summary>
/// Custom property drawer for AreaLightSettings
/// </summary>
[CustomPropertyDrawer(typeof(AreaLightSettings))]
public class AreaLightSettingsDrawer : PropertyDrawer
{
private SerializedProperty m_MaxAreaLights;
private SerializedProperty m_PreIntegratedFGD_GGXDisneyDiffuse;
private SerializedProperty m_PreIntegratedFGD_CharlieFabricLambert;
private SerializedProperty m_PreIntegratedFGD_Marschner;
private string m_CachedPropertyPath;
private void FindProperties(SerializedProperty property)
{
string currentPath = property.propertyPath;
if (m_CachedPropertyPath != currentPath)
{
m_MaxAreaLights = property.FindPropertyRelative("maxAreaLights");
m_PreIntegratedFGD_GGXDisneyDiffuse = property.FindPropertyRelative("preIntegratedFGD_GGXDisneyDiffuse");
m_PreIntegratedFGD_CharlieFabricLambert = property.FindPropertyRelative("preIntegratedFGD_CharlieFabricLambert");
m_PreIntegratedFGD_Marschner = property.FindPropertyRelative("preIntegratedFGD_Marschner");
m_CachedPropertyPath = currentPath;
}
}
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
FindProperties(property);
EditorGUI.BeginProperty(position, label, property);
Rect foldoutRect = new Rect(position.x, position.y, position.width, EditorGUIUtility.singleLineHeight);
property.isExpanded = EditorGUI.Foldout(foldoutRect, property.isExpanded, label, true);
if (!property.isExpanded)
{
EditorGUI.EndProperty();
return;
}
EditorGUI.indentLevel++;
float yOffset = EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
if (HasMissingShaders())
{
property.serializedObject.Update();
AutoFindShaders();
property.serializedObject.ApplyModifiedProperties();
}
DrawProperty(m_MaxAreaLights, ref yOffset, position);
DrawProperty(m_PreIntegratedFGD_GGXDisneyDiffuse, ref yOffset, position);
DrawProperty(m_PreIntegratedFGD_CharlieFabricLambert, ref yOffset, position);
DrawProperty(m_PreIntegratedFGD_Marschner, ref yOffset, position);
EditorGUI.indentLevel--;
EditorGUI.EndProperty();
}
private void AutoFindShaders()
{
const string packageShadersPath = "Packages/com.baddog.rendering.arealight/Shaders/";
TryLoadShader(m_PreIntegratedFGD_GGXDisneyDiffuse, packageShadersPath + "PreIntegratedFGD_GGXDisneyDiffuse.shader", "PreIntegratedFGD GGX Disney Diffuse");
TryLoadShader(m_PreIntegratedFGD_CharlieFabricLambert, packageShadersPath + "PreIntegratedFGD_CharlieFabricLambert.shader", "PreIntegratedFGD Charlie Fabric Lambert");
TryLoadShader(m_PreIntegratedFGD_Marschner, packageShadersPath + "PreIntegratedFGD_Marschner.shader", "PreIntegratedFGD Marschner");
}
private void TryLoadShader(SerializedProperty shaderRef, string path, string displayName)
{
if (shaderRef != null && shaderRef.objectReferenceValue == null)
{
var shader = AssetDatabase.LoadAssetAtPath<Shader>(path);
if (shader != null)
{
shaderRef.objectReferenceValue = shader;
}
}
}
private bool HasMissingShaders()
{
return (m_PreIntegratedFGD_GGXDisneyDiffuse != null && m_PreIntegratedFGD_GGXDisneyDiffuse.objectReferenceValue == null) ||
(m_PreIntegratedFGD_CharlieFabricLambert != null && m_PreIntegratedFGD_CharlieFabricLambert.objectReferenceValue == null) ||
(m_PreIntegratedFGD_Marschner != null && m_PreIntegratedFGD_Marschner.objectReferenceValue == null);
}
private void DrawProperty(SerializedProperty prop, ref float yOffset, Rect position)
{
if (prop != null)
{
Rect rect = new Rect(position.x, position.y + yOffset, position.width, EditorGUI.GetPropertyHeight(prop));
EditorGUI.PropertyField(rect, prop, true);
yOffset += EditorGUI.GetPropertyHeight(prop) + EditorGUIUtility.standardVerticalSpacing;
}
}
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
// Base height for foldout
float height = EditorGUIUtility.singleLineHeight;
// If not expanded, return just the foldout height
if (!property.isExpanded)
{
return height;
}
// Find properties to calculate height
FindProperties(property);
// Add height for all properties using cached references
height += GetPropertyHeight(m_MaxAreaLights);
height += GetPropertyHeight(m_PreIntegratedFGD_GGXDisneyDiffuse);
height += GetPropertyHeight(m_PreIntegratedFGD_CharlieFabricLambert);
height += GetPropertyHeight(m_PreIntegratedFGD_Marschner);
return height;
}
private float GetPropertyHeight(SerializedProperty prop)
{
if (prop != null)
{
return EditorGUI.GetPropertyHeight(prop) + EditorGUIUtility.standardVerticalSpacing;
}
return 0f;
}
}
}

View File

@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 6b01fe5d42b568a42864bdea19e82bbb
AssetOrigin:
serializedVersion: 1
productId: 346790
packageName: Realtime Area Light for URP
packageVersion: 1.3.0
assetPath: Packages/com.baddog.rendering.arealight/Editor/AreaLightSettingsDrawer.cs
uploadId: 884030

View File

@@ -0,0 +1,136 @@
using UnityEngine;
using UnityEditor;
namespace BadDog.Rendering.AreaLight
{
/// <summary>
/// Custom property drawer for AreaLightShadowSettings
/// </summary>
[CustomPropertyDrawer(typeof(AreaLightShadowSettings))]
public class AreaLightShadowSettingsDrawer : PropertyDrawer
{
private SerializedProperty m_ShadowAtlasResolution;
private SerializedProperty m_MaxShadowCastingLights;
private SerializedProperty m_ShadowFilter;
private SerializedProperty m_PcssShadowSoftness;
private SerializedProperty m_PcssBlockerSampleCount;
private SerializedProperty m_PcssFilterSampleCount;
private string m_CachedPropertyPath;
private void FindProperties(SerializedProperty property)
{
string currentPath = property.propertyPath;
if (m_CachedPropertyPath != currentPath)
{
m_ShadowAtlasResolution = property.FindPropertyRelative("shadowAtlasResolution");
m_MaxShadowCastingLights = property.FindPropertyRelative("maxShadowCastingLights");
m_ShadowFilter = property.FindPropertyRelative("shadowFilter");
m_PcssShadowSoftness = property.FindPropertyRelative("pcssShadowSoftness");
m_PcssBlockerSampleCount = property.FindPropertyRelative("pcssBlockerSampleCount");
m_PcssFilterSampleCount = property.FindPropertyRelative("pcssFilterSampleCount");
m_CachedPropertyPath = currentPath;
}
}
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
FindProperties(property);
EditorGUI.BeginProperty(position, label, property);
Rect foldoutRect = new Rect(position.x, position.y, position.width, EditorGUIUtility.singleLineHeight);
property.isExpanded = EditorGUI.Foldout(foldoutRect, property.isExpanded, label, true);
if (!property.isExpanded)
{
EditorGUI.EndProperty();
return;
}
EditorGUI.indentLevel++;
float yOffset = EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
DrawProperty(m_ShadowAtlasResolution, ref yOffset, position);
DrawProperty(m_MaxShadowCastingLights, ref yOffset, position);
Rect shadowFilterRect = new Rect(position.x, position.y + yOffset, position.width, EditorGUIUtility.singleLineHeight);
EditorGUI.PropertyField(shadowFilterRect, m_ShadowFilter, new GUIContent("Shadow Filter"));
yOffset += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
if (m_ShadowFilter != null)
{
AreaLightShadowSettings.BGAreaLightShadowMode shadowFilterMode =
(AreaLightShadowSettings.BGAreaLightShadowMode)m_ShadowFilter.enumValueIndex;
if (shadowFilterMode == AreaLightShadowSettings.BGAreaLightShadowMode.PCSS)
{
Rect headerRect = new Rect(position.x, position.y + yOffset, position.width, EditorGUIUtility.singleLineHeight);
EditorGUI.LabelField(headerRect, "PCSS", EditorStyles.boldLabel);
yOffset += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
DrawProperty(m_PcssShadowSoftness, ref yOffset, position);
DrawProperty(m_PcssBlockerSampleCount, ref yOffset, position);
DrawProperty(m_PcssFilterSampleCount, ref yOffset, position);
}
}
EditorGUI.indentLevel--;
EditorGUI.EndProperty();
}
private void DrawProperty(SerializedProperty prop, ref float yOffset, Rect position)
{
if (prop != null)
{
Rect rect = new Rect(position.x, position.y + yOffset, position.width, EditorGUI.GetPropertyHeight(prop));
EditorGUI.PropertyField(rect, prop, true);
yOffset += EditorGUI.GetPropertyHeight(prop) + EditorGUIUtility.standardVerticalSpacing;
}
}
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
float height = EditorGUIUtility.singleLineHeight;
if (!property.isExpanded)
{
return height;
}
FindProperties(property);
height += GetPropertyHeight(m_ShadowAtlasResolution);
height += GetPropertyHeight(m_MaxShadowCastingLights);
if (m_ShadowFilter != null)
{
height += EditorGUI.GetPropertyHeight(m_ShadowFilter) + EditorGUIUtility.standardVerticalSpacing;
AreaLightShadowSettings.BGAreaLightShadowMode shadowFilterMode =
(AreaLightShadowSettings.BGAreaLightShadowMode)m_ShadowFilter.enumValueIndex;
if (shadowFilterMode == AreaLightShadowSettings.BGAreaLightShadowMode.PCSS)
{
height += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
height += GetPropertyHeight(m_PcssShadowSoftness);
height += GetPropertyHeight(m_PcssBlockerSampleCount);
height += GetPropertyHeight(m_PcssFilterSampleCount);
}
}
return height;
}
private float GetPropertyHeight(SerializedProperty prop)
{
if (prop != null)
{
return EditorGUI.GetPropertyHeight(prop) + EditorGUIUtility.standardVerticalSpacing;
}
return 0f;
}
}
}

View File

@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: f79bc32ed4d4c8d4ebc5ad6468522cc9
AssetOrigin:
serializedVersion: 1
productId: 346790
packageName: Realtime Area Light for URP
packageVersion: 1.3.0
assetPath: Packages/com.baddog.rendering.arealight/Editor/AreaLightShadowSettingsDrawer.cs
uploadId: 884030

View File

@@ -0,0 +1,41 @@
using UnityEngine;
using UnityEditor;
namespace BadDog.Rendering.AreaLight
{
/// <summary>
/// Custom editor for AreaLighting Renderer Feature
/// </summary>
[CustomEditor(typeof(AreaLighting))]
public class AreaLightingEditor : UnityEditor.Editor
{
private SerializedProperty m_LightSettingsProp;
private SerializedProperty m_ShadowSettingsProp;
private void OnEnable()
{
m_LightSettingsProp = serializedObject.FindProperty("m_LightSettings");
m_ShadowSettingsProp = serializedObject.FindProperty("m_ShadowSettings");
}
public override void OnInspectorGUI()
{
serializedObject.Update();
EditorGUI.BeginDisabledGroup(true);
EditorGUILayout.PropertyField(serializedObject.FindProperty("m_Script"));
EditorGUI.EndDisabledGroup();
EditorGUILayout.Space();
EditorGUILayout.PropertyField(m_LightSettingsProp, new GUIContent("Light Settings"), true);
EditorGUILayout.Space();
EditorGUILayout.PropertyField(m_ShadowSettingsProp, new GUIContent("Shadow Settings"), true);
serializedObject.ApplyModifiedProperties();
}
}
}

View File

@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 2c1265e98fbe0b0419b0e22b927a87b2
AssetOrigin:
serializedVersion: 1
productId: 346790
packageName: Realtime Area Light for URP
packageVersion: 1.3.0
assetPath: Packages/com.baddog.rendering.arealight/Editor/AreaLightingEditor.cs
uploadId: 884030

View File

@@ -0,0 +1,98 @@
using UnityEngine;
using UnityEditor;
using UnityEngine.Rendering;
using BadDog.Rendering.AreaLight;
namespace BadDog.Rendering.AreaLight.Editor
{
[CustomEditor(typeof(BGAreaLight))]
[CanEditMultipleObjects]
public class BGAreaLightEditor : UnityEditor.Editor
{
private SerializedProperty m_CastShadows;
private SerializedProperty m_UseCustomShadow;
private SerializedProperty m_CustomShadowResolution;
private SerializedProperty m_ShadowCone;
private SerializedProperty m_ShadowStrength;
private SerializedProperty m_ShadowNormalBias;
private SerializedProperty m_ShadowDepthBias;
private SerializedProperty m_ShadowNearPlane;
private SerializedProperty m_RenderingLayerMask;
private void OnEnable()
{
m_CastShadows = serializedObject.FindProperty("m_CastShadows");
m_UseCustomShadow = serializedObject.FindProperty("m_UseCustomShadow");
m_CustomShadowResolution = serializedObject.FindProperty("m_CustomShadowResolution");
m_ShadowCone = serializedObject.FindProperty("m_ShadowCone");
m_ShadowStrength = serializedObject.FindProperty("m_ShadowStrength");
m_ShadowNormalBias = serializedObject.FindProperty("m_ShadowNormalBias");
m_ShadowDepthBias = serializedObject.FindProperty("m_ShadowDepthBias");
m_ShadowNearPlane = serializedObject.FindProperty("m_ShadowNearPlane");
m_RenderingLayerMask = serializedObject.FindProperty("m_RenderingLayerMask");
}
public override void OnInspectorGUI()
{
serializedObject.Update();
// Cast Shadows toggle
EditorGUILayout.PropertyField(m_CastShadows, new GUIContent("Cast Shadows", "Enable shadow casting for this area light."));
EditorGUILayout.Space();
// Disable all shadow settings if Cast Shadows is off
EditorGUI.BeginDisabledGroup(!m_CastShadows.boolValue);
EditorGUILayout.PropertyField(m_UseCustomShadow, new GUIContent("Custom Shadow", "Enable custom shadow settings for this area light."));
EditorGUILayout.Space();
EditorGUI.indentLevel++;
if (!m_UseCustomShadow.boolValue)
{
m_CustomShadowResolution.intValue = 0;
m_ShadowCone.floatValue = AreaShadowUtils.k_DefaultAreaLightShadowCone;
m_ShadowStrength.floatValue = 1f;
m_ShadowNormalBias.floatValue = 0.0f;
m_ShadowDepthBias.floatValue = 0.05f;
m_ShadowNearPlane.floatValue = 0.05f;
}
EditorGUI.BeginDisabledGroup(!m_UseCustomShadow.boolValue);
EditorGUILayout.PropertyField(m_CustomShadowResolution);
EditorGUILayout.PropertyField(m_ShadowCone);
EditorGUILayout.PropertyField(m_ShadowStrength);
EditorGUILayout.PropertyField(m_ShadowNormalBias);
EditorGUILayout.PropertyField(m_ShadowDepthBias);
EditorGUILayout.PropertyField(m_ShadowNearPlane);
EditorGUI.EndDisabledGroup();
EditorGUI.indentLevel--;
EditorGUI.EndDisabledGroup(); // End Cast Shadows disabled group
EditorGUILayout.Space();
DrawRenderingLayersField();
serializedObject.ApplyModifiedProperties();
}
private void DrawRenderingLayersField()
{
EditorGUI.showMixedValue = m_RenderingLayerMask.hasMultipleDifferentValues;
EditorGUI.BeginChangeCheck();
uint mask = m_RenderingLayerMask.uintValue;
mask = EditorGUILayout.RenderingLayerMaskField(
new GUIContent("Rendering Layers", "Rendering Layer Mask used for area light lighting/shadow filtering."),
mask);
if (EditorGUI.EndChangeCheck())
{
m_RenderingLayerMask.uintValue = mask;
}
EditorGUI.showMixedValue = false;
}
}
}

View File

@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 184bbf0b5e653a2468797f461744672d
AssetOrigin:
serializedVersion: 1
productId: 346790
packageName: Realtime Area Light for URP
packageVersion: 1.3.0
assetPath: Packages/com.baddog.rendering.arealight/Editor/BGAreaLightEditor.cs
uploadId: 884030

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: daa9c22d3d6be47409943f017363acb4
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 803dead2a59b2d845ba66743a62b6d28
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,350 @@
/*
Copyright(c) 2017, Eric Heitz, Jonathan Dupuy, Stephen Hill and David Neubelt.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* If you use(or adapt) the source code in your own work, please include a
reference to the paper:
Real-Time Polygonal-Light Shading with Linearly Transformed Cosines.
Eric Heitz, Jonathan Dupuy, Stephen Hill and David Neubelt.
ACM Transactions on Graphics (Proceedings of ACM SIGGRAPH 2016) 35(4), 2016.
Project page: https://eheitzresearch.wordpress.com/415-2/
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED.IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
// Content adapted from https://github.com/selfshadow/ltc_code
using System;
using UnityEngine;
namespace BadDog.Rendering.AreaLight.LTC
{
public struct Vec3
{
public double x;
public double y;
public double z;
};
public class Vec3Utilities
{
public static double Length(Vec3 vec3)
{
return Math.Sqrt(vec3.x * vec3.x + vec3.y * vec3.y + vec3.z * vec3.z);
}
}
public struct Matrix
{
public double m00;
public double m01;
public double m02;
public double m10;
public double m11;
public double m12;
public double m20;
public double m21;
public double m22;
};
public class MatrixUtilities
{
public static void Initialize(out Matrix m)
{
m.m00 = 0;
m.m01 = 0;
m.m02 = 0;
m.m10 = 0;
m.m11 = 0;
m.m12 = 0;
m.m20 = 0;
m.m21 = 0;
m.m22 = 0;
}
}
public struct LTCData
{
// Lobe magnitude
public double magnitude;
// Average Schlick Fresnel term
public double fresnel;
// Parametric representation (used by the fitter only!)
public Vec3 X;
public Vec3 Y;
public Vec3 Z;
public double m11;
public double m22;
public double m13;
public Matrix M;
// Last fitting error
public double error;
// Last amount of iterations
public int iterationsCount;
// Runtime matrix representation
public Matrix invM;
// Determinant of the matrix
public double detM;
}
public class LTCDataUtilities
{
static public void Initialize(out LTCData ltcData)
{
ltcData.magnitude = 1;
ltcData.fresnel = 1;
ltcData.X.x = 1;
ltcData.X.y = 0;
ltcData.X.z = 0;
ltcData.Y.x = 0;
ltcData.Y.y = 1;
ltcData.Y.z = 0;
ltcData.Z.x = 0;
ltcData.Z.y = 0;
ltcData.Z.z = 1;
ltcData.m11 = 1;
ltcData.m22 = 1;
ltcData.m13 = 0;
ltcData.error = 0;
ltcData.iterationsCount = 0;
ltcData.detM = 0;
MatrixUtilities.Initialize(out ltcData.M);
MatrixUtilities.Initialize(out ltcData.invM);
}
static public double[] GetFittingParms(in LTCData ltcData)
{
double[] tempParams = new double[]
{
ltcData.m11,
ltcData.m22,
ltcData.m13,
};
return tempParams;
}
static public void SetFittingParms(ref LTCData ltcData, double[] parameters, bool isotropic)
{
float tempM11 = Mathf.Max((float)parameters[0], 1e-7f);
float tempM22 = Mathf.Max((float)parameters[1], 1e-7f);
float tempM13 = (float)parameters[2];
if (isotropic)
{
ltcData.m11 = tempM11;
ltcData.m22 = tempM11;
ltcData.m13 = 0.0f;
}
else
{
ltcData.m11 = tempM11;
ltcData.m22 = tempM22;
ltcData.m13 = tempM13;
}
// Update the matrices
Update(ref ltcData);
}
static public void ComputeAverageTerms(IBRDF brdf, ref Vector3 tsView, float roughness, int sampleCount, ref LTCData ltcData)
{
// Initialize the values for the accumulation
ltcData.magnitude = 0.0f;
ltcData.fresnel = 0.0f;
ltcData.Z.x = 0.0f;
ltcData.Z.y = 0.0f;
ltcData.Z.z = 0.0f;
ltcData.error = 0.0f;
for (int j = 0; j < sampleCount; ++j)
{
for (int i = 0; i < sampleCount; ++i)
{
float U1 = (i + 0.5f) / sampleCount;
float U2 = (j + 0.5f) / sampleCount;
// sample
Vector3 tsLight = Vector3.zero;
brdf.GetSamplingDirection(ref tsView, roughness, U1, U2, ref tsLight);
// eval
double pdf;
double eval = brdf.Eval(ref tsView, ref tsLight, roughness, out pdf);
if (pdf == 0.0)
continue;
Vector3 H = Vector3.Normalize(tsView + tsLight);
// accumulate
double weight = eval / pdf;
if (double.IsNaN(weight))
{
// Should not happen
}
ltcData.magnitude += weight;
ltcData.fresnel += weight * Mathf.Pow(1 - Mathf.Max(0.0f, Vector3.Dot(tsView, H)), 5.0f);
ltcData.Z.x += weight * tsLight.x;
ltcData.Z.y += weight * tsLight.y;
ltcData.Z.z += weight * tsLight.z;
}
}
ltcData.magnitude /= (float)(sampleCount * sampleCount);
ltcData.fresnel /= (float)(sampleCount * sampleCount);
// Finish building the average TBN orthogonal basis
// clear y component, which should be zero with isotropic BRDFs
ltcData.Z.y = 0.0f;
double length = Vec3Utilities.Length(ltcData.Z);
if (length > 0.0)
{
ltcData.Z.x /= length;
ltcData.Z.y /= length;
ltcData.Z.z /= length;
}
else
{
ltcData.Z.x = 0;
ltcData.Z.y = 0;
ltcData.Z.z = 1;
}
ltcData.X.x = ltcData.Z.z;
ltcData.X.y = 0;
ltcData.X.z = -ltcData.Z.x;
ltcData.Y.x = 0;
ltcData.Y.y = 1;
ltcData.Y.z = 0;
}
// Heitz & Hill Method => Fit M, inverse to obtain target matrix
static public void Update(ref LTCData ltcData)
{
// Build the source matrix M for which we're exploring the parameter space
ltcData.M.m00 = ltcData.m11 * ltcData.X.x;
ltcData.M.m01 = ltcData.m22 * ltcData.Y.x;
ltcData.M.m02 = ltcData.m13 * ltcData.X.x + ltcData.Z.x;
ltcData.M.m10 = ltcData.m11 * ltcData.X.y;
ltcData.M.m11 = ltcData.m22 * ltcData.Y.y;
ltcData.M.m12 = ltcData.m13 * ltcData.X.y + ltcData.Z.y;
ltcData.M.m20 = ltcData.m11 * ltcData.X.z;
ltcData.M.m21 = ltcData.m22 * ltcData.Y.z;
ltcData.M.m22 = ltcData.m13 * ltcData.X.z + ltcData.Z.z;
// Build the final matrix required at runtime for LTC evaluation
ltcData.detM = Invert(in ltcData.M, ref ltcData.invM);
if (ltcData.detM < 0.0)
{
// SHOULD NEVER HAPPEN
}
// Kill useless coeffs in matrix
ltcData.invM.m01 = 0; // Row 0 - Col 1
ltcData.invM.m10 = 0; // Row 1 - Col 0
ltcData.invM.m12 = 0; // Row 1 - Col 2
ltcData.invM.m21 = 0; // Row 2 - Col 1
}
static double Invert(in Matrix _A, ref Matrix _B)
{
double det = (_A.m00 * _A.m11 * _A.m22 + _A.m01 * _A.m12 * _A.m20 + _A.m02 * _A.m10 * _A.m21)
- (_A.m20 * _A.m11 * _A.m02 + _A.m21 * _A.m12 * _A.m00 + _A.m22 * _A.m10 * _A.m01);
if (Math.Abs(det) < double.Epsilon)
{
// SHOULD NEVER HAPPEN
}
double invDet = 1.0 / det;
_B.m00 = +(_A.m11 * _A.m22 - _A.m21 * _A.m12) * invDet;
_B.m10 = -(_A.m10 * _A.m22 - _A.m20 * _A.m12) * invDet;
_B.m20 = +(_A.m10 * _A.m21 - _A.m20 * _A.m11) * invDet;
_B.m01 = -(_A.m01 * _A.m22 - _A.m21 * _A.m02) * invDet;
_B.m11 = +(_A.m00 * _A.m22 - _A.m20 * _A.m02) * invDet;
_B.m21 = -(_A.m00 * _A.m21 - _A.m20 * _A.m01) * invDet;
_B.m02 = +(_A.m01 * _A.m12 - _A.m11 * _A.m02) * invDet;
_B.m12 = -(_A.m00 * _A.m12 - _A.m10 * _A.m02) * invDet;
_B.m22 = +(_A.m00 * _A.m11 - _A.m10 * _A.m01) * invDet;
return det;
}
public static void GetSamplingDirection(LTCData ltcData, float _U1, float _U2, ref Vector3 _direction)
{
// float theta = Mathf.Asin(Mathf.Sqrt(_U1));
float theta = Mathf.Acos(Mathf.Sqrt(_U1));
float phi = 2.0f * Mathf.PI * _U2;
Vector3 D = new Vector3(Mathf.Sin(theta) * Mathf.Cos(phi), Mathf.Sin(theta) * Mathf.Sin(phi), Mathf.Cos(theta));
Transform(ltcData.M, D, ref _direction);
_direction.Normalize();
}
public static double Eval(LTCData ltcData, ref Vector3 _tsLight)
{
// Transform into original distribution space
Vector3 Loriginal = Vector3.zero;
Transform(ltcData.invM, _tsLight, ref Loriginal);
float l = Loriginal.magnitude;
Loriginal /= l;
// Estimate original distribution (a clamped cosine lobe)
double D = Math.Max(0.0, Loriginal.z) / Math.PI;
// Compute the Jacobian, roundDwo / roundDw
double jacobian = 1.0 / (ltcData.detM * l * l * l);
// Scale distribution
return ltcData.magnitude * D * jacobian;
}
public static void Transform(Matrix a, Vector3 b, ref Vector3 c)
{
// Annoying GLM library details:
// return vec3(
// m[0][0] * v.x + m[1][0] * v.y + m[2][0] * v.z,
// m[0][1] * v.x + m[1][1] * v.y + m[2][1] * v.z, (thank God, they didn't change the math!)
// m[0][2] * v.x + m[1][2] * v.y + m[2][2] * v.z);
c.x = (float)(b.x * a.m00 + b.y * a.m01 + b.z * a.m02);
c.y = (float)(b.x * a.m10 + b.y * a.m11 + b.z * a.m12);
c.z = (float)(b.x * a.m20 + b.y * a.m21 + b.z * a.m22);
}
}
}

View File

@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: c098ee5b80917ce448a1020af0828fcf
AssetOrigin:
serializedVersion: 1
productId: 346790
packageName: Realtime Area Light for URP
packageVersion: 1.3.0
assetPath: Packages/com.baddog.rendering.arealight/Editor/Material/LTCAreaLight/LTC.cs
uploadId: 884030

View File

@@ -0,0 +1,407 @@
using System;
using System.IO;
using Unity.Collections;
using Unity.Jobs;
using UnityEngine;
using BadDog.Rendering.AreaLight;
namespace BadDog.Rendering.AreaLight.LTC
{
public class LTCTableGenerator
{
public enum LTCTableParametrization
{
CosTheta,
Theta
}
// Minimal roughness to avoid singularities
const float k_MinRoughness = 0.001f;
const int k_MaxIterations = 100;
const float k_FitExploreDelta = 0.05f;
const float k_Tolerance = 1e-5f;
// Holds all the information required to achieve a LTC table generation
public class BRDFGenerator
{
public Type type;
public IBRDF brdf;
public LTCTableParametrization parametrization;
public bool shouldGenerate;
public int tableResolution;
public int sampleCount;
public string outputDir;
public BRDFGenerator(Type targetType, int tableResolution, int sampleCount, LTCTableParametrization parametrization, string outputDir)
{
this.type = targetType;
this.brdf = (IBRDF)Activator.CreateInstance(targetType);
this.shouldGenerate = true;
this.tableResolution = tableResolution;
this.sampleCount = sampleCount;
this.outputDir = outputDir;
this.parametrization = parametrization;
}
};
struct BRDFGeneratorJob : IJobParallelFor
{
[NativeDisableParallelForRestriction]
public NativeArray<LTCData> ltcData;
public int tableResolution;
public int sampleCount;
public LTCLightingModel lightingModel;
public LTCTableParametrization parametrization;
public void Fit(int roughnessIndex, int thetaIndex, NelderMead fitter, IBRDF brdf)
{
// Compute the roughness and cosTheta for this sample
float roughness, cosTheta;
GetRoughnessAndAngle(roughnessIndex, thetaIndex, tableResolution, parametrization, out roughness, out cosTheta);
// Compute the matching view vector
Vector3 tsView = new Vector3(Mathf.Sqrt(1 - cosTheta * cosTheta), 0, cosTheta);
// Compute BRDF's magnitude and average direction
LTCData currentLTCData;
LTCDataUtilities.Initialize(out currentLTCData);
LTCDataUtilities.ComputeAverageTerms(brdf, ref tsView, roughness, sampleCount, ref currentLTCData);
// Otherwise use average direction as Z vector
int previousLTCDataIndex = (thetaIndex - 1) * tableResolution + roughnessIndex;
LTCData previousLTC = ltcData[previousLTCDataIndex];
currentLTCData.m11 = previousLTC.m11;
currentLTCData.m22 = previousLTC.m22;
currentLTCData.m13 = previousLTC.m13;
LTCDataUtilities.Update(ref currentLTCData);
// Find best-fit LTC lobe (scale, alphax, alphay)
if (currentLTCData.magnitude > 1e-6)
{
double[] startFit = LTCDataUtilities.GetFittingParms(in currentLTCData);
double[] resultFit = new double[startFit.Length];
int localSampleCount = sampleCount;
currentLTCData.error = (float)fitter.FindFit(resultFit, startFit, (double)k_FitExploreDelta, (double)k_Tolerance, k_MaxIterations, (double[] parameters) =>
{
LTCDataUtilities.SetFittingParms(ref currentLTCData, parameters, false);
return ComputeError(currentLTCData, brdf, localSampleCount, ref tsView, roughness);
});
currentLTCData.iterationsCount = fitter.m_lastIterationsCount;
// Update LTC with final best fitting values
LTCDataUtilities.SetFittingParms(ref currentLTCData, resultFit, false);
}
// Store new valid result
int currentLTCDataIndex = thetaIndex * tableResolution + roughnessIndex;
ltcData[currentLTCDataIndex] = currentLTCData;
}
public void Execute(int roughnessIndex)
{
// Create the fitter
NelderMead fitter = new NelderMead(3);
IBRDF brdf = LTCAreaLight.GetBRDFInterface(lightingModel);
// Compute all the missing LTCData (0 of the first line is already done)
for (int thetaIndex = 1; thetaIndex < tableResolution; thetaIndex++)
{
Fit(roughnessIndex, thetaIndex, fitter, brdf);
}
}
}
static void GetRoughnessAndAngle(int roughnessIndex, int thetaIndex, int tableResolution, LTCTableParametrization parametrization, out float alpha, out float cosTheta)
{
float perceptualRoughness = (float)roughnessIndex / (tableResolution - 1);
alpha = Mathf.Max(k_MinRoughness, perceptualRoughness * perceptualRoughness);
if (parametrization == LTCTableParametrization.CosTheta)
{
// Parameterised by sqrt(1 - cos(theta))
float v = (float)thetaIndex / (tableResolution - 1);
cosTheta = 1.0f - v * v;
// Clamp to cos(1.57)
cosTheta = Mathf.Max(3.7540224885647058065387021283285e-4f, cosTheta);
}
else
{
float theta = Mathf.Min(1.57f, thetaIndex / (float)(tableResolution - 1) * 1.57079f);
cosTheta = Mathf.Cos(theta);
}
}
static public void FitInitial(BRDFGenerator brdfGenerator, NelderMead fitter, NativeArray<LTCData> ltcData, int roughnessIndex, int thetaIndex)
{
// Compute the roughness and cosTheta for this sample
float roughness, cosTheta;
GetRoughnessAndAngle(roughnessIndex, thetaIndex, brdfGenerator.tableResolution, brdfGenerator.parametrization, out roughness, out cosTheta);
// Compute the matching view vector
Vector3 tsView = new Vector3(Mathf.Sqrt(1 - cosTheta * cosTheta), 0, cosTheta);
// Compute BRDF's magnitude and average direction
LTCData currentLTCData;
LTCDataUtilities.Initialize(out currentLTCData);
LTCDataUtilities.ComputeAverageTerms(brdfGenerator.brdf, ref tsView, roughness, brdfGenerator.sampleCount, ref currentLTCData);
// if theta == 0 the lobe is rotationally symmetric and aligned with Z = (0 0 1)
currentLTCData.X.x = 1;
currentLTCData.X.y = 0;
currentLTCData.X.z = 0;
currentLTCData.Y.x = 0;
currentLTCData.Y.y = 1;
currentLTCData.Y.z = 0;
currentLTCData.Z.x = 0;
currentLTCData.Z.y = 0;
currentLTCData.Z.z = 1;
if (roughnessIndex == (brdfGenerator.tableResolution - 1))
{
// roughness = 1 or no available result
currentLTCData.m11 = 1.0f;
currentLTCData.m22 = 1.0f;
}
else
{
// init with roughness of previous fit
LTCData previousLTC = ltcData[roughnessIndex + 1];
currentLTCData.m11 = previousLTC.m11;
currentLTCData.m22 = previousLTC.m22;
}
currentLTCData.m13 = 0;
LTCDataUtilities.Update(ref currentLTCData);
// Find best-fit LTC lobe (scale, alphax, alphay)
if (currentLTCData.magnitude > 1e-6)
{
double[] startFit = LTCDataUtilities.GetFittingParms(in currentLTCData);
double[] resultFit = new double[startFit.Length];
currentLTCData.error = (float)fitter.FindFit(resultFit, startFit, k_FitExploreDelta, k_Tolerance, k_MaxIterations, (double[] parameters) =>
{
LTCDataUtilities.SetFittingParms(ref currentLTCData, parameters, true);
return ComputeError(currentLTCData, brdfGenerator.brdf, brdfGenerator.sampleCount, ref tsView, roughness);
});
currentLTCData.iterationsCount = fitter.m_lastIterationsCount;
// Update LTC with final best fitting values
LTCDataUtilities.SetFittingParms(ref currentLTCData, resultFit, true);
}
// Store new valid result
ltcData[roughnessIndex] = currentLTCData;
}
// Compute the error between the BRDF and the LTC using Multiple Importance Sampling
static float ComputeError(LTCData ltcData, IBRDF brdf, int sampleCount, ref Vector3 _tsView, float _alpha)
{
Vector3 tsLight = Vector3.zero;
double pdf_BRDF, eval_BRDF;
double pdf_LTC, eval_LTC;
float sumError = 0.0f;
for (int j = 0; j < sampleCount; ++j)
{
for (int i = 0; i < sampleCount; ++i)
{
float U1 = (i + 0.5f) / sampleCount;
float U2 = (j + 0.5f) / sampleCount;
// importance sample LTC
{
// sample
LTCDataUtilities.GetSamplingDirection(ltcData, U1, U2, ref tsLight);
eval_BRDF = brdf.Eval(ref _tsView, ref tsLight, _alpha, out pdf_BRDF);
eval_LTC = (float)LTCDataUtilities.Eval(ltcData, ref tsLight);
pdf_LTC = eval_LTC / ltcData.magnitude;
// error with MIS weight
float error = Mathf.Abs((float)(eval_BRDF - eval_LTC));
error = error * error * error; // Use L3 norm to favor large values over smaller ones
if (error != 0.0f)
error /= (float)pdf_LTC + (float)pdf_BRDF;
if (double.IsNaN(error))
{
// SHOULD NEVER HAPPEN
}
sumError += error;
}
// importance sample BRDF
{
// sample
brdf.GetSamplingDirection(ref _tsView, _alpha, U1, U2, ref tsLight);
// error with MIS weight
eval_BRDF = brdf.Eval(ref _tsView, ref tsLight, _alpha, out pdf_BRDF);
eval_LTC = LTCDataUtilities.Eval(ltcData, ref tsLight);
pdf_LTC = eval_LTC / ltcData.magnitude;
float error = Mathf.Abs((float)(eval_BRDF - eval_LTC));
error = error * error * error; // Use L3 norm to favor large values over smaller ones
if (error != 0.0f)
error /= (float)pdf_LTC + (float)pdf_BRDF;
if (double.IsNaN(error))
{
// SHOULD NEVER HAPPEN
}
sumError += error;
}
}
}
return sumError / ((float)sampleCount * sampleCount);
}
static public void ExecuteFittingJob(BRDFGenerator brdfGenerator, bool parallel)
{
// When dispatching the table on the two dimensions (X, Y) a set of constrains apply:
// - Every element (Xi, Yi) has a dependency on the previous one on the same column.
// - The first element of a column has a dependency on the first element of the previous column.
// - The element (0,0) doesn't have a dependency on any element.
// To be able to dispatch this as a job, we need to compute the first line linearly and then dispatch every column starting from the second element.
using (var ltcData = new NativeArray<LTCData>(brdfGenerator.tableResolution * brdfGenerator.tableResolution, Allocator.TempJob))
{
// Create the fitter
NelderMead fitter = new NelderMead(3);
Debug.Log("Running fitting job on the " + brdfGenerator.type.Name + " BRDF.");
// Fill the first line
for (int roughnessIndex = brdfGenerator.tableResolution - 1; roughnessIndex >= 0; roughnessIndex--)
FitInitial(brdfGenerator, fitter, ltcData, roughnessIndex, 0);
BRDFGeneratorJob brdfJob = new BRDFGeneratorJob
{
ltcData = ltcData,
tableResolution = brdfGenerator.tableResolution,
sampleCount = brdfGenerator.sampleCount,
lightingModel = brdfGenerator.brdf.GetLightingModel(),
parametrization = brdfGenerator.parametrization,
};
if (parallel)
{
// Create, run the job and wait for its completion.
JobHandle fittingJob = brdfJob.Schedule(brdfGenerator.tableResolution, 1);
fittingJob.Complete();
}
else
{
for (int i = 0; i < brdfGenerator.tableResolution; ++i)
{
brdfJob.Execute(i);
}
}
Debug.Log("Fitting done. Exporting the file");
// Export the table to disk
string BRDFName = brdfGenerator.type.Name;
FileInfo CSharpFileName = new FileInfo(Path.Combine(brdfGenerator.outputDir, "LtcData." + BRDFName + ".cs"));
ExportToCSharp(ltcData, brdfGenerator.tableResolution, brdfGenerator.parametrization, CSharpFileName, BRDFName);
}
}
static void ExportToCSharp(NativeArray<LTCData> ltcDataArray, int tableResolution, LTCTableParametrization parametrization, FileInfo _CSharpFileName, string brdfName)
{
string sourceCode = "";
LTCData ltcData;
ltcData.magnitude = 0.0f;
string tableName = "s_LtcMatrixData_" + brdfName;
sourceCode += "using UnityEngine;\n"
+ "using System;\n"
+ "\n"
+ "namespace BadDog.Rendering.AreaLight\n"
+ "{\n"
+ " public partial class LTCAreaLight\n"
+ " {\n"
+ " // [GENERATED CONTENT " + DateTime.Now.ToString("dd MMM yyyy HH:mm:ss") + "]\n"
+ " // Table contains 3x3 matrix coefficients of M^-1 for the fitting of the " + brdfName + " BRDF using the LTC technique\n"
+ " // Only the 0,2,4,6 of the 3x3 coefficients are emitted, and they are encoded into FP16\n"
+ " // From \"Real-Time Polygonal-Light Shading with Linearly Transformed Cosines\" 2016 (https://eheitzresearch.wordpress.com/415-2/)\n"
+ " //\n"
+ " // The table is accessed via LTCAreaLight." + tableName + "[<roughnessIndex> + 64 * <thetaIndex>] // Theta values are along the Y axis, Roughness values are along the X axis\n"
+ " // • roughness = ( <roughnessIndex> / " + (tableResolution - 1) + " )^2 (the table is indexed by perceptual roughness)\n";
if (parametrization == LTCTableParametrization.CosTheta)
sourceCode += " // • cosTheta = 1 - ( <thetaIndex> / " + (tableResolution - 1) + " )^2\n";
else
sourceCode += " // • theta = ( <thetaIndex> / " + (tableResolution - 1) + " )\n";
sourceCode += " //\n"
+ " public static ushort[] " + tableName + " = new ushort[" + tableResolution + " * " + tableResolution + " * 4]\n"
+ " {";
string lotsOfSpaces = " ";
float alpha, cosTheta;
for (int thetaIndex = 0; thetaIndex < tableResolution; thetaIndex++)
{
GetRoughnessAndAngle(0, thetaIndex, tableResolution, parametrization, out alpha, out cosTheta);
sourceCode += "\n";
if (parametrization == LTCTableParametrization.CosTheta)
sourceCode += " // Cos (theta) = " + cosTheta + "\n";
else
sourceCode += " // Theta = " + Mathf.Acos(cosTheta) + "\n";
for (int roughnessIndex = 0; roughnessIndex < tableResolution; roughnessIndex++)
{
// Compute the current ltc data index
int currentIndexData = roughnessIndex + thetaIndex * tableResolution;
ltcData = ltcDataArray[currentIndexData];
GetRoughnessAndAngle(roughnessIndex, thetaIndex, tableResolution, parametrization, out alpha, out cosTheta);
// Export the matrix as a list of 3x3 doubles, columns first. Only emit 0,2,4,6 elements of the list
// since others are zeroes or ones.
double factor = 1.0 / ltcData.invM.m22;
float val0 = (float) (factor * ltcData.invM.m00);
float val2 = (float) (factor * ltcData.invM.m20);
float val4 = (float) (factor * ltcData.invM.m11);
float val6 = (float) (factor * ltcData.invM.m02);
float fp16Max = 65504.0f;
Debug.Assert(Mathf.Abs(val0) <= fp16Max, "This FP32 value is too large to be converted to FP16.");
Debug.Assert(Mathf.Abs(val2) <= fp16Max, "This FP32 value is too large to be converted to FP16.");
Debug.Assert(Mathf.Abs(val4) <= fp16Max, "This FP32 value is too large to be converted to FP16.");
Debug.Assert(Mathf.Abs(val6) <= fp16Max, "This FP32 value is too large to be converted to FP16.");
string line = $" {Mathf.FloatToHalf(val0)}, {Mathf.FloatToHalf(val2)}, {Mathf.FloatToHalf(val4)}, {Mathf.FloatToHalf(val6)},";
if (line.Length < 45)
line += lotsOfSpaces.Substring(lotsOfSpaces.Length - (45 - line.Length)); // Pad with spaces
sourceCode += line;
sourceCode += "// alpha = " + alpha + "\n";
}
}
sourceCode += " };\n";
// End comment
sourceCode += "\n";
sourceCode += " // NOTE: Formerly, we needed to also export and create a table for the BRDF's amplitude factor + fresnel coefficient\n";
sourceCode += " // but it turns out these 2 factors are actually already precomputed and available in the FGD table corresponding\n";
sourceCode += " // to the " + brdfName + " BRDF, therefore they are no longer exported...\n";
// Close class and namespace
sourceCode += " }\n";
sourceCode += "}\n";
// Write content
using (StreamWriter W = _CSharpFileName.CreateText())
W.Write(sourceCode);
}
}
}

View File

@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: e427d3c389607064499b1c65b15a13a1
AssetOrigin:
serializedVersion: 1
productId: 346790
packageName: Realtime Area Light for URP
packageVersion: 1.3.0
assetPath: Packages/com.baddog.rendering.arealight/Editor/Material/LTCAreaLight/LTCTableGenerator.cs
uploadId: 884030

View File

@@ -0,0 +1,152 @@
using System;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using UnityEditor;
using System.Linq;
using BadDog.Rendering.AreaLight.LTC;
namespace BadDog.Rendering.AreaLight.LTC
{
public class LTCTableGeneratorEditor : EditorWindow
{
// The output directory for the tool
static string k_OutputDirectory = "./Assets/Generated/LTCTables/";
// Generated table's resolutions
const int k_TableResolution = 64;
// Sample count that will be used for the generation
const int k_DefaultSampleCount = 32;
// The array of lighting models that we need to generate
LTCTableGenerator.BRDFGenerator[] m_BRDFGeneratorArray = null;
// Sample count that will be used for the generation
int m_SampleCount = 32;
// Flag to generate in parallel
bool m_ParallelExecution = true;
// Defines which parametrization should be use when generating the tables
LTCTableGenerator.LTCTableParametrization m_Parametrization;
static Type[] ListAllBRDFTypes()
{
// This function lists all the classes that implement the interface IBRDF
List<Type> types = new List<Type>();
Type searchInterface = typeof(IBRDF);
return AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(s => s.GetTypes())
.Where(p => searchInterface.IsAssignableFrom(p) && !p.IsInterface).ToArray();
}
static void BuildBRDFGenerators(ref LTCTableGenerator.BRDFGenerator[] BRDFGeneratorArray)
{
// Collect all the BRDFs that we need to generate
Type[] brdfTypes = ListAllBRDFTypes();
if (brdfTypes.Length != 0)
{
BRDFGeneratorArray = new LTCTableGenerator.BRDFGenerator[brdfTypes.Length];
for (int i = 0; i < brdfTypes.Length; ++i)
{
BRDFGeneratorArray[i] = new LTCTableGenerator.BRDFGenerator(brdfTypes[i], k_TableResolution, k_DefaultSampleCount, LTCTableGenerator.LTCTableParametrization.CosTheta, k_OutputDirectory);
}
}
}
[MenuItem("BadDog/Rendering/Area Light/Generate LTC Tables")]
private static void Init()
{
// Create the window
LTCTableGeneratorEditor window = (LTCTableGeneratorEditor)EditorWindow.GetWindow(typeof(LTCTableGeneratorEditor));
// Name the window
window.titleContent.text = "LTC Tables Generator";
// Build the generators that we will be executing later
BuildBRDFGenerators(ref window.m_BRDFGeneratorArray);
// Display the window
window.Show();
}
private void OnGUI()
{
if (m_BRDFGeneratorArray == null)
BuildBRDFGenerators(ref m_BRDFGeneratorArray);
EditorGUILayout.LabelField("Recognized BRDF Types: " + m_BRDFGeneratorArray.Length);
EditorGUILayout.Separator();
// Display the generators and their toggles
int numActiveGenerators = 0;
for (int i = 0; i < m_BRDFGeneratorArray.Length; ++i)
{
LTCTableGenerator.BRDFGenerator currentGenerator = m_BRDFGeneratorArray[i];
currentGenerator.shouldGenerate = EditorGUILayout.Toggle(currentGenerator.type.Name, currentGenerator.shouldGenerate);
if (currentGenerator.shouldGenerate)
numActiveGenerators++;
EditorGUILayout.Space();
}
m_ParallelExecution = EditorGUILayout.Toggle("Parallel", m_ParallelExecution);
m_SampleCount = EditorGUILayout.IntField("Sample Count", m_SampleCount);
m_Parametrization = (LTCTableGenerator.LTCTableParametrization)EditorGUILayout.EnumPopup("Theta parametrization", m_Parametrization);
EditorGUILayout.Separator();
if (m_BRDFGeneratorArray.Length > 1)
{
EditorGUILayout.BeginHorizontal();
if (GUILayout.Button(new GUIContent("Select All", ""), EditorStyles.miniButton, GUILayout.ExpandWidth(false)))
{
for (int i = 0; i < m_BRDFGeneratorArray.Length; ++i)
{
m_BRDFGeneratorArray[i].shouldGenerate = true;
}
}
if (GUILayout.Button(new GUIContent("Select None", ""), EditorStyles.miniButton, GUILayout.ExpandWidth(false)))
{
for (int i = 0; i < m_BRDFGeneratorArray.Length; ++i)
{
m_BRDFGeneratorArray[i].shouldGenerate = false;
}
}
EditorGUILayout.EndHorizontal();
}
if (numActiveGenerators > 0)
{
EditorGUILayout.Separator();
EditorGUILayout.Space();
if (GUILayout.Button(new GUIContent("Generate LTC Tables", "")))
{
// Make sure target directory exists before creating any file!
DirectoryInfo outputDir = new DirectoryInfo(k_OutputDirectory);
if (!outputDir.Exists)
outputDir.Create();
for (int i = 0; i < m_BRDFGeneratorArray.Length; ++i)
{
EditorUtility.DisplayProgressBar("Generating LTC Tables", $"Generating {m_BRDFGeneratorArray[i].type.Name}", (float)i / m_BRDFGeneratorArray.Length);
if (m_BRDFGeneratorArray[i].shouldGenerate)
{
m_BRDFGeneratorArray[i].sampleCount = m_SampleCount;
m_BRDFGeneratorArray[i].parametrization = m_Parametrization;
LTCTableGenerator.ExecuteFittingJob(m_BRDFGeneratorArray[i], m_ParallelExecution);
}
}
EditorUtility.ClearProgressBar();
AssetDatabase.Refresh();
}
}
}
}
}

View File

@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 743bf0aadd3301645862ae4e8ab675ce
AssetOrigin:
serializedVersion: 1
productId: 346790
packageName: Realtime Area Light for URP
packageVersion: 1.3.0
assetPath: Packages/com.baddog.rendering.arealight/Editor/Material/LTCAreaLight/LTCTableGeneratorEditor.cs
uploadId: 884030

View File

@@ -0,0 +1,166 @@
using System;
namespace BadDog.Rendering.AreaLight.LTC
{
/// <summary>
/// Downhill simplex solver:
/// http://en.wikipedia.org/wiki/Nelder%E2%80%93Mead_method#One_possible_variation_of_the_NM_algorithm
/// Using the termination criterion from Numerical Recipes in C++ (3rd Ed.)
/// </summary>
public class NelderMead
{
// standard coefficients from Nelder-Mead
const double reflect = 1.0;
const double expand = 2.0;
const double contract = 0.5;
const double shrink = 0.5;
int DIM;
int NB_POINTS;
double[][] s;
double[] f;
public int m_lastIterationsCount;
public delegate double ObjectiveFunctionDelegate(double[] _parameters);
public NelderMead(int _dimensions)
{
DIM = _dimensions;
NB_POINTS = _dimensions + 1;
s = new double[NB_POINTS][];
for (int i = 0; i < NB_POINTS; i++)
s[i] = new double[_dimensions];
f = new double[NB_POINTS];
}
public double FindFit(double[] _pmin, double[] _start, double _delta, double _tolerance, int _maxIterations, ObjectiveFunctionDelegate _objectiveFn)
{
// initialise simplex
Mov(s[0], _start);
for (int i = 1; i < NB_POINTS; i++)
{
Mov(s[i], _start);
s[i][i - 1] += _delta;
}
// evaluate function at each point on simplex
for (int i = 0; i < NB_POINTS; i++)
f[i] = _objectiveFn(s[i]);
double[] o = new double[DIM]; // Centroid
double[] r = new double[DIM]; // Reflection
double[] c = new double[DIM]; // Contraction
double[] e = new double[DIM]; // Expansion
int lo = 0, hi, nh;
for (m_lastIterationsCount = 0; m_lastIterationsCount < _maxIterations; m_lastIterationsCount++)
{
// find lowest, highest and next highest
lo = hi = nh = 0;
for (int i = 1; i < NB_POINTS; i++)
{
if (f[i] < f[lo])
lo = i;
if (f[i] > f[hi])
{
nh = hi;
hi = i;
}
else if (f[i] > f[nh])
nh = i;
}
// stop if we've reached the required tolerance level
double a = Math.Abs(f[lo]);
double b = Math.Abs(f[hi]);
if (2.0 * Math.Abs(a - b) < (a + b) * _tolerance)
break;
// compute centroid (excluding the worst point)
Set(o, 0.0f);
for (int i = 0; i < NB_POINTS; i++)
{
if (i == hi)
continue;
Add(o, s[i]);
}
for (int i = 0; i < DIM; i++)
o[i] /= DIM;
// reflection
for (int i = 0; i < DIM; i++)
r[i] = o[i] + reflect * (o[i] - s[hi][i]);
double fr = _objectiveFn(r);
if (fr < f[nh])
{
if (fr < f[lo])
{
// expansion
for (int i = 0; i < DIM; i++)
e[i] = o[i] + expand * (o[i] - s[hi][i]);
double fe = _objectiveFn(e);
if (fe < fr)
{
Mov(s[hi], e);
f[hi] = fe;
continue;
}
}
Mov(s[hi], r);
f[hi] = fr;
continue;
}
// contraction
for (int i = 0; i < DIM; i++)
c[i] = o[i] - contract * (o[i] - s[hi][i]);
double fc = _objectiveFn(c);
if (fc < f[hi])
{
Mov(s[hi], c);
f[hi] = fc;
continue;
}
// reduction
for (int k = 0; k < NB_POINTS; k++)
{
if (k == lo)
continue;
for (int i = 0; i < DIM; i++)
s[k][i] = s[lo][i] + shrink * (s[k][i] - s[lo][i]);
f[k] = _objectiveFn(s[k]);
}
}
// return best point and its value
Mov(_pmin, s[lo]);
return f[lo];
}
void Mov(double[] r, double[] v)
{
for (int i = 0; i < DIM; ++i)
r[i] = v[i];
}
void Set(double[] r, double v)
{
for (int i = 0; i < DIM; ++i)
r[i] = v;
}
void Add(double[] r, double[] v)
{
for (int i = 0; i < DIM; ++i)
r[i] += v[i];
}
}
}

View File

@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 4e8c2e3b39598a649b575f912598799f
AssetOrigin:
serializedVersion: 1
productId: 346790
packageName: Realtime Area Light for URP
packageVersion: 1.3.0
assetPath: Packages/com.baddog.rendering.arealight/Editor/Material/LTCAreaLight/NelderMead.cs
uploadId: 884030

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 488ad7a3d1fd10546a26673c51f021ee
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,222 @@
using System.Collections.Generic;
using System.Reflection;
using UnityEngine;
using UnityEditor;
using UnityEngine.Rendering;
using UnityEngine.Experimental.Rendering;
namespace BadDog.Rendering.AreaLight.Editor
{
public class PreIntegratedFGDWindow : EditorWindow
{
private PreIntegratedFGD.FGDIndex m_FGDIndex = PreIntegratedFGD.FGDIndex.FGD_GGXAndDisneyDiffuse;
private RenderTexture m_PreviewTexture = null;
private bool m_IsInitialized = false;
private PreIntegratedFGD.FGDIndex m_LastFGDIndex = PreIntegratedFGD.FGDIndex.FGD_GGXAndDisneyDiffuse;
[MenuItem("BadDog/Rendering/Area Light/Pre-Integrated FGD Preview")]
private static void ShowWindow()
{
var window = GetWindow<PreIntegratedFGDWindow>("Pre-Integrated FGD Preview", true);
window.minSize = new Vector2(512, 512);
window.Show();
}
private void OnEnable()
{
InitializePreview();
}
private void OnDisable()
{
CleanupPreview();
}
private void OnGUI()
{
EditorGUILayout.Space();
DrawOptions();
EditorGUILayout.Space();
DrawPreview();
}
private void DrawOptions()
{
EditorGUILayout.BeginVertical("box");
EditorGUILayout.LabelField("Options", EditorStyles.boldLabel);
// Ensure we don't select Count
if (m_FGDIndex == PreIntegratedFGD.FGDIndex.Count)
{
m_FGDIndex = PreIntegratedFGD.FGDIndex.FGD_GGXAndDisneyDiffuse;
}
// Create enum without Count and Marschner (not implemented)
var validValues = System.Enum.GetValues(typeof(PreIntegratedFGD.FGDIndex));
var displayNames = new System.Collections.Generic.List<string>();
var enumValues = new System.Collections.Generic.List<PreIntegratedFGD.FGDIndex>();
foreach (PreIntegratedFGD.FGDIndex value in validValues)
{
if (value != PreIntegratedFGD.FGDIndex.Count && value != PreIntegratedFGD.FGDIndex.FGD_Marschner)
{
displayNames.Add(value.ToString());
enumValues.Add(value);
}
}
int currentIndex = enumValues.IndexOf(m_FGDIndex);
if (currentIndex < 0)
{
// If current index is invalid (e.g., Marschner), reset to first valid option
currentIndex = 0;
if (enumValues.Count > 0)
{
m_FGDIndex = enumValues[0];
}
}
EditorGUI.BeginChangeCheck();
int newIndex = EditorGUILayout.Popup("FGD Type", currentIndex, displayNames.ToArray());
bool changed = EditorGUI.EndChangeCheck();
if (changed && newIndex >= 0 && newIndex < enumValues.Count)
{
m_FGDIndex = enumValues[newIndex];
UpdatePreview();
}
if (GUILayout.Button("Refresh Preview"))
{
UpdatePreview();
}
EditorGUILayout.EndVertical();
}
private void DrawPreview()
{
EditorGUILayout.BeginVertical("box");
EditorGUILayout.LabelField("Preview", EditorStyles.boldLabel);
if (m_PreviewTexture != null && m_PreviewTexture.IsCreated())
{
int resolution = (int)PreIntegratedFGD.FGDTexture.Resolution;
Rect labelRect = GUILayoutUtility.GetLastRect();
Rect rect = GUILayoutUtility.GetRect(resolution, resolution, GUILayout.ExpandWidth(false), GUILayout.ExpandHeight(false));
rect.height = rect.width; // Make it square
// Center the preview horizontally
float availableWidth = EditorGUIUtility.currentViewWidth;
rect.x = (availableWidth - rect.width) * 0.5f;
var sRGBWrite = GL.sRGBWrite;
GL.sRGBWrite = false;
EditorGUI.DrawPreviewTexture(rect, m_PreviewTexture);
GL.sRGBWrite = sRGBWrite;
}
else
{
EditorGUILayout.HelpBox("Preview texture not available. Click 'Refresh Preview' to generate.", MessageType.Info);
}
EditorGUILayout.EndVertical();
}
private void InitializePreview()
{
if (m_IsInitialized)
{
return;
}
int resolution = (int)PreIntegratedFGD.FGDTexture.Resolution;
m_PreviewTexture = new RenderTexture(resolution, resolution, 0, GraphicsFormat.A2B10G10R10_UNormPack32)
{
hideFlags = HideFlags.HideAndDontSave,
filterMode = FilterMode.Bilinear,
wrapMode = TextureWrapMode.Clamp,
name = "PreIntegratedFGD_Preview"
};
m_PreviewTexture.Create();
m_IsInitialized = true;
UpdatePreview();
}
private void CleanupPreview()
{
if (m_IsInitialized)
{
PreIntegratedFGD.instance.Cleanup(m_LastFGDIndex);
m_IsInitialized = false;
}
if (m_PreviewTexture != null)
{
DestroyImmediate(m_PreviewTexture);
m_PreviewTexture = null;
}
}
private void UpdatePreview()
{
if (!m_IsInitialized || m_PreviewTexture == null)
{
return;
}
// Validate FGD index
if (m_FGDIndex == PreIntegratedFGD.FGDIndex.Count || (int)m_FGDIndex >= (int)PreIntegratedFGD.FGDIndex.Count)
{
Debug.LogError($"Invalid FGD index: {m_FGDIndex}");
return;
}
// Cleanup previous FGD index if it's different and was built
if (m_LastFGDIndex != m_FGDIndex)
{
// Validate last index before accessing array
if (m_LastFGDIndex != PreIntegratedFGD.FGDIndex.Count && (int)m_LastFGDIndex < (int)PreIntegratedFGD.FGDIndex.Count)
{
// Check if the previous index was actually built by checking refCounting
var fgdType = typeof(PreIntegratedFGD);
var refCountingField = fgdType.GetField("m_refCounting", BindingFlags.NonPublic | BindingFlags.Instance);
if (refCountingField != null)
{
var refCounting = refCountingField.GetValue(PreIntegratedFGD.instance) as int[];
if (refCounting != null && (int)m_LastFGDIndex >= 0 && (int)m_LastFGDIndex < refCounting.Length && refCounting[(int)m_LastFGDIndex] > 0)
{
PreIntegratedFGD.instance.Cleanup(m_LastFGDIndex);
}
}
}
m_LastFGDIndex = m_FGDIndex;
}
// Build new (this increments refCounting)
PreIntegratedFGD.instance.Build(m_FGDIndex);
// Render the FGD texture
CommandBuffer cmd = CommandBufferPool.Get("PreIntegratedFGD Preview");
PreIntegratedFGD.instance.RenderInit(m_FGDIndex, cmd);
Graphics.ExecuteCommandBuffer(cmd);
CommandBufferPool.Release(cmd);
// Get the internal texture using reflection
var fgdType2 = typeof(PreIntegratedFGD);
var fieldInfo = fgdType2.GetField("m_PreIntegratedFGD", BindingFlags.NonPublic | BindingFlags.Instance);
if (fieldInfo != null)
{
var textures = fieldInfo.GetValue(PreIntegratedFGD.instance) as RenderTexture[];
if (textures != null && (int)m_FGDIndex >= 0 && (int)m_FGDIndex < textures.Length && textures[(int)m_FGDIndex] != null && textures[(int)m_FGDIndex].IsCreated())
{
Graphics.Blit(textures[(int)m_FGDIndex], m_PreviewTexture);
}
}
Repaint();
}
}
}

View File

@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 44fa7453efc94394b87b7e9343c6a80f
AssetOrigin:
serializedVersion: 1
productId: 346790
packageName: Realtime Area Light for URP
packageVersion: 1.3.0
assetPath: Packages/com.baddog.rendering.arealight/Editor/Material/PreIntegratedFGD/PreIntegratedFGDWindow.cs
uploadId: 884030

View File

@@ -0,0 +1,66 @@
using UnityEngine;
using UnityEditor;
namespace BadDog.Rendering.AreaLight
{
/// <summary>
/// Restricts values to power-of-2 resolutions: 256, 512, 1024, 2048, 4096, 8192
/// </summary>
[CustomPropertyDrawer(typeof(ShadowAtlasResolutionAttribute))]
public class ShadowAtlasResolutionDrawer : PropertyDrawer
{
private static readonly int[] k_AllowedResolutions = { 256, 512, 1024, 2048, 4096, 8192 };
private static readonly string[] k_ResolutionLabels = { "256", "512", "1024", "2048", "4096", "8192" };
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
if (property.propertyType != SerializedPropertyType.Integer)
{
EditorGUI.PropertyField(position, property, label);
return;
}
int currentValue = property.intValue;
int selectedIndex = GetClosestResolutionIndex(currentValue);
EditorGUI.BeginProperty(position, label, property);
Rect popupRect = EditorGUI.PrefixLabel(position, label);
int newIndex = EditorGUI.Popup(popupRect, selectedIndex, k_ResolutionLabels);
if (newIndex != selectedIndex)
{
property.intValue = k_AllowedResolutions[newIndex];
}
else
{
int clampedValue = k_AllowedResolutions[selectedIndex];
if (currentValue != clampedValue)
{
property.intValue = clampedValue;
}
}
EditorGUI.EndProperty();
}
private int GetClosestResolutionIndex(int value)
{
int closestIndex = 0;
int minDiff = Mathf.Abs(value - k_AllowedResolutions[0]);
for (int i = 1; i < k_AllowedResolutions.Length; i++)
{
int diff = Mathf.Abs(value - k_AllowedResolutions[i]);
if (diff < minDiff)
{
minDiff = diff;
closestIndex = i;
}
}
return closestIndex;
}
}
}

View File

@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 7ac81e325169e984d818fa384ed60f5d
AssetOrigin:
serializedVersion: 1
productId: 346790
packageName: Realtime Area Light for URP
packageVersion: 1.3.0
assetPath: Packages/com.baddog.rendering.arealight/Editor/ShadowAtlasResolutionDrawer.cs
uploadId: 884030

View File

@@ -0,0 +1,21 @@
{
"name": "com.baddog.rendering.arealight.editor",
"rootNamespace": "",
"references": [
"BadDog.Rendering.AreaLight",
"Unity.RenderPipelines.Core.Runtime",
"Unity.RenderPipelines.Core.Editor"
],
"includePlatforms": [
"Editor"
],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}

View File

@@ -0,0 +1,14 @@
fileFormatVersion: 2
guid: e7d9aa655ea9afc4183b621c1d341004
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 346790
packageName: Realtime Area Light for URP
packageVersion: 1.3.0
assetPath: Packages/com.baddog.rendering.arealight/Editor/com.baddog.rendering.arealight.editor.asmdef
uploadId: 884030