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

530 lines
20 KiB

#if UNITY_EDITOR //This tool won't appear in builds
using System.Collections.Generic;
using System.Collections;
using UnityEngine;
using UnityEngine.Rendering;
[ExecuteInEditMode]
[SelectionBaseAttribute]
/// <summary>
/// This component generates a procedural color checker.
/// </summary>
public class ColorCheckerTool : MonoBehaviour
{
/// <summary>
/// Enum of the color checker mode.
/// Colors : This procedural color checker can be used for color and lighting calibration. Color fields are customizable and persistent, with up to 64 values.
/// Grayscale : These values have been measured without specular lighting using a cross-polarized filter, making it more accurate for light calibration in PBR.
/// MiddleGray : This is the mid-gray value.
/// Reflection : Useful for checking local reflections.
/// Stepped Luminance : Stepped luminance is a good way to check gamma calibration.
/// Materials :Each row represents a material with varying smoothness. Material fields are customizable and persistent, with up to 12 values.
/// Texture : Useful for calibration using captured data. Use the slicer to compare lit values to unlit, raw values. Pre-exposure can be disabled.
/// /// </summary>
public enum ColorCheckerModes {[InspectorName("Color Palette")]Colors, [InspectorName("Cross Polarized Grayscale")]Grayscale, MiddleGray, Reflection, SteppedLuminance, [InspectorName("Material Palette")]Materials,[InspectorName("External Texture")] Texture };
/// <summary>
/// Current mode of the color checker.
/// </summary>
public ColorCheckerModes Mode = ColorCheckerModes.Colors;
/// <summary>
/// Add the gradient field to the color checker.
/// </summary>
public bool addGradient = false;
/// <summary>
/// Add unlit comparative value on the procedural color modes.
/// </summary>
public bool unlitCompare = false;
/// <summary>
/// Instantiates spheres for each fields
/// </summary>
public bool sphereMode = false;
/// related to UI
[SerializeField] int fieldCount = 24;
[SerializeField] int materialFieldsCount = 6;
[SerializeField]internal int fieldsPerRow = 6;
[SerializeField] float gridThickness = 0.1f;
[SerializeField] float fieldSize = 0.1f;
[SerializeField] float gradientPower = 2.2f;
//related to material and geometry update
int fieldsToDisplay;
int fieldsPerRowToDisplay;
float sizeToDisplay;
internal bool sphereModeToDisplay;
bool gradientToDisplay;
float gridToDisplay;
/// <summary>
/// First color used by the gradient field.
/// </summary>
/// <returns></returns>
public Color32 gradientA =new Color32(19, 20, 22, 255);
/// <summary>
/// Second color used by the gradient field.
/// </summary>
/// <returns></returns>
public Color32 gradientB = new Color32(233, 233, 227, 255);
[SerializeField] Texture2D colorCheckerTexture;
/// <summary>
/// Texture used for the "Texture" mode, lit.
/// </summary>
public Texture2D userTexture;
/// <summary>
/// Texture used for the "Texture" mode, unlit.
/// </summary>
public Texture2D userTextureRaw;
/// <summary>
/// Slice between the lit and unlit texture for the "Texture" mode.
/// </summary>
public float textureSlice;
/// <summary>
/// Toggle to have the unlit texture for the "Texture" mode adaptive to exposure or not. Use false if using raw EXR values.
/// </summary>
public bool unlitTextureExposure = true;
static readonly Color32[] colorPalette = //This is use to initialize and reset the color palette
{
//Color Checker BabelColor Avg
new Color32(245, 245, 240, 255), //White
new Color32(201, 202, 201, 255), //Neutral 8
new Color32(161, 162, 162, 255), //Neutral 6.5
new Color32(120, 121, 121, 255), //Neutral 5
new Color32(83, 85, 85, 255), //Neutral 3.5
new Color32(50, 50, 51, 255), //Black
new Color32(42, 63, 147, 255), //Blue
new Color32(72, 149, 72, 255), //Green
new Color32(175, 50, 57, 255), //Red
new Color32(238, 200, 22, 255), //Yellow
new Color32(188, 84, 150, 255), //Magenta
new Color32(0, 137, 166, 255), //Cyan
new Color32(220, 123, 46, 255), //Orange
new Color32(72, 92, 168, 255), //Purplish Blue
new Color32(194, 84, 97, 255), //Moderate Red
new Color32(91, 59, 104, 255), //Purple
new Color32(161, 189, 62, 255), //Yellow Green
new Color32(229, 161, 40, 255), //Orange Yellow
new Color32(115, 82, 68, 255), //Dark Skin
new Color32(194, 149, 128, 255), //Light Skin
new Color32(93, 123, 157, 255), //Blue Sky
new Color32(91, 108, 65, 255), //Foliage
new Color32(130, 129, 175, 255), //Blue Flower
new Color32(99, 191, 171, 255), //Bluish Green
//PBR values example
new Color32(50, 50, 50, 255), //Coal
new Color32(243, 243, 243, 255), //Snow
new Color32(85, 61, 49, 255), //Dark Soil
new Color32(135, 92, 60, 255), //Varnished Wood
new Color32(114, 103, 91, 255), //Tree Bark
new Color32(123, 130, 52, 255), //Green Vegetation
new Color32(148, 125, 117, 255), //Bricks
new Color32(135, 136, 131, 255), //Old Concrete
new Color32(163, 163, 163, 255), //Grey Painting
new Color32(177, 167, 132, 255), //Sand
new Color32(192, 191, 187, 255), //Clean Cement
new Color32(224, 199, 168, 255), //Rough Wood
//Harmonics Pastels
new Color32(204, 157, 178, 255),
new Color32(188, 120, 140, 255),
new Color32(123, 102, 157, 255),
new Color32(103, 133, 166, 255),
new Color32(137, 167, 197, 255),
new Color32(119, 159, 139, 255),
//Harmonics Primaries
new Color32(49, 98, 125, 255),
new Color32(66, 130, 85, 255),
new Color32(217, 156, 52, 255),
new Color32(200, 115, 76, 255),
new Color32(175, 54, 60, 255),
new Color32(180, 67, 124, 255),
//Harmonics Cold
new Color32(55, 79, 137, 255),
new Color32(40, 97, 140, 255),
new Color32(89, 128, 159, 255),
new Color32(136, 159, 107, 255),
new Color32(97, 142, 117, 255),
new Color32(41, 83, 87, 255),
//Harmonics Warm
new Color32(142, 51, 34, 255),
new Color32(200, 115, 76, 255),
new Color32(212, 135, 23, 255),
new Color32(164, 94, 114, 255),
new Color32(202, 121, 140, 255),
new Color32(96, 60, 94, 255),
//Last four
new Color32(233,233,227,255),
new Color32(147,147,146,255),
new Color32(55,58,58,255),
new Color32(19,20,22,255)
};
/// <summary>
/// Color Array used for the "Color Palette" Mode.
/// </summary>
/// <returns></returns>
public Color32[] customColors = colorPalette.Clone() as Color32[];
public Color32[] textureColors = colorPalette.Clone() as Color32[];
/// <summary>
/// Color Array used for the "Cross Polarized Grayscale" mode. Those value have been probed without specular lighting using a cross polarized filter.
/// </summary>
/// <value></value>
public Color32[] CrossPolarizedGrayscale =
{
new Color32(19,20,22,255),
new Color32(55,58,58,255),
new Color32(101,102,100,255),
new Color32(147,147,146,255),
new Color32(186,188,187,255),
new Color32(233,233,227,255)
};
/// <summary>
/// Color Array used for the "Middle Gray" mode.
/// </summary>
/// <returns></returns>
public Color32[] MiddleGray = {new Color32(120, 121, 121, 255)};
/// <summary>
/// Color Array used for the "Stepped Luminance" mode.
/// </summary>
public Color32[] steppedLuminance = new Color32[16];
static readonly Color32[] materialPalette = //This is use to initialize and reset the material palette, the Alpha value is used to say if it is a metal or not.
{
new Color32(237,237,237,0), //Snow
new Color32(39,39,39,0), //Charcoal
new Color32(193,190,187,255), //Iron
new Color32(247,221,188,255), //Copper
new Color32(251,249,246,255),//Silver
new Color32(249,228,164,255),//Gold
new Color32(175, 54, 60, 0), //Red
new Color32(177, 167, 132, 0), //Sand
new Color32(87, 108, 67, 0), //Foliage
new Color32(98, 122, 157, 0), //Blue Sky
new Color32(245,245,246,255),//Aluminium
new Color32(242,230,176,255),//Brass
};
/// <summary>
/// Color Array used for the "Material" mode. The color's alpha is used to know if it is a metal (255) or not (0).
/// </summary>
/// <returns></returns>
public Color32[] customMaterials = materialPalette.Clone() as Color32[];
public bool[] isMetalBools = {false,false,true,true,true,true,false,false,false,false,true,true};
//not used right now, can be useful for URP implementation later.
internal bool isHDRP;
//Users can customize colors and materials, we use booleans to save the texture colors in next update.
bool saveCustomColors = false;
bool saveCustomMaterials = false;
//Geometry is instanciated as child
[SerializeField] GameObject ColorCheckerObject;
Renderer ColorCheckerRenderer;
MeshFilter ColorCheckerFilter;
void Awake()
{
if (ColorCheckerObject == null)
{
ColorCheckerObject = new GameObject("Colorchecker Geometry");
ColorCheckerObject.transform.position = transform.position;
ColorCheckerObject.transform.rotation= transform.rotation;
ColorCheckerObject.transform.localScale = transform.localScale;
ColorCheckerObject.tag = "EditorOnly";
ColorCheckerObject.transform.parent = transform;
ColorCheckerRenderer = ColorCheckerObject.AddComponent<MeshRenderer>();
ColorCheckerFilter = ColorCheckerObject.AddComponent<MeshFilter>();
ColorCheckerFilter.hideFlags = HideFlags.NotEditable;
ColorCheckerRenderer.sharedMaterial = Resources.Load<Material>("ColorCheckerMaterial");
ColorCheckerRenderer.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off;
}
else
{
ColorCheckerRenderer = ColorCheckerObject.GetComponent<MeshRenderer>();
ColorCheckerFilter = ColorCheckerObject.GetComponent<MeshFilter>();
}
tag = "EditorOnly"; //This Tool should not be used in build.
GenerateTexture();
UpdateGeometry();
UpdateMaterial();
//SteppedLuminance array setup
for (int i = 0; i < 16; i++)
{
byte luminance = (byte)(17 * i);
steppedLuminance[i] = new Color32(luminance,luminance,luminance,255);
}
}
void OnValidate()
{
UpdateMaterial(); //to make sure every binded properties in UI builder update the material.
}
void Update()
{
if (saveCustomColors)
{
saveTextureColors(customColors);
saveCustomColors = false;
}
if (saveCustomMaterials)
{
saveTextureColors(customMaterials);
saveCustomMaterials = false;
}
}
void GenerateTexture()
{
colorCheckerTexture = new Texture2D(8, 8, TextureFormat.RGBA32, 0, false);
colorCheckerTexture.name = "ProceduralColorcheckerTexture";
colorCheckerTexture.filterMode = FilterMode.Point;
colorCheckerTexture.hideFlags = HideFlags.HideAndDontSave;
UpdateTexture(textureColors);
}
internal void UpdateMaterial()
{
fieldsToDisplay = fieldCount;
fieldsPerRowToDisplay = fieldsPerRow;
sizeToDisplay = fieldSize;
sphereModeToDisplay = sphereMode;
gradientToDisplay = addGradient;
gridToDisplay = gridThickness;
bool unlitToDisplay = unlitCompare;
if (colorCheckerTexture==null) GenerateTexture();
Texture2D textureToDisplay = colorCheckerTexture;
switch (Mode)
{
case ColorCheckerModes.Colors:
UpdateTexture(textureColors);
saveCustomColors = true;
break;
case ColorCheckerModes.Grayscale:
UpdateTexture(textureColors);
fieldsToDisplay = 6;
fieldsPerRowToDisplay = 6;
break;
case ColorCheckerModes.MiddleGray:
fieldsToDisplay = 1;
fieldsPerRowToDisplay = 1;
colorCheckerTexture.SetPixel(0, 0, MiddleGray[0]);
colorCheckerTexture.Apply();
sizeToDisplay *= 4f;
gradientToDisplay = false;
break;
case ColorCheckerModes.Reflection:
fieldsToDisplay = 1;
fieldsPerRowToDisplay = 1;
colorCheckerTexture.SetPixel(0, 0, Color.white);
colorCheckerTexture.Apply();
sizeToDisplay *= 4f;
sphereModeToDisplay = true;
gradientToDisplay = false;
break;
case ColorCheckerModes.SteppedLuminance:
fieldsToDisplay = 16;
fieldsPerRowToDisplay = 16;
gridToDisplay = 0f;
sphereModeToDisplay = false;
UpdateTexture(textureColors);
break;
case ColorCheckerModes.Materials:
UpdateTexture(textureColors);
saveCustomMaterials = true;
fieldsToDisplay = materialFieldsCount*6;
fieldsPerRowToDisplay = 6;
sphereModeToDisplay = true;
unlitToDisplay = false;
gradientToDisplay = false;
break;
case ColorCheckerModes.Texture:
fieldsToDisplay = 1;
fieldsPerRowToDisplay = 1;
sizeToDisplay *= 6f;
sphereModeToDisplay = false;
unlitToDisplay = true;
gradientToDisplay = false;
gridToDisplay = 0f;
if(userTexture !=null)
{
textureToDisplay = userTexture;
}
else
{
UpdateTexture(new Color32[0]); //goes to grey texture if nothing is applied
}
break;
}
//Update properties
var propertyBlock = new MaterialPropertyBlock();
if(ColorCheckerRenderer==null) return;
ColorCheckerRenderer.GetPropertyBlock(propertyBlock);
if (propertyBlock!=null)
{
propertyBlock.SetInt("_Compare_to_Unlit", unlitToDisplay ? 1 : 0);
propertyBlock.SetInt("_NumberOfFields", fieldsToDisplay);
propertyBlock.SetInt("_FieldsPerRow", fieldsPerRowToDisplay);
propertyBlock.SetFloat("_gridThickness", gridToDisplay * 0.5f);
propertyBlock.SetFloat("_SquareSize", sizeToDisplay);
propertyBlock.SetInt("_Add_Gradient", gradientToDisplay ? 1 : 0);
propertyBlock.SetColor("_Gradient_Color_A",gradientA);
propertyBlock.SetColor("_Gradient_Color_B",gradientB);
propertyBlock.SetFloat("_gradient_power", gradientPower);
propertyBlock.SetInt("_sphereMode",sphereModeToDisplay? 1 :0);
propertyBlock.SetInt("_material_mode", Mode == ColorCheckerModes.Materials? 1 :0 );
propertyBlock.SetTexture("_CheckerTexture", textureToDisplay);
propertyBlock.SetInt("_texture_mode", Mode == ColorCheckerModes.Texture? 1 :0 );
propertyBlock.SetInt("_reflection_mode", Mode == ColorCheckerModes.Reflection? 1 :0 );
if (userTextureRaw!=null){propertyBlock.SetTexture("_rawTexture", userTextureRaw);}
propertyBlock.SetInt("_rawTexturePreExposure",unlitTextureExposure? 1 :0);
propertyBlock.SetFloat("_textureSlice", textureSlice);
ColorCheckerRenderer.SetPropertyBlock(propertyBlock);
}
}
internal void UpdateGeometry()
{
CombineInstance[] combine = new CombineInstance[1];
Mesh colorcheckerMesh = new Mesh();
colorcheckerMesh.hideFlags = HideFlags.HideAndDontSave;
int numberOfRows = Mathf.CeilToInt((float)fieldsToDisplay/(float)fieldsPerRowToDisplay);
if(sphereModeToDisplay)
{
combine = new CombineInstance[gradientToDisplay?fieldsToDisplay+1:fieldsToDisplay];
for (int i=0;i<fieldsToDisplay;i++)
{
combine[i].mesh=Resources.GetBuiltinResource<Mesh>("Sphere.fbx");
float unit = sizeToDisplay;
float scale = Mathf.Lerp(1,0.01f,gridToDisplay)*sizeToDisplay*0.5f; //field margin resize the spheres.
int lastFullRow = fieldsToDisplay - (fieldsToDisplay-((numberOfRows-1)*fieldsPerRowToDisplay));
float posx = i%fieldsPerRowToDisplay*sizeToDisplay+sizeToDisplay*0.5f;
int fieldsModulo = fieldsToDisplay%fieldsPerRowToDisplay;
if (i+1 > lastFullRow && fieldsModulo!=0) //checks if last row is incomplete to better center the spheres
{
int spaces = fieldsModulo*2;
int missing = fieldsPerRow - fieldsModulo;
float spacing = (float)missing/spaces;
posx += sizeToDisplay*spacing+(i-lastFullRow)*sizeToDisplay*spacing*2;
}
float posy = (i/fieldsPerRowToDisplay)*unit+unit*0.5f;
Vector3 pos = new Vector3(posx,posy,0f);
combine[i].transform = Matrix4x4.TRS(pos, Quaternion.identity,new Vector3(scale,scale,scale));
}
if (gradientToDisplay) //if gradient is enabled, we instanciate it
{
combine[fieldsToDisplay].mesh=Resources.GetBuiltinResource<Mesh>("Cube.fbx");
Vector3 scale = new Vector3(sizeToDisplay * fieldsPerRowToDisplay, sizeToDisplay, 0.02f);
Vector3 pos = new Vector3(scale.x*0.5f,scale.y*0.5f-sizeToDisplay,0);
combine[fieldsToDisplay].transform = Matrix4x4.TRS(pos, Quaternion.identity,scale);
}
colorcheckerMesh.CombineMeshes(combine);
}
else
{
numberOfRows = gradientToDisplay ? numberOfRows + 1 : numberOfRows;
combine[0].mesh=Resources.GetBuiltinResource<Mesh>("Cube.fbx");
Vector3 scale = new Vector3(sizeToDisplay * fieldsPerRowToDisplay, sizeToDisplay*numberOfRows, 0.02f);
Vector3 pos = new Vector3(scale.x*0.5f,scale.y*0.5f,0);
pos.y -= gradientToDisplay ? sizeToDisplay : 0;
combine[0].transform = Matrix4x4.TRS(pos, Quaternion.identity,scale);
colorcheckerMesh.CombineMeshes(combine);
}
ColorCheckerFilter.mesh = colorcheckerMesh;
}
internal void UpdateTexture(Color32[] newColors)
{
//Updating the texture Colors Array
if (colorCheckerTexture != null)
{
for (int y = 0; y < 8; y++)
{
for (int x = 0; x < 8; x++)
{
int pixel = x + y * 8;
colorCheckerTexture.SetPixel(x, y, pixel<newColors.Length ? newColors[pixel]: Color.grey);
}
}
colorCheckerTexture.Apply();
}
}
internal void saveTextureColors(Color32[] modeColors)
{
for (int i=0; i<modeColors.Length;i++)
{
modeColors[i] = textureColors[i];
}
}
internal void ResetColors()
{
switch (Mode)
{
case ColorCheckerModes.Colors:
for (int i = 0; i < 64; i++)
{
textureColors[i] = colorPalette[i];
}
break;
case ColorCheckerModes.Materials:
for (int i = 0; i < 12; i++)
{
textureColors[i] = materialPalette[i];
isMetalBools[i] = materialPalette[i].a == (byte)255 ? true : false;
}
break;
}
}
private void CheckPipeline() //not used right now as it only landed in HDRP, we are waiting for the Exposure node support in URP.
{
var currentPipeline = RenderPipelineManager.currentPipeline;
if (currentPipeline!=null)
{isHDRP = RenderPipelineManager.currentPipeline.GetType().ToString().Contains("HighDefinition");}
}
void OnDestroy()
{
if(ColorCheckerObject != null) DestroyImmediate(ColorCheckerObject);
}
}
#endif