将文本放在游戏对象上,但就像它被绘制一样

Posted

技术标签:

【中文标题】将文本放在游戏对象上,但就像它被绘制一样【英文标题】:Put a text onto a game-object BUT as if it was painted 【发布时间】:2020-01-21 02:59:33 【问题描述】:

我在网上寻找这个答案已经有一段时间了。 但显然我的搜索短语是错误的,或者真的那么难。但我对此表示怀疑。

想象一下,我有任何 3d 体。球体、圆柱体或立方体。

我想在那个对象上放一个文本。就好像它是一个贴纸或涂在那个物体上一样。 意思是,如果环绕它们,它将遵循对象的曲线或边缘。我设法创建了一些总是在我的对象前面或后面的文本。但这不是我想要的。

最接近我想要的看起来像这样:

我不敢相信,我需要为此编写代码。它必须可以通过子/父、画布和文本实现,只需使用检查器和组件即可。

是这样吗?

【问题讨论】:

我认为您确实需要一些将文本的每个顶点放置在正确位置的代码,但我不确定它是如何工作的。通常我所做的只是用文本创建一个纹理,但我确信这不是你想要的 嗯,Unity 可以为您将纹理应用到 UV 映射模型,但通常您将在 Unity 之外创建这些纹理。您可以假设自己将其实现到统一编辑器中,但对于一个问题来说,这个话题太宽泛了。可能已经存在一个统一资产,但要求一个库是堆栈溢出的主题。我建议寻找有关“紫外线映射”的教程。祝你好运! 我会研究贴花系统,然后通过制作自己的文本到纹理功能来扩展它们。 (因为那些贴花系统使用纹理)。 如果你想在不那么不规则的表面上使用它,并且有一些修复文本,那么你也可以使用 Unity 投影仪:docs.unity3d.com/Manual/class-Projector.html 要求结束此问题的投票是否有具体原因?如果是这样,我很抱歉,如果这个缺少“要更正的代码”。 【参考方案1】:

没那么简单,但您可以通过以下方式实现类似的目标。 这需要您的 3D 模型正确地进行 UV 映射,以便您可以简单地对其应用平面纹理。 设置可能听起来很复杂,但它真的很酷;)

    创建一个RenderTexture:在Asset 中执行鼠标右键CreateRenderTexture 并以你喜欢的方式调用它,例如TextTexture

为了以后有更好的分辨率,将 size 增加到例如2048*2048 当然取决于您的需求。

    创建新材质

使用刚刚创建的RenderTexture 作为Albedo 并将RenderingMode 设置为Fade(以便以后使其背景透明)

    创建一个新的Layer 并将其命名为例如TEXT

    Culling Mask 下的普通主Camera 排除刚刚创建的TEXT 层。所以它不会渲染我们的文本内容

    在场景中添加一个新的Camera(它只会渲染文本)并调用它,例如TextCamera

并进行以下设置:

删除其AudioListener 组件 Clear FlagsSolid Color Background → 颜色其实无关紧要,但请确保将 Alpha 级别设置为 0Culling Mask → 除了创建的TEXT 层之外什么都没有 Target Texture → 创建的RenderTexture

现在您已经有了一个材质,它具有动态可变纹理、透明背景和您喜欢的任何内容。所以让我们来吧,例如一个 UI.Text

    到您的场景中(我只是作为TextCamera 的子对象完成的,因此我可以在处理其他内容时简单地将其移出SceneView 中)添加Text(包括画布等 - Unity 通常会添加它自动)

使所有游戏对象(画布和文本)都具有层 TEXT,这样它们就不会被普通的 Camera 渲染,而只能被 TextCamera 渲染。

确保Canvas 使用RenderMode = WorldSpace(它不适用于覆盖画布)! 将Canvas 放在例如3TextCamera 前面的单位(或您喜欢的任何位置,以便稍后在纹理中看到文本)

为了获得更好的文本分辨率,我也会在TextRectTransform上设置width = 1000height = 1000Scale = 0.001, 0.001, 0.001Text 组件集中Font Size = 300 只是为了确保禁用 Raycast Target 选项

现在您可以简单地将创建的材质应用到您的 3D 对象并点击播放,应该会看到它变得完全透明,除了上面有文字。

因此,为了将其用作 3D 对象的叠加层,您可以使用例如只需复制原始对象,调用一个例如Inner 另一个Outer 并使Inner 成为Outer 的孩子。现在在Outer 上设置我们的文本材料。这是有效的,因为使用Fade 作为渲染模式的材质在不同的渲染链上渲染,该渲染链呈现在默认链之上。

→ Tadaaa 3D 对象,其表面应用了文本,甚至可以动态更改文本及其属性,如颜色等


整个事情都是动态的(除了创建图层)

既然您问:是的,您可以在脚本中完成这一切...... 除了创建新层!这在运行时是不可能的!

所以你必须事先知道你将使用的所有图层,然后你才能做类似的事情

[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;
    

基本上重现了之前的所有步骤。由于我们无法在运行时创建或编辑图层,因此您必须事先了解它们并将其输入为LayerToUse

我将所有东西都创建为原始对象的子对象,因此稍后在运行时也可以轻松控制和更改它。


更新

在较新的 Unity 版本中,我建议通过 TextMeshPro 交换 Text,它支持更多的文本格式和自动缩放等。

【讨论】:

非常感谢。这是相当复杂的,并且在“设计器 IDE”中需要做很多工作。代码方法会摆脱一些步骤吗? 嗯,你需要一个单独的层来处理新文本,所以在运行时添加它会变得非常棘手,因为你不能在运行时动态编辑/添加层。这在一定程度上取决于您的需求,但总的来说:是的,您基本上也可以在脚本中完成所有步骤......除了所说的创建新图层 我很失望,这个功能是如此“不方便”。我相信,我不是唯一一个有这种要求的人。这只是为了好玩,所以没有真正的生产力问题。 请查看我的更新 .. 我刚刚添加了一个脚本版本,可以在一个对象上为您完成整个设置。但是,您仍然需要事先设置图层 哦,使用渲染纹理的巧妙解决方案。非常好。【参考方案2】:

我做了一些有帮助的更改。

    您的代码:name = name + "_textDecal"; 可能意味着 innerObject.name = name + "_textDecal"; 在同一区域,需要添加innerObject.transform.localPosition = Vector3.zero;innerObject.transform.localRotaton = Quaternion.identity;否则文字如果移动或旋转对象,它似乎不会跟随对象

但我发现的真正问题是(在 Unity 2020.1.f1 上):原始网格的纹理只能在 Unity 编辑器中正确显示——独立构建显示黑色对象(或任何相机背景颜色) .

我看到的问题是否在 RenderTexture 文档(我无法立即再次找到)中解释得有些模糊,这暗示我需要在我的 Resources 文件夹中包含一个纹理(材质?),它具有与我在我的渲染纹理?

【讨论】:

【参考方案3】:

您可以使用 UI 文本游戏对象和 3D 对象来做到这一点。

步骤如下:

在您的空场景中,创建一个 3D 立方体。

创建一个 UI 文本对象。

拖动 Canvas 成为立方体的子对象。

将 Canvas 设置为 World Space 渲染模式,移除 Canvas Scaler 组件并设置 Width = Height = 1 且所有 Pos = 0。

设置文本 Width = Height = 100,所有 Scale = 0.01 和 Pos Z = -0.5。

【讨论】:

以上是关于将文本放在游戏对象上,但就像它被绘制一样的主要内容,如果未能解决你的问题,请参考以下文章

如何使用按钮在文本视图中显示文本?就像它是一个键盘[关闭]

iOS UIView 子类将透明文本绘制到背景

动画Bezier曲线,就像在iPhone屏幕上绘制一样

iOS UIView 子类,将透明文本绘制到背景

如何从 SVG 元素中停用元素,就像它不存在一样[重复]

PyQtGraph PlotWidget:如何强制每次绘制(更改范围)以进行“实时”绘图