问题
I was looking for this answer on the web for some time. But apparently my search phrases were wrong or it is really that hard. But I doubt it.
Imagine, I have any 3d-body. A sphere, cylinder or cube.
I want to put a text onto that object. As if it was a sticker or painted onto that object. Meaning, it would follow the objects curves or edges, if wrapping around them. I managed to create some texts which are ALWAYS in front or behind my object. But that is not what I want.
The closest to what I want looks like this:
I cannot believe, that I need to code for this. It must be achievable via child/parents, canvas and text, JUST using the inspector and components.
Is this so ?
回答1:
Not that simple but here is how you can achieve something similar. This requires your 3D models to be correctly UV mapped so you can simply apply a flat texture to it. The setup may sound complex but it is really cool ;)
Create a RenderTexture: In the
Assetdo right mouse click →Create→RenderTextureand call it however you like e.g.TextTexture- To have a better resolution later increase it's
sizeto e.g.2048*2048depedning on your needs of course.
- To have a better resolution later increase it's
Create a new Material
- using the just created
RenderTextureasAlbedo - and set the
RenderingModetoFade(in order to later have it's background transparent)
- using the just created
Create a new Layer and call it e.g.
TEXTIn you normal main
CameraunderCulling Maskexclude the just createdTEXTlayer. So it will not render our Text contentAdd a new
Camerato your scene (it will only render the text) and call it e.g.TextCameraand make the following settings:
- Remove its
AudioListenercomponent Clear Flags→Solid ColorBackground→ Color actually doesn't matter but make sure to set the Alpha level to0!Culling Mask→ Nothing except the createdTEXTlayerTarget Texture→ The createdRenderTexture
Now you already have a Material with a dynamically changeable Texture with transparent background and whatever content you like. So lets make it e.g. a UI.Text
- Remove its
To your scene (I did it simply as child of the
TextCameraso I can simply move it out of sight in the SceneView while working on other stuff) add aText(including the Canvas etc - Unity usually adds it automatically)- Make all GameObjects (Canvas and Text) have the Layer
TEXTso they will not be rendered by the normalCamerabut only by theTextCamera.
- Make sure the
CanvasusesRenderMode = WorldSpace(it won't work with overlay canvas)! - Place the
Canvasabout e.g.3units in front of theTextCamera(or wherever you like so the Text is visible in the texture later)
- To have better Text resolution I would also on the
Text - on the
RectTransformsetwidth = 1000,height = 1000,Scale = 0.001, 0.001, 0.001 - In the
Textcomponent setFont Size = 300 - Just to be sure disable the
Raycast Targetoption
- Make all GameObjects (Canvas and Text) have the Layer
And now you can simply apply the created material to your 3D Object and hit play and should see that it gets completely transparent except having the text on it.
So in order to use it as overlay for a 3D object you could e.g. simply duplicate the original object, call one e.g. Inner the other one Outer and make Inner a child of Outer. Now on the Outer you set our Text material. This works since a material using Fade as render mode is rendered on a different render chain which is rendered on top of the default one.
→ Tadaaa 3D object with Text applied to its surface and can even dynamically change the text and its properties like color etc
The whole thing dynamic (Except creating Layers)
Since you asked: Yes you can make this all in a script ... except creating new Layers! This is not possible on runtime!
So you have to know all the Layers you will use beforehand then you can do something like
[RequireComponent(typeof(MeshRenderer), typeof(MeshFilter))]
public class TextOnObjectManager : MonoBehaviour
{
// reference via Inspector if possible
[SerializeField] private Camera mainCamera;
[SerializeField] private string LayerToUse;
private void Awake()
{
// 0. make the clone of this and make it a child
var innerObject = new GameObject(name + "_original", typeof(MeshRenderer)).AddComponent<MeshFilter>();
innerObject.transform.SetParent(transform);
// copy over the mesh
innerObject.mesh = GetComponent<MeshFilter>().mesh;
name = name + "_textDecal";
// 1. Create and configure the RenderTexture
var renderTexture = new RenderTexture(2048, 2048, 24) { name = name + "_RenderTexture" };
// 2. Create material
var textMaterial = new Material(Shader.Find("Standard"));
// assign the new renderTexture as Albedo
textMaterial.SetTexture("_MainTex", renderTexture);
// set RenderMode to Fade
textMaterial.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.SrcAlpha);
textMaterial.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha);
textMaterial.SetInt("_ZWrite", 0);
textMaterial.DisableKeyword("_ALPHATEST_ON");
textMaterial.EnableKeyword("_ALPHABLEND_ON");
textMaterial.DisableKeyword("_ALPHAPREMULTIPLY_ON");
textMaterial.renderQueue = 3000;
// 3. WE CAN'T CREATE A NEW LAYER AT RUNTIME SO CONFIGURE THEM BEFOREHAND AND USE LayerToUse
// 4. exclude the Layer in the normal camera
if (!mainCamera) mainCamera = Camera.main;
mainCamera.cullingMask &= ~(1 << LayerMask.NameToLayer(LayerToUse));
// 5. Add new Camera as child of this object
var camera = new GameObject("TextCamera").AddComponent<Camera>();
camera.transform.SetParent(transform, false);
camera.backgroundColor = new Color(0, 0, 0, 0);
camera.clearFlags = CameraClearFlags.Color;
camera.cullingMask = 1 << LayerMask.NameToLayer(LayerToUse);
// make it render to the renderTexture
camera.targetTexture = renderTexture;
camera.forceIntoRenderTexture = true;
// 6. add the UI to your scene as child of the camera
var Canvas = new GameObject("Canvas", typeof(RectTransform)).AddComponent<Canvas>();
Canvas.transform.SetParent(camera.transform, false);
Canvas.gameObject.AddComponent<CanvasScaler>();
Canvas.renderMode = RenderMode.WorldSpace;
var canvasRectTransform = Canvas.GetComponent<RectTransform>();
canvasRectTransform.anchoredPosition3D = new Vector3(0, 0, 3);
canvasRectTransform.sizeDelta = Vector2.one;
var text = new GameObject("Text", typeof(RectTransform)).AddComponent<Text>();
text.transform.SetParent(Canvas.transform, false);
var textRectTransform = text.GetComponent<RectTransform>();
textRectTransform.localScale = Vector3.one * 0.001f;
textRectTransform.sizeDelta = new Vector2(2000, 1000);
text.font = Resources.GetBuiltinResource<Font>("Arial.ttf");
text.fontStyle = FontStyle.Bold;
text.alignment = TextAnchor.MiddleCenter;
text.color = Color.red;
text.fontSize = 300;
text.horizontalOverflow = HorizontalWrapMode.Wrap;
text.verticalOverflow = VerticalWrapMode.Overflow;
Canvas.gameObject.layer = LayerMask.NameToLayer(LayerToUse);
text.gameObject.layer = LayerMask.NameToLayer(LayerToUse);
text.text = "This is a dynamically generated example!";
// 7. finally assign the material to the child object and hope everything works ;)
innerObject.GetComponent<MeshRenderer>().material = textMaterial;
}
}
Basically reproducing all the steps from before. Since we can't create or edit Layers on runtime you have to know them beforehand and enter it as LayerToUse.
I created every thing as child of the original object so it is easy to control and change it also later on runtime.
来源:https://stackoverflow.com/questions/58021797/put-a-text-onto-a-game-object-but-as-if-it-was-painted