This commit is contained in:
2021-06-13 10:28:03 +02:00
parent eb70603c85
commit df2d24cbd3
7487 changed files with 943244 additions and 0 deletions

View File

@@ -0,0 +1,948 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace UnityEngine.Rendering
{
/// <summary>
/// IBitArray interface.
/// </summary>
public interface IBitArray
{
/// <summary>Gets the capacity of this BitArray. This is the number of bits that are usable.</summary>
uint capacity { get; }
/// <summary>Return `true` if all the bits of this BitArray are set to 0. Returns `false` otherwise.</summary>
bool allFalse { get; }
/// <summary>Return `true` if all the bits of this BitArray are set to 1. Returns `false` otherwise.</summary>
bool allTrue { get; }
/// <summary>
/// An indexer that allows access to the bit at a given index. This provides both read and write access.
/// </summary>
/// <param name="index">Index of the bit.</param>
/// <returns>State of the bit at the provided index.</returns>
bool this[uint index] { get; set; }
/// <summary>Writes the bits in the array in a human-readable form. This is as a string of 0s and 1s packed by 8 bits. This is useful for debugging.</summary>
string humanizedData { get; }
/// <summary>
/// Perform an AND bitwise operation between this BitArray and the one you pass into the function and return the result. Both BitArrays must have the same capacity. This will not change current BitArray values.
/// </summary>
/// <param name="other">BitArray with which to the And operation.</param>
/// <returns>The resulting bit array.</returns>
IBitArray BitAnd(IBitArray other);
/// <summary>
/// Perform an OR bitwise operation between this BitArray and the one you pass into the function and return the result. Both BitArrays must have the same capacity. This will not change current BitArray values.
/// </summary>
/// <param name="other">BitArray with which to the Or operation.</param>
/// <returns>The resulting bit array.</returns>
IBitArray BitOr(IBitArray other);
/// <summary>
/// Return the BitArray with every bit inverted.
/// </summary>
/// <returns></returns>
IBitArray BitNot();
}
// /!\ Important for serialization:
// Serialization helper will rely on the name of the struct type.
// In order to work, it must be BitArrayN where N is the capacity without suffix.
/// <summary>
/// Bit array of size 8.
/// </summary>
[Serializable]
[System.Diagnostics.DebuggerDisplay("{this.GetType().Name} {humanizedData}")]
public struct BitArray8 : IBitArray
{
[SerializeField]
byte data;
/// <summary>Number of elements in the bit array.</summary>
public uint capacity => 8u;
/// <summary>True if all bits are 0.</summary>
public bool allFalse => data == 0u;
/// <summary>True if all bits are 1.</summary>
public bool allTrue => data == byte.MaxValue;
/// <summary>Returns the bit array in a human readable form.</summary>
public string humanizedData => String.Format("{0, " + capacity + "}", Convert.ToString(data, 2)).Replace(' ', '0');
/// <summary>
/// Returns the state of the bit at a specific index.
/// </summary>
/// <param name="index">Index of the bit.</param>
/// <returns>State of the bit at the provided index.</returns>
public bool this[uint index]
{
get => BitArrayUtilities.Get8(index, data);
set => BitArrayUtilities.Set8(index, ref data, value);
}
/// <summary>
/// Constructor.
/// </summary>
/// <param name="initValue">Initialization value.</param>
public BitArray8(byte initValue) => data = initValue;
/// <summary>
/// Constructor.
/// </summary>
/// <param name="bitIndexTrue">List of indices where bits should be set to true.</param>
public BitArray8(IEnumerable<uint> bitIndexTrue)
{
data = (byte)0u;
if (bitIndexTrue == null)
return;
for (int index = bitIndexTrue.Count() - 1; index >= 0; --index)
{
uint bitIndex = bitIndexTrue.ElementAt(index);
if (bitIndex >= capacity) continue;
data |= (byte)(1u << (int)bitIndex);
}
}
/// <summary>
/// Bit-wise Not operator
/// </summary>
/// <param name="a">Bit array with which to do the operation.</param>
/// <returns>The resulting bit array.</returns>
public static BitArray8 operator~(BitArray8 a) => new BitArray8((byte)~a.data);
/// <summary>
/// Bit-wise Or operator
/// </summary>
/// <param name="a">First bit array.</param>
/// <param name="b">Second bit array.</param>
/// <returns>The resulting bit array.</returns>
public static BitArray8 operator|(BitArray8 a, BitArray8 b) => new BitArray8((byte)(a.data | b.data));
/// <summary>
/// Bit-wise And operator
/// </summary>
/// <param name="a">First bit array.</param>
/// <param name="b">Second bit array.</param>
/// <returns>The resulting bit array.</returns>
public static BitArray8 operator&(BitArray8 a, BitArray8 b) => new BitArray8((byte)(a.data & b.data));
/// <summary>
/// Bit-wise And
/// </summary>
/// <param name="other">Bit array with which to do the operation.</param>
/// <returns>The resulting bit array.</returns>
public IBitArray BitAnd(IBitArray other) => this & (BitArray8)other;
/// <summary>
/// Bit-wise Or
/// </summary>
/// <param name="other">Bit array with which to do the operation.</param>
/// <returns>The resulting bit array.</returns>
public IBitArray BitOr(IBitArray other) => this | (BitArray8)other;
/// <summary>
/// Bit-wise Not
/// </summary>
/// <returns>The resulting bit array.</returns>
public IBitArray BitNot() => ~this;
/// <summary>
/// Equality operator.
/// </summary>
/// <param name="a">First bit array.</param>
/// <param name="b">Second bit array.</param>
/// <returns>True if both bit arrays are equals.</returns>
public static bool operator==(BitArray8 a, BitArray8 b) => a.data == b.data;
/// <summary>
/// Inequality operator.
/// </summary>
/// <param name="a">First bit array.</param>
/// <param name="b">Second bit array.</param>
/// <returns>True if the bit arrays are not equals.</returns>
public static bool operator!=(BitArray8 a, BitArray8 b) => a.data != b.data;
/// <summary>
/// Equality operator.
/// </summary>
/// <param name="obj">Bit array to compare to.</param>
/// <returns>True if the provided bit array is equal to this..</returns>
public override bool Equals(object obj) => obj is BitArray8 && ((BitArray8)obj).data == data;
/// <summary>
/// Get the hashcode of the bit array.
/// </summary>
/// <returns>Hashcode of the bit array.</returns>
public override int GetHashCode() => 1768953197 + data.GetHashCode();
}
/// <summary>
/// Bit array of size 16.
/// </summary>
[Serializable]
[System.Diagnostics.DebuggerDisplay("{this.GetType().Name} {humanizedData}")]
public struct BitArray16 : IBitArray
{
[SerializeField]
ushort data;
/// <summary>Number of elements in the bit array.</summary>
public uint capacity => 16u;
/// <summary>True if all bits are 0.</summary>
public bool allFalse => data == 0u;
/// <summary>True if all bits are 1.</summary>
public bool allTrue => data == ushort.MaxValue;
/// <summary>Returns the bit array in a human readable form.</summary>
public string humanizedData => System.Text.RegularExpressions.Regex.Replace(String.Format("{0, " + capacity + "}", Convert.ToString(data, 2)).Replace(' ', '0'), ".{8}", "$0.").TrimEnd('.');
/// <summary>
/// Returns the state of the bit at a specific index.
/// </summary>
/// <param name="index">Index of the bit.</param>
/// <returns>State of the bit at the provided index.</returns>
public bool this[uint index]
{
get => BitArrayUtilities.Get16(index, data);
set => BitArrayUtilities.Set16(index, ref data, value);
}
/// <summary>
/// Constructor.
/// </summary>
/// <param name="initValue">Initialization value.</param>
public BitArray16(ushort initValue) => data = initValue;
/// <summary>
/// Constructor.
/// </summary>
/// <param name="bitIndexTrue">List of indices where bits should be set to true.</param>
public BitArray16(IEnumerable<uint> bitIndexTrue)
{
data = (ushort)0u;
if (bitIndexTrue == null)
return;
for (int index = bitIndexTrue.Count() - 1; index >= 0; --index)
{
uint bitIndex = bitIndexTrue.ElementAt(index);
if (bitIndex >= capacity) continue;
data |= (ushort)(1u << (int)bitIndex);
}
}
/// <summary>
/// Bit-wise Not operator
/// </summary>
/// <param name="a">Bit array with which to do the operation.</param>
/// <returns>The resulting bit array.</returns>
public static BitArray16 operator~(BitArray16 a) => new BitArray16((ushort)~a.data);
/// <summary>
/// Bit-wise Or operator
/// </summary>
/// <param name="a">First bit array.</param>
/// <param name="b">Second bit array.</param>
/// <returns>The resulting bit array.</returns>
public static BitArray16 operator|(BitArray16 a, BitArray16 b) => new BitArray16((ushort)(a.data | b.data));
/// <summary>
/// Bit-wise And operator
/// </summary>
/// <param name="a">First bit array.</param>
/// <param name="b">Second bit array.</param>
/// <returns>The resulting bit array.</returns>
public static BitArray16 operator&(BitArray16 a, BitArray16 b) => new BitArray16((ushort)(a.data & b.data));
/// <summary>
/// Bit-wise And
/// </summary>
/// <param name="other">Bit array with which to do the operation.</param>
/// <returns>The resulting bit array.</returns>
public IBitArray BitAnd(IBitArray other) => this & (BitArray16)other;
/// <summary>
/// Bit-wise Or
/// </summary>
/// <param name="other">Bit array with which to do the operation.</param>
/// <returns>The resulting bit array.</returns>
public IBitArray BitOr(IBitArray other) => this | (BitArray16)other;
/// <summary>
/// Bit-wise Not
/// </summary>
/// <returns>The resulting bit array.</returns>
public IBitArray BitNot() => ~this;
/// <summary>
/// Equality operator.
/// </summary>
/// <param name="a">First bit array.</param>
/// <param name="b">Second bit array.</param>
/// <returns>True if both bit arrays are equals.</returns>
public static bool operator==(BitArray16 a, BitArray16 b) => a.data == b.data;
/// <summary>
/// Inequality operator.
/// </summary>
/// <param name="a">First bit array.</param>
/// <param name="b">Second bit array.</param>
/// <returns>True if the bit arrays are not equals.</returns>
public static bool operator!=(BitArray16 a, BitArray16 b) => a.data != b.data;
/// <summary>
/// Equality operator.
/// </summary>
/// <param name="obj">Bit array to compare to.</param>
/// <returns>True if the provided bit array is equal to this..</returns>
public override bool Equals(object obj) => obj is BitArray16 && ((BitArray16)obj).data == data;
/// <summary>
/// Get the hashcode of the bit array.
/// </summary>
/// <returns>Hashcode of the bit array.</returns>
public override int GetHashCode() => 1768953197 + data.GetHashCode();
}
/// <summary>
/// Bit array of size 32.
/// </summary>
[Serializable]
[System.Diagnostics.DebuggerDisplay("{this.GetType().Name} {humanizedData}")]
public struct BitArray32 : IBitArray
{
[SerializeField]
uint data;
/// <summary>Number of elements in the bit array.</summary>
public uint capacity => 32u;
/// <summary>True if all bits are 0.</summary>
public bool allFalse => data == 0u;
/// <summary>True if all bits are 1.</summary>
public bool allTrue => data == uint.MaxValue;
string humanizedVersion => Convert.ToString(data, 2);
/// <summary>Returns the bit array in a human readable form.</summary>
public string humanizedData => System.Text.RegularExpressions.Regex.Replace(String.Format("{0, " + capacity + "}", Convert.ToString(data, 2)).Replace(' ', '0'), ".{8}", "$0.").TrimEnd('.');
/// <summary>
/// Returns the state of the bit at a specific index.
/// </summary>
/// <param name="index">Index of the bit.</param>
/// <returns>State of the bit at the provided index.</returns>
public bool this[uint index]
{
get => BitArrayUtilities.Get32(index, data);
set => BitArrayUtilities.Set32(index, ref data, value);
}
/// <summary>
/// Constructor.
/// </summary>
/// <param name="initValue">Initialization value.</param>
public BitArray32(uint initValue) => data = initValue;
/// <summary>
/// Constructor.
/// </summary>
/// <param name="bitIndexTrue">List of indices where bits should be set to true.</param>
public BitArray32(IEnumerable<uint> bitIndexTrue)
{
data = 0u;
if (bitIndexTrue == null)
return;
for (int index = bitIndexTrue.Count() - 1; index >= 0; --index)
{
uint bitIndex = bitIndexTrue.ElementAt(index);
if (bitIndex >= capacity) continue;
data |= 1u << (int)bitIndex;
}
}
/// <summary>
/// Bit-wise And
/// </summary>
/// <param name="other">Bit array with which to do the operation.</param>
/// <returns>The resulting bit array.</returns>
public IBitArray BitAnd(IBitArray other) => this & (BitArray32)other;
/// <summary>
/// Bit-wise Or
/// </summary>
/// <param name="other">Bit array with which to do the operation.</param>
/// <returns>The resulting bit array.</returns>
public IBitArray BitOr(IBitArray other) => this | (BitArray32)other;
/// <summary>
/// Bit-wise Not
/// </summary>
/// <returns>The resulting bit array.</returns>
public IBitArray BitNot() => ~this;
/// <summary>
/// Bit-wise Not operator
/// </summary>
/// <param name="a">Bit array with which to do the operation.</param>
/// <returns>The resulting bit array.</returns>
public static BitArray32 operator~(BitArray32 a) => new BitArray32(~a.data);
/// <summary>
/// Bit-wise Or operator
/// </summary>
/// <param name="a">First bit array.</param>
/// <param name="b">Second bit array.</param>
/// <returns>The resulting bit array.</returns>
public static BitArray32 operator|(BitArray32 a, BitArray32 b) => new BitArray32(a.data | b.data);
/// <summary>
/// Bit-wise And operator
/// </summary>
/// <param name="a">First bit array.</param>
/// <param name="b">Second bit array.</param>
/// <returns>The resulting bit array.</returns>
public static BitArray32 operator&(BitArray32 a, BitArray32 b) => new BitArray32(a.data & b.data);
/// <summary>
/// Equality operator.
/// </summary>
/// <param name="a">First bit array.</param>
/// <param name="b">Second bit array.</param>
/// <returns>True if both bit arrays are equals.</returns>
public static bool operator==(BitArray32 a, BitArray32 b) => a.data == b.data;
/// <summary>
/// Inequality operator.
/// </summary>
/// <param name="a">First bit array.</param>
/// <param name="b">Second bit array.</param>
/// <returns>True if the bit arrays are not equals.</returns>
public static bool operator!=(BitArray32 a, BitArray32 b) => a.data != b.data;
/// <summary>
/// Equality operator.
/// </summary>
/// <param name="obj">Bit array to compare to.</param>
/// <returns>True if the provided bit array is equal to this..</returns>
public override bool Equals(object obj) => obj is BitArray32 && ((BitArray32)obj).data == data;
/// <summary>
/// Get the hashcode of the bit array.
/// </summary>
/// <returns>Hashcode of the bit array.</returns>
public override int GetHashCode() => 1768953197 + data.GetHashCode();
}
/// <summary>
/// Bit array of size 64.
/// </summary>
[Serializable]
[System.Diagnostics.DebuggerDisplay("{this.GetType().Name} {humanizedData}")]
public struct BitArray64 : IBitArray
{
[SerializeField]
ulong data;
/// <summary>Number of elements in the bit array.</summary>
public uint capacity => 64u;
/// <summary>True if all bits are 0.</summary>
public bool allFalse => data == 0uL;
/// <summary>True if all bits are 1.</summary>
public bool allTrue => data == ulong.MaxValue;
/// <summary>Returns the bit array in a human readable form.</summary>
public string humanizedData => System.Text.RegularExpressions.Regex.Replace(String.Format("{0, " + capacity + "}", Convert.ToString((long)data, 2)).Replace(' ', '0'), ".{8}", "$0.").TrimEnd('.');
/// <summary>
/// Returns the state of the bit at a specific index.
/// </summary>
/// <param name="index">Index of the bit.</param>
/// <returns>State of the bit at the provided index.</returns>
public bool this[uint index]
{
get => BitArrayUtilities.Get64(index, data);
set => BitArrayUtilities.Set64(index, ref data, value);
}
/// <summary>
/// Constructor.
/// </summary>
/// <param name="initValue">Initialization value.</param>
public BitArray64(ulong initValue) => data = initValue;
/// <summary>
/// Constructor.
/// </summary>
/// <param name="bitIndexTrue">List of indices where bits should be set to true.</param>
public BitArray64(IEnumerable<uint> bitIndexTrue)
{
data = 0L;
if (bitIndexTrue == null)
return;
for (int index = bitIndexTrue.Count() - 1; index >= 0; --index)
{
uint bitIndex = bitIndexTrue.ElementAt(index);
if (bitIndex >= capacity) continue;
data |= 1uL << (int)bitIndex;
}
}
/// <summary>
/// Bit-wise Not operator
/// </summary>
/// <param name="a">Bit array with which to do the operation.</param>
/// <returns>The resulting bit array.</returns>
public static BitArray64 operator~(BitArray64 a) => new BitArray64(~a.data);
/// <summary>
/// Bit-wise Or operator
/// </summary>
/// <param name="a">First bit array.</param>
/// <param name="b">Second bit array.</param>
/// <returns>The resulting bit array.</returns>
public static BitArray64 operator|(BitArray64 a, BitArray64 b) => new BitArray64(a.data | b.data);
/// <summary>
/// Bit-wise And operator
/// </summary>
/// <param name="a">First bit array.</param>
/// <param name="b">Second bit array.</param>
/// <returns>The resulting bit array.</returns>
public static BitArray64 operator&(BitArray64 a, BitArray64 b) => new BitArray64(a.data & b.data);
/// <summary>
/// Bit-wise And
/// </summary>
/// <param name="other">Bit array with which to do the operation.</param>
/// <returns>The resulting bit array.</returns>
public IBitArray BitAnd(IBitArray other) => this & (BitArray64)other;
/// <summary>
/// Bit-wise Or
/// </summary>
/// <param name="other">Bit array with which to do the operation.</param>
/// <returns>The resulting bit array.</returns>
public IBitArray BitOr(IBitArray other) => this | (BitArray64)other;
/// <summary>
/// Bit-wise Not
/// </summary>
/// <returns>The resulting bit array.</returns>
public IBitArray BitNot() => ~this;
/// <summary>
/// Equality operator.
/// </summary>
/// <param name="a">First bit array.</param>
/// <param name="b">Second bit array.</param>
/// <returns>True if both bit arrays are equals.</returns>
public static bool operator==(BitArray64 a, BitArray64 b) => a.data == b.data;
/// <summary>
/// Inequality operator.
/// </summary>
/// <param name="a">First bit array.</param>
/// <param name="b">Second bit array.</param>
/// <returns>True if the bit arrays are not equals.</returns>
public static bool operator!=(BitArray64 a, BitArray64 b) => a.data != b.data;
/// <summary>
/// Equality operator.
/// </summary>
/// <param name="obj">Bit array to compare to.</param>
/// <returns>True if the provided bit array is equal to this..</returns>
public override bool Equals(object obj) => obj is BitArray64 && ((BitArray64)obj).data == data;
/// <summary>
/// Get the hashcode of the bit array.
/// </summary>
/// <returns>Hashcode of the bit array.</returns>
public override int GetHashCode() => 1768953197 + data.GetHashCode();
}
/// <summary>
/// Bit array of size 128.
/// </summary>
[Serializable]
[System.Diagnostics.DebuggerDisplay("{this.GetType().Name} {humanizedData}")]
public struct BitArray128 : IBitArray
{
[SerializeField]
ulong data1;
[SerializeField]
ulong data2;
/// <summary>Number of elements in the bit array.</summary>
public uint capacity => 128u;
/// <summary>True if all bits are 0.</summary>
public bool allFalse => data1 == 0uL && data2 == 0uL;
/// <summary>True if all bits are 1.</summary>
public bool allTrue => data1 == ulong.MaxValue && data2 == ulong.MaxValue;
/// <summary>Returns the bit array in a human readable form.</summary>
public string humanizedData =>
System.Text.RegularExpressions.Regex.Replace(String.Format("{0, " + 64u + "}", Convert.ToString((long)data2, 2)).Replace(' ', '0'), ".{8}", "$0.")
+ System.Text.RegularExpressions.Regex.Replace(String.Format("{0, " + 64u + "}", Convert.ToString((long)data1, 2)).Replace(' ', '0'), ".{8}", "$0.").TrimEnd('.');
/// <summary>
/// Returns the state of the bit at a specific index.
/// </summary>
/// <param name="index">Index of the bit.</param>
/// <returns>State of the bit at the provided index.</returns>
public bool this[uint index]
{
get => BitArrayUtilities.Get128(index, data1, data2);
set => BitArrayUtilities.Set128(index, ref data1, ref data2, value);
}
/// <summary>
/// Constructor.
/// </summary>
/// <param name="initValue1">Initialization value 1.</param>
/// <param name="initValue2">Initialization value 2.</param>
public BitArray128(ulong initValue1, ulong initValue2)
{
data1 = initValue1;
data2 = initValue2;
}
/// <summary>
/// Constructor.
/// </summary>
/// <param name="bitIndexTrue">List of indices where bits should be set to true.</param>
public BitArray128(IEnumerable<uint> bitIndexTrue)
{
data1 = data2 = 0uL;
if (bitIndexTrue == null)
return;
for (int index = bitIndexTrue.Count() - 1; index >= 0; --index)
{
uint bitIndex = bitIndexTrue.ElementAt(index);
if (bitIndex < 64u)
data1 |= 1uL << (int)bitIndex;
else if (bitIndex < capacity)
data2 |= 1uL << (int)(bitIndex - 64u);
}
}
/// <summary>
/// Bit-wise Not operator
/// </summary>
/// <param name="a">First bit array.</param>
/// <returns>The resulting bit array.</returns>
public static BitArray128 operator~(BitArray128 a) => new BitArray128(~a.data1, ~a.data2);
/// <summary>
/// Bit-wise Or operator
/// </summary>
/// <param name="a">First bit array.</param>
/// <param name="b">Second bit array.</param>
/// <returns>The resulting bit array.</returns>
public static BitArray128 operator|(BitArray128 a, BitArray128 b) => new BitArray128(a.data1 | b.data1, a.data2 | b.data2);
/// <summary>
/// Bit-wise And operator
/// </summary>
/// <param name="a">First bit array.</param>
/// <param name="b">Second bit array.</param>
/// <returns>The resulting bit array.</returns>
public static BitArray128 operator&(BitArray128 a, BitArray128 b) => new BitArray128(a.data1 & b.data1, a.data2 & b.data2);
/// <summary>
/// Bit-wise And
/// </summary>
/// <param name="other">Bit array with which to do the operation.</param>
/// <returns>The resulting bit array.</returns>
public IBitArray BitAnd(IBitArray other) => this & (BitArray128)other;
/// <summary>
/// Bit-wise Or
/// </summary>
/// <param name="other">Bit array with which to do the operation.</param>
/// <returns>The resulting bit array.</returns>
public IBitArray BitOr(IBitArray other) => this | (BitArray128)other;
/// <summary>
/// Bit-wise Not
/// </summary>
/// <returns>The resulting bit array.</returns>
public IBitArray BitNot() => ~this;
/// <summary>
/// Equality operator.
/// </summary>
/// <param name="a">First bit array.</param>
/// <param name="b">Second bit array.</param>
/// <returns>True if both bit arrays are equals.</returns>
public static bool operator==(BitArray128 a, BitArray128 b) => a.data1 == b.data1 && a.data2 == b.data2;
/// <summary>
/// Inequality operator.
/// </summary>
/// <param name="a">First bit array.</param>
/// <param name="b">Second bit array.</param>
/// <returns>True if the bit arrays are not equals.</returns>
public static bool operator!=(BitArray128 a, BitArray128 b) => a.data1 != b.data1 || a.data2 != b.data2;
/// <summary>
/// Equality operator.
/// </summary>
/// <param name="obj">Bit array to compare to.</param>
/// <returns>True if the provided bit array is equal to this..</returns>
public override bool Equals(object obj) => (obj is BitArray128) && data1.Equals(((BitArray128)obj).data1) && data2.Equals(((BitArray128)obj).data2);
/// <summary>
/// Get the hashcode of the bit array.
/// </summary>
/// <returns>Hashcode of the bit array.</returns>
public override int GetHashCode()
{
var hashCode = 1755735569;
hashCode = hashCode * -1521134295 + data1.GetHashCode();
hashCode = hashCode * -1521134295 + data2.GetHashCode();
return hashCode;
}
}
/// <summary>
/// Bit array of size 256.
/// </summary>
[Serializable]
[System.Diagnostics.DebuggerDisplay("{this.GetType().Name} {humanizedData}")]
public struct BitArray256 : IBitArray
{
[SerializeField]
ulong data1;
[SerializeField]
ulong data2;
[SerializeField]
ulong data3;
[SerializeField]
ulong data4;
/// <summary>Number of elements in the bit array.</summary>
public uint capacity => 256u;
/// <summary>True if all bits are 0.</summary>
public bool allFalse => data1 == 0uL && data2 == 0uL && data3 == 0uL && data4 == 0uL;
/// <summary>True if all bits are 1.</summary>
public bool allTrue => data1 == ulong.MaxValue && data2 == ulong.MaxValue && data3 == ulong.MaxValue && data4 == ulong.MaxValue;
/// <summary>Returns the bit array in a human readable form.</summary>
public string humanizedData =>
System.Text.RegularExpressions.Regex.Replace(String.Format("{0, " + 64u + "}", Convert.ToString((long)data4, 2)).Replace(' ', '0'), ".{8}", "$0.")
+ System.Text.RegularExpressions.Regex.Replace(String.Format("{0, " + 64u + "}", Convert.ToString((long)data3, 2)).Replace(' ', '0'), ".{8}", "$0.")
+ System.Text.RegularExpressions.Regex.Replace(String.Format("{0, " + 64u + "}", Convert.ToString((long)data2, 2)).Replace(' ', '0'), ".{8}", "$0.")
+ System.Text.RegularExpressions.Regex.Replace(String.Format("{0, " + 64u + "}", Convert.ToString((long)data1, 2)).Replace(' ', '0'), ".{8}", "$0.").TrimEnd('.');
/// <summary>
/// Returns the state of the bit at a specific index.
/// </summary>
/// <param name="index">Index of the bit.</param>
/// <returns>State of the bit at the provided index.</returns>
public bool this[uint index]
{
get => BitArrayUtilities.Get256(index, data1, data2, data3, data4);
set => BitArrayUtilities.Set256(index, ref data1, ref data2, ref data3, ref data4, value);
}
/// <summary>
/// Constructor.
/// </summary>
/// <param name="initValue1">Initialization value 1.</param>
/// <param name="initValue2">Initialization value 2.</param>
/// <param name="initValue3">Initialization value 3.</param>
/// <param name="initValue4">Initialization value 4.</param>
public BitArray256(ulong initValue1, ulong initValue2, ulong initValue3, ulong initValue4)
{
data1 = initValue1;
data2 = initValue2;
data3 = initValue3;
data4 = initValue4;
}
/// <summary>
/// Constructor.
/// </summary>
/// <param name="bitIndexTrue">List of indices where bits should be set to true.</param>
public BitArray256(IEnumerable<uint> bitIndexTrue)
{
data1 = data2 = data3 = data4 = 0uL;
if (bitIndexTrue == null)
return;
for (int index = bitIndexTrue.Count() - 1; index >= 0; --index)
{
uint bitIndex = bitIndexTrue.ElementAt(index);
if (bitIndex < 64u)
data1 |= 1uL << (int)bitIndex;
else if (bitIndex < 128u)
data2 |= 1uL << (int)(bitIndex - 64u);
else if (bitIndex < 192u)
data3 |= 1uL << (int)(bitIndex - 128u);
else if (bitIndex < capacity)
data4 |= 1uL << (int)(bitIndex - 192u);
}
}
/// <summary>
/// Bit-wise Not operator
/// </summary>
/// <param name="a">Bit array with which to do the operation.</param>
/// <returns>The resulting bit array.</returns>
public static BitArray256 operator~(BitArray256 a) => new BitArray256(~a.data1, ~a.data2, ~a.data3, ~a.data4);
/// <summary>
/// Bit-wise Or operator
/// </summary>
/// <param name="a">First bit array.</param>
/// <param name="b">Second bit array.</param>
/// <returns>The resulting bit array.</returns>
public static BitArray256 operator|(BitArray256 a, BitArray256 b) => new BitArray256(a.data1 | b.data1, a.data2 | b.data2, a.data3 | b.data3, a.data4 | b.data4);
/// <summary>
/// Bit-wise And operator
/// </summary>
/// <param name="a">First bit array.</param>
/// <param name="b">Second bit array.</param>
/// <returns>The resulting bit array.</returns>
public static BitArray256 operator&(BitArray256 a, BitArray256 b) => new BitArray256(a.data1 & b.data1, a.data2 & b.data2, a.data3 & b.data3, a.data4 & b.data4);
/// <summary>
/// Bit-wise And
/// </summary>
/// <param name="other">Bit array with which to do the operation.</param>
/// <returns>The resulting bit array.</returns>
public IBitArray BitAnd(IBitArray other) => this & (BitArray256)other;
/// <summary>
/// Bit-wise Or
/// </summary>
/// <param name="other">Bit array with which to do the operation.</param>
/// <returns>The resulting bit array.</returns>
public IBitArray BitOr(IBitArray other) => this | (BitArray256)other;
/// <summary>
/// Bit-wise Not
/// </summary>
/// <returns>The resulting bit array.</returns>
public IBitArray BitNot() => ~this;
/// <summary>
/// Equality operator.
/// </summary>
/// <param name="a">First bit array.</param>
/// <param name="b">Second bit array.</param>
/// <returns>True if both bit arrays are equals.</returns>
public static bool operator==(BitArray256 a, BitArray256 b) => a.data1 == b.data1 && a.data2 == b.data2 && a.data3 == b.data3 && a.data4 == b.data4;
/// <summary>
/// Inequality operator.
/// </summary>
/// <param name="a">First bit array.</param>
/// <param name="b">Second bit array.</param>
/// <returns>True if the bit arrays are not equals.</returns>
public static bool operator!=(BitArray256 a, BitArray256 b) => a.data1 != b.data1 || a.data2 != b.data2 || a.data3 != b.data3 || a.data4 != b.data4;
/// <summary>
/// Equality operator.
/// </summary>
/// <param name="obj">Bit array to compare to.</param>
/// <returns>True if the provided bit array is equal to this..</returns>
public override bool Equals(object obj) =>
(obj is BitArray256)
&& data1.Equals(((BitArray256)obj).data1)
&& data2.Equals(((BitArray256)obj).data2)
&& data3.Equals(((BitArray256)obj).data3)
&& data4.Equals(((BitArray256)obj).data4);
/// <summary>
/// Get the hashcode of the bit array.
/// </summary>
/// <returns>Hashcode of the bit array.</returns>
public override int GetHashCode()
{
var hashCode = 1870826326;
hashCode = hashCode * -1521134295 + data1.GetHashCode();
hashCode = hashCode * -1521134295 + data2.GetHashCode();
hashCode = hashCode * -1521134295 + data3.GetHashCode();
hashCode = hashCode * -1521134295 + data4.GetHashCode();
return hashCode;
}
}
/// <summary>
/// Bit array utility class.
/// </summary>
public static class BitArrayUtilities
{
//written here to not duplicate the serialized accessor and runtime accessor
/// <summary>
/// Get a bit at a specific index.
/// </summary>
/// <param name="index">Bit index.</param>
/// <param name="data">Bit array data.</param>
/// <returns>The value of the bit at the specific index.</returns>
public static bool Get8(uint index, byte data) => (data & (1u << (int)index)) != 0u;
/// <summary>
/// Get a bit at a specific index.
/// </summary>
/// <param name="index">Bit index.</param>
/// <param name="data">Bit array data.</param>
/// <returns>The value of the bit at the specific index.</returns>
public static bool Get16(uint index, ushort data) => (data & (1u << (int)index)) != 0u;
/// <summary>
/// Get a bit at a specific index.
/// </summary>
/// <param name="index">Bit index.</param>
/// <param name="data">Bit array data.</param>
/// <returns>The value of the bit at the specific index.</returns>
public static bool Get32(uint index, uint data) => (data & (1u << (int)index)) != 0u;
/// <summary>
/// Get a bit at a specific index.
/// </summary>
/// <param name="index">Bit index.</param>
/// <param name="data">Bit array data.</param>
/// <returns>The value of the bit at the specific index.</returns>
public static bool Get64(uint index, ulong data) => (data & (1uL << (int)index)) != 0uL;
/// <summary>
/// Get a bit at a specific index.
/// </summary>
/// <param name="index">Bit index.</param>
/// <param name="data1">Bit array data 1.</param>
/// <param name="data2">Bit array data 2.</param>
/// <returns>The value of the bit at the specific index.</returns>
public static bool Get128(uint index, ulong data1, ulong data2)
=> index < 64u
? (data1 & (1uL << (int)index)) != 0uL
: (data2 & (1uL << (int)(index - 64u))) != 0uL;
/// <summary>
/// Get a bit at a specific index.
/// </summary>
/// <param name="index">Bit index.</param>
/// <param name="data1">Bit array data 1.</param>
/// <param name="data2">Bit array data 2.</param>
/// <param name="data3">Bit array data 3.</param>
/// <param name="data4">Bit array data 4.</param>
/// <returns>The value of the bit at the specific index.</returns>
public static bool Get256(uint index, ulong data1, ulong data2, ulong data3, ulong data4)
=> index < 128u
? index < 64u
? (data1 & (1uL << (int)index)) != 0uL
: (data2 & (1uL << (int)(index - 64u))) != 0uL
: index < 192u
? (data3 & (1uL << (int)(index - 128u))) != 0uL
: (data4 & (1uL << (int)(index - 192u))) != 0uL;
/// <summary>
/// Set a bit at a specific index.
/// </summary>
/// <param name="index">Bit index.</param>
/// <param name="data">Bit array data.</param>
/// <param name="value">Value to set the bit to.</param>
public static void Set8(uint index, ref byte data, bool value) => data = (byte)(value ? (data | (1u << (int)index)) : (data & ~(1u << (int)index)));
/// <summary>
/// Set a bit at a specific index.
/// </summary>
/// <param name="index">Bit index.</param>
/// <param name="data">Bit array data.</param>
/// <param name="value">Value to set the bit to.</param>
public static void Set16(uint index, ref ushort data, bool value) => data = (ushort)(value ? (data | (1u << (int)index)) : (data & ~(1u << (int)index)));
/// <summary>
/// Set a bit at a specific index.
/// </summary>
/// <param name="index">Bit index.</param>
/// <param name="data">Bit array data.</param>
/// <param name="value">Value to set the bit to.</param>
public static void Set32(uint index, ref uint data, bool value) => data = (value ? (data | (1u << (int)index)) : (data & ~(1u << (int)index)));
/// <summary>
/// Set a bit at a specific index.
/// </summary>
/// <param name="index">Bit index.</param>
/// <param name="data">Bit array data.</param>
/// <param name="value">Value to set the bit to.</param>
public static void Set64(uint index, ref ulong data, bool value) => data = (value ? (data | (1uL << (int)index)) : (data & ~(1uL << (int)index)));
/// <summary>
/// Set a bit at a specific index.
/// </summary>
/// <param name="index">Bit index.</param>
/// <param name="data1">Bit array data 1.</param>
/// <param name="data2">Bit array data 2.</param>
/// <param name="value">Value to set the bit to.</param>
public static void Set128(uint index, ref ulong data1, ref ulong data2, bool value)
{
if (index < 64u)
data1 = (value ? (data1 | (1uL << (int)index)) : (data1 & ~(1uL << (int)index)));
else
data2 = (value ? (data2 | (1uL << (int)(index - 64u))) : (data2 & ~(1uL << (int)(index - 64u))));
}
/// <summary>
/// Set a bit at a specific index.
/// </summary>
/// <param name="index">Bit index.</param>
/// <param name="data1">Bit array data 1.</param>
/// <param name="data2">Bit array data 2.</param>
/// <param name="data3">Bit array data 3.</param>
/// <param name="data4">Bit array data 4.</param>
/// <param name="value">Value to set the bit to.</param>
public static void Set256(uint index, ref ulong data1, ref ulong data2, ref ulong data3, ref ulong data4, bool value)
{
if (index < 64u)
data1 = (value ? (data1 | (1uL << (int)index)) : (data1 & ~(1uL << (int)index)));
else if (index < 128u)
data2 = (value ? (data2 | (1uL << (int)(index - 64u))) : (data2 & ~(1uL << (int)(index - 64u))));
else if (index < 192u)
data3 = (value ? (data3 | (1uL << (int)(index - 64u))) : (data3 & ~(1uL << (int)(index - 128u))));
else
data4 = (value ? (data4 | (1uL << (int)(index - 64u))) : (data4 & ~(1uL << (int)(index - 192u))));
}
}
}

View File

@@ -0,0 +1,75 @@
using System;
using System.Collections.Generic;
namespace UnityEngine.Rendering
{
/// <summary>
/// Bridge class for camera captures.
/// </summary>
public static class CameraCaptureBridge
{
private static Dictionary<Camera, HashSet<Action<RenderTargetIdentifier, CommandBuffer>>> actionDict =
new Dictionary<Camera, HashSet<Action<RenderTargetIdentifier, CommandBuffer>>>();
private static bool _enabled;
/// <summary>
/// Enable camera capture.
/// </summary>
public static bool enabled
{
get
{
return _enabled;
}
set
{
_enabled = value;
}
}
/// <summary>
/// Provides the set actions to the renderer to be triggered at the end of the render loop for camera capture
/// </summary>
/// <param name="camera">The camera to get actions for</param>
/// <returns>Enumeration of actions</returns>
public static IEnumerator<Action<RenderTargetIdentifier, CommandBuffer>> GetCaptureActions(Camera camera)
{
if (!actionDict.TryGetValue(camera, out var actions))
return null;
return actions.GetEnumerator();
}
/// <summary>
/// Adds actions for camera capture
/// </summary>
/// <param name="camera">The camera to add actions for</param>
/// <param name="action">The action to add</param>
public static void AddCaptureAction(Camera camera, Action<RenderTargetIdentifier, CommandBuffer> action)
{
actionDict.TryGetValue(camera, out var actions);
if (actions == null)
{
actions = new HashSet<Action<RenderTargetIdentifier, CommandBuffer>>();
actionDict.Add(camera, actions);
}
actions.Add(action);
}
/// <summary>
/// Removes actions for camera capture
/// </summary>
/// <param name="camera">The camera to remove actions for</param>
/// <param name="action">The action to remove</param>
public static void RemoveCaptureAction(Camera camera, Action<RenderTargetIdentifier, CommandBuffer> action)
{
if (camera == null)
return;
if (actionDict.TryGetValue(camera, out var actions))
actions.Remove(action);
}
}
}

View File

@@ -0,0 +1,303 @@
namespace UnityEngine.Rendering
{
// Has to be kept in sync with PhysicalCamera.hlsl
/// <summary>
/// A set of color manipulation utilities.
/// </summary>
public static class ColorUtils
{
/// <summary>
/// Calibration constant (K) used for our virtual reflected light meter. Modifying this will lead to a change on how average scene luminance
/// gets mapped to exposure.
/// </summary>
static public float s_LightMeterCalibrationConstant = 12.5f;
/// <summary>
/// Factor used for our lens system w.r.t. exposure calculation. Modifying this will lead to a change on how linear exposure
/// multipliers are computed from EV100 values (and viceversa). s_LensAttenuation models transmission attenuation and lens vignetting.
/// Note that according to the standard ISO 12232, a lens saturates at s_LensAttenuation = 0.78f (under ISO 100).
/// </summary>
static public float s_LensAttenuation = 0.65f;
/// <summary>
/// Scale applied to exposure caused by lens imperfection. It is computed from s_LensAttenuation as follow:
/// (78 / ( S * q )) where S = 100 and q = s_LensAttenuation
/// </summary>
static public float lensImperfectionExposureScale
{
get => (78.0f / (100.0f * s_LensAttenuation));
}
/// <summary>
/// An analytical model of chromaticity of the standard illuminant, by Judd et al.
/// http://en.wikipedia.org/wiki/Standard_illuminant#Illuminant_series_D
/// Slightly modifed to adjust it with the D65 white point (x=0.31271, y=0.32902).
/// </summary>
/// <param name="x"></param>
/// <returns></returns>
public static float StandardIlluminantY(float x) => 2.87f * x - 3f * x * x - 0.27509507f;
/// <summary>
/// CIE xy chromaticity to CAT02 LMS.
/// http://en.wikipedia.org/wiki/LMS_color_space#CAT02
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <returns></returns>
public static Vector3 CIExyToLMS(float x, float y)
{
float Y = 1f;
float X = Y * x / y;
float Z = Y * (1f - x - y) / y;
float L = 0.7328f * X + 0.4296f * Y - 0.1624f * Z;
float M = -0.7036f * X + 1.6975f * Y + 0.0061f * Z;
float S = 0.0030f * X + 0.0136f * Y + 0.9834f * Z;
return new Vector3(L, M, S);
}
/// <summary>
/// Converts white balancing parameter to LMS coefficients.
/// </summary>
/// <param name="temperature">A temperature offset, in range [-100;100].</param>
/// <param name="tint">A tint offset, in range [-100;100].</param>
/// <returns>LMS coefficients.</returns>
public static Vector3 ColorBalanceToLMSCoeffs(float temperature, float tint)
{
// Range ~[-1.5;1.5] works best
float t1 = temperature / 65f;
float t2 = tint / 65f;
// Get the CIE xy chromaticity of the reference white point.
// Note: 0.31271 = x value on the D65 white point
float x = 0.31271f - t1 * (t1 < 0f ? 0.1f : 0.05f);
float y = StandardIlluminantY(x) + t2 * 0.05f;
// Calculate the coefficients in the LMS space.
var w1 = new Vector3(0.949237f, 1.03542f, 1.08728f); // D65 white point
var w2 = CIExyToLMS(x, y);
return new Vector3(w1.x / w2.x, w1.y / w2.y, w1.z / w2.z);
}
/// <summary>
/// Pre-filters shadows, midtones and highlights trackball values for shader use.
/// </summary>
/// <param name="inShadows">A color used for shadows.</param>
/// <param name="inMidtones">A color used for midtones.</param>
/// <param name="inHighlights">A color used for highlights.</param>
/// <returns>The three input colors pre-filtered for shader use.</returns>
public static (Vector4, Vector4, Vector4) PrepareShadowsMidtonesHighlights(in Vector4 inShadows, in Vector4 inMidtones, in Vector4 inHighlights)
{
float weight;
var shadows = inShadows;
shadows.x = Mathf.GammaToLinearSpace(shadows.x);
shadows.y = Mathf.GammaToLinearSpace(shadows.y);
shadows.z = Mathf.GammaToLinearSpace(shadows.z);
weight = shadows.w * (Mathf.Sign(shadows.w) < 0f ? 1f : 4f);
shadows.x = Mathf.Max(shadows.x + weight, 0f);
shadows.y = Mathf.Max(shadows.y + weight, 0f);
shadows.z = Mathf.Max(shadows.z + weight, 0f);
shadows.w = 0f;
var midtones = inMidtones;
midtones.x = Mathf.GammaToLinearSpace(midtones.x);
midtones.y = Mathf.GammaToLinearSpace(midtones.y);
midtones.z = Mathf.GammaToLinearSpace(midtones.z);
weight = midtones.w * (Mathf.Sign(midtones.w) < 0f ? 1f : 4f);
midtones.x = Mathf.Max(midtones.x + weight, 0f);
midtones.y = Mathf.Max(midtones.y + weight, 0f);
midtones.z = Mathf.Max(midtones.z + weight, 0f);
midtones.w = 0f;
var highlights = inHighlights;
highlights.x = Mathf.GammaToLinearSpace(highlights.x);
highlights.y = Mathf.GammaToLinearSpace(highlights.y);
highlights.z = Mathf.GammaToLinearSpace(highlights.z);
weight = highlights.w * (Mathf.Sign(highlights.w) < 0f ? 1f : 4f);
highlights.x = Mathf.Max(highlights.x + weight, 0f);
highlights.y = Mathf.Max(highlights.y + weight, 0f);
highlights.z = Mathf.Max(highlights.z + weight, 0f);
highlights.w = 0f;
return (shadows, midtones, highlights);
}
/// <summary>
/// Pre-filters lift, gamma and gain trackball values for shader use.
/// </summary>
/// <param name="inLift">A color used for lift.</param>
/// <param name="inGamma">A color used for gamma.</param>
/// <param name="inGain">A color used for gain.</param>
/// <returns>The three input colors pre-filtered for shader use.</returns>
public static (Vector4, Vector4, Vector4) PrepareLiftGammaGain(in Vector4 inLift, in Vector4 inGamma, in Vector4 inGain)
{
var lift = inLift;
lift.x = Mathf.GammaToLinearSpace(lift.x) * 0.15f;
lift.y = Mathf.GammaToLinearSpace(lift.y) * 0.15f;
lift.z = Mathf.GammaToLinearSpace(lift.z) * 0.15f;
float lumLift = Luminance(lift);
lift.x = lift.x - lumLift + lift.w;
lift.y = lift.y - lumLift + lift.w;
lift.z = lift.z - lumLift + lift.w;
lift.w = 0f;
var gamma = inGamma;
gamma.x = Mathf.GammaToLinearSpace(gamma.x) * 0.8f;
gamma.y = Mathf.GammaToLinearSpace(gamma.y) * 0.8f;
gamma.z = Mathf.GammaToLinearSpace(gamma.z) * 0.8f;
float lumGamma = Luminance(gamma);
gamma.w += 1f;
gamma.x = 1f / Mathf.Max(gamma.x - lumGamma + gamma.w, 1e-03f);
gamma.y = 1f / Mathf.Max(gamma.y - lumGamma + gamma.w, 1e-03f);
gamma.z = 1f / Mathf.Max(gamma.z - lumGamma + gamma.w, 1e-03f);
gamma.w = 0f;
var gain = inGain;
gain.x = Mathf.GammaToLinearSpace(gain.x) * 0.8f;
gain.y = Mathf.GammaToLinearSpace(gain.y) * 0.8f;
gain.z = Mathf.GammaToLinearSpace(gain.z) * 0.8f;
float lumGain = Luminance(gain);
gain.w += 1f;
gain.x = gain.x - lumGain + gain.w;
gain.y = gain.y - lumGain + gain.w;
gain.z = gain.z - lumGain + gain.w;
gain.w = 0f;
return (lift, gamma, gain);
}
/// <summary>
/// Pre-filters colors used for the split toning effect.
/// </summary>
/// <param name="inShadows">A color used for shadows.</param>
/// <param name="inHighlights">A color used for highlights.</param>
/// <param name="balance">The balance between the shadow and highlight colors, in range [-100;100].</param>
/// <returns>The two input colors pre-filtered for shader use.</returns>
public static (Vector4, Vector4) PrepareSplitToning(in Vector4 inShadows, in Vector4 inHighlights, float balance)
{
// As counter-intuitive as it is, to make split-toning work the same way it does in
// Adobe products we have to do all the maths in sRGB... So do not convert these to
// linear before sending them to the shader, this isn't a bug!
var shadows = inShadows;
var highlights = inHighlights;
// Balance is stored in `shadows.w`
shadows.w = balance / 100f;
highlights.w = 0f;
return (shadows, highlights);
}
/// <summary>
/// Returns the luminance of the specified color. The input is considered to be in linear
/// space with sRGB primaries and a D65 white point.
/// </summary>
/// <param name="color">The color to compute the luminance for.</param>
/// <returns>A luminance value.</returns>
public static float Luminance(in Color color) => color.r * 0.2126729f + color.g * 0.7151522f + color.b * 0.072175f;
/// <summary>
/// Computes an exposure value (EV100) from physical camera settings.
/// </summary>
/// <param name="aperture">The camera aperture.</param>
/// <param name="shutterSpeed">The camera exposure time.</param>
/// <param name="ISO">The camera sensor sensitivity.</param>
/// <returns>An exposure value, in EV100.</returns>
public static float ComputeEV100(float aperture, float shutterSpeed, float ISO)
{
// References:
// "Moving Frostbite to PBR" (Sébastien Lagarde & Charles de Rousiers)
// https://seblagarde.files.wordpress.com/2015/07/course_notes_moving_frostbite_to_pbr_v32.pdf
// "Implementing a Physically Based Camera" (Padraic Hennessy)
// https://placeholderart.wordpress.com/2014/11/16/implementing-a-physically-based-camera-understanding-exposure/
// EV number is defined as:
// 2^ EV_s = N^2 / t and EV_s = EV_100 + log2 (S /100)
// This gives
// EV_s = log2 (N^2 / t)
// EV_100 + log2 (S /100) = log2 (N^2 / t)
// EV_100 = log2 (N^2 / t) - log2 (S /100)
// EV_100 = log2 (N^2 / t . 100 / S)
return Mathf.Log((aperture * aperture) / shutterSpeed * 100f / ISO, 2f);
}
/// <summary>
/// Converts an exposure value (EV100) to a linear multiplier.
/// </summary>
/// <param name="EV100">The exposure value to convert, in EV100.</param>
/// <returns>A linear multiplier.</returns>
public static float ConvertEV100ToExposure(float EV100)
{
// Compute the maximum luminance possible with H_sbs sensitivity
// maxLum = 78 / ( S * q ) * N^2 / t
// = 78 / ( S * q ) * 2^ EV_100
// = 78 / (100 * s_LensAttenuation) * 2^ EV_100
// = lensImperfectionExposureScale * 2^ EV
// Reference: http://en.wikipedia.org/wiki/Film_speed
float maxLuminance = lensImperfectionExposureScale * Mathf.Pow(2.0f, EV100);
return 1.0f / maxLuminance;
}
/// <summary>
/// Converts a linear multiplier to an exposure value (EV100).
/// </summary>
/// <param name="exposure">A linear multiplier.</param>
/// <returns>An exposure value, in EV100.</returns>
public static float ConvertExposureToEV100(float exposure)
{
// Compute the maximum luminance possible with H_sbs sensitivity
// EV_100 = log2( S * q / (78 * exposure) )
// = log2( 100 * s_LensAttenuation / (78 * exposure) )
// = log2( 1.0f / (lensImperfectionExposureScale * exposure) )
// Reference: http://en.wikipedia.org/wiki/Film_speed
return Mathf.Log(1.0f / (lensImperfectionExposureScale * exposure), 2.0f);
}
/// <summary>
/// Computes an exposure value (EV100) from an average luminance value.
/// </summary>
/// <param name="avgLuminance">An average luminance value.</param>
/// <returns>An exposure value, in EV100.</returns>
public static float ComputeEV100FromAvgLuminance(float avgLuminance)
{
// The middle grey used will be determined by the s_LightMeterCalibrationConstant.
// The suggested (ISO 2720) range is 10.64 to 13.4. Common values used by
// manufacturers range from 11.37 to 14. Ref: https://en.wikipedia.org/wiki/Light_meter
// The default is 12.5% as it is the closest to 12.7% in order to have
// a middle gray at 18% with a sqrt(2) room for specular highlights
// Note that this gives equivalent results as using an incident light meter
// with a calibration constant of C=314.
float K = s_LightMeterCalibrationConstant;
return Mathf.Log(avgLuminance * 100f / K, 2f);
}
/// <summary>
/// Computes the required ISO to reach <paramref name="targetEV100"/>.
/// </summary>
/// <param name="aperture">The camera aperture.</param>
/// <param name="shutterSpeed">The camera exposure time.</param>
/// <param name="targetEV100">The target exposure value (EV100) to reach.</param>
/// <returns>The required sensor sensitivity (ISO).</returns>
public static float ComputeISO(float aperture, float shutterSpeed, float targetEV100) => ((aperture * aperture) * 100f) / (shutterSpeed * Mathf.Pow(2f, targetEV100));
/// <summary>
/// Converts a color value to its 32-bit hexadecimal representation.
/// </summary>
/// <param name="c">The color to convert.</param>
/// <returns>A 32-bit hexadecimal representation of the color.</returns>
public static uint ToHex(Color c) => ((uint)(c.a * 255) << 24) | ((uint)(c.r * 255) << 16) | ((uint)(c.g * 255) << 8) | (uint)(c.b * 255);
/// <summary>
/// Converts a 32-bit hexadecimal value to a color value.
/// </summary>
/// <param name="hex">A 32-bit hexadecimal value.</param>
/// <returns>A color value.</returns>
public static Color ToRGBA(uint hex) => new Color(((hex >> 16) & 0xff) / 255f, ((hex >> 8) & 0xff) / 255f, (hex & 0xff) / 255f, ((hex >> 24) & 0xff) / 255f);
}
}

View File

@@ -0,0 +1,166 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine.Experimental.Rendering;
namespace UnityEngine.Rendering
{
using UnityObject = UnityEngine.Object;
/// <summary>
/// Set of utility functions for the Core Scriptable Render Pipeline Library related to Matrix operations
/// </summary>
public static class CoreMatrixUtils
{
/// <summary>
/// This function provides the equivalent of multiplying matrix parameter inOutMatrix with a translation matrix defined by the parameter translation.
/// The order of the equivalent multiplication is inOutMatrix * translation.
/// </summary>
/// <param name="inOutMatrix">Matrix to multiply with translation.</param>
/// <param name="translation">Translation component to multiply to the matrix.</param>
public static void MatrixTimesTranslation(ref Matrix4x4 inOutMatrix, Vector3 translation)
{
inOutMatrix.m03 += (inOutMatrix.m00 * translation.x + inOutMatrix.m01 * translation.y + inOutMatrix.m02 * translation.z);
inOutMatrix.m13 += (inOutMatrix.m10 * translation.x + inOutMatrix.m11 * translation.y + inOutMatrix.m12 * translation.z);
inOutMatrix.m23 += (inOutMatrix.m20 * translation.x + inOutMatrix.m21 * translation.y + inOutMatrix.m22 * translation.z);
}
/// <summary>
/// This function provides the equivalent of multiplying a translation matrix defined by the parameter translation with the matrix specified by the parameter inOutMatrix.
/// The order of the equivalent multiplication is translation * inOutMatrix.
/// </summary>
/// <param name="inOutMatrix">Matrix to multiply with translation.</param>
/// <param name="translation">Translation component to multiply to the matrix.</param>
public static void TranslationTimesMatrix(ref Matrix4x4 inOutMatrix, Vector3 translation)
{
inOutMatrix.m00 += translation.x * inOutMatrix.m30;
inOutMatrix.m01 += translation.x * inOutMatrix.m31;
inOutMatrix.m02 += translation.x * inOutMatrix.m32;
inOutMatrix.m03 += translation.x * inOutMatrix.m33;
inOutMatrix.m10 += translation.y * inOutMatrix.m30;
inOutMatrix.m11 += translation.y * inOutMatrix.m31;
inOutMatrix.m12 += translation.y * inOutMatrix.m32;
inOutMatrix.m13 += translation.y * inOutMatrix.m33;
inOutMatrix.m20 += translation.z * inOutMatrix.m30;
inOutMatrix.m21 += translation.z * inOutMatrix.m31;
inOutMatrix.m22 += translation.z * inOutMatrix.m32;
inOutMatrix.m23 += translation.z * inOutMatrix.m33;
}
/// <summary>
/// Multiplies a matrix with a perspective matrix. This function is faster than performing the full matrix multiplication.
/// The operation order is perspective * rhs.
/// </summary>
/// <param name="perspective">The perspective matrix to multiply with rhs.</param>
/// <param name="rhs">A matrix to be multiply to perspective.</param>
/// <returns>Returns the matrix that is the result of the multiplication.</returns>
public static Matrix4x4 MultiplyPerspectiveMatrix(Matrix4x4 perspective, Matrix4x4 rhs)
{
Matrix4x4 outMat;
outMat.m00 = perspective.m00 * rhs.m00;
outMat.m01 = perspective.m00 * rhs.m01;
outMat.m02 = perspective.m00 * rhs.m02;
outMat.m03 = perspective.m00 * rhs.m03;
outMat.m10 = perspective.m11 * rhs.m10;
outMat.m11 = perspective.m11 * rhs.m11;
outMat.m12 = perspective.m11 * rhs.m12;
outMat.m13 = perspective.m11 * rhs.m13;
outMat.m20 = perspective.m22 * rhs.m20 + perspective.m23 * rhs.m30;
outMat.m21 = perspective.m22 * rhs.m21 + perspective.m23 * rhs.m31;
outMat.m22 = perspective.m22 * rhs.m22 + perspective.m23 * rhs.m32;
outMat.m23 = perspective.m22 * rhs.m23 + perspective.m23 * rhs.m33;
outMat.m30 = -rhs.m20;
outMat.m31 = -rhs.m21;
outMat.m32 = -rhs.m22;
outMat.m33 = -rhs.m23;
return outMat;
}
// An orthographic projection is centered if (right+left) == 0 and (top+bottom) == 0
private static Matrix4x4 MultiplyOrthoMatrixCentered(Matrix4x4 ortho, Matrix4x4 rhs)
{
Matrix4x4 outMat;
outMat.m00 = ortho.m00 * rhs.m00;
outMat.m01 = ortho.m00 * rhs.m01;
outMat.m02 = ortho.m00 * rhs.m02;
outMat.m03 = ortho.m00 * rhs.m03;
outMat.m10 = ortho.m11 * rhs.m10;
outMat.m11 = ortho.m11 * rhs.m11;
outMat.m12 = ortho.m11 * rhs.m12;
outMat.m13 = ortho.m11 * rhs.m13;
outMat.m20 = ortho.m22 * rhs.m20 + ortho.m23 * rhs.m30;
outMat.m21 = ortho.m22 * rhs.m21 + ortho.m23 * rhs.m31;
outMat.m22 = ortho.m22 * rhs.m22 + ortho.m23 * rhs.m32;
outMat.m23 = ortho.m22 * rhs.m23 + ortho.m23 * rhs.m33;
outMat.m30 = rhs.m20;
outMat.m31 = rhs.m21;
outMat.m32 = rhs.m22;
outMat.m33 = rhs.m23;
return outMat;
}
// General case has m03 and m13 != 0
private static Matrix4x4 MultiplyGenericOrthoMatrix(Matrix4x4 ortho, Matrix4x4 rhs)
{
Matrix4x4 outMat;
outMat.m00 = ortho.m00 * rhs.m00 + ortho.m03 * rhs.m30;
outMat.m01 = ortho.m00 * rhs.m01 + ortho.m03 * rhs.m31;
outMat.m02 = ortho.m00 * rhs.m02 + ortho.m03 * rhs.m32;
outMat.m03 = ortho.m00 * rhs.m03 + ortho.m03 * rhs.m33;
outMat.m10 = ortho.m11 * rhs.m10 + ortho.m13 * rhs.m30;
outMat.m11 = ortho.m11 * rhs.m11 + ortho.m13 * rhs.m31;
outMat.m12 = ortho.m11 * rhs.m12 + ortho.m13 * rhs.m32;
outMat.m13 = ortho.m11 * rhs.m13 + ortho.m13 * rhs.m33;
outMat.m20 = ortho.m22 * rhs.m20 + ortho.m23 * rhs.m30;
outMat.m21 = ortho.m22 * rhs.m21 + ortho.m23 * rhs.m31;
outMat.m22 = ortho.m22 * rhs.m22 + ortho.m23 * rhs.m32;
outMat.m23 = ortho.m22 * rhs.m23 + ortho.m23 * rhs.m33;
outMat.m30 = rhs.m20;
outMat.m31 = rhs.m21;
outMat.m32 = rhs.m22;
outMat.m33 = rhs.m23;
return outMat;
}
/// <summary>
/// Multiplies a matrix with an orthographic matrix. This function is faster than performing the full matrix multiplication.
/// The operation order is ortho * rhs.
/// </summary>
/// <param name="ortho">The ortho matrix to multiply with rhs.</param>
/// <param name="rhs">A matrix to be multiply to perspective.</param>
/// <param name="centered">If true, it means that right and left are equivalently distant from center and similarly top/bottom are equivalently distant from center.</param>
/// <returns>Returns the matrix that is the result of the multiplication.</returns>
public static Matrix4x4 MultiplyOrthoMatrix(Matrix4x4 ortho, Matrix4x4 rhs, bool centered)
{
return centered ? MultiplyGenericOrthoMatrix(ortho, rhs) : MultiplyOrthoMatrixCentered(ortho, rhs);
}
/// <summary>
/// Multiplies a matrix with a projection matrix. This function is faster than performing the full matrix multiplication.
/// The operation order is projMatrix * rhs.
/// </summary>
/// <param name="projMatrix">The projection matrix to multiply with rhs.</param>
/// <param name="rhs">A matrix to be multiply to perspective.</param>
/// <param name="orthoCentered">If true, the projection matrix is a centered ( right+left == top+bottom == 0) orthographic projection, otherwise it is a perspective matrix..</param>
/// <returns>Returns the matrix that is the result of the multiplication.</returns>
public static Matrix4x4 MultiplyProjectionMatrix(Matrix4x4 projMatrix, Matrix4x4 rhs, bool orthoCentered)
{
return orthoCentered
? MultiplyOrthoMatrixCentered(projMatrix, rhs)
: MultiplyPerspectiveMatrix(projMatrix, rhs);
}
}
}

View File

@@ -0,0 +1,90 @@
namespace UnityEngine.Rendering
{
// This file can't be in the editor assembly as we need to access it in runtime-editor-specific
// places like OnGizmo etc and we don't want to add the editor assembly as a dependency of the
// runtime one
// The UI layout/styling in this panel is broken and can't match the one from built-ins
// preference panels as everything needed is internal/private (at the time of writing this
// comment)
#if UNITY_EDITOR
using UnityEditor;
class Styles
{
public static readonly GUIContent userDefaults = EditorGUIUtility.TrTextContent("Use Defaults");
}
public static class CoreRenderPipelinePreferences
{
static bool m_Loaded = false;
// Added default Colors so that they can be reverted back to these values
static Color s_VolumeGizmoColorDefault = new Color(0.2f, 0.8f, 0.1f, 0.5f);
static Color s_VolumeGizmoColor = s_VolumeGizmoColorDefault;
static readonly Color kPreviewCameraBackgroundColorDefault = new Color(82f / 255.0f, 82f / 255.0f, 82.0f / 255.0f, 0.0f);
public static Color volumeGizmoColor
{
get => s_VolumeGizmoColor;
set
{
if (s_VolumeGizmoColor == value) return;
s_VolumeGizmoColor = value;
EditorPrefs.SetInt(Keys.volumeGizmoColor, (int)ColorUtils.ToHex(value));
}
}
public static Color previewBackgroundColor => kPreviewCameraBackgroundColorDefault;
static class Keys
{
internal const string volumeGizmoColor = "CoreRP.Volume.GizmoColor";
}
[SettingsProvider]
static SettingsProvider PreferenceGUI()
{
return new SettingsProvider("Preferences/Colors/SRP", SettingsScope.User)
{
guiHandler = searchContext =>
{
if (!m_Loaded)
Load();
Rect r = EditorGUILayout.GetControlRect();
r.xMin = 10;
EditorGUIUtility.labelWidth = 251;
volumeGizmoColor = EditorGUI.ColorField(r, "Volume Gizmo Color", volumeGizmoColor);
if (GUILayout.Button(Styles.userDefaults, GUILayout.Width(120)))
{
RevertColors();
}
}
};
}
static void RevertColors()
{
volumeGizmoColor = s_VolumeGizmoColorDefault;
}
static CoreRenderPipelinePreferences()
{
Load();
}
static void Load()
{
s_VolumeGizmoColor = GetColor(Keys.volumeGizmoColor, new Color(0.2f, 0.8f, 0.1f, 0.5f));
m_Loaded = true;
}
static Color GetColor(string key, Color defaultValue)
{
int value = EditorPrefs.GetInt(key, (int)ColorUtils.ToHex(defaultValue));
return ColorUtils.ToRGBA((uint)value);
}
}
#endif
}

View File

@@ -0,0 +1,31 @@
using System;
namespace UnityEngine.Rendering
{
/// <summary>
/// Delegate utility class.
/// </summary>
public static class DelegateUtility
{
/// <summary>
/// Cast a delegate.
/// </summary>
/// <param name="source">Source delegate.</param>
/// <param name="type">Type of the delegate.</param>
/// <returns>Cast delegate.</returns>
public static Delegate Cast(Delegate source, Type type)
{
if (source == null)
return null;
Delegate[] delegates = source.GetInvocationList();
if (delegates.Length == 1)
return Delegate.CreateDelegate(type,
delegates[0].Target, delegates[0].Method);
Delegate[] delegatesDest = new Delegate[delegates.Length];
for (int nDelegate = 0; nDelegate < delegates.Length; nDelegate++)
delegatesDest[nDelegate] = Delegate.CreateDelegate(type,
delegates[nDelegate].Target, delegates[nDelegate].Method);
return Delegate.Combine(delegatesDest);
}
}
}

View File

@@ -0,0 +1,386 @@
using static UnityEngine.Mathf;
namespace UnityEngine.Rendering
{
/// <summary>
/// An implementation of Hable's artist-friendly tonemapping curve.
/// http://filmicworlds.com/blog/filmic-tonemapping-with-piecewise-power-curves/
/// </summary>
public class HableCurve
{
/// <summary>
/// Individual curve segment.
/// </summary>
public class Segment
{
/// <summary>
/// The offset of the segment on the X axis.
/// </summary>
public float offsetX;
/// <summary>
/// The offset of the segment on the Y axis.
/// </summary>
public float offsetY;
/// <summary>
/// The scale of the segment on the X axis.
/// </summary>
public float scaleX;
/// <summary>
/// The scale of the segment on the Y axis.
/// </summary>
public float scaleY;
/// <summary>
/// <c>ln(A)</c> constant in the power curve <c>y = e^(ln(A) + B*ln(x))</c>.
/// </summary>
public float lnA;
/// <summary>
/// <c>B</c> constant in the power curve <c>y = e^(ln(A) + B*ln(x))</c>.
/// </summary>
public float B;
/// <summary>
/// Evaluate a point on the curve.
/// </summary>
/// <param name="x">The point to evaluate.</param>
/// <returns>The value of the curve, at the point specified.</returns>
public float Eval(float x)
{
float x0 = (x - offsetX) * scaleX;
float y0 = 0f;
// log(0) is undefined but our function should evaluate to 0. There are better ways
// to handle this, but it's doing it the slow way here for clarity.
if (x0 > 0)
y0 = Exp(lnA + B * Log(x0));
return y0 * scaleY + offsetY;
}
}
struct DirectParams
{
internal float x0;
internal float y0;
internal float x1;
internal float y1;
internal float W;
internal float overshootX;
internal float overshootY;
internal float gamma;
}
/// <summary>
/// The white point.
/// </summary>
public float whitePoint { get; private set; }
/// <summary>
/// The inverse of the white point.
/// </summary>
/// <seealso cref="whitePoint"/>
public float inverseWhitePoint { get; private set; }
/// <summary>
/// The start of the linear section (middle segment of the curve).
/// </summary>
public float x0 { get; private set; }
/// <summary>
/// The end of the linear section (middle segment of the curve).
/// </summary>
public float x1 { get; private set; }
/// <summary>
/// The three segments of the curve.
/// </summary>
public readonly Segment[] segments = new Segment[3];
/// <summary>
/// Creates a new curve.
/// </summary>
public HableCurve()
{
for (int i = 0; i < 3; i++)
segments[i] = new Segment();
uniforms = new Uniforms(this);
}
/// <summary>
/// Evaluates a point on the curve.
/// </summary>
/// <param name="x"></param>
/// <returns></returns>
public float Eval(float x)
{
float normX = x * inverseWhitePoint;
int index = (normX < x0) ? 0 : ((normX < x1) ? 1 : 2);
var segment = segments[index];
float ret = segment.Eval(normX);
return ret;
}
/// <summary>
/// Initializes the curve.
/// </summary>
/// <param name="toeStrength">The strength of the transition between the curve's toe and the curve's mid-section. A value of 0 results in no transition and a value of 1 results in a very hard transition.</param>
/// <param name="toeLength">The length of the curve's toe. Higher values result in longer toes and therefore contain more of the dynamic range.</param>
/// <param name="shoulderStrength">The strength of the transition between the curve's midsection and the curve's shoulder. A value of 0 results in no transition and a value of 1 results in a very hard transition.</param>
/// <param name="shoulderLength">The amount of f-stops to add to the dynamic range of the curve. This is how much of the highlights that the curve takes into account.</param>
/// <param name="shoulderAngle">How much overshoot to add to the curve's shoulder.</param>
/// <param name="gamma">A gamma correction to the entire curve.</param>
public void Init(float toeStrength, float toeLength, float shoulderStrength, float shoulderLength, float shoulderAngle, float gamma)
{
var dstParams = new DirectParams();
// This is not actually the display gamma. It's just a UI space to avoid having to
// enter small numbers for the input.
const float kPerceptualGamma = 2.2f;
// Constraints
{
toeLength = Pow(Clamp01(toeLength), kPerceptualGamma);
toeStrength = Clamp01(toeStrength);
shoulderAngle = Clamp01(shoulderAngle);
shoulderStrength = Clamp(shoulderStrength, 1e-5f, 1f - 1e-5f);
shoulderLength = Max(0f, shoulderLength);
gamma = Max(1e-5f, gamma);
}
// Apply base params
{
// Toe goes from 0 to 0.5
float x0 = toeLength * 0.5f;
float y0 = (1f - toeStrength) * x0; // Lerp from 0 to x0
float remainingY = 1f - y0;
float initialW = x0 + remainingY;
float y1_offset = (1f - shoulderStrength) * remainingY;
float x1 = x0 + y1_offset;
float y1 = y0 + y1_offset;
// Filmic shoulder strength is in F stops
float extraW = Pow(2f, shoulderLength) - 1f;
float W = initialW + extraW;
dstParams.x0 = x0;
dstParams.y0 = y0;
dstParams.x1 = x1;
dstParams.y1 = y1;
dstParams.W = W;
// Bake the linear to gamma space conversion
dstParams.gamma = gamma;
}
dstParams.overshootX = (dstParams.W * 2f) * shoulderAngle * shoulderLength;
dstParams.overshootY = 0.5f * shoulderAngle * shoulderLength;
InitSegments(dstParams);
}
void InitSegments(DirectParams srcParams)
{
var paramsCopy = srcParams;
whitePoint = srcParams.W;
inverseWhitePoint = 1f / srcParams.W;
// normalize params to 1.0 range
paramsCopy.W = 1f;
paramsCopy.x0 /= srcParams.W;
paramsCopy.x1 /= srcParams.W;
paramsCopy.overshootX = srcParams.overshootX / srcParams.W;
float toeM = 0f;
float shoulderM = 0f;
{
float m, b;
AsSlopeIntercept(out m, out b, paramsCopy.x0, paramsCopy.x1, paramsCopy.y0, paramsCopy.y1);
float g = srcParams.gamma;
// Base function of linear section plus gamma is
// y = (mx+b)^g
//
// which we can rewrite as
// y = exp(g*ln(m) + g*ln(x+b/m))
//
// and our evaluation function is (skipping the if parts):
/*
float x0 = (x - offsetX) * scaleX;
y0 = exp(m_lnA + m_B*log(x0));
return y0*scaleY + m_offsetY;
*/
var midSegment = segments[1];
midSegment.offsetX = -(b / m);
midSegment.offsetY = 0f;
midSegment.scaleX = 1f;
midSegment.scaleY = 1f;
midSegment.lnA = g * Log(m);
midSegment.B = g;
toeM = EvalDerivativeLinearGamma(m, b, g, paramsCopy.x0);
shoulderM = EvalDerivativeLinearGamma(m, b, g, paramsCopy.x1);
// apply gamma to endpoints
paramsCopy.y0 = Max(1e-5f, Pow(paramsCopy.y0, paramsCopy.gamma));
paramsCopy.y1 = Max(1e-5f, Pow(paramsCopy.y1, paramsCopy.gamma));
paramsCopy.overshootY = Pow(1f + paramsCopy.overshootY, paramsCopy.gamma) - 1f;
}
this.x0 = paramsCopy.x0;
this.x1 = paramsCopy.x1;
// Toe section
{
var toeSegment = segments[0];
toeSegment.offsetX = 0;
toeSegment.offsetY = 0f;
toeSegment.scaleX = 1f;
toeSegment.scaleY = 1f;
float lnA, B;
SolveAB(out lnA, out B, paramsCopy.x0, paramsCopy.y0, toeM);
toeSegment.lnA = lnA;
toeSegment.B = B;
}
// Shoulder section
{
// Use the simple version that is usually too flat
var shoulderSegment = segments[2];
float x0 = (1f + paramsCopy.overshootX) - paramsCopy.x1;
float y0 = (1f + paramsCopy.overshootY) - paramsCopy.y1;
float lnA, B;
SolveAB(out lnA, out B, x0, y0, shoulderM);
shoulderSegment.offsetX = (1f + paramsCopy.overshootX);
shoulderSegment.offsetY = (1f + paramsCopy.overshootY);
shoulderSegment.scaleX = -1f;
shoulderSegment.scaleY = -1f;
shoulderSegment.lnA = lnA;
shoulderSegment.B = B;
}
// Normalize so that we hit 1.0 at our white point. We wouldn't have do this if we
// skipped the overshoot part.
{
// Evaluate shoulder at the end of the curve
float scale = segments[2].Eval(1f);
float invScale = 1f / scale;
segments[0].offsetY *= invScale;
segments[0].scaleY *= invScale;
segments[1].offsetY *= invScale;
segments[1].scaleY *= invScale;
segments[2].offsetY *= invScale;
segments[2].scaleY *= invScale;
}
}
// Find a function of the form:
// f(x) = e^(lnA + Bln(x))
// where
// f(0) = 0; not really a constraint
// f(x0) = y0
// f'(x0) = m
void SolveAB(out float lnA, out float B, float x0, float y0, float m)
{
B = (m * x0) / y0;
lnA = Log(y0) - B * Log(x0);
}
// Convert to y=mx+b
void AsSlopeIntercept(out float m, out float b, float x0, float x1, float y0, float y1)
{
float dy = (y1 - y0);
float dx = (x1 - x0);
if (dx == 0)
m = 1f;
else
m = dy / dx;
b = y0 - x0 * m;
}
// f(x) = (mx+b)^g
// f'(x) = gm(mx+b)^(g-1)
float EvalDerivativeLinearGamma(float m, float b, float g, float x)
{
return g * m * Pow(m * x + b, g - 1f);
}
/// <summary>
/// An utility class to ease the binding of curve parameters to shaders.
/// </summary>
public class Uniforms
{
HableCurve parent;
internal Uniforms(HableCurve parent)
{
this.parent = parent;
}
/// <summary>
/// Main curve settings, stored as <c>(inverseWhitePoint, x0, x1, 0)</c>.
/// </summary>
public Vector4 curve => new Vector4(parent.inverseWhitePoint, parent.x0, parent.x1, 0f);
/// <summary>
/// Toe segment settings, stored as <c>(offsetX, offsetY, scaleX, scaleY)</c>.
/// </summary>
public Vector4 toeSegmentA => new Vector4(parent.segments[0].offsetX, parent.segments[0].offsetY, parent.segments[0].scaleX, parent.segments[0].scaleY);
/// <summary>
/// Toe segment settings, stored as <c>(ln1, B, 0, 0)</c>.
/// </summary>
public Vector4 toeSegmentB => new Vector4(parent.segments[0].lnA, parent.segments[0].B, 0f, 0f);
/// <summary>
/// Mid segment settings, stored as <c>(offsetX, offsetY, scaleX, scaleY)</c>.
/// </summary>
public Vector4 midSegmentA => new Vector4(parent.segments[1].offsetX, parent.segments[1].offsetY, parent.segments[1].scaleX, parent.segments[1].scaleY);
/// <summary>
/// Mid segment settings, stored as <c>(ln1, B, 0, 0)</c>.
/// </summary>
public Vector4 midSegmentB => new Vector4(parent.segments[1].lnA, parent.segments[1].B, 0f, 0f);
/// <summary>
/// Shoulder segment settings, stored as <c>(offsetX, offsetY, scaleX, scaleY)</c>.
/// </summary>
public Vector4 shoSegmentA => new Vector4(parent.segments[2].offsetX, parent.segments[2].offsetY, parent.segments[2].scaleX, parent.segments[2].scaleY);
/// <summary>
/// Shoulder segment settings, stored as <c>(ln1, B, 0, 0)</c>.
/// </summary>
public Vector4 shoSegmentB => new Vector4(parent.segments[2].lnA, parent.segments[2].B, 0f, 0f);
}
/// <summary>
/// An instance of the <see cref="Uniforms"/> utility class for this curve.
/// </summary>
public readonly Uniforms uniforms;
}
}

View File

@@ -0,0 +1,31 @@
namespace UnityEngine.Rendering
{
/// <summary>
/// An utility class to compute samples on the Halton sequence.
/// https://en.wikipedia.org/wiki/Halton_sequence
/// </summary>
public static class HaltonSequence
{
/// <summary>
/// Gets a deterministic sample in the Halton sequence.
/// </summary>
/// <param name="index">The index in the sequence.</param>
/// <param name="radix">The radix of the sequence.</param>
/// <returns>A sample from the Halton sequence.</returns>
public static float Get(int index, int radix)
{
float result = 0f;
float fraction = 1f / radix;
while (index > 0)
{
result += (index % radix) * fraction;
index /= radix;
fraction /= radix;
}
return result;
}
}
}

View File

@@ -0,0 +1,167 @@
using System;
using UnityEngine.Scripting.APIUpdating;
namespace UnityEngine.Rendering
{
/// <summary>
/// Material quality flags.
/// </summary>
[Flags]
[MovedFrom("Utilities")]
public enum MaterialQuality
{
/// <summary>Low Material Quality.</summary>
Low = 1 << 0,
/// <summary>Medium Material Quality.</summary>
Medium = 1 << 1,
/// <summary>High Material Quality.</summary>
High = 1 << 2
}
/// <summary>
/// Material Quality utility class.
/// </summary>
[MovedFrom("Utilities")]
public static class MaterialQualityUtilities
{
/// <summary>
/// Keywords strings for Material Quality levels.
/// </summary>
public static string[] KeywordNames =
{
"MATERIAL_QUALITY_LOW",
"MATERIAL_QUALITY_MEDIUM",
"MATERIAL_QUALITY_HIGH",
};
/// <summary>
/// String representation of the MaterialQuality enum.
/// </summary>
public static string[] EnumNames = Enum.GetNames(typeof(MaterialQuality));
/// <summary>
/// Keywords for Material Quality levels.
/// </summary>
public static ShaderKeyword[] Keywords =
{
new ShaderKeyword(KeywordNames[0]),
new ShaderKeyword(KeywordNames[1]),
new ShaderKeyword(KeywordNames[2]),
};
/// <summary>
/// Returns the highest available quality level in a MaterialQuality bitfield.
/// </summary>
/// <param name="levels">Input MaterialQuality bitfield.</param>
/// <returns>The highest available quality level.</returns>
public static MaterialQuality GetHighestQuality(this MaterialQuality levels)
{
for (var i = Keywords.Length - 1; i >= 0; --i)
{
var level = (MaterialQuality)(1 << i);
if ((levels & level) != 0)
return level;
}
return 0;
}
/// <summary>
/// Returns the closest available quality level in a MaterialQuality bitfield.
/// </summary>
/// <param name="availableLevels">Available MaterialQuality bitfield.</param>
/// <param name="requestedLevel">Input MaterialQuality level.</param>
/// <returns>The closest available quality level.</returns>
public static MaterialQuality GetClosestQuality(this MaterialQuality availableLevels, MaterialQuality requestedLevel)
{
// Special fallback when there are no available quality levels. Needs to match in the shader stripping code
if (availableLevels == 0)
return MaterialQuality.Low;
// First we want to find the closest available quality level below the requested one.
int requestedLevelIndex = ToFirstIndex(requestedLevel);
MaterialQuality chosenQuality = (MaterialQuality)0;
for (int i = requestedLevelIndex; i >= 0; --i)
{
var level = FromIndex(i);
if ((level & availableLevels) != 0)
{
chosenQuality = level;
break;
}
}
if (chosenQuality != 0)
return chosenQuality;
// If none is found then we fallback to the closest above.
for (var i = requestedLevelIndex + 1; i < Keywords.Length; ++i)
{
var level = FromIndex(i);
var diff = Math.Abs(requestedLevel - level);
if ((level & availableLevels) != 0)
{
chosenQuality = level;
break;
}
}
Debug.Assert(chosenQuality != 0);
return chosenQuality;
}
/// <summary>
/// Set the global keyword for the provided MaterialQuality.
/// </summary>
/// <param name="level">MaterialQuality level to set the keyword for.</param>
public static void SetGlobalShaderKeywords(this MaterialQuality level)
{
for (var i = 0; i < KeywordNames.Length; ++i)
{
if ((level & (MaterialQuality)(1 << i)) != 0)
Shader.EnableKeyword(KeywordNames[i]);
else
Shader.DisableKeyword(KeywordNames[i]);
}
}
/// <summary>
/// Set the global keyword for the provided MaterialQuality.
/// </summary>
/// <param name="level">MaterialQuality level to set the keyword for.</param>
/// <param name="cmd">Command Buffer used to setup the keyword.</param>
public static void SetGlobalShaderKeywords(this MaterialQuality level, CommandBuffer cmd)
{
for (var i = 0; i < KeywordNames.Length; ++i)
{
if ((level & (MaterialQuality)(1 << i)) != 0)
cmd.EnableShaderKeyword(KeywordNames[i]);
else
cmd.DisableShaderKeyword(KeywordNames[i]);
}
}
/// <summary>
/// Returns the index (in the MaterialQuality enum) of the first available level.
/// </summary>
/// <param name="level">MaterialQuality bitfield.</param>
/// <returns>The index of the first available level.</returns>
public static int ToFirstIndex(this MaterialQuality level)
{
for (var i = 0; i < KeywordNames.Length; ++i)
{
if ((level & (MaterialQuality)(1 << i)) != 0)
return i;
}
return -1;
}
/// <summary>
/// Returns the enum equivalent of the index in the MaterialQuality enum list.
/// </summary>
/// <param name="index">Index of the material quality.</param>
/// <returns>The equivalent enum.</returns>
public static MaterialQuality FromIndex(int index) => (MaterialQuality)(1 << index);
}
}

View File

@@ -0,0 +1,340 @@
using System;
using System.IO;
using UnityEngine.Assertions;
#if UNITY_EDITOR
using UnityEditor;
using System.Reflection;
#endif
namespace UnityEngine.Rendering
{
#if UNITY_EDITOR
/// <summary>
/// The resources that need to be reloaded in Editor can live in Runtime.
/// The reload call should only be done in Editor context though but it
/// could be called from runtime entities.
/// </summary>
public static class ResourceReloader
{
/// <summary>
/// Looks for resources in the given <paramref name="container"/> object and reload the ones
/// that are missing or broken.
/// This version will still return null value without throwing error if the issue is due to
/// AssetDatabase being not ready. But in this case the assetDatabaseNotReady result will be true.
/// </summary>
/// <param name="container">The object containing reload-able resources</param>
/// <param name="basePath">The base path for the package</param>
/// <returns>
/// - 1 hasChange: True if something have been reloaded.
/// - 2 assetDatabaseNotReady: True if the issue preventing loading is due to state of AssetDatabase
/// </returns>
public static (bool hasChange, bool assetDatabaseNotReady) TryReloadAllNullIn(System.Object container, string basePath)
{
try
{
return (ReloadAllNullIn(container, basePath), false);
}
catch (Exception e)
{
if (!(e.Data.Contains("InvalidImport") && e.Data["InvalidImport"] is int && (int)e.Data["InvalidImport"] == 1))
throw e;
return (false, true);
}
}
/// <summary>
/// Looks for resources in the given <paramref name="container"/> object and reload the ones
/// that are missing or broken.
/// </summary>
/// <param name="container">The object containing reload-able resources</param>
/// <param name="basePath">The base path for the package</param>
/// <returns>True if something have been reloaded.</returns>
public static bool ReloadAllNullIn(System.Object container, string basePath)
{
if (IsNull(container))
return false;
var changed = false;
foreach (var fieldInfo in container.GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static))
{
//Recurse on sub-containers
if (IsReloadGroup(fieldInfo))
{
changed |= FixGroupIfNeeded(container, fieldInfo);
changed |= ReloadAllNullIn(fieldInfo.GetValue(container), basePath);
}
//Find null field and reload them
var attribute = GetReloadAttribute(fieldInfo);
if (attribute != null)
{
if (attribute.paths.Length == 1)
{
changed |= SetAndLoadIfNull(container, fieldInfo, GetFullPath(basePath, attribute),
attribute.package == ReloadAttribute.Package.Builtin);
}
else if (attribute.paths.Length > 1)
{
changed |= FixArrayIfNeeded(container, fieldInfo, attribute.paths.Length);
var array = (Array)fieldInfo.GetValue(container);
if (IsReloadGroup(array))
{
//Recurse on each sub-containers
for (int index = 0; index < attribute.paths.Length; ++index)
{
changed |= FixGroupIfNeeded(array, index);
changed |= ReloadAllNullIn(array.GetValue(index), basePath);
}
}
else
{
bool builtin = attribute.package == ReloadAttribute.Package.Builtin;
//Find each null element and reload them
for (int index = 0; index < attribute.paths.Length; ++index)
changed |= SetAndLoadIfNull(array, index, GetFullPath(basePath, attribute, index), builtin);
}
}
}
}
if (changed && container is UnityEngine.Object c)
EditorUtility.SetDirty(c);
return changed;
}
static bool FixGroupIfNeeded(System.Object container, FieldInfo info)
{
if (IsNull(container, info))
{
var type = info.FieldType;
var value = type.IsSubclassOf(typeof(ScriptableObject))
? ScriptableObject.CreateInstance(type)
: Activator.CreateInstance(type);
info.SetValue(
container,
value
);
return true;
}
return false;
}
static bool FixGroupIfNeeded(Array array, int index)
{
Assert.IsNotNull(array);
if (IsNull(array.GetValue(index)))
{
var type = array.GetType().GetElementType();
var value = type.IsSubclassOf(typeof(ScriptableObject))
? ScriptableObject.CreateInstance(type)
: Activator.CreateInstance(type);
array.SetValue(
value,
index
);
return true;
}
return false;
}
static bool FixArrayIfNeeded(System.Object container, FieldInfo info, int length)
{
if (IsNull(container, info) || ((Array)info.GetValue(container)).Length < length)
{
info.SetValue(
container,
Activator.CreateInstance(info.FieldType, length)
);
return true;
}
return false;
}
static ReloadAttribute GetReloadAttribute(FieldInfo fieldInfo)
{
var attributes = (ReloadAttribute[])fieldInfo
.GetCustomAttributes(typeof(ReloadAttribute), false);
if (attributes.Length == 0)
return null;
return attributes[0];
}
static bool IsReloadGroup(FieldInfo info)
=> info.FieldType
.GetCustomAttributes(typeof(ReloadGroupAttribute), false).Length > 0;
static bool IsReloadGroup(Array field)
=> field.GetType().GetElementType()
.GetCustomAttributes(typeof(ReloadGroupAttribute), false).Length > 0;
static bool IsNull(System.Object container, FieldInfo info)
=> IsNull(info.GetValue(container));
static bool IsNull(System.Object field)
=> field == null || field.Equals(null);
static UnityEngine.Object Load(string path, Type type, bool builtin)
{
// Check if asset exist.
// Direct loading can be prevented by AssetDatabase being reloading.
var guid = AssetDatabase.AssetPathToGUID(path);
if (!builtin && String.IsNullOrEmpty(guid))
throw new Exception($"Cannot load. Incorrect path: {path}");
// Else the path is good. Attempt loading resource if AssetDatabase available.
UnityEngine.Object result;
if (builtin && type == typeof(Shader))
result = Shader.Find(path);
else
result = AssetDatabase.LoadAssetAtPath(path, type);
if (IsNull(result))
{
var e = new Exception($"Cannot load. Path {path} is correct but AssetDatabase cannot load now.");
e.Data["InvalidImport"] = 1;
throw e;
}
return result;
}
static bool SetAndLoadIfNull(System.Object container, FieldInfo info,
string path, bool builtin)
{
if (IsNull(container, info))
{
info.SetValue(container, Load(path, info.FieldType, builtin));
return true;
}
return false;
}
static bool SetAndLoadIfNull(Array array, int index, string path, bool builtin)
{
var element = array.GetValue(index);
if (IsNull(element))
{
array.SetValue(Load(path, array.GetType().GetElementType(), builtin), index);
return true;
}
return false;
}
static string GetFullPath(string basePath, ReloadAttribute attribute, int index = 0)
{
string path;
switch (attribute.package)
{
case ReloadAttribute.Package.Builtin:
path = attribute.paths[index];
break;
case ReloadAttribute.Package.Root:
path = basePath + "/" + attribute.paths[index];
break;
default:
throw new ArgumentException("Unknown Package Path!");
}
return path;
}
}
#endif
/// <summary>
/// Attribute specifying information to reload with <see cref="ResourceReloader"/>. This is only
/// used in the editor and doesn't have any effect at runtime.
/// </summary>
/// <seealso cref="ResourceReloader"/>
/// <seealso cref="ReloadGroupAttribute"/>
[AttributeUsage(AttributeTargets.Field)]
public sealed class ReloadAttribute : Attribute
{
/// <summary>
/// Lookup method for a resource.
/// </summary>
public enum Package
{
/// <summary>
/// Used for builtin resources when the resource isn't part of the package (i.e. builtin
/// shaders).
/// </summary>
Builtin,
/// <summary>
/// Used for resources inside the package.
/// </summary>
Root
};
#if UNITY_EDITOR
/// <summary>
/// The lookup method.
/// </summary>
public readonly Package package;
/// <summary>
/// Search paths.
/// </summary>
public readonly string[] paths;
#endif
/// <summary>
/// Creates a new <see cref="ReloadAttribute"/> for an array by specifying each resource
/// path individually.
/// </summary>
/// <param name="paths">Search paths</param>
/// <param name="package">The lookup method</param>
public ReloadAttribute(string[] paths, Package package = Package.Root)
{
#if UNITY_EDITOR
this.paths = paths;
this.package = package;
#endif
}
/// <summary>
/// Creates a new <see cref="ReloadAttribute"/> for a single resource.
/// </summary>
/// <param name="path">Search path</param>
/// <param name="package">The lookup method</param>
public ReloadAttribute(string path, Package package = Package.Root)
: this(new[] { path }, package)
{}
/// <summary>
/// Creates a new <see cref="ReloadAttribute"/> for an array using automatic path name
/// numbering.
/// </summary>
/// <param name="pathFormat">The format used for the path</param>
/// <param name="rangeMin">The array start index (inclusive)</param>
/// <param name="rangeMax">The array end index (exclusive)</param>
/// <param name="package">The lookup method</param>
public ReloadAttribute(string pathFormat, int rangeMin, int rangeMax,
Package package = Package.Root)
{
#if UNITY_EDITOR
this.package = package;
paths = new string[rangeMax - rangeMin];
for (int index = rangeMin, i = 0; index < rangeMax; ++index, ++i)
paths[i] = string.Format(pathFormat, index);
#endif
}
}
/// <summary>
/// Attribute specifying that it contains element that should be reloaded.
/// If the instance of the class is null, the system will try to recreate
/// it with the default constructor.
/// Be sure classes using it have default constructor!
/// </summary>
/// <seealso cref="ReloadAttribute"/>
[AttributeUsage(AttributeTargets.Class)]
public sealed class ReloadGroupAttribute : Attribute
{}
}

View File

@@ -0,0 +1,25 @@
using System;
using UnityEngine;
using UnityEngine.Rendering;
/// <summary>
/// Setup a specific render pipeline on scene loading.
/// </summary>
[ExecuteAlways]
public class SceneRenderPipeline : MonoBehaviour
{
/// <summary>
/// Scriptable Render Pipeline Asset to setup on scene load.
/// </summary>
public RenderPipelineAsset renderPipelineAsset;
void OnEnable()
{
GraphicsSettings.renderPipelineAsset = renderPipelineAsset;
}
void OnValidate()
{
GraphicsSettings.renderPipelineAsset = renderPipelineAsset;
}
}

View File

@@ -0,0 +1,262 @@
using System;
using System.Runtime.CompilerServices;
namespace UnityEngine.Rendering
{
// Due to limitations in the builtin AnimationCurve we need this custom wrapper.
// Improvements:
// - Dirty state handling so we know when a curve has changed or not
// - Looping support (infinite curve)
// - Zero-value curve
// - Cheaper length property
/// <summary>
/// A wrapper around <c>AnimationCurve</c> to automatically bake it into a texture.
/// </summary>
[Serializable]
public class TextureCurve : IDisposable
{
const int k_Precision = 128; // Edit LutBuilder3D if you change this value
const float k_Step = 1f / k_Precision;
/// <summary>
/// The number of keys in the curve.
/// </summary>
[field: SerializeField]
public int length { get; private set; } // Calling AnimationCurve.length is very slow, let's cache it
[SerializeField]
bool m_Loop;
[SerializeField]
float m_ZeroValue;
[SerializeField]
float m_Range;
[SerializeField]
AnimationCurve m_Curve;
AnimationCurve m_LoopingCurve;
Texture2D m_Texture;
bool m_IsCurveDirty;
bool m_IsTextureDirty;
/// <summary>
/// Retrieves the key at index.
/// </summary>
/// <param name="index">The index to look for.</param>
/// <returns>A key.</returns>
public Keyframe this[int index] => m_Curve[index];
/// <summary>
/// Creates a new <see cref="TextureCurve"/> from an existing <c>AnimationCurve</c>.
/// </summary>
/// <param name="baseCurve">The source <c>AnimationCurve</c>.</param>
/// <param name="zeroValue">The default value to use when the curve doesn't have any key.</param>
/// <param name="loop">Should the curve automatically loop in the given <paramref name="bounds"/>?</param>
/// <param name="bounds">The boundaries of the curve.</param>
public TextureCurve(AnimationCurve baseCurve, float zeroValue, bool loop, in Vector2 bounds)
: this(baseCurve.keys, zeroValue, loop, bounds) {}
/// <summary>
/// Creates a new <see cref="TextureCurve"/> from an arbitrary number of keyframes.
/// </summary>
/// <param name="keys">An array of Keyframes used to define the curve.</param>
/// <param name="zeroValue">The default value to use when the curve doesn't have any key.</param>
/// <param name="loop">Should the curve automatically loop in the given <paramref name="bounds"/>?</param>
/// <param name="bounds">The boundaries of the curve.</param>
public TextureCurve(Keyframe[] keys, float zeroValue, bool loop, in Vector2 bounds)
{
m_Curve = new AnimationCurve(keys);
m_ZeroValue = zeroValue;
m_Loop = loop;
m_Range = bounds.magnitude;
length = keys.Length;
SetDirty();
}
/// <summary>
/// Finalizer.
/// </summary>
~TextureCurve() {}
/// <summary>
/// Cleans up the internal texture resource.
/// </summary>
[Obsolete("Please use Release() instead.")]
public void Dispose() {}
/// <summary>
/// Releases the internal texture resource.
/// </summary>
public void Release()
{
CoreUtils.Destroy(m_Texture);
m_Texture = null;
}
/// <summary>
/// Marks the curve as dirty to trigger a redraw of the texture the next time <see cref="GetTexture"/>
/// is called.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetDirty()
{
m_IsCurveDirty = true;
m_IsTextureDirty = true;
}
static TextureFormat GetTextureFormat()
{
if (SystemInfo.SupportsTextureFormat(TextureFormat.RHalf))
return TextureFormat.RHalf;
if (SystemInfo.SupportsTextureFormat(TextureFormat.R8))
return TextureFormat.R8;
return TextureFormat.ARGB32;
}
/// <summary>
/// Gets the texture representation of this curve.
/// </summary>
/// <returns>A 128x1 texture.</returns>
public Texture2D GetTexture()
{
if (m_Texture == null)
{
m_Texture = new Texture2D(k_Precision, 1, GetTextureFormat(), false, true);
m_Texture.name = "CurveTexture";
m_Texture.hideFlags = HideFlags.HideAndDontSave;
m_Texture.filterMode = FilterMode.Bilinear;
m_Texture.wrapMode = TextureWrapMode.Clamp;
m_IsTextureDirty = true;
}
if (m_IsTextureDirty)
{
var pixels = new Color[k_Precision];
for (int i = 0; i < pixels.Length; i++)
pixels[i].r = Evaluate(i * k_Step);
m_Texture.SetPixels(pixels);
m_Texture.Apply(false, false);
m_IsTextureDirty = false;
}
return m_Texture;
}
/// <summary>
/// Evaluate a time value on the curve.
/// </summary>
/// <param name="time">The time within the curve you want to evaluate.</param>
/// <returns>The value of the curve, at the point in time specified.</returns>
public float Evaluate(float time)
{
if (m_IsCurveDirty)
length = m_Curve.length;
if (length == 0)
return m_ZeroValue;
if (!m_Loop || length == 1)
return m_Curve.Evaluate(time);
if (m_IsCurveDirty)
{
if (m_LoopingCurve == null)
m_LoopingCurve = new AnimationCurve();
var prev = m_Curve[length - 1];
prev.time -= m_Range;
var next = m_Curve[0];
next.time += m_Range;
m_LoopingCurve.keys = m_Curve.keys; // GC pressure
m_LoopingCurve.AddKey(prev);
m_LoopingCurve.AddKey(next);
m_IsCurveDirty = false;
}
return m_LoopingCurve.Evaluate(time);
}
/// <summary>
/// Adds a new key to the curve.
/// </summary>
/// <param name="time">The time at which to add the key.</param>
/// <param name="value">The value for the key.</param>
/// <returns>The index of the added key, or -1 if the key could not be added.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int AddKey(float time, float value)
{
int r = m_Curve.AddKey(time, value);
if (r > -1)
SetDirty();
return r;
}
/// <summary>
/// Removes the keyframe at <paramref name="index"/> and inserts <paramref name="key"/>.
/// </summary>
/// <param name="index"></param>
/// <param name="key"></param>
/// <returns>The index of the keyframe after moving it.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int MoveKey(int index, in Keyframe key)
{
int r = m_Curve.MoveKey(index, key);
SetDirty();
return r;
}
/// <summary>
/// Removes a key.
/// </summary>
/// <param name="index">The index of the key to remove.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void RemoveKey(int index)
{
m_Curve.RemoveKey(index);
SetDirty();
}
/// <summary>
/// Smoothes the in and out tangents of the keyframe at <paramref name="index"/>. A <paramref name="weight"/> of 0 evens out tangents.
/// </summary>
/// <param name="index">The index of the keyframe to be smoothed.</param>
/// <param name="weight">The smoothing weight to apply to the keyframe's tangents.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SmoothTangents(int index, float weight)
{
m_Curve.SmoothTangents(index, weight);
SetDirty();
}
}
/// <summary>
/// A <see cref="VolumeParameter"/> that holds a <see cref="TextureCurve"/> value.
/// </summary>
[Serializable]
public class TextureCurveParameter : VolumeParameter<TextureCurve>
{
/// <summary>
/// Creates a new <see cref="TextureCurveParameter"/> instance.
/// </summary>
/// <param name="value">The initial value to store in the parameter.</param>
/// <param name="overrideState">The initial override state for the parameter.</param>
public TextureCurveParameter(TextureCurve value, bool overrideState = false)
: base(value, overrideState) {}
/// <summary>
/// Release implementation.
/// </summary>
public override void Release() => m_Value.Release();
// TODO: TextureCurve interpolation
}
}

View File

@@ -0,0 +1,148 @@
namespace UnityEngine.Rendering
{
/// <summary>Utility for tiles layout</summary>
public static class TileLayoutUtils
{
/// <summary>Try decompose the givent rect into tiles given the parameter</summary>
/// <param name="src">The rect to split</param>
/// <param name="tileSize">The size of the tiles</param>
/// <param name="main">Computed main area</param>
/// <param name="topRow">Computed top row area</param>
/// <param name="rightCol">Computed right column area</param>
/// <param name="topRight">Computed top right corner area</param>
/// <returns>If true, the tiles decomposition is a success</returns>
public static bool TryLayoutByTiles(
RectInt src,
uint tileSize,
out RectInt main,
out RectInt topRow,
out RectInt rightCol,
out RectInt topRight)
{
if (src.width < tileSize || src.height < tileSize)
{
main = new RectInt(0, 0, 0, 0);
topRow = new RectInt(0, 0, 0, 0);
rightCol = new RectInt(0, 0, 0, 0);
topRight = new RectInt(0, 0, 0, 0);
return false;
}
int mainRows = src.height / (int)tileSize;
int mainCols = src.width / (int)tileSize;
int mainWidth = mainCols * (int)tileSize;
int mainHeight = mainRows * (int)tileSize;
main = new RectInt
{
x = src.x,
y = src.y,
width = mainWidth,
height = mainHeight,
};
topRow = new RectInt
{
x = src.x,
y = src.y + mainHeight,
width = mainWidth,
height = src.height - mainHeight
};
rightCol = new RectInt
{
x = src.x + mainWidth,
y = src.y,
width = src.width - mainWidth,
height = mainHeight
};
topRight = new RectInt
{
x = src.x + mainWidth,
y = src.y + mainHeight,
width = src.width - mainWidth,
height = src.height - mainHeight
};
return true;
}
/// <summary>Try decompose the givent rect into rows given the parameter</summary>
/// <param name="src">The rect to split</param>
/// <param name="tileSize">The size of the tiles</param>
/// <param name="main">Computed main area</param>
/// <param name="other">Computed other area</param>
/// <returns>If true, the tiles decomposition is a success</returns>
public static bool TryLayoutByRow(
RectInt src,
uint tileSize,
out RectInt main,
out RectInt other)
{
if (src.height < tileSize)
{
main = new RectInt(0, 0, 0, 0);
other = new RectInt(0, 0, 0, 0);
return false;
}
int mainRows = src.height / (int)tileSize;
int mainHeight = mainRows * (int)tileSize;
main = new RectInt
{
x = src.x,
y = src.y,
width = src.width,
height = mainHeight,
};
other = new RectInt
{
x = src.x,
y = src.y + mainHeight,
width = src.width,
height = src.height - mainHeight
};
return true;
}
/// <summary>Try decompose the givent rect into columns given the parameter</summary>
/// <param name="src">The rect to split</param>
/// <param name="tileSize">The size of the tiles</param>
/// <param name="main">Computed main area</param>
/// <param name="other">Computed other area</param>
/// <returns>If true, the tiles decomposition is a success</returns>
public static bool TryLayoutByCol(
RectInt src,
uint tileSize,
out RectInt main,
out RectInt other)
{
if (src.width < tileSize)
{
main = new RectInt(0, 0, 0, 0);
other = new RectInt(0, 0, 0, 0);
return false;
}
int mainCols = src.width / (int)tileSize;
int mainWidth = mainCols * (int)tileSize;
main = new RectInt
{
x = src.x,
y = src.y,
width = mainWidth,
height = src.height,
};
other = new RectInt
{
x = src.x + mainWidth,
y = src.y,
width = src.width - mainWidth,
height = src.height
};
return true;
}
}
}

View File

@@ -0,0 +1,22 @@
namespace UnityEngine.Rendering
{
/// <summary>
/// XR Utility class.
/// </summary>
public static class XRUtils
{
/// <summary>
/// Draw the XR occlusion mesh.
/// </summary>
/// <param name="cmd">Command Buffer used to draw the occlusion mesh.</param>
/// <param name="camera">Camera for which the occlusion mesh is rendered.</param>
/// <param name="stereoEnabled">True if stereo rendering is enabled.</param>
public static void DrawOcclusionMesh(CommandBuffer cmd, Camera camera, bool stereoEnabled = true) // Optional stereoEnabled is for SRP-specific stereo logic
{
if ((!XRGraphics.enabled) || (!camera.stereoEnabled) || (!stereoEnabled))
return;
UnityEngine.RectInt normalizedCamViewport = new UnityEngine.RectInt(0, 0, camera.pixelWidth, camera.pixelHeight);
cmd.DrawOcclusionMesh(normalizedCamViewport);
}
}
}