制作这个颜色查看器的目的

一直以来我都觉得调试GPU代码是一件很困难的事情,在屏幕上显示的颜色会被限制在0和1之间,所以我很希望能够有那么一个工具,能够让我切切实实的看到输出的数据。如果是写的CPU代码的话,又会对伽马空间的矫正之类的有一些担心,而且可复用程度不够高。因此我就希望能够在GPU传回的图像上直接显示颜色和其对应的数值。

碰巧之前在Shader Toy上看到了一些在GPU上输出文字的案例,研究了一下之后,就制作了这么一个颜色查看器

GPU显示文字的原理

一种是将文字储存到一张贴图上,通过采样这张贴图来显示这些文字,可以通过SDF的方法,自由的调节文字的粗细,也不会有锯齿;另一种是将文字直接用01数据来储存,对应到屏幕上,0的区域显示黑色,1的区域显示白色,本文就采用了这种方式;当然Shader Toy上还有一种是将文字用曲线来储存的,应该是涉及到傅里叶变换。

本文的标准文字是8*12个像素大,每三行进行合并的话,就是一个24 * 4的01矩阵,这就能将一个文字使用uint4来储存,每个通道还多余8bit。具体可以看Shader Toy案例里的描述,我觉得写的十分直观了。

但是这个案例里面有一点我觉得不太好的地方,他对于每一个像素,都要计算每一个文字的颜色,也就是说文字越多计算的复杂度越大。我想的是最好能够使用StructuredBuffer的方式,先用一个pass计算要显示的文字,然后对于每一个像素,计算出要显示的文字的编号,再在Structured Buffer中找到对应的文字,最终显示出来。这样每个像素应该之多只需要做一次文字到颜色的计算,能够提高效率。

颜色查看器具体的操作

  1. 首先要先把需要显示的文字对应的uint4值计算出来,这一步之前的案例里已经有现成的数据了。最好是将这些数据组成一个数列,这样我们之后能够用其编号来访问文字对应的数据。
  2. 执行一个(1, 1, 1)的Compute Shader,获取要采样的像素点的颜色。对像素点的每一位,都将其转换成要输出的文字对应的编号,储存到_Append_CharacterIndexBuffer中。像本文中需要显示7这个数字的话,需要添加23这个编号到我们的输出数据中。同时,由于每一个像素点有rgba四个通道,但我们输出的是一个RWStructuredBuffer<int>的数据,我们还需要一个列表_RW_LengthBuffer(相当于指针),记录每一个通道对应的数据数列的区间。
  3. 对整个画面执行Compute Shader,根据每一个像素点的位置,判断是显示放大的图像、每个通道对应的文字或是原始的画面。如果要显示文字的话,根据需要显示的RGBA通道,找到对应的_RW_LengthBuffer获得编号区间,再找到_Struct_CharacterIndexBuffer中获得对应的编号,通过编号找到对应的文字。最后再根据像素的位置,求出在该文字中应当显示的颜色。
  4. 在Unity中我选择了用Volume和Renderer Feature的方法,对后处理之前的画面进行颜色查看的操作。
  5. 整体想法还是尽量的减少采样,减少文字到颜色的计算,以及减少分支判断。

Font.hlsl

就是案例中的8*12的文字对应的uint4表,这里将其用数列来保存。

#define character_spc characterSet[0]
#define character_exc characterSet[1]
#define character_quo characterSet[2]
#define character_hsh characterSet[3]
#define character_dol characterSet[4]
#define character_pct characterSet[5]
#define character_amp characterSet[6]
#define character_apo characterSet[7]
#define character_lbr characterSet[8]
#define character_rbr characterSet[9]
#define character_ast characterSet[10]
#define character_crs characterSet[11]
#define character_com characterSet[12]
#define character_dsh characterSet[13]
#define character_per characterSet[14]
#define character_lsl characterSet[15]
#define character_0 characterSet[16]
#define character_1 characterSet[17]
#define character_2 characterSet[18]
#define character_3 characterSet[19]
#define character_4 characterSet[20]
#define character_5 characterSet[21]
#define character_6 characterSet[22]
#define character_7 characterSet[23]
#define character_8 characterSet[24]
#define character_9 characterSet[25]
#define character_col characterSet[26]
#define character_scl characterSet[27]
#define character_les characterSet[28]
#define character_equ characterSet[29]
#define character_grt characterSet[30]
#define character_que characterSet[31]
#define character_ats characterSet[32]
#define character_A characterSet[33]
#define character_B characterSet[34]
#define character_C characterSet[35]
#define character_D characterSet[36]
#define character_E characterSet[37]
#define character_F characterSet[38]
#define character_G characterSet[39]
#define character_H characterSet[40]
#define character_I characterSet[41]
#define character_J characterSet[42]
#define character_K characterSet[43]
#define character_L characterSet[44]
#define character_M characterSet[45]
#define character_N characterSet[46]
#define character_O characterSet[47]
#define character_P characterSet[48]
#define character_Q characterSet[49]
#define character_R characterSet[50]
#define character_S characterSet[51]
#define character_T characterSet[52]
#define character_U characterSet[53]
#define character_V characterSet[54]
#define character_W characterSet[55]
#define character_X characterSet[56]
#define character_Y characterSet[57]
#define character_Z characterSet[58]
#define character_lsb characterSet[59]
#define character_rsl characterSet[60]
#define character_rsb characterSet[61]
#define character_pow characterSet[62]
#define character_usc characterSet[63]
#define character_a characterSet[64]
#define character_b characterSet[65]
#define character_c characterSet[66]
#define character_d characterSet[67]
#define character_e characterSet[68]
#define character_f characterSet[69]
#define character_g characterSet[70]
#define character_h characterSet[71]
#define character_i characterSet[72]
#define character_j characterSet[73]
#define character_k characterSet[74]
#define character_l characterSet[75]
#define character_m characterSet[76]
#define character_n characterSet[77]
#define character_o characterSet[78]
#define character_p characterSet[79]
#define character_q characterSet[80]
#define character_r characterSet[81]
#define character_s characterSet[82]
#define character_t characterSet[83]
#define character_u characterSet[84]
#define character_v characterSet[85]
#define character_w characterSet[86]
#define character_x characterSet[87]
#define character_y characterSet[88]
#define character_z characterSet[89]
#define character_lpa characterSet[90]
#define character_bar characterSet[91]
#define character_rpa characterSet[92]
#define character_tid characterSet[93]
#define character_lar characterSet[94]

static int4 characterSet[] = 
{
    int4(0x000000,0x000000,0x000000,0x000000),
    int4(0x003078,0x787830,0x300030,0x300000),
    int4(0x006666,0x662400,0x000000,0x000000),
    int4(0x006C6C,0xFE6C6C,0x6CFE6C,0x6C0000),
    int4(0x30307C,0xC0C078,0x0C0CF8,0x303000),
    int4(0x000000,0xC4CC18,0x3060CC,0x8C0000),
    int4(0x0070D8,0xD870FA,0xDECCDC,0x760000),
    int4(0x003030,0x306000,0x000000,0x000000),
    int4(0x000C18,0x306060,0x603018,0x0C0000),
    int4(0x006030,0x180C0C,0x0C1830,0x600000),
    int4(0x000000,0x663CFF,0x3C6600,0x000000),
    int4(0x000000,0x18187E,0x181800,0x000000),
    int4(0x000000,0x000000,0x000038,0x386000),
    int4(0x000000,0x0000FE,0x000000,0x000000),
    int4(0x000000,0x000000,0x000038,0x380000),
    int4(0x000002,0x060C18,0x3060C0,0x800000),
    int4(0x007CC6,0xD6D6D6,0xD6D6C6,0x7C0000),
    int4(0x001030,0xF03030,0x303030,0xFC0000),
    int4(0x0078CC,0xCC0C18,0x3060CC,0xFC0000),
    int4(0x0078CC,0x0C0C38,0x0C0CCC,0x780000),
    int4(0x000C1C,0x3C6CCC,0xFE0C0C,0x1E0000),
    int4(0x00FCC0,0xC0C0F8,0x0C0CCC,0x780000),
    int4(0x003860,0xC0C0F8,0xCCCCCC,0x780000),
    int4(0x00FEC6,0xC6060C,0x183030,0x300000),
    int4(0x0078CC,0xCCEC78,0xDCCCCC,0x780000),
    int4(0x0078CC,0xCCCC7C,0x181830,0x700000),
    int4(0x000000,0x383800,0x003838,0x000000),
    int4(0x000000,0x383800,0x003838,0x183000),
    int4(0x000C18,0x3060C0,0x603018,0x0C0000),
    int4(0x000000,0x007E00,0x7E0000,0x000000),
    int4(0x006030,0x180C06,0x0C1830,0x600000),
    int4(0x0078CC,0x0C1830,0x300030,0x300000),
    int4(0x007CC6,0xC6DEDE,0xDEC0C0,0x7C0000),
    int4(0x003078,0xCCCCCC,0xFCCCCC,0xCC0000),
    int4(0x00FC66,0x66667C,0x666666,0xFC0000),
    int4(0x003C66,0xC6C0C0,0xC0C666,0x3C0000),
    int4(0x00F86C,0x666666,0x66666C,0xF80000),
    int4(0x00FE62,0x60647C,0x646062,0xFE0000),
    int4(0x00FE66,0x62647C,0x646060,0xF00000),
    int4(0x003C66,0xC6C0C0,0xCEC666,0x3E0000),
    int4(0x00CCCC,0xCCCCFC,0xCCCCCC,0xCC0000),
    int4(0x007830,0x303030,0x303030,0x780000),
    int4(0x001E0C,0x0C0C0C,0xCCCCCC,0x780000),
    int4(0x00E666,0x6C6C78,0x6C6C66,0xE60000),
    int4(0x00F060,0x606060,0x626666,0xFE0000),
    int4(0x00C6EE,0xFEFED6,0xC6C6C6,0xC60000),
    int4(0x00C6C6,0xE6F6FE,0xDECEC6,0xC60000),
    int4(0x00386C,0xC6C6C6,0xC6C66C,0x380000),
    int4(0x00FC66,0x66667C,0x606060,0xF00000),
    int4(0x00386C,0xC6C6C6,0xCEDE7C,0x0C1E00),
    int4(0x00FC66,0x66667C,0x6C6666,0xE60000),
    int4(0x0078CC,0xCCC070,0x18CCCC,0x780000),
    int4(0x00FCB4,0x303030,0x303030,0x780000),
    int4(0x00CCCC,0xCCCCCC,0xCCCCCC,0x780000),
    int4(0x00CCCC,0xCCCCCC,0xCCCC78,0x300000),
    int4(0x00C6C6,0xC6C6D6,0xD66C6C,0x6C0000),
    int4(0x00CCCC,0xCC7830,0x78CCCC,0xCC0000),
    int4(0x00CCCC,0xCCCC78,0x303030,0x780000),
    int4(0x00FECE,0x981830,0x6062C6,0xFE0000),
    int4(0x003C30,0x303030,0x303030,0x3C0000),
    int4(0x000080,0xC06030,0x180C06,0x020000),
    int4(0x003C0C,0x0C0C0C,0x0C0C0C,0x3C0000),
    int4(0x10386C,0xC60000,0x000000,0x000000),
    int4(0x000000,0x000000,0x000000,0x00FF00),
    int4(0x000000,0x00780C,0x7CCCCC,0x760000),
    int4(0x00E060,0x607C66,0x666666,0xDC0000),
    int4(0x000000,0x0078CC,0xC0C0CC,0x780000),
    int4(0x001C0C,0x0C7CCC,0xCCCCCC,0x760000),
    int4(0x000000,0x0078CC,0xFCC0CC,0x780000),
    int4(0x00386C,0x6060F8,0x606060,0xF00000),
    int4(0x000000,0x0076CC,0xCCCC7C,0x0CCC78),
    int4(0x00E060,0x606C76,0x666666,0xE60000),
    int4(0x001818,0x007818,0x181818,0x7E0000),
    int4(0x000C0C,0x003C0C,0x0C0C0C,0xCCCC78),
    int4(0x00E060,0x60666C,0x786C66,0xE60000),
    int4(0x007818,0x181818,0x181818,0x7E0000),
    int4(0x000000,0x00FCD6,0xD6D6D6,0xC60000),
    int4(0x000000,0x00F8CC,0xCCCCCC,0xCC0000),
    int4(0x000000,0x0078CC,0xCCCCCC,0x780000),
    int4(0x000000,0x00DC66,0x666666,0x7C60F0),
    int4(0x000000,0x0076CC,0xCCCCCC,0x7C0C1E),
    int4(0x000000,0x00EC6E,0x766060,0xF00000),
    int4(0x000000,0x0078CC,0x6018CC,0x780000),
    int4(0x000020,0x60FC60,0x60606C,0x380000),
    int4(0x000000,0x00CCCC,0xCCCCCC,0x760000),
    int4(0x000000,0x00CCCC,0xCCCC78,0x300000),
    int4(0x000000,0x00C6C6,0xD6D66C,0x6C0000),
    int4(0x000000,0x00C66C,0x38386C,0xC60000),
    int4(0x000000,0x006666,0x66663C,0x0C18F0),
    int4(0x000000,0x00FC8C,0x1860C4,0xFC0000),
    int4(0x001C30,0x3060C0,0x603030,0x1C0000),
    int4(0x001818,0x181800,0x181818,0x180000),
    int4(0x00E030,0x30180C,0x183030,0xE00000),
    int4(0x0073DA,0xCE0000,0x000000,0x000000),
    int4(0x000000,0x10386C,0xC6C6FE,0x000000),
};

ColorInspector.cs

提供文字的颜色,背景的颜色,放大镜的放大倍率,文字的大小这些参数,方便实际使用时进行调整。当游戏不运行的时候,传入需要采样的坐标;当游戏运行的时候,传入鼠标的位置作为采样的坐标。

using System;

namespace UnityEngine.Rendering.Universal
{
    [Serializable, VolumeComponentMenu("Debugger/Color Inspector")]
    public class ColorInspector : VolumeComponent, IPostProcessComponent
    {
        public BoolParameter isEnabled = new BoolParameter(false);

        public ColorParameter textColor = new ColorParameter(Color.white);
        public ColorParameter backgroundColor = new ColorParameter(Color.black);

        public Vector2Parameter samplePosition = new Vector2Parameter(new Vector2(200, 400));
        public ClampedIntParameter sampleWindowSize = new ClampedIntParameter(20, 1, 100);
        public ClampedFloatParameter sampleResolution = new ClampedFloatParameter(5f, 1f, 20f);
        public Vector2Parameter fontSize = new Vector2Parameter(new Vector2(100, 100));

        public bool IsActive()
        {
            return isEnabled.value;
        }

        public bool IsTileCompatible()
        {
            return false;
        }
    }
}

ColorInspectorRendererFeature.cs

很普通的将ComputeShader参数暴露给使用者,当场景中存在ColorInspector这个Volume的时候,执行Color Inspector Pass。

using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
using System.Collections.Generic;

public class ColorInspectorRendererFeature : ScriptableRendererFeature
{
    [System.Serializable]
    public class ColorInspectorSetting
    {
        public ComputeShader colorInspectorComputeShader;
    }

    private ColorInspectorRenderPass colorInspectorRenderPass;
    public ColorInspectorSetting settings = new ColorInspectorSetting();

    public override void Create()
    {
        colorInspectorRenderPass = new ColorInspectorRenderPass(settings);
    }

    public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
    {
        ColorInspector colorInspector = VolumeManager.instance.stack.GetComponent<ColorInspector>();
        if(colorInspector != null)
        {
            if(colorInspector.IsActive())
            {
                if (renderingData.cameraData.isSceneViewCamera || renderingData.cameraData.camera.cameraType == CameraType.Game)
                {
                    colorInspectorRenderPass.Setup(colorInspector);
                    renderer.EnqueuePass(colorInspectorRenderPass);
                }
            }
        }
    }
}

ColorInspectorRenderPass.cs

需要执行两次Compute Shader,第一次只执行一个线程,用来获取颜色并输出两个Buffer,一个用于确定RGBA通道对应的编号,另一个用于查找编号对应的文字对应的uint4值,第二次对整个画面进行计算,判断出需要具体绘制的东西。Buffer的创建和销毁可能有点问题,不过关系不大。

using System;
using UnityEngine;
using System.Collections.Generic;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;

public class ColorInspectorRenderPass : ScriptableRenderPass, IDisposable
{
    static int MAX_DEBUG_LENGTH = 35;
    static int[] zeroArray;

    private const string profilerTag = "Color Inspector";

    private ProfilingSampler inspectSampler = new ProfilingSampler("Color Inspect Pass");
    
    private RenderTargetHandle cameraColorHandle;
    private RenderTargetIdentifier cameraColorIden;

    private static readonly int colorInspectTextureID = Shader.PropertyToID("_ColorInspectBuffer");
    private RenderTargetHandle colorInspectTextureHandle;
    private RenderTargetIdentifier colorInspectTextureIden;

    private ColorInspectorRendererFeature.ColorInspectorSetting settings;
    private ComputeShader colorInspectorComputeShader;
    private ColorInspector colorInspector;
    private Vector2Int textureSize;

    private ComputeBuffer lengthBuffer;
    private ComputeBuffer characterIndexBuffer;

    public ColorInspectorRenderPass(ColorInspectorRendererFeature.ColorInspectorSetting settings)
    {
        profilingSampler = new ProfilingSampler(profilerTag);

        this.settings = settings;
        renderPassEvent = RenderPassEvent.BeforeRenderingPostProcessing;
        colorInspectorComputeShader = settings.colorInspectorComputeShader;

        cameraColorHandle.Init("_CameraColorTexture");
        cameraColorIden = cameraColorHandle.Identifier();

        colorInspectTextureHandle.Init(colorInspectTextureID);
        colorInspectTextureIden = colorInspectTextureHandle.Identifier();

        zeroArray = new int[35] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                                0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                                0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                                0, 0, 0, 0, 0,};

        if (lengthBuffer != null)
        {
            lengthBuffer.Release();
        }
        lengthBuffer = new ComputeBuffer(5, 4, ComputeBufferType.Structured);

        if(characterIndexBuffer != null)
        {
            characterIndexBuffer.Release();
        }
        characterIndexBuffer = new ComputeBuffer(MAX_DEBUG_LENGTH, 4, ComputeBufferType.Append);
    }

    public void Setup(ColorInspector colorDebugger)
    {
        this.colorInspector = colorDebugger;
    }

    public override void Configure(CommandBuffer cmd, RenderTextureDescriptor cameraTextureDescriptor)
    {
        RenderTextureDescriptor desc = cameraTextureDescriptor;
        textureSize = new Vector2Int(cameraTextureDescriptor.width, cameraTextureDescriptor.height);
        desc.enableRandomWrite = true;
        cmd.GetTemporaryRT(colorInspectTextureID, desc);
    }

    private void DoDebug(CommandBuffer cmd, RenderTargetIdentifier colorid, RenderTargetIdentifier inspectid, ComputeShader computeShader)
    {
        characterIndexBuffer.SetCounterValue(0);
        characterIndexBuffer.SetData(zeroArray);

        if (Application.isPlaying)
        {
            cmd.SetComputeVectorParam(computeShader, "_SamplePosition", Input.mousePosition);
        }
        else
        {
            cmd.SetComputeVectorParam(computeShader, "_SamplePosition", colorInspector.samplePosition.value);
        }
        cmd.SetComputeVectorParam(computeShader, "_TextureSize", new Vector4(textureSize.x, textureSize.y, 1f / textureSize.x, 1f / textureSize.y));

        int fetchColorKernel = computeShader.FindKernel("FetchMain");
        cmd.SetComputeTextureParam(computeShader, fetchColorKernel, "_ColorTexture", colorid);
        cmd.SetComputeBufferParam(computeShader, fetchColorKernel, "_Append_CharacterIndexBuffer", characterIndexBuffer);
        cmd.SetComputeBufferParam(computeShader, fetchColorKernel, "_RW_LengthBuffer", lengthBuffer);
        cmd.DispatchCompute(computeShader, fetchColorKernel, 1, 1, 1);


        int inspectKernel = computeShader.FindKernel("ColorInspectMain");
        computeShader.GetKernelThreadGroupSizes(inspectKernel, out uint x, out uint y, out uint z);
        cmd.SetComputeTextureParam(computeShader, inspectKernel, "_ColorTexture", colorid);
        cmd.SetComputeBufferParam(computeShader, inspectKernel, "_RW_LengthBuffer", lengthBuffer);
        cmd.SetComputeBufferParam(computeShader, inspectKernel, "_Struct_CharacterIndexBuffer", characterIndexBuffer);
        cmd.SetComputeTextureParam(computeShader, inspectKernel, "_RW_ColorInspectTexture", inspectid);
        cmd.SetComputeVectorParam(computeShader, "_TextColor", colorInspector.textColor.value.linear);
        cmd.SetComputeVectorParam(computeShader, "_TextBackgroundColor", colorInspector.backgroundColor.value.linear);
        cmd.SetComputeFloatParam(computeShader, "_SampleWindowSize", (float)colorInspector.sampleWindowSize.value);
        cmd.SetComputeFloatParam(computeShader, "_SampleResolution", (float)colorInspector.sampleResolution.value);

        cmd.SetComputeVectorParam(computeShader, "_FontSize", colorInspector.fontSize.value);


        cmd.DispatchCompute(computeShader, inspectKernel,
                    Mathf.CeilToInt((float)textureSize.x / x),
                    Mathf.CeilToInt((float)textureSize.y / y),
                    1);

        cmd.Blit(colorInspectTextureIden, cameraColorIden);
    }

    public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
    {
        CommandBuffer cmd = CommandBufferPool.Get(profilerTag);
        context.ExecuteCommandBuffer(cmd);
        cmd.Clear();

        using (new ProfilingScope(cmd, inspectSampler))
        {
            DoDebug(cmd, cameraColorIden, colorInspectTextureIden, colorInspectorComputeShader);
        }

        context.ExecuteCommandBuffer(cmd);
        cmd.Clear();
        CommandBufferPool.Release(cmd);
    }

    public override void FrameCleanup(CommandBuffer cmd)
    {
        cmd.ReleaseTemporaryRT(colorInspectTextureID);
    }

    public void Release()
    {
        lengthBuffer.Release();
        characterIndexBuffer.Release();
    }

    public void Dispose()
    {
        Release();
    }
}

ColorInspectorComputeShader.compute

大部分之前已经讲过了。这里用AppendStructuredBuffer或者普通的StructuredBuffer来保存RGBA通道的编号应该是都可行的。for循环如果不加[unroll]的话,可能会导致计算出来的某几位和实际的数字不相同,就当是个bug好了。colRowIndextextColRowIndex在计算文字中某个像素的颜色时特别关键(我在Shder Toy上分别记作textColRowIndexcharacterColRowIndex,懒得改了),一个是用来得到当前的文字,另一个则是得到当前文字里具体的像素的颜色。

很需要注意uintint的除法,有时候弄得很混乱,有时候甚至和floor混用,有点抽象。

#pragma kernel FetchMain
#pragma kernel ColorInspectMain

#include "Font.hlsl"

Texture2D<float4> _ColorTexture;
RWTexture2D<float4> _RW_ColorInspectTexture;
RWStructuredBuffer<uint> _RW_LengthBuffer;
AppendStructuredBuffer<uint> _Append_CharacterIndexBuffer;
StructuredBuffer<uint> _Struct_CharacterIndexBuffer;

SamplerState sampler_LinearClamp;

float2 _SamplePosition;
float4 _TextureSize;

float4 _TextColor;
float _SampleWindowSize;
float _SampleResolution;
float2 _FontSize;
float4 _TextBackgroundColor;

void FloatToCharacterIndex(float floatValue, inout uint totalLength)
{
    float signValue = sign(floatValue);
    floatValue = signValue * floatValue;

    if(signValue == -1)
    {
        _Append_CharacterIndexBuffer.Append(13);
        totalLength ++;
    }

    if (signValue == 0)
    {
        _Append_CharacterIndexBuffer.Append(16);
        totalLength ++;
    }
    else
    {
        int valueLength = max((int)log10(floatValue) + 1, 1);

        //Specify unroll to prevent bugs
        [unroll(5)]
        for (int i = 0; i < valueLength; i++)
        {
            _Append_CharacterIndexBuffer.Append(((uint)floatValue / (uint)pow(10, valueLength - i - 1)) % 10 + 16);
            totalLength ++;
        }
    }

    //Handle with two decimals
    float fracValue = frac(floatValue);
    _Append_CharacterIndexBuffer.Append(14);
    totalLength ++;

    _Append_CharacterIndexBuffer.Append((int)(fracValue * 10) % 10 + 16);
    totalLength ++;

    _Append_CharacterIndexBuffer.Append((int)(fracValue * 100) % 10 + 16);
    totalLength ++;
}

[numthreads(1, 1, 1)]
void FetchMain(uint3 id : SV_DispatchThreadID)
{
    uint2 clamppedPosition = clamp(floor(_SamplePosition), 0, _TextureSize.xy - 1);
    float4 sampleColor = _ColorTexture.Load(uint3(clamppedPosition, 0));
    
    float4 vectorFour = sampleColor;

    uint totalLength = 0;
    _RW_LengthBuffer[0] = 0;

    FloatToCharacterIndex(vectorFour.x, totalLength);
    _RW_LengthBuffer[1] = totalLength;

    FloatToCharacterIndex(vectorFour.y, totalLength);
    _RW_LengthBuffer[2] = totalLength;

    FloatToCharacterIndex(vectorFour.z, totalLength);
    _RW_LengthBuffer[3] = totalLength;

    FloatToCharacterIndex(vectorFour.w, totalLength);
    _RW_LengthBuffer[4] = totalLength;

}

float GetBit(int characterInt, int bitIndex)
{
    //in bBound detection can be removed
    bool inBound = (bitIndex >= 0) && (bitIndex <= 23);
    return inBound ? (characterInt >> bitIndex) & 0x1 : 0.0;
}

bool GetTextBracket(int2 currentPosition, int2 textWindowUpperLeft, int2 fontSize, inout int2 colRowIndex, inout int2 textColRowIndex)
{
    float2 offset = currentPosition - textWindowUpperLeft;
    offset.y = -offset.y;

    colRowIndex = floor(offset * rcp(fontSize));
    textColRowIndex = (offset - colRowIndex * fontSize) / (int2)_FontSize;

    int rowLimit = 3;
    int colLimit = _RW_LengthBuffer[colRowIndex.y + 1] - _RW_LengthBuffer[colRowIndex.y];
    bool inTextBracket = (colRowIndex.y >= 0) && (colRowIndex.y <= rowLimit) && (colRowIndex.x >= 0) && (colRowIndex.x <= colLimit - 1);
    return inTextBracket;
}

float4 GetTextColor(int2 currentPosition, int2 colRowIndex, int2 textColRowIndex)
{
    int characterIndex = _RW_LengthBuffer[colRowIndex.y] + colRowIndex.x;
    int4 character = characterSet[_Struct_CharacterIndexBuffer[characterIndex]];
    textColRowIndex = int2(7, 11) - textColRowIndex;

    int bitIndex = textColRowIndex.x + textColRowIndex.y * 8;
    int bracket = bitIndex / 24u;
    float characterWeight = GetBit(character[3 - bracket], bitIndex - bracket * 24);
    return lerp(_TextBackgroundColor, _TextColor, characterWeight);
}

float4 GetSampleColor(int2 currentPosition, int2 samplePosition, int resolution)
{
    int2 sampleOffset = currentPosition - samplePosition;
    int2 sampleCoord = floor((float2)sampleOffset / resolution);
    float4 sampleColor = _ColorTexture.SampleLevel(sampler_LinearClamp, (samplePosition + sampleCoord + 0.5) * _TextureSize.zw, 0);
    return sampleColor;
}

bool CheckInRectangle(uint2 currentPosition, uint2 bottomLeft, uint2 topRight, inout bool onBorder)
{
    onBorder = any(currentPosition == bottomLeft) || any(currentPosition == topRight);
    return all(currentPosition >= bottomLeft) && all(currentPosition <= topRight);
}

[numthreads(8, 8, 1)]
void ColorInspectMain(uint3 id : SV_DispatchThreadID)
{
    int2 clamppedPosition = clamp(floor(_SamplePosition), 0, _TextureSize.xy - 1);
    float4 mainTex = _ColorTexture.Load(int3(id.xy, 0));

    int sampleWindowSize = _SampleWindowSize;
    int halfSampleWindowSize = sampleWindowSize >> 1;
    int halfSampleWindowSizePlus = (sampleWindowSize ++) >> 1;

    //Draw sample window
    bool onBorder = 0;
    bool inColor = CheckInRectangle(id.xy, clamppedPosition - halfSampleWindowSize, clamppedPosition + halfSampleWindowSizePlus, onBorder);
    float4 sampleColor = GetSampleColor(id.xy, clamppedPosition, _SampleResolution);
    sampleColor = lerp(sampleColor, frac(sampleColor + 0.5), onBorder);

    //Draw text window
    int2 bracketSize = int2(8, 12) * (int2)_FontSize;
    int2 colRowIndex = 0;
    int2 textColRowIndex = 0;
    int2 textUpperLeftConor = clamppedPosition + halfSampleWindowSizePlus + int2(1, 0);
    bool inTextBracket = GetTextBracket(id.xy, textUpperLeftConor, bracketSize, colRowIndex, textColRowIndex);
    float4 textColor = GetTextColor(id.xy, colRowIndex, textColRowIndex);
    
    float4 finalColor = lerp(mainTex, sampleColor, inColor);
    finalColor.rgb = lerp(finalColor.rgb, textColor.rgb, textColor.a * inTextBracket);

    _RW_ColorInspectTexture[id.xy] = finalColor;
}

写后感

好久没有写新的博客啦,好不容易整了点有趣的活。

在GPU中显示文字确实是个有趣的事情。做的过程中要思考每一个比特的偏移,每一个像素在文字当中的偏移,每一个文字在整个画面中的偏移,确实蛮考验耐心的。使用各种index,利用index来访问数列,就有一种在写普通的C语言的感觉。目前显示的数字只支持从-9999.99到9999.99,不过多了也没什么特别的必要罢了,再细致的话还可以判断颜色是不是NAN和INF,这里就暂不考虑了。