- Home /
Is it possible to create a colour gradient across an entire mesh if only a few vertices have colour?
I have been struggling to create a shader for my model of ~40,000 vertices. I have 144 vertices which I have assigned a colour to. (These 144 vertices represent real life data that I have obtained from sensors, so I can not increase the number of vertices initially coloured)
I would like to create a colour gradient between these vertices, so that the entire model is coloured, similar to a heat map.
I've tried using Lerp on the vertex matrix, but because the vertex numbers are seemingly random compared to their location, I'm not getting the interpolation between appropriate locations, rather neighbouring vertex numbers.
I have tried using something similar to this, but as I have so few vertices initially coloured, I just end up with a black model with colours spots: https://forum.unity.com/threads/standard-shader-with-vertex-colors.316529/
My question: is it possible to do it in this way? If not, is there another way? I can't seem to find one.
Answer by Harinezumi · Apr 09, 2018 at 03:09 PM
Interesting, I've been working on something scarily similar...
Anyway, I just iterate through all the vertices of the model (mine is about 9000 vertices), and interpolate using the distance from each of my colored points. I use a Gradient object and a float[] heatValues (one value for each vertex) that stores the current "heat" value, then evaluate the gradient for each vertex, and finally assign the vertex colors.
It is faster than expected, there's no FPS drop even if I update every frame.
Does this make sense?
That definitely makes me feel less crazy attempting this! Your description makes sense, just in the process of trying to write the code to do it - thank you :)
For some reason, I can't get it to work. I end up with a fuzzy one coloured object. I've tried debugging and I think the logic is correct, but something (obviously) isn't right...
Here is the method I've written for the colour interpolation, I've tried to base it on your advice, but I'm not sure where I'm going wrong!
private void ColourInterpolation()
{
List<$$anonymous$$eyValuePair<float, Vector3>> distances = new List<$$anonymous$$eyValuePair<float, Vector3>>();
for (int i = 0; i < _vertices.Length; i++)
{
var skip = false;
foreach (var sensor in _sensorLocations)
{
var localVertex = transform.TransformPoint(_vertices[i]);
if (sensor.Value.x == $$anonymous$$ath.Round(localVertex.x, 1) &&
sensor.Value.y == $$anonymous$$ath.Round(localVertex.y, 1) &&
sensor.Value.z == $$anonymous$$ath.Round(localVertex.z, 1))
{
skip = true;
break;
}
}
if (skip)
{
skip = false;
continue;
}
$$anonymous$$eyValuePair<int, Vector3> $$anonymous$$1 = new $$anonymous$$eyValuePair<int, Vector3>();
$$anonymous$$eyValuePair<int, Vector3> $$anonymous$$2 = new $$anonymous$$eyValuePair<int, Vector3>();
float $$anonymous$$1Distance = float.$$anonymous$$axValue;
float $$anonymous$$2Distance = float.$$anonymous$$axValue;
foreach ($$anonymous$$eyValuePair<int, Vector3> sensor in _sensorLocations)
{
float distance = Vector3.Distance(_vertices[i], sensor.Value);
if (distance < $$anonymous$$1Distance)
{
$$anonymous$$2Distance = $$anonymous$$1Distance;
$$anonymous$$1Distance = distance;
$$anonymous$$2 = $$anonymous$$1;
$$anonymous$$1 = sensor;
}
else if (distance < $$anonymous$$2Distance)
{
$$anonymous$$2 = sensor;
$$anonymous$$2Distance = distance;
}
}
float proportion = $$anonymous$$ath.Abs($$anonymous$$1Distance / ($$anonymous$$1Distance + $$anonymous$$2Distance)); // Gives proprtion towards $$anonymous$$1
_colors[i] = Color32.Lerp(_colors[$$anonymous$$1.$$anonymous$$ey], _colors[$$anonymous$$2.$$anonymous$$ey], proportion);
}
Your algorithm seems good, but I think you need the 3 closest sensors to get a uniformly interpolated color in 3D, and you might need to do interpolate using barycentric coordinates, see this article.
I think you can also simplify the check if the vertex is one of the sensors like this:
// this works if _sensorLocations is a dictionary of vertex index to sensor vertex position
if (_sensorLocations.Contains$$anonymous$$ey(i)) { continue; }
Something still isn't right with my interpolation...I'm now getting multiple colours, but I'm getting a massive mess...think an explosion in a paint factory! I've adapted the barycentric equations to incorporate 3D rather than just 2D, but the results seem to be the same.
Is there something obvious I'm doing wrong?
$$anonymous$$eyValuePair<int, Vector3> $$anonymous$$1 = new $$anonymous$$eyValuePair<int, Vector3>();
$$anonymous$$eyValuePair<int, Vector3> $$anonymous$$2 = new $$anonymous$$eyValuePair<int, Vector3>();
$$anonymous$$eyValuePair<int, Vector3> $$anonymous$$3 = new $$anonymous$$eyValuePair<int, Vector3>();
float $$anonymous$$1Distance = float.$$anonymous$$axValue;
float $$anonymous$$2Distance = float.$$anonymous$$axValue;
float $$anonymous$$3Distance = float.$$anonymous$$axValue;
foreach ($$anonymous$$eyValuePair<int, Vector3> sensor in _sensorLocations)
{
float distance = Vector3.Distance(_vertices[i], sensor.Value);
if (distance < $$anonymous$$1Distance)
{
$$anonymous$$3Distance = $$anonymous$$2Distance;
$$anonymous$$2Distance = $$anonymous$$1Distance;
$$anonymous$$1Distance = distance;
$$anonymous$$3 = $$anonymous$$2;
$$anonymous$$2 = $$anonymous$$1;
$$anonymous$$1 = sensor;
}
else if (distance < $$anonymous$$2Distance)
{
$$anonymous$$3 = $$anonymous$$2;
$$anonymous$$2 = sensor;
$$anonymous$$3Distance = $$anonymous$$2Distance;
$$anonymous$$2Distance = distance;
}
else if (distance < $$anonymous$$3Distance)
{
$$anonymous$$3 = sensor;
$$anonymous$$3Distance = distance;
}
}
float Xv1 = $$anonymous$$1.Value.x;
float Xv2 = $$anonymous$$2.Value.x;
float Xv3 = $$anonymous$$3.Value.x;
float Yv1 = $$anonymous$$1.Value.y;
float Yv2 = $$anonymous$$2.Value.y;
float Yv3 = $$anonymous$$3.Value.y;
float Zv1 = $$anonymous$$1.Value.z;
float Zv2 = $$anonymous$$2.Value.z;
float Zv3 = $$anonymous$$3.Value.z;
float Px = _vertices[i].x;
float Py = _vertices[i].y;
float Pz = _vertices[i].z;
float Wv1 = ((Yv2 - Yv3) * (Px - Xv3) + (Xv3 - Xv2) * (Py - Yv3)) / ((Yv2 - Yv3) * (Xv1 - Xv3) + (Xv3 - Xv2) * (Yv1 - Yv3));
float Wv2 = ((Yv3 - Yv1) * (Px - Xv3) + (Xv1 - Xv3) * (Py - Yv3)) / ((Yv2 - Yv3) * (Xv1 - Xv3) + (Xv3 - Xv2) * (Yv1 - Yv3));
float Wv3 = (Pz - Wv1*Zv1 - Wv2*Zv2)/Zv3;
Byte r = (Byte)((_colors[$$anonymous$$1.$$anonymous$$ey].r * Wv1 + _colors[$$anonymous$$2.$$anonymous$$ey].r * Wv2 + _colors[$$anonymous$$3.$$anonymous$$ey].r * Wv3) / 3);
Byte g = (Byte)((_colors[$$anonymous$$1.$$anonymous$$ey].g * Wv1 + _colors[$$anonymous$$2.$$anonymous$$ey].g * Wv2 + _colors[$$anonymous$$3.$$anonymous$$ey].g * Wv3) / 3);
Byte b = (Byte)((_colors[$$anonymous$$1.$$anonymous$$ey].b * Wv1 + _colors[$$anonymous$$2.$$anonymous$$ey].b * Wv2 + _colors[$$anonymous$$3.$$anonymous$$ey].b * Wv3) / 3);
_colors[i] = new Color32(r, g, b, 255);
Your answer