/// <summary> /// Calculate the tristimulus values for the given combination of light source, material and observer. /// </summary> /// <returns>A float array of size 3 containing X, Y and Z in each cell respectively.</returns> public static Vector3 CalculateTristimulusValues(LightSource lightSource, Material material, Observer observer, bool clipInvisible = false) { // TODO: This may not be the most efficient of all approaches, make sure you change this to be more streamlined. // Normalize spectra. // List<SpectralData> spectraBank = new List<SpectralData>(); spectraBank.Add(lightSource.SpectralPowerDistribution); spectraBank.Add(material.ReflectanceDistribution); spectraBank.Add(observer.ResponseSpectra[0]); spectraBank.Add(observer.ResponseSpectra[1]); spectraBank.Add(observer.ResponseSpectra[2]); Utilities.NormalizeSpectra(spectraBank); // Calculate the normalizing constant for tristimulus integration // float K = Utilities.TristimulusNormalizingConstant(lightSource, observer); float summation; int stepSize = lightSource.SpectralPowerDistribution.StepSize; // The wave data we need is nested deep inside objects. So grab it into local arrays // for convenience of coding. // float[] lightSourceData, materialData, observerXData, observerYData, observerZData; if (clipInvisible) { // Find out what indexes correspond to wavelengths between 380 and 780 nm. // Since the data is normalized, calculating based on 1 source should be enough. int startIndex = (380 - lightSource.SpectralPowerDistribution.LowestWavelength) / lightSource.SpectralPowerDistribution.StepSize; int count = (780 - 380) / lightSource.SpectralPowerDistribution.StepSize + 1; // Sanity check if (startIndex < 0) { throw new ArgumentException("wavelength data provided started after 380 nm"); } lightSourceData = lightSource.SpectralPowerDistribution.WaveData.GetRange(startIndex, count).ToArray(); materialData = material.ReflectanceDistribution.WaveData.GetRange(startIndex, count).ToArray(); observerXData = observer.ResponseSpectra[0].WaveData.GetRange(startIndex, count).ToArray(); observerYData = observer.ResponseSpectra[1].WaveData.GetRange(startIndex, count).ToArray(); observerZData = observer.ResponseSpectra[2].WaveData.GetRange(startIndex, count).ToArray(); } else { lightSourceData = lightSource.SpectralPowerDistribution.WaveData.ToArray(); materialData = material.ReflectanceDistribution.WaveData.ToArray(); observerXData = observer.ResponseSpectra[0].WaveData.ToArray(); observerYData = observer.ResponseSpectra[1].WaveData.ToArray(); observerZData = observer.ResponseSpectra[2].WaveData.ToArray(); } // Calculate the L*M product array. This reduces the repeated multiplication operations that would otherwise be // required. // float[] lmProductArray = Utilities.ComputeLMProductArray(lightSourceData, materialData); Vector3 tristimulusValues = new Vector3(); // Calculate X // summation = Utilities.ComputeSummationTerm(lmProductArray, observerXData); tristimulusValues.X = K * summation * (float)stepSize; // Calculate Y // summation = Utilities.ComputeSummationTerm(lmProductArray, observerYData); tristimulusValues.Y = K * summation * (float)stepSize; // Calculate Z // summation = Utilities.ComputeSummationTerm(lmProductArray, observerZData); tristimulusValues.Z = K * summation * (float)stepSize; return tristimulusValues; }
private static Material RefineMetamer(LightSource referenceLight, Material referenceMaterial, Observer referenceObserver, Material oreMaterial, float nudgeAmount, float marginOfError) { int xPole = (520 - 380) / 10; int yPole = (620 - 380) / 10; int zPole = (670 - 380) / 10; Vector3 referenceXYZ = CalculateTristimulusValues(referenceLight, referenceMaterial, referenceObserver, true); bool refiningComplete = false; long j = 0; while (!refiningComplete) { j++; // Calculate deltas Vector3 xyz = CalculateTristimulusValues(referenceLight, oreMaterial, referenceObserver); Vector3 delta = referenceXYZ - xyz; // check if good enough if (delta.Length() < marginOfError) { refiningComplete = true; break; } for (int i = 0; i < oreMaterial.ReflectanceDistribution.WaveData.Count; i++) { float xadjustment = (delta.X * nudgeAmount) / Math.Max(Math.Abs(xPole - i), 1); float yadjustment = (delta.Y * nudgeAmount) / Math.Max(Math.Abs(yPole - i), 1); float zadjustment = (delta.Z * nudgeAmount) / Math.Max(Math.Abs(zPole - i), 1); float newValue = oreMaterial.ReflectanceDistribution.WaveData[i] + xadjustment + yadjustment + zadjustment; oreMaterial.ReflectanceDistribution.WaveData[i] = Math.Max(newValue, 0); } } return oreMaterial; }
private static Material BruteForceMetamericMaterial(LightSource referenceLight, Material referenceMaterial, Observer referenceObserver, float marginOfError) { Vector3 referenceXYZ = CalculateTristimulusValues(referenceLight, referenceMaterial, referenceObserver, true); // calculate the metameric material Material metamericMaterial = new Material(); bool metamerFound = false; int i = 0; while(!metamerFound) { metamericMaterial.Name = "Autogenerated material"; metamericMaterial.ReflectanceDistribution = SpectralData.CreateRandomSpectrum(380, 780, 10, null); Vector3 xyz = CalculateTristimulusValues(referenceLight, metamericMaterial, referenceObserver); float deltaX = Math.Abs(referenceXYZ.X - xyz.X); float deltaY = Math.Abs(referenceXYZ.Y - xyz.Y); float deltaZ = Math.Abs(referenceXYZ.Z - xyz.Z); if (deltaX < marginOfError && deltaY < marginOfError && deltaZ < marginOfError) { break; } i++; } Console.WriteLine("iterations: " + i); return metamericMaterial; }
/// <summary> /// Calculates the Tristimulus normalizing constant using the formula /// K = 100.0 / (total * stepSize) /// Where total is the sum of products of lightsource power and observers Y channel response /// </summary> /// <param name="lightSource">Light source</param> /// <param name="observer">The observer</param> /// <returns>The calculated tristimulus constant</returns> public static float TristimulusNormalizingConstant(LightSource lightSource, Observer observer) { int stepSize = lightSource.SpectralPowerDistribution.StepSize; int start = lightSource.SpectralPowerDistribution.LowestWavelength; int end = lightSource.SpectralPowerDistribution.HighestWavelength; int i, index; float[] observerYData = observer.ResponseSpectra[1].WaveData.ToArray(); float[] lightSourceData = lightSource.SpectralPowerDistribution.WaveData.ToArray(); float total = (float)0.0; for (i=start, index=0; i <= end; i += stepSize, index++) { total += lightSourceData[index] * observerYData[index]; } return (float)100.0 / (total * (float)stepSize); }
public static Material CreateMetamericMaterialSmart(LightSource referenceLight, Material referenceMaterial, Observer referenceObserver) { Stopwatch stopwatch = new Stopwatch(); // Start measuring time stopwatch.Start(); // 1: Calculate the reference XYZ values. Vector3 referenceXYZ = CalculateTristimulusValues(referenceLight, referenceMaterial, referenceObserver); // 2: Calculate a rough metameric material using pure brute force. Material metamericMaterial = BruteForceMetamericMaterial(referenceLight, referenceMaterial, referenceObserver, 10.0f); //// 3: Now we refine the material so that the metameric match is good. //metamericMaterial = RefineMetamer(referenceLight, referenceMaterial, referenceObserver, // metamericMaterial, 0.001f, 3); //// 4: Now we refine the material so that the metameric match is very good. //metamericMaterial = RefineMetamer(referenceLight, referenceMaterial, referenceObserver, // metamericMaterial, 0.001f, 2); // 5: Now we refine the material so that the metameric match is very good. //metamericMaterial = RefineMetamer(referenceLight, referenceMaterial, referenceObserver, // metamericMaterial, 0.001f, 3); // Stop measuring time stopwatch.Stop(); Console.WriteLine("Time spent was " + stopwatch.Elapsed.Milliseconds + " milliseconds"); Console.WriteLine("Metamer found!"); Console.WriteLine(metamericMaterial.Name); foreach (float sample in metamericMaterial.ReflectanceDistribution.WaveData) { Console.WriteLine("value: " + sample); } return metamericMaterial; }
/// <summary> /// Read in all the observers from the given file. /// </summary> /// <param name="fileName">Path to the data file.</param> protected void ReadInObservers(string fileName) { XmlDocument observersXML = new XmlDocument(); observersXML.Load(fileName); foreach (XmlElement observerXML in observersXML.GetElementsByTagName(XMLDataConstants.Observer)) { Observer observer = new Observer(); observer.Initialize(observerXML); this.Observers.Add(observer); } }