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

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


在Unity开发过程中,如果需要输出调试日志只需要在C#中调用Debug.Log即可,但是Shader由于硬件结构上的问题无法像C#一样轻松地输出调试日志。因此在Shader编码过程中调试就成了一个很困难的事情,比如想知道VS中某个中间变量结果是否正确等等。我写的这个工具就是希望能把Shader中的变量能像C#一样输出,解决调试中遇到的困难。当然原理与C#中的日志输出是完全不同的,针对不同的Shader解决方法也是不同的。

一、开发环境

Unity 2019.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可参见延迟渲染:https://docs.unity3d.com/Manual/RenderTech-DeferredShading.html1.  对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和CSDebug.compute。2.  对执行脚本进行改造由于ComputeShader的执行通常有两种,一种是直接执行,另一种是在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);

}

}

与执行直接调用的三个函数一样,但是由于CommandBuffer不是立即执行,而是延迟执行的,因此DispatchCompute之后ComputeBuffer并没有真正执行,也就无法获取调试的内容。CSDebug.ComputeShaderDebugSet使用的位置同直接执行,但是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使用,提出改进意见。https://github.com/zouchunyi/ShaderDebug


文末,再次感谢邹春毅的分享,如果您有任何独到的见解或者发现也欢迎联系我们,一起探讨。(QQ群:793972859)

也欢迎大家来积极参与U Sparkle开发者计划,简称“US”,代表你和我,代表UWA和开发者在一起!

UWA GPM正式上线!

近期精彩回顾

【厚积薄发】GPU Skinning不生效问题

【万象更新】GOT Online已支持Unreal最新版本

【学堂上新】DOTS深度研究之原理分析篇

【充电一刻】一分钟,读懂UWA性能报告

unity 如何获取到屏幕中间_Unity通用渲染管线Shader日志输出工具相关推荐

  1. unity 如何获取到屏幕中间_【Unity】屏幕空间位置变换到世界空间位置的方法

    屏幕空间像素的位置,是一个二维的浮点数,而世界空间的位置,则是三维的浮点数.实现的基本思路很简单,是世界空间位置变换到屏幕空间位置的逆过程,只是稍微有些区别.如果对图形渲染管线中的坐标变换没有弄清楚, ...

  2. unity 烘焙参数 设置_Unity通用渲染管线(URP)系列(九)——点光源和聚光灯

    200+篇教程总入口,欢迎收藏: 放牛的星星:[教程汇总+持续更新]Unity从入门到入坟--收藏这一篇就够了​zhuanlan.zhihu.com 本文重点内容: 1.支持更多类型的灯光 2.包含实 ...

  3. unity 如何获取到屏幕中间_请问如何获得场景的中心点在屏幕上的坐标?

    该楼层疑似违规已被系统折叠 隐藏此楼查看此楼 using UnityEngine; using System.Collections; public class click : MonoBehavio ...

  4. extjs 渲染之前的方法_Unity通用渲染管线(URP)系列(十一)——后处理(Bloom)...

    200+篇教程总入口,欢迎收藏: 放牛的星星:[教程汇总+持续更新]Unity从入门到入坟--收藏这一篇就够了​zhuanlan.zhihu.com 本文重点内容: 1.创建简单的post-FX栈 2 ...

  5. Unity Android平台下插件/SDK开发通用流程

    本文主要面向对Android开发不甚了解的Unity开发者,介绍了基于最新的Android Studio的标准Android开发环境与项目结构的配置流程,在此基础上,开发者可以快速的进行SDK的接入与 ...

  6. unity 自动将文件上传_unity如何存储文件夹

    玩转Unity资源,对象和序列化(上) 文章目录[点击展开](?)[+] 这是一系列文章中的第二章,覆盖了Unity5的Assets,Resources和资源管理 本文将从Unity编辑器和运行时两个 ...

  7. Android适配 获取手机屏幕的分辨率

    如何将一个应用程序适配在不同的手机上,虽然这不算是一个技术问题,但是对于刚刚做屏幕的开发人员来说,还真不是一件多么简单的事情. 首先:你需要在AndroidManifest.xml文件的<man ...

  8. Unity中的UGUI屏幕适配

    本文分享Unity中的UGUI屏幕适配 屏幕适配一直是一个老生常谈的问题, 虽然只是项目一开始的时候会用到, 但是还是有很多东西需要学习和了解, 今天给大家分享下一些个人的学习和总结. 各种坐标 屏幕 ...

  9. Unity - 通用渲染管线(URP)1.渲染、后处理

    这是一篇详细讲解URP的文章,涉及具体的使用和原理,翻译自Unity官方的文档. 本文由 祝你万事顺利 出品,转载请注明出处. 简介 URP是一种预置的可编程渲染管线.可以实现快速的渲染而不需要sha ...

最新文章

  1. Python通过一个网页地址获得网页标题Title
  2. Qt 2D绘图功能简单总结
  3. 重磅发布|新一代云原生数据仓库AnalyticDB「SQL智能诊断」功能详解
  4. 【Android】 Android中Log调试详解
  5. 脉冲神经网络基础知识,SpikeProp
  6. 亚马逊最大无人售货超市开张,云端结账随拿随走
  7. python转义字符_python转义字符
  8. Java实现对称密钥算法
  9. 信庭嵌入式工作室-ARM应用技术之体系结构应用(中)
  10. 如何在 Windows 中删除运行历史记录
  11. Backstepping反步法控制四旋翼无人机(一)
  12. C stdlib.h
  13. 使用 阿里云 播放器播放 .flv 和 hls(.m3u8) 格式的视频流
  14. 阿蒙森 斯科特_斯科特的单元测试定律
  15. 一入职就遇上Mysql亿级优化,方案都改了5遍
  16. 阿里云应用实时监控服务ARMS接入
  17. Vue3-uniapp上传图片到七牛云(身份证信息)
  18. Activity工作流入门篇
  19. 【图像识别】基于BP神经网络实现手写体大写字母识别附matlab代码
  20. rectangle函数的使用

热门文章

  1. java获取实体类的属性和值
  2. x86的32位汇编快速入门
  3. C++中如何定义动态数组
  4. base64链接转为地址php,php将图片链接转换为base64编码文件流
  5. 创建QT项目只有一个pro文件
  6. 2、AD工程创建步骤
  7. origin数据平滑_研发工程师必备:20条实用origin技能,让作图效率飞起来
  8. jcenter和maven下载失败Can't connect to SOCKS proxy:Connection refused: connect
  9. Android开发之添加QQ群的方法(官方代码)
  10. 缓存目录的区别getCacheDir()、getFilesDir()、getExternalFilesDir()、getExternalCacheDir()的作用