You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

348 lines
12 KiB

/*
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;
namespace UnityEngine.Rendering.HighDefinition.LTC
{
internal struct Vec3
{
public double x;
public double y;
public double z;
};
internal class Vec3Utilities
{
internal static double Length(Vec3 vec3)
{
return Math.Sqrt(vec3.x * vec3.x + vec3.y * vec3.y + vec3.z * vec3.z);
}
}
internal 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;
};
internal class MatrixUtilities
{
internal 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;
}
}
internal 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;
}
internal 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);
}
}
}