How to apply a RenderTexture onto another texture?

I’m trying to write text on the clothes of my character.

One way suggested on this forum is to write the text onto a RenderTexture and apply it to my character’s texture.

How do I apply the RenderTexture onto another texture?

I tried to add the RenderTexture as a second material to my character, which ended up in way too many draw calls. So there are probably better ways.

That’s a pretty spot-on suggestion.

It’s also a task which takes a lot of specific research, so I’m happy to explain.

To apply the RenderTexture to the base Texture2D, you need a way of generating the RenderTexture.

In order to do this, you can utilize an Orthographic camera and use it to take a snapshot of a TextMesh. To make that happen, you’ll need the following:

•An Orthographic camera

•A TextMesh

•A GUIStyle for the TextMesh

•A GUIContent for the GUIStyle

With those assembled, you can now adjust a few settings for these components:

By setting the camera’s orthographic width/height to 1 each, you can treat the coordinates of its contents as UV coordinates, to place the text exactly where you’ll want it to be on the texture. Additionally, set its position to (0.5, 0.5, -1) to center its view looking at a 0-1 range. And let’s throw in a few important settings for later:

texCamera.clearFlags = CameraClearFlags.SolidColor;
texCamera.backgroundColor = Color.clear;

The TextMesh will be your primary means of writing out the text. This should be on its own rendering layer which your main camera won’t see, while the orthographic camera should only be able to look at this layer.

The GUIStyle should copy all the settings of your TextMesh. For example, it should have the same font, fontSize, fontStyle… everything. The GUIContent is simply there to hold matching text for the GUIStyle to use. Then, its text alignment will need a few tweaks added in as well:

textMesh.alignment = TextAlignment.Center;
textMesh.anchor = TextAnchor.MiddleCenter;

The GUIStyle isn't part of the TextMesh, so why do we have it?

The GUIStyle gives us access to a handy function: GUIStyle.CalcSize() - This function will return the approximate dimensions of your text with the font, style, and size assigned to it. Bear in mind, however, that a TextMesh is generated at 1/10th size, so you’ll want to multiply the result of CalcSize() by 0.1 to get a proper result.

Okay, now we’ve got a starting point. Once you’ve generated your TextMesh and used the GUIStyle/GUIContent to learn the text size, scale the TextMesh down and position/rotate it as needed:

float shirtUVSpace = MINIMUM_USABLE_UV_SPACE_ON_TARGET_TEXTURE;
Vector2 textMeshSize = guiStyle.CalcSize(guiContent);
float textScale = shirtUVSpace / Mathf.Max(textMeshSize.x, textMeshSize.y)
textMesh.transform.localScale = new Vector3(textScale, textScale, 1.0f);
textMesh.transform.position = CENTER_OF_SHIRT_UV; // 0.5, 0.5 if centered in general
textMesh.transform.rotation = ROTATION_OF_SHIRT_UV; // as necessary

At this point, you now have your contents created, positioned, and scaled, and are ready to actually utilize a RenderTexture. (Yep, it's finally time to address the concept in your question)

Now, you’ll need to render the camera (which, at this point, can be on a disabled GameObject and will still function properly, while not automatically wanting to render during every frame) to a RenderTexture, then grab the pixels from the RenderTexture to apply to a regular Texture2D. For the most part, this is pretty much standard fare for using a RenderTexture, so here’s an example of implementation:

// First, a few variables you'll want
Material mat; // This will include a shader mentioned later
Camera orthoCam;
// ...
mat = new Material(Shader.Find("Hidden/TextureStamp"));

mat.SetTexture("_TShirtTex", tShirtTex);

RenderTexture rtSource = RenderTexture.GetTemporary(tShirtTex.width, tShirtTex.height, 0);
RenderTexture rtDestination = RenderTexture.GetTemporary(tShirtTex.width, tShirtTex.height, 0);

RenderTexture currentRT = RenderTexture.active;
orthoCam.targetTexture = rtSource;
RenderTexture.active = rtSource;
orthoCam.Render();
Graphics.Blit(rtSource, rtDestination, mat);
tex.ReadPixels(new Rect(0, 0, tShirtTex.width, tShirtTex.height), 0, 0);
RenderTexture.active = currentRT;
orthoCam.targetTexture = null;

RenderTexture.ReleaseTemporary(rtSource);
RenderTexture.ReleaseTemporary(rtDestination);

There are certainly ways to improve efficiency during this, but to start, let’s just make sure this all works…

Now almost everything is ready, but that Material needs a shader. The Shader is there to take the original T-Shirt Texture and blend it back in with the text stamped on top of it. By having the camera’s background clear to, well, “clear” we’ve defined a background behind the text as having zero opacity, so the blending is a single, quick pass from the shader.

Shader "Hidden/TextureStamp"
{
	Properties
	{
		_MainTex ("New Texture", 2D) = "white" {} // The new texture
		_TShirtTex ("T-Shirt Texture", 2D) = "white" {} // The t-shirt texture to stamp the new one onto
	}
	SubShader
	{
		// No culling or depth
		Cull Off ZWrite Off ZTest Always

		Pass
		{
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			
			#include "UnityCG.cginc"

			struct appdata
			{
				float4 vertex : POSITION;
				float2 uv : TEXCOORD0;
			};

			struct v2f
			{
				float2 uv : TEXCOORD0;
				float4 vertex : SV_POSITION;
			};

			v2f vert (appdata v)
			{
				v2f o;
				o.vertex = UnityObjectToClipPos(v.vertex);
				o.uv = v.uv;
				return o;
			}
			
			sampler2D _MainTex;
			sampler2D _TShirtTex;

			fixed4 frag (v2f i) : SV_Target
			{
				fixed4 newTex = tex2D(_MainTex, i.uv);
				fixed4 tShirtTex = tex2D(_TShirtTex, i.uv);
				
				tShirtTex = lerp(oldTex, newTex, newTex.a);

				return tShirtTex;
			}
			ENDCG
		}
	}
}

(Disclaimer: I had to edit a lot of this to assemble it all in a relevant manner, so I apologize in advance for any mistakes or general typos)