main方法 如何去掉http debug日志_Unity通用渲染管线Shader日志输出工具

55d678bf967627b2b942f89cdfc488ac.png

今年的春节很特别,由于新型冠状病毒的原因,只能宅在家里为国做贡献。也正因为宅在家里,给了自己大量写代码的时间,也终于完成了曾经一直想写但是没有时间写的一个工具。

废话不多话,直接进入正题。

在Unity开发过程中,如果需要输出调试日志只需要在C#中调用Debug.Log即可,但是Shader由于硬件结构上的问题无法像C#一样轻松的输出调试日志。因此在Shader编码过程中调试就成为了一个很困难的事情,比如想知道VS中某个中间变量结果是否正确等等。

我写的这个工具就是希望能把Shader中的变量能像C#一样能够输出,解决调试中遇到的困难。当然原理与C#中的日志输出是完全不同的,针对不同的Shader解决方法也是不同的。


开发环境

Unity2019.3+URP

支持的调试Shader类型:VertexShader、FragmentShader、ComputeShader

VertexShader中的日志输出

顶点着色器与像素着色器是两个必须的着色器,但是不要忘记,在两者还有一个可选的着色器,几何着色器(Geometry Shader)。

关于几何着色器我这里就不详细阐述了,大家可以自行Google。

由于几何着色器可以为模型添加新的顶点,并且还没有经过光栅化,因此我们可以将顶点着色器中需要输出的变量存储到纹理通道中,然后在几何着色阶段利用新增的顶点将这个变量的内容画到屏幕上。

下面直接介绍使用方法:

以调试Lit.shader为例(工程中参见LitDebugVertex.shader)

先看下效果:

0514e913b085c699e4ffa036410be6e5.png
对红圈内的模型Shader进行日志输出

8705bb6db5f14dc7796629abeb6ca0d9.png
调试过程中的模型会以Wireframe的模式渲染,点击某一个顶点会输出调试的日志

对需要日志输出的Shader进行简单改造

1)在原先fragment声明的地方插入如下代码,然后注释掉原先的声明

#pragma vertex LitPassVertex
//#pragma fragment LitPassFragment
//1、VertexDebug: 在#pragma fragment xxx后前添加,同时注释掉此行
#pragma geometry geom		//关闭调试注释此行
#pragma fragment debugFrag	//关闭调试注释此行
#define VERTEX_DEBUG_ENABLE	//关闭调试注释此行
#define VERTEX_DEBUG_INDEX 0    //选取的顶点所在三角形index(0,1,2,3-表示全部检测)
#include "Packages/com.seasun.graphics/Shaders/Debug/VertexDebug.hlsl"

如果想取消调试,恢复到正常的渲染模式,可以注释掉上述标记的3行代码,并恢复原先的fragment函数声明。

2)因为替换了fragment函数,所以需要修改原先vertex函数的名称

//2、VertexDebug: 修改Vert函数分布传入4个参数:返回类型,函数名,数据结构体名称,结构体实例
VERTEX_DEBUG_FUN(Varyings, LitPassVertex, Attributes, input)
//Varyings LitPassVertex(Attributes input)
{
    Varyings output = (Varyings)0;

	float4 mrtValue = 0;

    UNITY_SETUP_INSTANCE_ID(input);
    UNITY_TRANSFER_INSTANCE_ID(input, output);
    UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(output);

3)初始化

    UNITY_SETUP_INSTANCE_ID(input);
    UNITY_TRANSFER_INSTANCE_ID(input, output);
    UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(output);

    VertexPositionInputs vertexInput = GetVertexPositionInputs(input.positionOS.xyz);

    //3、VertexDebug: 初始化,传递投影后的坐标值
    VERTEX_DEBUG_INIT(vertexInput.positionCS)

4)添加想要输出的变量

#if defined(_MAIN_LIGHT_SHADOWS) && !defined(_RECEIVE_SHADOWS_OFF)
    output.shadowCoord = GetShadowCoord(vertexInput);
#endif

    output.positionCS = vertexInput.positionCS;

	//4、VertexDebug: 根据屏幕采点,自动选择顶点(可选,也可以自己填写)
	if (VERTEX_DEBUG_AUTO_JUDGE)
	{
		//5、VertexDebug: 输入想要调试输出的变量,支持xy2个参数
		VERTEX_DEBUG_VALUE(xy, input.lightmapUV.xy)
	}

由于Shader是并行执行,因此在调试期间会有多个顶点执行同样的一段代码,因此这里有两种方法来指定某一个顶点输入。

一种是像示例中的一样使用这个宏,然后在场景的运行的时候按住Alt,用鼠标点击模型的顶点,然后就会输出选中顶点的日志(如果游戏顶点比较密集,会输出多个顶点的日志)。

另一种方式是自己设置约束,来在Shader中指定某一个顶点输出日志。

5)改写返回

    //6、VertexDebug: 将原始输出结构放入宏中
    VERTEX_DEBUG_OUTPUT(output)
    //return output;

开始调试

在调试场景中,找个任意一个GameObject,挂载VertexDebug.cs脚本,然后启动游戏。

按住Alt,用鼠标点击待调试模型的顶点。

调节摄像机的视角,使待调试的顶点进行放大,避免其他顶点的干扰。

FragmentShader中的日志输出

像素着色器不像顶点着色器那样,中间有几何着色器辅助输出,因此像素着色器中的调试信息只能存储到颜色缓冲区中。但是存储到颜色缓冲区中的内容不仅会影响最终的渲染结果,也会受到后期等因素的影响。假如我们使用MRT,就可以解决上述问题。

Unity中的MRT可参见延迟渲染

https://docs.unity3d.com/Manual/RenderTech-DeferredShading.html​docs.unity3d.com

对URP进行改造

URP由于使用正向渲染,因此并没有启用MRT,所以需要稍微改造,已到达支持的目的。具体内容这里就不阐述了,可以在工程中搜索宏FRAGMENG_DEBUG查看改造的内容。

下面直接介绍使用方法:

以调试Lit.shader为例(工程中参见LitDebugFragment.shader)

先看下效果:

4c2c686a67f7fec4a2000fe0365451d4.png
木材Shader为需要调试的,插入代码后,渲染结果不会受到任何影响

84c258496590b485fd7958bae1822d49.png
按住Ctrl,点击需要显示输出内容的像素点,同时会在屏幕和Console中输出内容

对需要日志输出的Shader进行简单改造

1)在HLSLPROGRAM前添加

	//1、FragmentDebug:添加混合模式
	Blend 1 One Zero

指定SV_Target1的混合方式

2)在Fragment函数前添加

	//--------------------------------------
	// GPU Instancing
	#pragma multi_compile_instancing

	//2、FragmentDebug: 在Fragment函数前添加
	#pragma multi_compile __ FRAGMENT_DEBUG_ENABLE
	#include "Packages/com.seasun.graphics/Shaders/Debug/FragmentDebug.hlsl"

	#include "ShaderPass/LitInput.hlsl"

3)改造Fragment函数名和初始化

// Used in Standard (Physically Based) shader
//half4 LitPassFragment(Varyings input) : SV_Target
//3、FragmentDebug: 修改Frag函数分别传入3个参数:函数名、v2f结构体名称、结构体实例
FRAGMENT_DEBUG_FUN(LitPassFragment, Varyings, input)
{
	//4、FragmentDebug: 初始化
	FRAGMENT_DEBUG_INIT

4)增添想输出的变量和改造返回

    half4 color = UniversalFragmentPBR(inputData, surfaceData.albedo, surfaceData.metallic, surfaceData.specular, surfaceData.smoothness, surfaceData.occlusion, surfaceData.emission, surfaceData.alpha);

    color.rgb = MixFog(color.rgb, inputData.fogCoord);

    //5、FragmentDebug: 输入想要调试输出的变量,支持xyz3个参数
    FRAGMENT_DEBUG_VALUE(xyz, surfaceData.albedo)

    //6、FragmentDebug: 将原始结果放入宏中
    FRAGMENT_DEBUG_OUTPUT(color)
    //return color;

开始调试

在PlayerSetting中增添宏FRAGMENG_DEBUG,删除此宏会自动关掉全部功能,包括对URP的改造。

在调试场景中,找个任意一个GameObject,挂载FragmentDebug.cs脚本,然后启动游戏。

按住Ctrl,用鼠标点击待调试模型的像素点,会在Console和屏幕中输出日志内容。

Ctrl+D可以显示和隐藏屏幕中的调试窗口。


ComputeShader中的日志输出

ComputeShader与上面的VS与PS不同,是完全两套流水线,基于GPGPU设计,天然就支持数据从GPU回传数据到CPU。这个工具为了更方便的调试输出,只是对原本的方法进行了一些封装。

下面直接介绍使用方法:

使用示例参见仓库中的CSTest.cs和CSDebug.compute

对执行脚本进行改造

由于ComputeShader的执行通常有2种,一种是直接执行,另一种是在CommandBuffer中执行;针对这两种方法使用上略有差别。

1)直接执行

    private void ExcuteCSManual()
    {
        CSDebug.ComputeShaderDebugSet("Debug1", m_ComputeShader, kernel);
        CSDebug.ComputeShaderDebugSet("Debug2", m_ComputeShader, kernel);

        m_ComputeShader.SetTexture(kernel, "Result", m_RenderTexture);
        m_ComputeShader.SetTexture(kernel, "Source", m_SrcTexture);
        m_ComputeShader.Dispatch(kernel, m_RenderTexture.width, m_RenderTexture.height, 1);

        Debug.Log("CS1 : " + CSDebug.ComputeShaderDebugGet("Debug1"));
        Debug.Log("CS2 : " + CSDebug.ComputeShaderDebugGet("Debug2"));

        CSDebug.ComputeShaderDebugRelease();
    }

在Dispatch之前设置变量名,可以根据实际情况设置多个,其中Debug1和Debug2为变量名。

在执行完Dispatch之后调用CSDebug.ComputeShaderDebugGet来获取ComputeShader中输出的数值。

最后执行CSDebug.ComputeShaderDebugRelease()来释放ComputeBuffer。

2)在CommandBuffer中执行

   private void ExcuteCSCommand(ScriptableRenderContext context, Camera camera)
    {
        if (camera == Camera.main)
        {
            if (m_ExcuteCommand)
            {
                m_ExcuteCommand = false;
            }
            else
            {
                return;
            }
            Debug.Log("CS1 : " + CSDebug.ComputeShaderDebugGet("Debug1"));
            Debug.Log("CS2 : " + CSDebug.ComputeShaderDebugGet("Debug2"));
            CSDebug.ComputeShaderDebugRelease();

            CommandBuffer command = CommandBufferPool.Get("ExcuteCSCommand");
            CSDebug.ComputeShaderDebugSet("Debug1", m_ComputeShader, kernel);
            CSDebug.ComputeShaderDebugSet("Debug2", m_ComputeShader, kernel);

            command.SetComputeTextureParam(m_ComputeShader, kernel, "Result", m_RenderTexture);
            command.SetComputeTextureParam(m_ComputeShader, kernel, "Source", m_SrcTexture);
            command.DispatchCompute(m_ComputeShader, kernel, m_RenderTexture.width, m_RenderTexture.height, 1);

            context.ExecuteCommandBuffer(command);
            CommandBufferPool.Release(command);
        }
    }

与执行直接调用的3个函数一样,但是由于CommandBuffer不是立即执行,而是延迟执行的,因此DispatchCompute之后ComputeBuffer并没有真正执行,也就无法获取调试的内容。

CSDebug.ComputeShaderDebugSet使用的位置同直接执行,但是Get和Release两个方法需要放到Set之前。也就是说,每次Get出来的是上一次执行的结果,第一次执行输出的内容为0。

对ComputeShader进行改造

#pragma kernel CSMain

//1) 在定义前添加
#include "Packages/com.seasun.graphics/Shaders/Debug/CSDebug.hlsl" 

RWTexture2D<float4> Result;
Texture2D Source;

//2)定义变量,其中变量名同C#中的定义
DEBUG_DEF(Debug1)
DEBUG_DEF(Debug2)

[numthreads(1, 1, 1)]
void CSMain(uint3 id : SV_DispatchThreadID)
{
	int i = id.x;
	int j = id.y;
	float c = Source[float2(i, j)].x * 0.3 + Source[float2(i, j)].y * 0.2 + Source[float2(i, j)].z * 0.5;
	Result[float2(i, j)] = float4(c, c, c, Source[float2(i, j)].w);

	if (i == 100 && j == 100)
	{
		//3)增添想要输出的变量
		DEBUG_VALUE(Debug1, Source[float2(i, j)].x)
		DEBUG_VALUE(Debug2, Source[float2(i, j)].y)
	}
}

一共3个步骤,这里就不再细说了。

开始调试

在PlayerSetting中增添CS_DEBUG宏,然后运行场景。

由于ComputeShader不像普通的Shader一样支持宏编译和变体,因此ComputeShader中宏的实现采用文件替换的方式间接实现。每次修改完宏之后需要在编辑器模式下执行一次CSDebug中的任意方法才能真正生效(也可以在编辑器模式下调试一次即可)。

4ae8860014db0f5fc8b5b3b91fcbea1d.png
点击右上角的两个按钮进行测试,结果在Console中输出

仓库地址

欢迎大家Clone使用,提出改进意见。

zouchunyi/ShaderDebug​github.com
8a6e72810b2baf57bb7b1518e290e7e6.png
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值