2026-04-16 오브젝트 그림자
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 803dead2a59b2d845ba66743a62b6d28
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 488ad7a3d1fd10546a26673c51f021ee
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user