I’m hoping to get some insight on a few problems on a niche topic.
I am attempting to generate Texture2D images of meshes on the fly during runtime. This is mainly for turning GameObjects into inventory items without having to explicitly implement each one on the dev side. I am having some success but have run into some strange issues I don’t know how to approach.
First, the in-editor result:
Decent results, however you can see the handaxe in the bottom-left has a weird mirroring issue (#1) despite it appearing to have the same setup as other items. Additionally, there are no shadows, which I probably must accept (#2).
This is the in-build result:
Only the outlines of the meshes are drawn (#3). If you look closely you can see there is indeed a tad bit of color (if that’s significant).
Questions (code below):
issue #1: How can this weird mirroring possibly happen? The matrices are the same per operation, and submeshes are each drawn once. I am perplexed.
issue #2: Can you ‘add’ light to CommandBuffer operations? Do I maybe have a setup ‘backwards’ (i.e. Light.AddCommandBuffer)
issue #3: Why is this build-specific? Is it depth-bound? Could it be Material-related (even though some color shows?)
If these were your issues, what would you poke at first? Maybe I’m simply trying to achieve this with the wrong approach. How would you implement this functionality? Or maybe I’m trying too hard to be lazy and should create each image by hand (ew please no).
Here is my code:
/**
* Create a Texture2D image of the given item to be used in the UI.
* Locks *just* in case of active RenderTexture contention.
*/
public static IEnumerator BakeInventoryImage(ContainableBody item, Action<Texture2D> callback)
{
Vector2 size = GetPositionalDeltas(item.gridSize.x, item.gridSize.y); // tile count
Vector2Int sizeInt = new Vector2Int(Mathf.RoundToInt(size.x), Mathf.RoundToInt(size.y)); // size in pixels
RenderTexture renderTexture = RenderTexture.GetTemporary(sizeInt.x, sizeInt.y, 0, RenderTextureFormat.ARGB32, RenderTextureReadWrite.Default, 8);
Debug.Assert(renderTexture.isReadable, instance.name + ": renderTexture.isReadable=false");
// Make target texture image
Texture2D image = new Texture2D(sizeInt.x, sizeInt.y, TextureFormat.ARGB32, false);
Debug.Assert(image.isReadable, instance.name + ": Texture2D.isReadable=false");
image.name = item.name + ".Baked";
image.wrapMode = TextureWrapMode.Clamp;
yield return new WaitForEndOfFrame();
DrawMesh(item, renderTexture);
lock (instance)
{
RenderTexture.active = renderTexture;
image.ReadPixels(new Rect(0, 0, renderTexture.width, renderTexture.height), 0, 0);
image.Apply();
// Restore main active render texture
RenderTexture.active = null;
}
RenderTexture.ReleaseTemporary(renderTexture);
callback(image);
}
/**
* Draw the given item onto the given renderTexture using a commandBuffer.
* Scale the mesh and handle rotation by using the renderer's transform.
*
* Note that a call to ExecuteCommandBuffer may(?) require waiting for end of frame beforehand.
*/
private static void DrawMesh(ContainableBody item, RenderTexture renderTexture)
{
Transform rendererTransform = item.renderer.transform;
Vector3 rendererScale = rendererTransform.lossyScale;
Mesh mesh = item.meshFilter.sharedMesh;
Bounds meshBounds = mesh.bounds;
List<Material> materials = new List<Material>();
item.renderer.GetSharedMaterials(materials);
float viewBoxRadius = 25f;
Vector3 pivotedCenterDiff = Vector3.Scale(rendererScale, meshBounds.center - (Vector3)((Vector2)(item.meshRotation * meshBounds.center)));
meshBounds.size = new Vector3(item.gridSize.x, item.gridSize.y) * MenuManager.ItemBorderScale / MenuManager.InventoryCellScale;
Matrix4x4 meshMatrix = Matrix4x4.TRS(pivotedCenterDiff, item.meshRotation, rendererScale);
Matrix4x4 lookMatrix = Matrix4x4.TRS(new Vector3(0, 0, -viewBoxRadius), Quaternion.identity, Vector3.one);
Matrix4x4 orthoMatrix = Matrix4x4.Ortho(meshBounds.min.x, meshBounds.max.x, meshBounds.min.y, meshBounds.max.y, -viewBoxRadius, 2f * viewBoxRadius);
// Create a CommandBuffer 'canvas' to draw the mesh onto
CommandBuffer commandBuffer = new CommandBuffer();
commandBuffer.name = item.name + " Item Drawer";
commandBuffer.SetRenderTarget(renderTexture);
commandBuffer.SetViewProjectionMatrices(lookMatrix, orthoMatrix);
commandBuffer.ClearRenderTarget(true, true, Color.Lerp(Color.clear, Color.white, .5f));
// draw each submesh
for (int i = 0; i < mesh.subMeshCount; i++)
{
commandBuffer.DrawMesh(mesh, meshMatrix, materials[i], i);
}
Graphics.ExecuteCommandBuffer(commandBuffer);
}