制作这个颜色查看器的目的
一直以来我都觉得调试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中找到对应的文字,最终显示出来。这样每个像素应该之多只需要做一次文字到颜色的计算,能够提高效率。
颜色查看器具体的操作
- 首先要先把需要显示的文字对应的
uint4
值计算出来,这一步之前的案例里已经有现成的数据了。最好是将这些数据组成一个数列,这样我们之后能够用其编号来访问文字对应的数据。 - 执行一个(1, 1, 1)的Compute Shader,获取要采样的像素点的颜色。对像素点的每一位,都将其转换成要输出的文字对应的编号,储存到
_Append_CharacterIndexBuffer
中。像本文中需要显示7
这个数字的话,需要添加23
这个编号到我们的输出数据中。同时,由于每一个像素点有rgba
四个通道,但我们输出的是一个RWStructuredBuffer<int>
的数据,我们还需要一个列表_RW_LengthBuffer
(相当于指针),记录每一个通道对应的数据数列的区间。 - 对整个画面执行Compute Shader,根据每一个像素点的位置,判断是显示放大的图像、每个通道对应的文字或是原始的画面。如果要显示文字的话,根据需要显示的RGBA通道,找到对应的
_RW_LengthBuffer
获得编号区间,再找到_Struct_CharacterIndexBuffer
中获得对应的编号,通过编号找到对应的文字。最后再根据像素的位置,求出在该文字中应当显示的颜色。 - 在Unity中我选择了用Volume和Renderer Feature的方法,对后处理之前的画面进行颜色查看的操作。
- 整体想法还是尽量的减少采样,减少文字到颜色的计算,以及减少分支判断。
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好了。colRowIndex
和textColRowIndex
在计算文字中某个像素的颜色时特别关键(我在Shder Toy上分别记作textColRowIndex
和characterColRowIndex
,懒得改了),一个是用来得到当前的文字,另一个则是得到当前文字里具体的像素的颜色。
很需要注意uint
和int
的除法,有时候弄得很混乱,有时候甚至和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,这里就暂不考虑了。