From 0a77ef57b705db9df6997ec291c6ce180e533769 Mon Sep 17 00:00:00 2001 From: drewcassidy Date: Fri, 19 Jun 2020 01:01:46 -0700 Subject: [PATCH] Play around more with text rendering --- .gitignore | 1 + .../DecalProjectorTest.cs | 0 Assets/Scripts/TextRenderTest.cs | 90 ++++++++ Assets/Shaders/TMPBlit.shader | 51 +++++ Source/ConformalDecals/ConformalDecals.csproj | 4 + Source/ConformalDecals/ModuleConformalText.cs | 1 + Source/ConformalDecals/Text/TextRenderer.cs | 194 ++++++++---------- Source/ConformalDecals/Text/TextSettings.cs | 5 + 8 files changed, 232 insertions(+), 114 deletions(-) rename Assets/{Plugins => Scripts}/DecalProjectorTest.cs (100%) create mode 100644 Assets/Scripts/TextRenderTest.cs create mode 100644 Assets/Shaders/TMPBlit.shader create mode 100644 Source/ConformalDecals/Text/TextSettings.cs diff --git a/.gitignore b/.gitignore index 56251b1..7fb5c0f 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ Assets/* !Assets/Shaders/ !Assets/Textures/ +!Assets/Scripts/ KSP/ Library/ Logs/ diff --git a/Assets/Plugins/DecalProjectorTest.cs b/Assets/Scripts/DecalProjectorTest.cs similarity index 100% rename from Assets/Plugins/DecalProjectorTest.cs rename to Assets/Scripts/DecalProjectorTest.cs diff --git a/Assets/Scripts/TextRenderTest.cs b/Assets/Scripts/TextRenderTest.cs new file mode 100644 index 0000000..8369fe3 --- /dev/null +++ b/Assets/Scripts/TextRenderTest.cs @@ -0,0 +1,90 @@ +using System.Collections; +using System.Collections.Generic; +using TMPro; +using UnityEngine; +using UnityEngine.Rendering; + +public class TextRenderTest : MonoBehaviour { + //[InspectorButton("go")] public bool button; + + public Camera _camera; + + public GameObject _cameraObject; + + public TextMeshPro _tmp; + + public Material _blitMaterial; + + public Material _targetMaterial; + + public RenderTexture renderTex; + private float pixelDensity = 36; + private int MaxTextureSize = 4096; + + public const TextureFormat TextTextureFormat = TextureFormat.RG16; + public const RenderTextureFormat TextRenderTextureFormat = RenderTextureFormat.R8; + + // Start is called before the first frame update + void Start() { + Debug.Log("starting..."); + + StartCoroutine(OnRender()); + } + + // Update is called once per frame + void Update() { + } + + private void go() { + Debug.Log("starting..."); + } + + private IEnumerator OnRender() { + Debug.Log("starting...2"); + + // calculate camera and texture size + _tmp.ForceMeshUpdate(); + var mesh = _tmp.mesh; + mesh.RecalculateBounds(); + var bounds = mesh.bounds; + Debug.Log(bounds.size); + + var width = Mathf.NextPowerOfTwo((int) (bounds.size.x * pixelDensity)); + var height = Mathf.NextPowerOfTwo((int) (bounds.size.y * pixelDensity)); + + Debug.Log($"width = {width}"); + Debug.Log($"height = {height}"); + + _camera.orthographicSize = height / pixelDensity / 2; + _camera.aspect = (float) width / height; + + _cameraObject.transform.localPosition = new Vector3(bounds.center.x, bounds.center.y, -1); + + width = Mathf.Min(width, MaxTextureSize); + height = Mathf.Min(height, MaxTextureSize); + + // setup texture + var texture = new Texture2D(width, height, TextTextureFormat, true); + _targetMaterial.mainTexture = texture; + + // setup render texture + renderTex = RenderTexture.GetTemporary(width, height, 0, TextRenderTextureFormat, RenderTextureReadWrite.Linear, 1); + renderTex.autoGenerateMips = true; + _camera.targetTexture = renderTex; + + // setup material + _blitMaterial.mainTexture = _tmp.font.atlas; + + // draw the mesh + Graphics.DrawMesh(mesh, _tmp.renderer.localToWorldMatrix, _blitMaterial, 0, _camera, 0); + _camera.Render(); + + yield return null; + + RenderTexture.active = renderTex; + texture.ReadPixels(new Rect(0, 0, width, height), 0, 0, true); + texture.Apply(false, true); + + RenderTexture.ReleaseTemporary(renderTex); + } +} diff --git a/Assets/Shaders/TMPBlit.shader b/Assets/Shaders/TMPBlit.shader new file mode 100644 index 0000000..1ef6391 --- /dev/null +++ b/Assets/Shaders/TMPBlit.shader @@ -0,0 +1,51 @@ +Shader "ConformalDecals/TMP_Blit" +{ + Properties + { + _MainTex("_MainTex (RGB spec(A))", 2D) = "white" {} + + } + SubShader + { + Tags { "Queue" = "Transparent" } + Cull Off + ZWrite Off + + Pass + { + Blend One One + + CGPROGRAM + #pragma vertex vert + #pragma fragment frag + + sampler2D _MainTex; + + #include "UnityCG.cginc" + #include "Lighting.cginc" + #include "AutoLight.cginc" + + struct v2f { + float4 pos : SV_POSITION; + float2 uv : TEXCOORD0; + }; + + v2f vert(float4 vertex : POSITION, float2 uv : TEXCOORD0) { + v2f o; + o.pos = UnityObjectToClipPos(vertex); + o.uv = uv; + return o; + } + + fixed4 frag (v2f i) : SV_Target { + fixed4 c = 0; + + c.r = tex2D(_MainTex,(i.uv)).a; + + return c; + } + + ENDCG + } + } +} \ No newline at end of file diff --git a/Source/ConformalDecals/ConformalDecals.csproj b/Source/ConformalDecals/ConformalDecals.csproj index 13a5b8a..d8e5c33 100644 --- a/Source/ConformalDecals/ConformalDecals.csproj +++ b/Source/ConformalDecals/ConformalDecals.csproj @@ -52,6 +52,9 @@ dlls\UnityEngine.PhysicsModule.dll + + dlls\UnityEngine.UI.dll + @@ -70,6 +73,7 @@ + diff --git a/Source/ConformalDecals/ModuleConformalText.cs b/Source/ConformalDecals/ModuleConformalText.cs index 322e4de..659b694 100644 --- a/Source/ConformalDecals/ModuleConformalText.cs +++ b/Source/ConformalDecals/ModuleConformalText.cs @@ -1,3 +1,4 @@ +using ConformalDecals.Text; using ConformalDecals.Util; using TMPro; using UnityEngine; diff --git a/Source/ConformalDecals/Text/TextRenderer.cs b/Source/ConformalDecals/Text/TextRenderer.cs index e9bfe89..c625328 100644 --- a/Source/ConformalDecals/Text/TextRenderer.cs +++ b/Source/ConformalDecals/Text/TextRenderer.cs @@ -1,146 +1,112 @@ using System; -using ConformalDecals.Util; +using System.Collections; using TMPro; using UnityEngine; +using UnityEngine.Rendering; + +namespace ConformalDecals.Text { + [KSPAddon(KSPAddon.Startup.FlightAndEditor, true)] + public class TextRenderer : MonoBehaviour { + public static TextRenderer Instance { + get { + if (!_instance._isSetup) { + _instance.Setup(); + } -namespace ConformalDecals { - public class TextRenderer { - private struct GlyphInfo { - public TMP_Glyph glyph; - public Vector2Int size; - public Vector2Int position; - public int fontIndex; - public bool needsResample; + return _instance; + } } - private struct FontInfo { - public TMP_FontAsset font; - public Texture2D fontAtlas; - public Color32[] fontAtlasColors; - } + public const TextureFormat TextTextureFormat = TextureFormat.Alpha8; + public const RenderTextureFormat TextRenderTextureFormat = RenderTextureFormat.R8; - public static Texture2D RenderToTexture(TMP_FontAsset font, string text) { - Debug.Log($"Rendering text: {text}"); - var charArray = text.ToCharArray(); - var glyphInfoArray = new GlyphInfo[charArray.Length]; - var fontInfoArray = new FontInfo[charArray.Length]; + private const string BlitShader = "ConformalDecals/TMP_Blit"; + private const int MaxTextureSize = 4096; - var baseScale = font.fontInfo.Scale; + private static TextRenderer _instance; - var padding = (int) font.fontInfo.Padding; - var ascender = (int) font.fontInfo.Ascender; - var descender = (int) font.fontInfo.Descender; - var baseline = (int) baseScale * (descender + padding); - Debug.Log($"baseline: {baseline}"); - Debug.Log($"ascender: {ascender}"); - Debug.Log($"descender: {descender}"); - Debug.Log($"baseScale: {baseScale}"); + private bool _isSetup; + private TextMeshPro _tmp; + private GameObject _cameraObject; + private Camera _camera; + private Material _blitMaterial; - fontInfoArray[0].font = font; + private void Start() { + if (_instance._isSetup) { + Debug.Log("[ConformalDecals] Duplicate TextRenderer created???"); + } - int xAdvance = 0; - for (var i = 0; i < charArray.Length; i++) { + Debug.Log("[ConformalDecals] Creating TextRenderer Object"); + _instance = this; + DontDestroyOnLoad(gameObject); + } - var glyphFont = TMP_FontUtilities.SearchForGlyph(font, charArray[i], out var glyph); + public void Setup() { + if (_isSetup) return; - if (glyphFont == font) { - glyphInfoArray[i].fontIndex = 0; - } - else { - for (int f = 1; i < charArray.Length; i++) { - if (fontInfoArray[f].font == null) { - fontInfoArray[f].font = glyphFont; - glyphInfoArray[i].fontIndex = f; - break; - } - - if (fontInfoArray[f].font == glyphFont) { - glyphInfoArray[i].fontIndex = f; - break; - } - } - } - Debug.Log($"getting font info for character: '{charArray[i]}'"); - Debug.Log($"character font: {glyphFont.name}"); + Debug.Log("[ConformalDecals] Setting Up TextRenderer Object"); - glyphInfoArray[i].glyph = glyph; - glyphInfoArray[i].needsResample = false; + _tmp = gameObject.AddComponent(); + _tmp.renderer.enabled = false; // dont automatically render - float elementScale = glyph.scale; + _cameraObject = new GameObject("ConformalDecals text camera"); + _cameraObject.transform.parent = transform; + _cameraObject.transform.SetPositionAndRotation(Vector3.back, Quaternion.identity); - if (glyphFont == font) { - if (!Mathf.Approximately(elementScale, 1)) { - glyphInfoArray[i].needsResample = true; - } + _camera = _cameraObject.AddComponent(); + _camera.enabled = false; // dont automatically render + _camera.orthographic = true; + _camera.depthTextureMode = DepthTextureMode.None; + _camera.nearClipPlane = 0.1f; + _camera.farClipPlane = 2f; + _isSetup = true; - elementScale *= baseScale; - } - else { - var fontScale = glyphFont.fontInfo.Scale / glyphFont.fontInfo.PointSize; - if (!Mathf.Approximately(fontScale, baseScale)) { - glyphInfoArray[i].needsResample = true; - } + _blitMaterial = new Material(Shabby.Shabby.FindShader(BlitShader)); + } - elementScale *= fontScale; - } + public Texture2D RenderToTexture(Texture2D texture2D, TMP_FontAsset font, string text, float fontSize, float pixelDensity) { + // generate text mesh + _tmp.SetText(text); + _tmp.font = font; + _tmp.fontSize = fontSize; + _tmp.ForceMeshUpdate(); - Debug.Log($"character scale: {glyphFont.fontInfo.Scale / glyphFont.fontInfo.PointSize}"); - Debug.Log($"character needs resampling: {glyphInfoArray[i].needsResample}"); + // calculate camera and texture size + var mesh = _tmp.mesh; + var bounds = mesh.bounds; - glyphInfoArray[i].size.x = (int) ((glyph.width + (padding * 2)) * elementScale); - glyphInfoArray[i].size.y = (int) ((glyph.height + (padding * 2)) * elementScale); - glyphInfoArray[i].position.x = (int) ((xAdvance + glyph.xOffset - padding) * elementScale); - glyphInfoArray[i].position.y = (int) ((baseline + glyph.yOffset - padding) * elementScale); - - Debug.Log($"character size: {glyphInfoArray[i].size}"); - Debug.Log($"character position: {glyphInfoArray[i].position}"); - } + var width = Mathf.NextPowerOfTwo((int) (bounds.size.x * pixelDensity)); + var height = Mathf.NextPowerOfTwo((int) (bounds.size.y * pixelDensity)); - // calculate texture bounds - int xOffset = glyphInfoArray[0].position.x; - var textureWidth = (glyphInfoArray[charArray.Length - 1].position.x + glyphInfoArray[charArray.Length - 1].size.x) - xOffset; - var textureHeight = (int) baseScale * (ascender + descender + padding * 2); + _camera.orthographicSize = height / pixelDensity / 2; + _camera.aspect = (float) width / height; - // ensure texture sizes are powers of 2 - textureWidth = Mathf.NextPowerOfTwo(textureWidth); - textureHeight = Mathf.NextPowerOfTwo(textureHeight); - Debug.Log($"texture is {textureWidth} x {textureHeight}"); + _cameraObject.transform.localPosition = new Vector3(bounds.center.x, bounds.center.y, -1); - var texture = new Texture2D(textureWidth, textureHeight, TextureFormat.Alpha8, true); + width = Mathf.Min(width, MaxTextureSize); + height = Mathf.Max(height, MaxTextureSize); - var colors = new Color32[textureWidth * textureHeight]; + // setup render texture + var renderTex = RenderTexture.GetTemporary(width, height, 0, TextRenderTextureFormat, RenderTextureReadWrite.Linear, 1); + _camera.targetTexture = renderTex; - for (var i = 0; i < fontInfoArray.Length; i++) { - if (fontInfoArray[i].font == null) break; - fontInfoArray[i].fontAtlas = fontInfoArray[i].font.atlas; - fontInfoArray[i].fontAtlasColors = fontInfoArray[i].fontAtlas.GetPixels32(); - } + // setup material + _blitMaterial.SetTexture(PropertyIDs._MainTex, font.atlas); + _blitMaterial.SetPass(0); - for (int i = 0; i < charArray.Length; i++) { - var glyphInfo = glyphInfoArray[i]; - var glyph = glyphInfo.glyph; - var fontInfo = fontInfoArray[glyphInfo.fontIndex]; - - var srcPos = new Vector2Int((int) glyph.x, (int) glyph.y); - var dstPos = glyphInfo.position; - dstPos.x += xOffset; - var dstSize = glyphInfo.size; - - Debug.Log($"rendering character number {i}"); - - if (glyphInfo.needsResample) { - var srcSize = new Vector2(glyph.width, glyph.height); - TextureUtils.BlitRectBilinearAlpha(fontInfo.fontAtlas, srcPos, srcSize, texture, colors, dstPos, dstSize, TextureUtils.BlitMode.Add); - } - else { - TextureUtils.BlitRectAlpha(fontInfo.fontAtlas, fontInfo.fontAtlasColors, srcPos, texture, colors, dstPos, dstSize, TextureUtils.BlitMode.Add); - } - } + // draw the mesh + Graphics.DrawMeshNow(mesh, _tmp.renderer.localToWorldMatrix); - texture.Apply(true); + var request = AsyncGPUReadback.Request(renderTex, 0, TextTextureFormat); - return texture; + request.WaitForCompletion(); + + if (request.hasError) { + throw new Exception("[ConformalDecals] Error encountered trying to request render texture data from the GPU!"); + } + + } } } \ No newline at end of file diff --git a/Source/ConformalDecals/Text/TextSettings.cs b/Source/ConformalDecals/Text/TextSettings.cs new file mode 100644 index 0000000..366c02f --- /dev/null +++ b/Source/ConformalDecals/Text/TextSettings.cs @@ -0,0 +1,5 @@ +namespace ConformalDecals.Text { + public struct TextSettings { + + } +} \ No newline at end of file