543 lines
17 KiB
C#
543 lines
17 KiB
C#
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
|
|
namespace JBooth.MicroSplat
|
|
{
|
|
|
|
public class MicroSplatProceduralTextureConfig : ScriptableObject
|
|
{
|
|
#if UNITY_EDITOR
|
|
public enum ValueMode
|
|
{
|
|
Scalar,
|
|
EightBit
|
|
}
|
|
|
|
public ValueMode valueMode = ValueMode.Scalar;
|
|
#endif
|
|
|
|
public enum TableSize
|
|
{
|
|
k64 = 64,
|
|
k128 = 128,
|
|
k256 = 256,
|
|
k512 = 512,
|
|
k1024 = 1024,
|
|
k2048 = 2048,
|
|
k4096 = 4096
|
|
}
|
|
|
|
|
|
public void ResetToDefault()
|
|
{
|
|
layers = new List<Layer>(3);
|
|
layers.Add(new Layer());
|
|
layers.Add(new Layer());
|
|
layers.Add(new Layer());
|
|
layers[1].textureIndex = 1;
|
|
layers[1].slopeActive = true;
|
|
layers[1].slopeCurve = new AnimationCurve(new Keyframe[4] { new Keyframe(0.03f, 0), new Keyframe(0.06f, 1), new Keyframe(0.16f, 1), new Keyframe(0.2f, 0) });
|
|
layers[0].slopeActive = true;
|
|
layers[0].textureIndex = 2;
|
|
layers[0].slopeCurve = new AnimationCurve(new Keyframe[2] { new Keyframe(0.13f, 0), new Keyframe(0.25f, 1) });
|
|
}
|
|
|
|
public TableSize proceduralCurveTextureSize = TableSize.k256;
|
|
|
|
[System.Serializable]
|
|
public class Layer
|
|
{
|
|
[System.Serializable]
|
|
public class Filter
|
|
{
|
|
public float center = 0.5f;
|
|
public float width = 0.1f;
|
|
public float contrast = 1;
|
|
}
|
|
|
|
|
|
public float weight = 1;
|
|
public int textureIndex = 0;
|
|
public bool noiseActive;
|
|
public float noiseFrequency = 1;
|
|
public float noiseOffset = 0;
|
|
public Vector2 noiseRange = new Vector2(0, 1);
|
|
|
|
public Vector4 biomeWeights = new Vector4(1, 1, 1, 1);
|
|
public Vector4 biomeWeights2 = new Vector4(1, 1, 1, 1);
|
|
|
|
public bool heightActive;
|
|
public AnimationCurve heightCurve = AnimationCurve.Linear(0, 1, 1, 1);
|
|
public Filter heightFilter = new Filter();
|
|
|
|
public bool slopeActive;
|
|
public AnimationCurve slopeCurve = AnimationCurve.Linear(0, 1, 1, 1);
|
|
public Filter slopeFilter = new Filter();
|
|
|
|
public bool erosionMapActive;
|
|
public AnimationCurve erosionMapCurve = AnimationCurve.Linear(0, 1, 1, 1);
|
|
public Filter erosionFilter = new Filter();
|
|
|
|
public bool cavityMapActive;
|
|
public AnimationCurve cavityMapCurve = AnimationCurve.Linear(0, 1, 1, 1);
|
|
public Filter cavityMapFilter = new Filter();
|
|
|
|
public enum CurveMode
|
|
{
|
|
Curve,
|
|
BoostFilter,
|
|
HighPass,
|
|
LowPass,
|
|
CutFilter
|
|
}
|
|
|
|
public CurveMode heightCurveMode = CurveMode.Curve;
|
|
public CurveMode slopeCurveMode = CurveMode.Curve;
|
|
public CurveMode erosionCurveMode = CurveMode.Curve;
|
|
public CurveMode cavityCurveMode = CurveMode.Curve;
|
|
|
|
#if UNITY_EDITOR
|
|
public bool heightCurveSheet = false;
|
|
public bool slopeCurveSheet = false;
|
|
public bool erosionCurveSheet = false;
|
|
public bool cavityCurveSheet = false;
|
|
#endif
|
|
|
|
public Layer Copy()
|
|
{
|
|
Layer l = new Layer();
|
|
l.weight = weight;
|
|
l.textureIndex = textureIndex;
|
|
l.noiseActive = noiseActive;
|
|
l.noiseFrequency = noiseFrequency;
|
|
l.noiseOffset = noiseOffset;
|
|
l.noiseRange = noiseRange;
|
|
l.biomeWeights = biomeWeights;
|
|
l.biomeWeights2 = biomeWeights2;
|
|
l.heightActive = heightActive;
|
|
l.slopeActive = slopeActive;
|
|
l.erosionMapActive = erosionMapActive;
|
|
l.cavityMapActive = cavityMapActive;
|
|
l.heightCurve = new AnimationCurve(heightCurve.keys);
|
|
l.slopeCurve = new AnimationCurve(slopeCurve.keys);
|
|
l.erosionMapCurve = new AnimationCurve(erosionMapCurve.keys);
|
|
l.cavityMapCurve = new AnimationCurve(cavityMapCurve.keys);
|
|
l.cavityMapFilter = cavityMapFilter;
|
|
l.heightFilter = heightFilter;
|
|
l.slopeFilter = slopeFilter;
|
|
l.erosionFilter = erosionFilter;
|
|
l.heightCurveMode = heightCurveMode;
|
|
l.slopeCurveMode = slopeCurveMode;
|
|
l.erosionCurveMode = erosionCurveMode;
|
|
l.cavityCurveMode = cavityCurveMode;
|
|
return l;
|
|
}
|
|
}
|
|
|
|
[System.Serializable]
|
|
public class HSVCurve
|
|
{
|
|
public AnimationCurve H = AnimationCurve.Linear(0, 0.5f, 1, 0.5f);
|
|
public AnimationCurve S = AnimationCurve.Linear(0, 0.0f, 1, 0.0f);
|
|
public AnimationCurve V = AnimationCurve.Linear(0, 0.0f, 1, 0.0f);
|
|
}
|
|
|
|
|
|
public List<Gradient> heightGradients = new List<Gradient>();
|
|
public List<HSVCurve> heightHSV = new List<HSVCurve>();
|
|
public List<Gradient> slopeGradients = new List<Gradient>();
|
|
public List<HSVCurve> slopeHSV = new List<HSVCurve>();
|
|
|
|
|
|
[HideInInspector]
|
|
public List<Layer> layers = new List<Layer>();
|
|
|
|
|
|
// cached textures, generated on demand when Syncing
|
|
[HideInInspector] public Texture2D curveTex;
|
|
[HideInInspector] public Texture2D paramTex;
|
|
[HideInInspector] public Texture2D heightGradientTex;
|
|
[HideInInspector] public Texture2D heightHSVTex;
|
|
[HideInInspector] public Texture2D slopeGradientTex;
|
|
[HideInInspector] public Texture2D slopeHSVTex;
|
|
|
|
public Texture2D FindSubAssetTexture(string name)
|
|
{
|
|
#if UNITY_EDITOR
|
|
var subs = UnityEditor.AssetDatabase.LoadAllAssetRepresentationsAtPath(UnityEditor.AssetDatabase.GetAssetPath(this));
|
|
Debug.Log(subs);
|
|
foreach (var s in subs)
|
|
{
|
|
if (s.name == name)
|
|
{
|
|
Texture2D t = (Texture2D)s;
|
|
if (t != null)
|
|
{
|
|
return t;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
return null;
|
|
}
|
|
|
|
// 32, 128 LUT for curves. R = Height, G = Slope
|
|
public Texture2D GetHeightGradientTexture(MicroSplatPropData propData)
|
|
{
|
|
int height = propData.maxTextures; // max layers
|
|
int width = 128;
|
|
if (heightGradientTex == null)
|
|
{
|
|
heightGradientTex = FindSubAssetTexture("heightGradientTex");
|
|
}
|
|
|
|
if (heightGradientTex == null)
|
|
{
|
|
heightGradientTex = new Texture2D(width, height, TextureFormat.RGBA32, false);
|
|
#if UNITY_EDITOR
|
|
heightGradientTex.name = "heightGradientTex";
|
|
UnityEditor.AssetDatabase.AddObjectToAsset(heightGradientTex, this);
|
|
UnityEditor.AssetDatabase.ImportAsset(UnityEditor.AssetDatabase.GetAssetPath(heightGradientTex));
|
|
#endif
|
|
}
|
|
|
|
Color grey = Color.grey;
|
|
for (int i = 0; i < heightGradients.Count; ++i)
|
|
{
|
|
for (int x = 0; x < width; ++x)
|
|
{
|
|
Color c = grey;
|
|
float v = (float)x / width;
|
|
c = heightGradients[i].Evaluate(v);
|
|
heightGradientTex.SetPixel(x, i, c);
|
|
}
|
|
}
|
|
for (int i = heightGradients.Count; i < propData.maxTextures; ++i)
|
|
{
|
|
for (int x = 0; x < width; ++x)
|
|
{
|
|
heightGradientTex.SetPixel(x, i, grey);
|
|
}
|
|
}
|
|
|
|
|
|
heightGradientTex.Apply(false, false);
|
|
return heightGradientTex;
|
|
}
|
|
|
|
|
|
// 32, 128 LUT for HSV curves
|
|
public Texture2D GetHeightHSVTexture(MicroSplatPropData propData)
|
|
{
|
|
int height = propData.maxTextures; // max layers
|
|
int width = 128;
|
|
if (heightHSVTex == null)
|
|
{
|
|
heightHSVTex = FindSubAssetTexture("heightHSVTex");
|
|
}
|
|
|
|
if (heightHSVTex == null)
|
|
{
|
|
heightHSVTex = new Texture2D(width, height, TextureFormat.RGBA32, false);
|
|
#if UNITY_EDITOR
|
|
heightHSVTex.name = "heightHSVTex";
|
|
UnityEditor.AssetDatabase.AddObjectToAsset(heightHSVTex, this);
|
|
UnityEditor.AssetDatabase.ImportAsset(UnityEditor.AssetDatabase.GetAssetPath(heightHSVTex));
|
|
#endif
|
|
}
|
|
|
|
Color grey = Color.grey;
|
|
for (int i = 0; i < heightHSV.Count; ++i)
|
|
{
|
|
for (int x = 0; x < width; ++x)
|
|
{
|
|
Color c = grey;
|
|
float v = (float)x / width;
|
|
c.r = heightHSV[i].H.Evaluate(v) * 0.5f + 0.5f;
|
|
c.g = heightHSV[i].S.Evaluate(v) * 0.5f + 0.5f;
|
|
c.b = heightHSV[i].V.Evaluate(v) * 0.5f + 0.5f;
|
|
heightHSVTex.SetPixel(x, i, c);
|
|
}
|
|
}
|
|
for (int i = heightHSV.Count; i < propData.maxTextures; ++i)
|
|
{
|
|
for (int x = 0; x < width; ++x)
|
|
{
|
|
heightHSVTex.SetPixel(x, i, grey);
|
|
}
|
|
}
|
|
|
|
|
|
heightHSVTex.Apply(false, false);
|
|
|
|
#if UNITY_EDITOR
|
|
UnityEditor.EditorUtility.SetDirty(heightHSVTex);
|
|
#endif
|
|
|
|
return heightHSVTex;
|
|
}
|
|
|
|
// 32, 128 LUT for curves. R = Height, G = Slope
|
|
public Texture2D GetSlopeGradientTexture(MicroSplatPropData propData)
|
|
{
|
|
int height = propData.maxTextures; // max layers
|
|
int width = 128;
|
|
|
|
if (slopeGradientTex == null)
|
|
{
|
|
slopeGradientTex = FindSubAssetTexture("slopeGradientTex");
|
|
}
|
|
|
|
if (slopeGradientTex == null)
|
|
{
|
|
slopeGradientTex = new Texture2D(width, height, TextureFormat.RGBA32, false);
|
|
#if UNITY_EDITOR
|
|
slopeGradientTex.name = "slopeGradientTex";
|
|
UnityEditor.AssetDatabase.AddObjectToAsset(slopeGradientTex, this);
|
|
UnityEditor.AssetDatabase.ImportAsset(UnityEditor.AssetDatabase.GetAssetPath(slopeGradientTex));
|
|
#endif
|
|
}
|
|
|
|
Color grey = Color.grey;
|
|
for (int i = 0; i < slopeGradients.Count; ++i)
|
|
{
|
|
for (int x = 0; x < width; ++x)
|
|
{
|
|
Color c = grey;
|
|
float v = (float)x / width;
|
|
c = slopeGradients[i].Evaluate(v);
|
|
slopeGradientTex.SetPixel(x, i, c);
|
|
}
|
|
}
|
|
for (int i = slopeGradients.Count; i < propData.maxTextures; ++i)
|
|
{
|
|
for (int x = 0; x < width; ++x)
|
|
{
|
|
slopeGradientTex.SetPixel(x, i, grey);
|
|
}
|
|
}
|
|
|
|
|
|
slopeGradientTex.Apply(false, false);
|
|
|
|
#if UNITY_EDITOR
|
|
UnityEditor.EditorUtility.SetDirty(slopeGradientTex);
|
|
#endif
|
|
return slopeGradientTex;
|
|
}
|
|
|
|
|
|
// 32, 128 LUT for HSV curves
|
|
public Texture2D GetSlopeHSVTexture(MicroSplatPropData propData)
|
|
{
|
|
int height = propData.maxTextures; // max layers
|
|
int width = 128;
|
|
|
|
if (slopeHSVTex == null)
|
|
{
|
|
slopeHSVTex = FindSubAssetTexture("slopeHSVTex");
|
|
}
|
|
|
|
if (slopeHSVTex == null)
|
|
{
|
|
slopeHSVTex = new Texture2D(width, height, TextureFormat.RGBA32, false);
|
|
#if UNITY_EDITOR
|
|
slopeHSVTex.name = "slopeHSVTex";
|
|
UnityEditor.AssetDatabase.AddObjectToAsset(slopeHSVTex, this);
|
|
UnityEditor.AssetDatabase.ImportAsset(UnityEditor.AssetDatabase.GetAssetPath(slopeHSVTex));
|
|
#endif
|
|
}
|
|
|
|
Color grey = Color.grey;
|
|
for (int i = 0; i < slopeHSV.Count; ++i)
|
|
{
|
|
for (int x = 0; x < width; ++x)
|
|
{
|
|
Color c = grey;
|
|
float v = (float)x / width;
|
|
c.r = slopeHSV[i].H.Evaluate(v) * 0.5f + 0.5f;
|
|
c.g = slopeHSV[i].S.Evaluate(v) * 0.5f + 0.5f;
|
|
c.b = slopeHSV[i].V.Evaluate(v) * 0.5f + 0.5f;
|
|
slopeHSVTex.SetPixel(x, i, c);
|
|
}
|
|
}
|
|
for (int i = slopeHSV.Count; i < propData.maxTextures; ++i)
|
|
{
|
|
for (int x = 0; x < width; ++x)
|
|
{
|
|
slopeHSVTex.SetPixel(x, i, grey);
|
|
}
|
|
}
|
|
|
|
#if UNITY_EDITOR
|
|
UnityEditor.EditorUtility.SetDirty(slopeHSVTex);
|
|
#endif
|
|
|
|
slopeHSVTex.Apply(false, false);
|
|
return slopeHSVTex;
|
|
}
|
|
|
|
float CompFilter(MicroSplatProceduralTextureConfig.Layer.Filter f, MicroSplatProceduralTextureConfig.Layer.CurveMode mode, float v)
|
|
{
|
|
float pos = Mathf.Abs(v - f.center) * (1.0f / Mathf.Max(f.width, 0.0001f));
|
|
pos = Mathf.Clamp01(Mathf.Pow(pos, f.contrast));
|
|
|
|
switch (mode)
|
|
{
|
|
case Layer.CurveMode.BoostFilter:
|
|
return 1.0f - pos;
|
|
case Layer.CurveMode.LowPass:
|
|
return v > f.center ? 1.0f - pos : 1;
|
|
case Layer.CurveMode.HighPass:
|
|
return v < f.center ? 1.0f - pos : 1;
|
|
case Layer.CurveMode.CutFilter:
|
|
return pos;
|
|
}
|
|
Debug.LogError("Unhandled case in ProceduralTextureConfig::CompFilter");
|
|
return 0;
|
|
}
|
|
|
|
// 32, tableSize LUT for curves. R = Height, G = Slope, B = Cavity, A = Erosion
|
|
public Texture2D GetCurveTexture(MicroSplatPropData propData)
|
|
{
|
|
int height = propData.maxTextures; // max layers
|
|
int width = (int)proceduralCurveTextureSize;
|
|
|
|
if (curveTex == null)
|
|
{
|
|
curveTex = FindSubAssetTexture("curveTex");
|
|
}
|
|
|
|
if (curveTex != null && curveTex.width != width)
|
|
{
|
|
DestroyImmediate(curveTex, true);
|
|
curveTex = null;
|
|
}
|
|
|
|
if (curveTex == null)
|
|
{
|
|
curveTex = new Texture2D(width, height, TextureFormat.RGBA32, false, true);
|
|
#if UNITY_EDITOR
|
|
curveTex.name = "curveTex";
|
|
UnityEditor.AssetDatabase.AddObjectToAsset(curveTex, this);
|
|
UnityEditor.AssetDatabase.ImportAsset(UnityEditor.AssetDatabase.GetAssetPath(curveTex));
|
|
#endif
|
|
}
|
|
|
|
|
|
Color white = Color.white;
|
|
for (int i = 0; i < layers.Count; ++i)
|
|
{
|
|
for (int x = 0; x < width; ++x)
|
|
{
|
|
Color c = white;
|
|
float v = (float)x / width;
|
|
if (layers[i].heightActive)
|
|
{
|
|
if (layers[i].heightCurveMode == Layer.CurveMode.Curve)
|
|
{
|
|
c.r = layers[i].heightCurve.Evaluate(v);
|
|
}
|
|
else
|
|
{
|
|
c.r = CompFilter(layers[i].heightFilter, layers[i].heightCurveMode, v);
|
|
}
|
|
}
|
|
if (layers[i].slopeActive)
|
|
{
|
|
if (layers[i].slopeCurveMode == Layer.CurveMode.Curve)
|
|
{
|
|
c.g = layers[i].slopeCurve.Evaluate(v);
|
|
}
|
|
else
|
|
{
|
|
c.g = CompFilter(layers[i].slopeFilter, layers[i].slopeCurveMode, v);
|
|
}
|
|
}
|
|
if (layers[i].cavityMapActive)
|
|
{
|
|
if (layers[i].cavityCurveMode == Layer.CurveMode.Curve)
|
|
{
|
|
c.b = layers[i].cavityMapCurve.Evaluate(v);
|
|
}
|
|
else
|
|
{
|
|
c.b = CompFilter(layers[i].cavityMapFilter, layers[i].cavityCurveMode, v);
|
|
}
|
|
}
|
|
if (layers[i].erosionMapActive)
|
|
{
|
|
if (layers[i].erosionCurveMode == Layer.CurveMode.Curve)
|
|
{
|
|
c.a = layers[i].erosionMapCurve.Evaluate(v);
|
|
}
|
|
else
|
|
{
|
|
c.a = CompFilter(layers[i].erosionFilter, layers[i].erosionCurveMode, v);
|
|
}
|
|
}
|
|
curveTex.SetPixel(x, i, c);
|
|
}
|
|
}
|
|
|
|
|
|
curveTex.Apply(false, false);
|
|
#if UNITY_EDITOR
|
|
UnityEditor.EditorUtility.SetDirty(curveTex);
|
|
#endif
|
|
return curveTex;
|
|
}
|
|
|
|
|
|
|
|
// 4x32 LUT, noise aprams in x0 (RGBA), weights in x1.r
|
|
public Texture2D GetParamTexture()
|
|
{
|
|
int height = 32; // max textures
|
|
int width = 4;
|
|
if (paramTex == null || paramTex.format != TextureFormat.RGBAHalf || paramTex.width != width)
|
|
{
|
|
paramTex = new Texture2D(width, height, TextureFormat.RGBAHalf, false, true);
|
|
#if UNITY_EDITOR
|
|
paramTex.name = "paramTex";
|
|
UnityEditor.AssetDatabase.AddObjectToAsset(paramTex, this);
|
|
UnityEditor.AssetDatabase.ImportAsset(UnityEditor.AssetDatabase.GetAssetPath(paramTex));
|
|
#endif
|
|
}
|
|
|
|
|
|
Color black = new Color(0, 0, 0, 0);
|
|
for (int i = 0; i < layers.Count; ++i)
|
|
{
|
|
Color c0 = black;
|
|
Color c1 = black;
|
|
if (layers[i].noiseActive)
|
|
{
|
|
c0.r = layers[i].noiseFrequency;
|
|
c0.g = layers[i].noiseRange.x;
|
|
c0.b = layers[i].noiseRange.y;
|
|
c0.a = layers[i].noiseOffset;
|
|
}
|
|
c1.r = layers[i].weight;
|
|
c1.g = layers[i].textureIndex;
|
|
paramTex.SetPixel(0, i, c0);
|
|
paramTex.SetPixel(1, i, c1);
|
|
Vector4 bw = layers[i].biomeWeights;
|
|
paramTex.SetPixel(2, i, new Color(bw.x, bw.y, bw.z, bw.w));
|
|
Vector4 bw2 = layers[i].biomeWeights2;
|
|
paramTex.SetPixel(3, i, new Color(bw2.x, bw2.y, bw2.z, bw2.w));
|
|
}
|
|
|
|
paramTex.Apply(false, false);
|
|
|
|
#if UNITY_EDITOR
|
|
UnityEditor.EditorUtility.SetDirty(paramTex);
|
|
#endif
|
|
return paramTex;
|
|
}
|
|
|
|
}
|
|
|
|
} |