diff --git a/CHANGELOG.md b/CHANGELOG.md index c275fcd..7f84844 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,15 +1,19 @@ # Changelog All notable changes to this project will be documented in this file. -## [Unreleased] +## [v0.1.0] - 2022-05-13 ### Added - Support for bold text rendering +- Support for italic text rendering - Custom Shader GUI for easier material editing - Raymarching options for more control - Support for more raymarching algorithms for the future +- Debug options to show used steps and the 3D uvs ### Changed - Raymarching usage for more control and new algorithms in the future +- Boundaries structure for raymarching +- Outline rendering is now a shader feature and can be fully disabled ### Removed - Unused shader uniforms diff --git a/Editor/Scripts/TMP3D_UnlitShaderGUI.cs b/Editor/Scripts/TMP3D_UnlitShaderGUI.cs index b4050cb..a914d28 100644 --- a/Editor/Scripts/TMP3D_UnlitShaderGUI.cs +++ b/Editor/Scripts/TMP3D_UnlitShaderGUI.cs @@ -11,6 +11,7 @@ public class TMP3D_UnlitShaderGUI : TMP_BaseShaderGUI static TMP3D_ShaderFeature s_volumeModeFeature; static TMP3D_ShaderFeature s_raymarchModeFeature; static TMP3D_ShaderFeature s_maxStepsFeature; + static ShaderFeature s_debugFeature; static bool s_general = true; static bool s_outline = true; @@ -44,6 +45,13 @@ static TMP3D_UnlitShaderGUI() keywords = new[] { "_MAXSTEPS_32", "_MAXSTEPS_64", "_MAXSTEPS_96", "_MAXSTEPS_128" }, keywordLabels = new[] { new GUIContent("32"), new GUIContent("64"), new GUIContent("96"), new GUIContent("128") } }; + s_debugFeature = new ShaderFeature() + { + undoLabel = "Debug", + label = new GUIContent("Debug Mode"), + keywords = new[] { "DEBUG_STEPS", "DEBUG_MASK" }, + keywordLabels = new[] { new GUIContent("None"), new GUIContent("Steps"), new GUIContent("Mask") } + }; } protected override void DoGUI() @@ -56,18 +64,18 @@ protected override void DoGUI() EndPanel(); - s_outline = BeginPanel("Outline", s_outlineFeature, s_outline); - if (s_outline) + s_3D = BeginPanel("3D", s_3D); + if (s_3D) { - DoOutlinePanel(); + Do3DPanel(); } EndPanel(); - s_3D = BeginPanel("3D", s_3D); - if (s_3D) + s_outline = BeginPanel("Outline", s_outlineFeature, s_outline); + if (s_outline) { - Do3DPanel(); + DoOutlinePanel(); } EndPanel(); @@ -129,6 +137,8 @@ void DoDebugPanel() DoFloat("_GradientScale", "Gradient Scale"); DoFloat("_TextureWidth", "Texture Width"); DoFloat("_TextureHeight", "Texture Height"); + s_debugFeature.ReadState(m_Material); + s_debugFeature.DoPopup(m_Editor, m_Material); EditorGUI.indentLevel -= 1; EditorGUILayout.Space(); } diff --git a/README.md b/README.md index d073c01..9994662 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@

TMP3D Logo

- + GitHub (Pre-)Release Date

# Text Mesh Pro 3D @@ -12,16 +12,20 @@ An extension for Text Mesh Pro that makes 3D text possible using shaders. - [x] 3D character properties and animation - [x] Outline support - [x] Bold text support -- [ ] Italic text support +- [x] Italic text support +- [x] Debug rendering - [ ] Solid, surface shader - [ ] Solid, unlit shader (UI) - [ ] Documentation ### In Evaluation -- [ ] Bevelled text -- [ ] Full fledged freeform text using textures +- [ ] Bevelled text[^1] +- [ ] Full fledged freeform text using textures[^1] - [ ] Translucent, unlit shader -- [ ] VR optimizations +- [ ] VR optimizations[^2] + +[^1]: Tempering with the SDF values in the distance makes raymarching more difficult, I need to find a good way of evaluating the shortest distance. +[^2]: I currently don't have access to any VR device and, therefore, cannot optimize for it right now. ## Compatibility @@ -31,12 +35,12 @@ An extension for Text Mesh Pro that makes 3D text possible using shaders. | DirectX 12 | :heavy_check_mark: Compatible | :heavy_check_mark: Compatible | :heavy_check_mark: Compatible | | Vulkan | :heavy_check_mark: Compatible | :heavy_check_mark: Compatible | :heavy_check_mark: Compatible | | OpenGL Core | :heavy_check_mark: Compatible | :warning: Invalid | :heavy_check_mark: Compatible | -| OpenGLES2[^1] | :x: Incompatible | :warning: Invalid | :x: Incompatible | +| OpenGLES2[^3] | :x: Incompatible | :warning: Invalid | :x: Incompatible | | OpenGLES3 | :heavy_check_mark: Compatible | :warning: Invalid | :heavy_check_mark: Compatible | -| Metal[^2] | :wavy_dash: To Be Tested | :wavy_dash: To Be Tested | :wavy_dash: To Be Tested | +| Metal[^4] | :wavy_dash: To Be Tested | :wavy_dash: To Be Tested | :wavy_dash: To Be Tested | -[^1]: Support for OpenGLES2 is currently **NOT** planned. -[^2]: I currently don't have access to any Mac and, therefore, cannot test it for Metal right now. +[^3]: Support for OpenGLES2 is currently **NOT** planned. +[^4]: I currently don't have access to any Mac and, therefore, cannot test it for Metal right now. If the shader doesn't work for a compatible combination try to reimport the shader file first! When the issue persists contact me! @@ -49,6 +53,8 @@ When the issue persists contact me! 3. Select `Add package from git URL...` 4. Enter `https://github.com/Ikaroon/TMP3D.git` as url and confirm +This method will always install the current state of the git. To get a released version head to the [release page](https://github.com/Ikaroon/TMP3D/releases)! + ## How to use To understand how to setup a TextMeshPro for 3D you can check out the sample in the package. For downloading that follow these steps: 1. Open the package manager in Unity from `Window>Package Manager` @@ -61,10 +67,10 @@ If you still need help, here are some steps how you setup a TextMeshPro for 3D: 1. Create a new FontAsset by using the `Font Asset Creator` from `Window>Text Mesh Pro>Font Asset Creator` 2. Expand the created Asset and select the Material 3. Change the Material's shader to `TextMeshPro/3D/Unlit` -4. Create a TextMeshPro in a scene from `3D Object>Text - TextMeshPro` NOT from `UI>Text - TextMeshPro` this is not supported yet. +4. Create a TextMeshPro in a scene from `3D Object>Text - TextMeshPro` **NOT** from `UI>Text - TextMeshPro` this is not supported yet. 5. Add a TMP3D_Handler component from `Script>Ikaroon.TMP3D>TMP3D_Handler` 6. Apply the FontAsset to the TextMeshPro component -7. You have now acces to 3D text! +7. You have now access to 3D text! ## Notice Work on this project happens in my freetime and, therefore, I cannot promise if and when certain features are added. I am considering to open this up for contribution but for now you can manipulate the code as you please. This project is MIT licensed and may be used freely. (Check the license file for more information) diff --git a/Runtime/Shaders/Lib/Raymarching/Common.cginc b/Runtime/Shaders/Lib/Raymarching/Common.cginc index f0d5366..1b4d453 100644 --- a/Runtime/Shaders/Lib/Raymarching/Common.cginc +++ b/Runtime/Shaders/Lib/Raymarching/Common.cginc @@ -8,6 +8,30 @@ float3 Temp_ViewDir; float3 Temp_LocalStartPos; +float3 Temp_LocalPos; +float3 GetRaymarchLocalPos() +{ + return Temp_LocalPos; +} + +float Temp_Bound; +float GetRaymarchBound() +{ + return Temp_Bound; +} + +float Temp_Value; +float GetRaymarchValue() +{ + return Temp_Value; +} + +float3 Temp_Mask3D; +float3 GetRaymarchMask3D() +{ + return Temp_Mask3D; +} + void PrepareTMP3DRaymarch(tmp3d_g2f input) { float3 viewDir = normalize(input.worldPos.xyz - _WorldSpaceCameraPos.xyz); diff --git a/Runtime/Shaders/Lib/Raymarching/PenaltyMarcher.cginc b/Runtime/Shaders/Lib/Raymarching/PenaltyMarcher.cginc index 380b53e..a6f9c47 100644 --- a/Runtime/Shaders/Lib/Raymarching/PenaltyMarcher.cginc +++ b/Runtime/Shaders/Lib/Raymarching/PenaltyMarcher.cginc @@ -16,14 +16,14 @@ void InitializeRaymarcher(tmp3d_g2f input) temp_Input = input; } -void NextRaymarch(out float3 localPos, out float bound, out float value, float offset) +void NextRaymarch(float offset) { - localPos = GetRaymarchLocalPosition(temp_Progress); - float3 mask3D = PositionToMask(localPos, temp_Input); - bound = IsInBounds(mask3D); - value = 1 - SampleSDF3D(saturate(mask3D), temp_Input); + Temp_LocalPos = GetRaymarchLocalPosition(temp_Progress); + Temp_Mask3D = PositionToMask(Temp_LocalPos, temp_Input); + Temp_Bound = IsInBounds(Temp_Mask3D); + Temp_Value = 1 - SampleSDF3D(saturate(Temp_Mask3D), temp_Input); - float sdfDistance = max((value - offset) * GradientToLocalLength(temp_Input), _RaymarchMinStep); + float sdfDistance = max((Temp_Value - offset) * GradientToLocalLength(temp_Input), _RaymarchMinStep); float3 viewDir = GetRaymarchLocalDirection(); float length1 = length(normalize(viewDir.xy) * sdfDistance); float length2 = length(viewDir.xy); diff --git a/Runtime/Shaders/Lib/Raymarching/StandardMarcher.cginc b/Runtime/Shaders/Lib/Raymarching/StandardMarcher.cginc index 4f0f49a..e2a3a08 100644 --- a/Runtime/Shaders/Lib/Raymarching/StandardMarcher.cginc +++ b/Runtime/Shaders/Lib/Raymarching/StandardMarcher.cginc @@ -14,14 +14,14 @@ void InitializeRaymarcher(tmp3d_g2f input) temp_Input = input; } -void NextRaymarch(out float3 localPos, out float bound, out float value, float offset) +void NextRaymarch(float offset) { - localPos = GetRaymarchLocalPosition(temp_Progress); - float3 mask3D = PositionToMask(localPos, temp_Input); - bound = IsInBounds(mask3D); - value = 1 - SampleSDF3D(saturate(mask3D), temp_Input); + Temp_LocalPos = GetRaymarchLocalPosition(temp_Progress); + Temp_Mask3D = PositionToMask(Temp_LocalPos, temp_Input); + Temp_Bound = IsInBounds(Temp_Mask3D); + Temp_Value = 1 - SampleSDF3D(saturate(Temp_Mask3D), temp_Input); - float sdfDistance = max((value - offset) * GradientToLocalLength(temp_Input), _RaymarchMinStep); + float sdfDistance = max((Temp_Value - offset) * GradientToLocalLength(temp_Input), _RaymarchMinStep); float3 viewDir = GetRaymarchLocalDirection(); float length1 = length(normalize(viewDir.xy) * sdfDistance); float length2 = length(viewDir.xy); diff --git a/Runtime/Shaders/Lib/TMP3D_Common.cginc b/Runtime/Shaders/Lib/TMP3D_Common.cginc index efe31f0..791fa64 100644 --- a/Runtime/Shaders/Lib/TMP3D_Common.cginc +++ b/Runtime/Shaders/Lib/TMP3D_Common.cginc @@ -12,8 +12,8 @@ float InverseLerp(float a, float b, float x) float SampleSDF3D(float3 mask3D, tmp3d_g2f input) { float2 maskUV = float2(0, 0); - maskUV.x = saturate(lerp(input.boundariesUV.x, input.boundariesUV.z, mask3D.x)); - maskUV.y = saturate(lerp(input.boundariesUV.y, input.boundariesUV.w, mask3D.y)); + maskUV.x = saturate(lerp(input.boundariesUV.x, input.boundariesUV.x + input.boundariesUV.z, mask3D.x)); + maskUV.y = saturate(lerp(input.boundariesUV.y, input.boundariesUV.y + input.boundariesUV.w, mask3D.y)); return tex2D(_MainTex, maskUV).a; } @@ -34,16 +34,17 @@ void ClipBounds(float3 mask3D) float3 PositionToMask(float3 localPos, tmp3d_g2f input) { - float3 mask3D = float3(-1,-1,-1); - mask3D.x = InverseLerp(input.boundariesLocal.x, input.boundariesLocal.z, localPos.x); - mask3D.y = InverseLerp(input.boundariesLocal.y, input.boundariesLocal.w, localPos.y); + float3 mask3D = float3(-1, -1, -1); + mask3D.y = InverseLerp(input.boundariesLocal.y, input.boundariesLocal.y + input.boundariesLocal.w, localPos.y); + float xOffset = saturate(mask3D.y) * input.boundariesLocalZ.z; + mask3D.x = InverseLerp(input.boundariesLocal.x, input.boundariesLocal.x + input.boundariesLocal.z, localPos.x - xOffset); mask3D.z = InverseLerp(input.boundariesLocalZ.x, input.boundariesLocalZ.y, localPos.z); return mask3D; } float GradientToLocalLength(tmp3d_g2f input) { - float l = abs(input.boundariesLocal.x - input.boundariesLocal.z); + float l = input.boundariesLocal.z; return l * 0.01 * _GradientScale; } @@ -62,7 +63,7 @@ tmp3d_v2g TMP3D_VERT(tmp3d_a2v input) } // "Creates a vertex" with an offset and boundary values -tmp3d_g2f CreateVertex(tmp3d_v2g input, float3 positionOffset, float4 boundariesUV, float4 boundariesLocal, float2 boundariesLocalZ) +tmp3d_g2f CreateVertex(tmp3d_v2g input, float3 positionOffset, float4 boundariesUV, float4 boundariesLocal, float4 boundariesLocalZ) { tmp3d_g2f output; @@ -92,7 +93,7 @@ tmp3d_g2f CreateVertex(tmp3d_v2g input, float3 positionOffset, float4 boundaries return output; } -void TMP3D_FILLGEOMETRY(triangle tmp3d_v2g input[3], inout TriangleStream triStream, float3 def, float3 normal, float4 boundariesUV, float4 boundariesLocal, float2 boundariesLocalZ) +void TMP3D_FILLGEOMETRY(triangle tmp3d_v2g input[3], inout TriangleStream triStream, float3 def, float3 normal, float4 boundariesUV, float4 boundariesLocal, float4 boundariesLocalZ) { // Top triStream.RestartStrip(); @@ -131,7 +132,7 @@ void TMP3D_FILLGEOMETRY(triangle tmp3d_v2g input[3], inout TriangleStream triStream, float3 def, float3 normal, float4 boundariesUV, float4 boundariesLocal, float2 boundariesLocalZ) +void TMP3D_FILLGEOMETRY_INVERTED(triangle tmp3d_v2g input[3], inout TriangleStream triStream, float3 def, float3 normal, float4 boundariesUV, float4 boundariesLocal, float4 boundariesLocalZ) { // Top triStream.RestartStrip(); @@ -180,25 +181,25 @@ void TMP3D_GEOM(triangle tmp3d_v2g input[3], inout TriangleStream tri float depth = input[0].texcoord2.r; float3 normal = input[0].normal * depth; - float minUVx = min(input[0].texcoord0.x, min(input[1].texcoord0.x, input[2].texcoord0.x)); - float minUVy = min(input[0].texcoord0.y, min(input[1].texcoord0.y, input[2].texcoord0.y)); - float maxUVx = max(input[0].texcoord0.x, max(input[1].texcoord0.x, input[2].texcoord0.x)); - float maxUVy = max(input[0].texcoord0.y, max(input[1].texcoord0.y, input[2].texcoord0.y)); - float4 boundariesUV = float4(minUVx, minUVy, maxUVx, maxUVy); + float skewUV = abs(input[1].texcoord0.x - input[0].texcoord0.x); + float widthUV = abs(input[2].texcoord0.x - input[1].texcoord0.x); + float heightUV = abs(input[1].texcoord0.y - input[0].texcoord0.y); + float xUV = min(input[0].texcoord0.x, input[2].texcoord0.x); + float yUV = min(input[0].texcoord0.y, input[1].texcoord0.y); + float4 boundariesUV = float4(xUV, yUV, widthUV, heightUV); float3 v0local = mul(unity_WorldToObject, float4(input[0].position.xyz, 1)).xyz; float3 v1local = mul(unity_WorldToObject, float4(input[1].position.xyz, 1)).xyz; float3 v2local = mul(unity_WorldToObject, float4(input[2].position.xyz, 1)).xyz; - float minWorldx = min(v0local.x, min(v1local.x, v2local.x)); - float minWorldy = min(v0local.y, min(v1local.y, v2local.y)); - float maxWorldx = max(v0local.x, max(v1local.x, v2local.x)); - float maxWorldy = max(v0local.y, max(v1local.y, v2local.y)); - float4 boundariesLocal = float4(minWorldx, minWorldy, maxWorldx, maxWorldy); + float skewLocal = abs(v1local.x - v0local.x); + float widthLocal = abs(v2local.x - v1local.x); + float heightLocal = abs(v1local.y - v0local.y); + float xLocal = min(v0local.x, v2local.x); + float yLocal = min(v0local.y, v1local.y); + float4 boundariesLocal = float4(xLocal, yLocal, widthLocal, heightLocal); - float minWorldz = min(v0local.z, min(v1local.z, v2local.z)); - float maxWorldz = max(v0local.z, max(v1local.z, v2local.z)); - float2 boundariesLocalZ = float2(minWorldz - depth, maxWorldz); + float4 boundariesLocalZ = float4(-depth, 0, skewLocal, skewUV); TMP3D_FILLGEOMETRY(input, triStream, def, normal, boundariesUV, boundariesLocal, boundariesLocalZ); } @@ -213,25 +214,25 @@ void TMP3D_GEOM_INVERTED(triangle tmp3d_v2g input[3], inout TriangleStream 0.5) { - return o; + return ValidateOutput(o, i); } + #endif clip(bound); @@ -155,11 +174,11 @@ Shader "TextMeshPro/3D/Unlit" o.depth = compute_depth(UnityObjectToClipPos(localPos)); o.color = float4(c.rgb * input.color, 1); - return o; + return ValidateOutput(o, i); } } - return o; + return ValidateOutput(o, MAX_STEPS); } ENDCG diff --git a/Samples~/SamplesSolidText/Fonts/ARVO SDF3D.asset b/Samples~/SamplesSolidText/Fonts/ARVO SDF3D.asset index 4c3f35f..39f0dd6 100644 --- a/Samples~/SamplesSolidText/Fonts/ARVO SDF3D.asset +++ b/Samples~/SamplesSolidText/Fonts/ARVO SDF3D.asset @@ -5200,7 +5200,8 @@ Material: m_PrefabAsset: {fileID: 0} m_Name: ARVO SDF Material m_Shader: {fileID: 4800000, guid: b3bf92bc409c84043aa5e057cee0810b, type: 3} - m_ShaderKeywords: _RAYMARCH_SIMPLE + m_ShaderKeywords: OUTLINE_ON _MAXSTEPS_96 _RAYMARCHER_STANDARD _RAYMARCH_SIMPLE + _VOLUMEMODE_SURFACE m_LightmapFlags: 4 m_EnableInstancingVariants: 0 m_DoubleSidedGI: 0 @@ -5218,6 +5219,10 @@ Material: m_Texture: {fileID: 0} m_Scale: {x: 1, y: 1} m_Offset: {x: 0, y: 0} + - _DepthAlbedo: + m_Texture: {fileID: 2800000, guid: 1f5c2b9d845f24b46ae38c52bacad652, type: 3} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} - _FaceTex: m_Texture: {fileID: 0} m_Scale: {x: 1, y: 1} @@ -5287,10 +5292,11 @@ Material: - _UnderlaySoftness: 0 - _VertexOffsetX: 0 - _VertexOffsetY: 0 - - _WeightBold: 0.75 - - _WeightNormal: 0 + - _WeightBold: 0.65 + - _WeightNormal: 0.5 m_Colors: - _ClipRect: {r: -32767, g: -32767, b: 32767, a: 32767} + - _Color: {r: 1, g: 1, b: 1, a: 1} - _EnvMatrixRotation: {r: 0, g: 0, b: 0, a: 0} - _FaceColor: {r: 1, g: 1, b: 1, a: 1} - _GlowColor: {r: 0, g: 1, b: 0, a: 0.5} diff --git a/Samples~/SamplesSolidText/Scenes/Sample_TMP3D.unity b/Samples~/SamplesSolidText/Scenes/Sample_TMP3D.unity index d50b122..fc08808 100644 --- a/Samples~/SamplesSolidText/Scenes/Sample_TMP3D.unity +++ b/Samples~/SamplesSolidText/Scenes/Sample_TMP3D.unity @@ -277,7 +277,7 @@ MonoBehaviour: m_OnCullStateChanged: m_PersistentCalls: m_Calls: [] - m_text: 3D Text + m_text: 3D Text m_isRightToLeft: 0 m_fontAsset: {fileID: 11400000, guid: aadddd7b82985b240b284d433fa64263, type: 2} m_sharedMaterial: {fileID: 2928379036357712455, guid: aadddd7b82985b240b284d433fa64263, type: 2} diff --git a/package.json b/package.json index 657cf55..39d8357 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "com.ikaroon.tmp3d", - "version": "0.1.0-pre.2", + "version": "0.1.0", "displayName": "Text Mesh Pro 3D Support", "description": "An extension for Text Mesh Pro that makes 3D text possible using shaders.", "unity": "2020.3",