700字范文,内容丰富有趣,生活中的好帮手!
700字范文 > Unity通用渲染管线Shader日志输出工具

Unity通用渲染管线Shader日志输出工具

时间:2022-03-07 22:01:55

相关推荐

Unity通用渲染管线Shader日志输出工具

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

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

一、开发环境

Unity .3+URP

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

二、VertexShader中的日志输出

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

关于几何着色器就不详细阐述了,大家可以自行查阅相关资料。

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

1. 下面直接介绍使用方法:

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

先看下效果:

对红圈内的模型Shader进行日志输出

调试过程中的模型会以Wireframe的模式渲染,点击某一个顶点会输出调试的日志2. 对需要日志输出的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;3. 开始调试

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

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

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

三、FragmentShader中的日志输出

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

Unity中的MRT可参见延迟渲染:

/Manual/RenderTech-DeferredShading.html

1. 对URP进行改造

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

2. 下面直接介绍使用方法:

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

先看下效果:

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

按住Ctrl,点击需要显示输出内容的像素点,同时会在屏幕和Console中输出内容3. 对需要日志输出的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;4. 开始调试

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

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

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

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

四、ComputeShader中的日志输出

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

1. 下面直接介绍使用方法:

使用示例参见仓库中的CSTest.cs和pute。

2. 对执行脚本进行改造

由于ComputeShader的执行通常有两种,一种是直接执行,另一种是在CommandBuffer中执行。

针对这两种方法使用上略有差别:

1)直接执行

private void ExcuteCSManual() { puteShaderDebugSet("Debug1", m_ComputeShader, kernel); puteShaderDebugSet("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 : " + puteShaderDebugGet("Debug1")); Debug.Log("CS2 : " + puteShaderDebugGet("Debug2")); puteShaderDebugRelease(); }

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

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

最后执行puteShaderDebugRelease()来释放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 : " + puteShaderDebugGet("Debug1")); Debug.Log("CS2 : " + puteShaderDebugGet("Debug2")); puteShaderDebugRelease(); CommandBuffer command = CommandBufferPool.Get("ExcuteCSCommand"); puteShaderDebugSet("Debug1", m_ComputeShader, kernel); puteShaderDebugSet("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); } }

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

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

3. 对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个步骤,这里就不再细说了。

4. 开始调试

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

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

点击右上角的两个按钮进行测试,结果在Console中输出五、仓库地址

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

/zouchunyi/ShaderDebug

感谢作者邹春毅供稿。欢迎转发分享,未经作者授权请勿转载。如果您有任何独到的见解或者发现也欢迎联系我们,一起探讨。(QQ群:793972859)

作者主页:/people/zou-chun-yi-45,作者也是U Sparkle活动参与者,UWA欢迎更多开发朋友加入U Sparkle开发者计划,这个舞台有你更精彩!

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。