5

I'm using a text mesh to place text on a 3D object, but as you all know, the text mesh does not have any normals...

http://docs.unity3d.com/Documentation/Components/class-TextMesh.html

...so it does not light correctly. I've done a search and found many people having trouble with lighting 3D text mesh because of it doesn't have any normals, but I haven't found a solution to adding normals to a text mesh object, so that is my question.

How can I add a normal to a text mesh so that it lights correctly?

Thanks so much in advance for your wisdom!

noob
  • 17,131
  • 18
  • 103
  • 168
BeachRunnerFred
  • 17,150
  • 34
  • 132
  • 231
  • I think normal depends upon the material and shader being used. Dunno if it depends upon meshes or not. Try changing the shader to something which allows normal, like specular – noob Apr 27 '13 at 00:17
  • @Creator A normal is just a non-zero vector that's orthogonal to face of a poly in the mesh. It's used by the shaders to render the lighting. The shaders I need to use rely on normals being defined for the mesh that's being lit. In this case, the text mesh. The problem is the mesh doesn't have any normals. So the text is always rendered as a solid white color regardless of the light environment it's in. – BeachRunnerFred Apr 27 '13 at 01:43
  • Is there a reason why you use Text Mesh and not a mesh made from a 3D Software? I would guess you're planning on changing the text at run-time? – LightStriker Apr 27 '13 at 04:14
  • @LightStriker - Exactly, I want the text to be dynamic. – BeachRunnerFred Apr 30 '13 at 00:51
  • The unity tag is for Microsoft Unity. Don't misuse it. – Lex Li May 07 '13 at 09:47

1 Answers1

6

I did something similar for lighting a 3D texture. I hope my answer isn't overkill. So this code was a hack I wrote awhile back, it's inefficient and only supports a single directional light (helpful cg lighting tuts here). Hopefully this is enough to get you started.

To create a 3D texture using my code below you have to go through a little more work:

  1. Create an empty game object
  2. Attach Component->Mesh->Text Mesh
  3. Attach Component TextMeshNormals (included below)
  4. Attach Component->Mesh->Mesh Renderer
  5. Assign a material to the Mesh Renderer that uses my GUI/LitText shader below

In the shader you'll notice a text normal attribute. In practice you would have your gameObject's Update() method update the _Normal property with the direction the text is facing so that it reflects a change in orientation. The text is planar so 1 normal is all we need. To test I manually set the normal to (0,0,-1,1), since the default Text Mesh faces down -Z.

Because this script doesn't run in the editor, your text won't show up until you run a scene in preview.

The shader:

Shader "GUI/LitText" { 
Properties { 
   _MainTex ("Font Texture", 2D) = "white" {} 
   _Color ("Text Color", Color) = (1,1,1,1) 
   _Normal ("Text Normal",Vector) = (0,0,0,1)
} 

SubShader {
    Blend SrcAlpha OneMinusSrcAlpha
    Pass { 
        Color [_Color] 
        SetTexture [_MainTex] { 
            combine primary, texture * primary 
        } 
    } 
    pass {
         Tags { "LightMode" = "ForwardBase" } 

         CGPROGRAM

         #pragma vertex vert  
         #pragma fragment frag 

         uniform sampler2D _MainTex;  
         uniform float4 _Color; // define shader property for shaders
         uniform float4 _Normal;

         // The following built-in uniforms (apart from _LightColor0) 
         // are defined in "UnityCG.cginc", which could be #included 
         uniform float4 unity_Scale; // w = 1/scale; see _World2Object
         uniform float3 _WorldSpaceCameraPos;
         uniform float4x4 _Object2World; // model matrix
         uniform float4x4 _World2Object; // inverse model matrix 
            // (all but the bottom-right element have to be scaled 
            // with unity_Scale.w if scaling is important) 
         uniform float4 _WorldSpaceLightPos0; 
            // position or direction of light source
         uniform float4 _LightColor0; 
            // color of light source (from "Lighting.cginc")

         struct vertexInput {
            float4 vertex : POSITION;
            float3 normal : NORMAL;
            float4 texcoord : TEXCOORD0;
         };
         struct vertexOutput {
            float4 pos : SV_POSITION;
            float4 col : COLOR;
            float4 tex : TEXCOORD0;
         };

         vertexOutput vert(vertexInput input) 
         {
            vertexOutput output;

            float4x4 modelMatrix = _Object2World;
            float4x4 modelMatrixInverse = _World2Object; 

            float3 normalDirection = normalize(float3(mul(_Normal, modelMatrixInverse)));
            float3 lightDirection = normalize(float3(_WorldSpaceLightPos0));

            float3 diffuseReflection = float3(_LightColor0) * float3(_Color)
               * max(0.0, dot(normalDirection, lightDirection));

            output.col = float4(diffuseReflection, 1.0);
            output.pos = mul(UNITY_MATRIX_MVP, input.vertex);
            output.tex = input.texcoord;
            return output;
         }

         float4 frag(vertexOutput input) : COLOR
         {
            half4 color = tex2D(_MainTex, float2(input.tex));
            // use color.a to get alpha from text texture, rgb comes from vertex shader                        
            return float4(input.col.r,input.col.g,input.col.b,color.a);
         }

         ENDCG
    }
}
}

And the helper script:

public class TextMeshNormals : MonoBehaviour {
    private TextMesh textMesh;

    // Use this for initialization
    void Start () {
        // reassign font texture to our material
        textMesh = transform.GetComponent<TextMesh>();
        renderer.material.mainTexture = textMesh.font.material.mainTexture;
    }
}

Update Unity 4.5.X use this slightly updated version:

Shader "GUI/LitText" { 
Properties { 
   _MainTex ("Font Texture", 2D) = "white" {} 
   _Color ("Text Color", Color) = (1,1,1,1) 
   _Normal ("Text Normal",Vector) = (0,0,0,1)
} 

SubShader {
    Blend SrcAlpha OneMinusSrcAlpha
    Pass { 
        Color [_Color] 
        SetTexture [_MainTex] { 
            combine primary, texture * primary 
        } 
    } 
    pass {
         Tags { "LightMode" = "ForwardBase" } 

         CGPROGRAM

         #pragma vertex vert  
         #pragma fragment frag 

         uniform sampler2D _MainTex;  
         uniform float4 _Color; // define shader property for shaders
         uniform float4 _Normal;
         uniform float4 _LightColor0; 
             // color of light source (from "Lighting.cginc")
         struct vertexInput {
            float4 vertex : POSITION;
            float3 normal : NORMAL;
            float4 texcoord : TEXCOORD0;
         };
         struct vertexOutput {
            float4 pos : SV_POSITION;
            float4 col : COLOR;
            float4 tex : TEXCOORD0;
         };

         vertexOutput vert(vertexInput input) 
         {
            vertexOutput output;

            float4x4 modelMatrix = _Object2World;
            float4x4 modelMatrixInverse = _World2Object; 

            float3 normalDirection = normalize(float3(mul(_Normal, modelMatrixInverse)));
            float3 lightDirection = normalize(float3(_WorldSpaceLightPos0));

            float3 diffuseReflection = float3(_LightColor0) * float3(_Color)
               * max(0.0, dot(normalDirection, lightDirection));

            output.col = float4(diffuseReflection, 1.0);
            output.pos = mul(UNITY_MATRIX_MVP, input.vertex);
            output.tex = input.texcoord;
            return output;
         }

         float4 frag(vertexOutput input) : COLOR
         {
            half4 color = tex2D(_MainTex, float2(input.tex));
            // use color.a to get alpha from text texture, rgb comes from vertex shader                        
            return float4(input.col.r,input.col.g,input.col.b,color.a);
         }

         ENDCG
    }
}
}
Jerdak
  • 3,898
  • 1
  • 22
  • 35
  • +1: nice solution. Does the TextMesh component need to be disabled?Since it seems that is now TextMeshNormals responsible for rendering the text. – Heisenbug Apr 27 '13 at 09:07
  • @Heisenbug Nice idea, I hadn't thought to check. After looking at the code I'm not sure if TextMesh can be disabled. It seems to be a pure component w/ no `enabled` member. – Jerdak Apr 27 '13 at 13:27
  • Thanks, @Jerdak, that's very helpful! I'll give it a shot. – BeachRunnerFred Apr 27 '13 at 14:06
  • Forgot I could just call Object.Destroy on the component. Doing so removes the text so there still seems to be a link to the TextMesh somewhere. I'll have to dig deeper. – Jerdak Apr 27 '13 at 16:08
  • @Jerdak, do you have any thoughts on how I can support spotlights? – BeachRunnerFred Apr 28 '13 at 05:35
  • 1
    @BeachRunnerFred Take a look at [this attenuation example](http://en.wikibooks.org/wiki/Cg_Programming/Unity/Light_Attenuation). The most likely path is that you have to create a 2nd (or in my example 3rd) pass using `{Lightmode = ForwardAdd}` to get lighting from other sources. – Jerdak Apr 29 '13 at 01:43
  • @Jerdak I tried to implement your fix in Unity 4.5.4. I copied your shader code into LitText.shader and Unity gave me a bunch of errors: EG: redefinition of unity_Scale. I commented out that line and a couple of other variable definitions that overwrote existing ones. But line 63: float3 normalDirection = normalize(float3(mul(_Normal, modelMatrixInverse))); Caused: Shader error in 'GUI/LitText': incorrect number of arguments to numeric-type constructor at line 63 I switched the syntax a bit and lost the error but the next line had a similar one. Is this a version problem? – Piwakawaka Nov 08 '14 at 16:42
  • 1
    @Piwakawaka Added slightly modified version above. I haven't touched shaders in a year but it looks like several of the built-in variables from `UnityCG.cginc` are now automatically included. I'm not sure if it's a parsing error but simply commenting the lines out appears to be problematic so I just removed them all. – Jerdak Nov 08 '14 at 23:03
  • Thanks @Jerdak. I still get: Shader error in 'GUI/LitText': incorrect number of arguments to numeric-type constructor at line 47 (float3 normalDirection = normalize(float3(mul(_Normal, modelMatrixInverse)));) I can run the preview in the editor, but no text is rendered, if I switch the renderer to GUI/Text Shader then it renders. It doesn't render on a device either. I noticed that when I inspected the Text Normal property it was at 0,0,0,0 - but changing that at run time did not help. Not sure if this screen shot could help you understand what I am missing? http://i.imgur.com/UzrLKiY.jpg – Piwakawaka Nov 10 '14 at 05:38