- Home /
'Planet' height based, Texture2D setPixel performance queries.
Hello,
So I've been working on generating random meshes for planets, and then coloring those meshes based on the height of the UV map at that point. The mesh generation is relatively quick, less than a second for 40 segments down and 80 segments across. But when it comes to setting pixel colours, even a small texture size of 126x126 takes around 5 seconds to complete.
Currently my UV map stretches across the entire planet, after the mesh is fully generated I run another script with moves across the UV in a for loop using x and y values, proportionately to the texture size.
So with a texture of 126x126 the script would do a total of 15876 calculations, for each square in the texture. In each calculation, the real world position of the UV map at that point is grabbed, the distance to the center of the planet calculated and then a corresponding color is assigned to that point in the texture.
I'm wondering if anyone knows how to rapidly speed this process up, it's my first time doing it so I feel like I'm being really inefficient somewhere. I want this to be part of a game and I don't want users to have to wait more than 10 seconds for a new scene to load. I would much prefer a larger texture size as well. At the moment, 126 isn't really cutting it, as you can see.
Incoming wall of code:
var radius :float = 10.0;
var perlinRange :float = 0.05;
var segments :int =20;
private var Planet : GameObject;
Planet = transform.parent.gameObject;
private var textureMaxHeight = (radius*1.0);
private var textureMinHeight = (radius*(1.0-perlinRange));
private var defaultMaterial = Resources.Load("Materials/Generation/Terrain/PlanetMaterial") as Material;
private var newMaterial :Material = new Material(Shader.Find("Diffuse"));
newMaterial.color = defaultMaterial.color;
private var colorDefault = newMaterial.color;
private var PlanetTexture :Texture2D = new Texture2D(126, 126);
private var worldPosition:Vector3;
private var heightVariance:float;
private var colorOutput:Color;
private var mesh: Mesh;
private var tris: int[];
private var uvs: Vector2[];
private var verts: Vector3[];
function Start(){
mesh = GetComponent(MeshFilter).mesh;
tris = mesh.triangles;
uvs = mesh.uv;
verts = mesh.vertices;
for (var yC : int = 0; yC < PlanetTexture.height; ++yC){
for (var xC : int = 0; xC < PlanetTexture.width; ++xC){
worldPosition = UvTo3D(Vector2((xC*1.0)/PlanetTexture.width,(yC*1.0)/PlanetTexture.height));
heightVariance = (Vector3.Distance(worldPosition,Planet.transform.position)-textureMinHeight)/
(textureMaxHeight-textureMinHeight);
colorOutput = colorDefault;
if( heightVariance < 0.75){
colorOutput.r = 0.2;
colorOutput.g = 0.2;
colorOutput.b = 0.6;
}else if( heightVariance > 0.8){
colorOutput.r = 0.0;
colorOutput.g = 0.4;
colorOutput.b = 0.0;
}
colorOutput.r += heightVariance/2;
colorOutput.g += heightVariance/2;
colorOutput.b += heightVariance/2;
PlanetTexture.SetPixel (xC, yC, colorOutput);
}
}
PlanetTexture.Apply();
renderer.material = newMaterial;
renderer.material.mainTexture = PlanetTexture;
renderer.material.mainTexture.wrapMode = TextureWrapMode.Clamp;
}
function UvTo3D(checkUv: Vector2): Vector3 {
for (var i: int = 0; i < tris.length; i += 3){
var u1: Vector2 = uvs[tris[i]]; // get the triangle UVs
var u2: Vector2 = uvs[tris[i+1]];
var u3: Vector2 = uvs[tris[i+2]];
// calculate triangle area - if zero, skip it
var a: float = Area(u1, u2, u3); if (a == 0) continue;
// calculate barycentric coordinates of u1, u2 and u3
// if anyone is negative, point is outside the triangle: skip it
var a1: float = Area(u2, u3, checkUv)/a; if (a1 < 0) continue;
var a2: float = Area(u3, u1, checkUv)/a; if (a2 < 0) continue;
var a3: float = Area(u1, u2, checkUv)/a; if (a3 < 0) continue;
// point inside the triangle - find mesh position by interpolation...
var p3D: Vector3 = a1*verts[tris[i]]+a2*verts[tris[i+1]]+a3*verts[tris[i+2]];
// and return it in world coordinates:
return transform.TransformPoint(p3D);
}
// point outside any uv triangle: return Vector3.zero
return Vector3.zero;
}
// calculate signed triangle area using a kind of "2D cross product":
function Area(p1: Vector2, p2: Vector2, p3: Vector2): float {
var v1: Vector2 = p1 - p3;
var v2: Vector2 = p2 - p3;
return (v1.x * v2.y - v1.y * v2.x)/2;
}
Are you accessing mesh.anything every cycle? $$anonymous$$esh.anything that returns an array is actually copying the array before giving it to you, so you need to cache that, use your cache, then reassign it.
Assu$$anonymous$$g that's not it, what's your coloring code look like?
Well actually my UvTo3D function does grab the mesh every time. I'll throw that in the start function, brb.
Ok, so accessing mesh only once. Not really much improvement in the speed. I'll show my code, editing it into the bottom of the main post.
Answer by whydoidoit · Apr 24, 2013 at 05:11 PM
The fastest way would be to allocate a Color32[] the width*height of the texture, set those values using (y * width) + x (or even better, just an incrementing index), then use SetPixels32 to assign the array just before you Apply.
That said that's a huge loop over the triangles for each point. Need to think about how to stop that.
Yep it's probably the 24m barycentric calculations (on average) that cause the slow down...
How about calculating the centroid of each triangle and doing a nearest triangle calculation.
You could do something like this; turning the problem on its head:
Iterate through the triangles
For each triangle calculate the UV point that falls within it (closest to its centroid), fill in a 2d array with this information
Iterate through the UVs
Lookup the triangle that is closest from the array
Do the barycentric calculation and work out the height for the current point.
I haven't encountered much use of the Color32 system prior. The function would create an array of colors in a better performance format? And I would have to choose a color from that array based on the height I receive?
You could make a perfect version by having that array be a list of all the triangles that are near the uv - so calculate the uv of each point in the triangle and add it to the list at the location. Then you only have a few triangles to test for a particular UV. Better still you might be able to do that calculation on the first pass with just a replacement if you find a better candidate triangle.
How would that loop perform any quicker? The height changes over each triangle and I need to calculate that height to assign a color. If I just get the height of the center of the triangle, then the entire triangle would be the same color.
I don't think I'm understanding what your trying to get at.