您需要 登录 才可以下载或查看,没有账号?注册
x
Hello . 大家好
今天给大家带来的是 一文详解RenderDoc截帧DXBC编译我是守城的麦田
1DXBC简介
DXBC指令是D3D着色器语言使用的指令,HLSL高级着色语言经过编译器编译之后,会生成相应的DXBC指令。DXBC指令可以理解为GPU需要真正执行的指令。 OpenGL或者说是其它GPU厂商,他们提供的指令其实跟DXBC大同小异,略微有差异的也只是某些特殊的指令不是硬件支持而已。虽然编译器大部分情况下可以帮助我们优化代码,但是由于编译器特别智能,在某些情况下并不能保证代码是最优的方式。了解DXBC指令可以帮助我们在编写Shader时,能够写出更加可靠、性能优异的代码。另外了解DXBC指令,在某些情况下可以帮助我们更好的逆向其它游戏的一些材质效果。2开始准备
DXBC指令其实非常简单,学习起来也非常容易,它的套路可以说是非常的单一,大多数指令可以归纳为这样的形式:
下面是一个片段着色器的编译结果
我们这次测试的最基本的Shader如下
为了方便复制: Pass { HLSLPROGRAM #pragma vertex vert #pragma fragment frag #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
struct Attributes { float4 positionOS : POSITION; };
struct Varyings { float4 positionHCS : SV_POSITION; };
CBUFFER_START(UnityPerMaterial) half4 _BaseColor; CBUFFER_END
Varyings vert(Attributes IN) { Varyings OUT; OUT.positionHCS = TransformObjectToHClip(IN.positionOS.xyz); return OUT; }
half4 frag() : SV_Target { return _BaseColor; } ENDHLSL
3片元阶段分析 挂载上RenderDoc后,找到对应的渲染Pass
当前Shader只包含顶点和片元着色器,我们先从PS阶段读起,PS阶段代码如下所示:
ps_4_0 dcl_constantbuffer cb0[1], immediateIndexed dcl_output o0.xyzw 0: mov o0.xyzw, cb0[0].xyzw 1: retDXBC中外部传入的变量:
以下涉及到的DXBC官方解释链接如下:
https://docs.microsoft.com/zh-cn/windows/win32/direct3dhlsl/dx-graphics-hlsl-sm4-registers-ps-4-0?redirectedfrom=MSDN
ps_4_0本部分包含像素着色器版本 4_0 实现的输入和输出寄存器的参考信息,即当前的HLSL寄存器版本为4.0dcl_constantbuffer cb0[1]声明着色器常量缓冲区这句话表明声明寄存器 cb0 的常量缓冲区,其中包含 1 个元素。可以使用文本索引访问这些元素。immediateIndexed使用文本值为缓冲区编制索引。输出 returndcl_output声明一个着色器输出寄存器。 o0.xyzwo0,o1,o2这种类型的寄存器,为输出寄存器,整个DXBC内要去猜测这个寄存器的含义。 mov指令使用句式:mov dst, srcdst 是目标寄存器。src 是一个源寄存器。即在源寄存器和目标寄存器内移动数据。0: mov o0.xyzw, cb0[0].xyzw这里将常量缓冲区内的数据移动到目标缓冲区内ret指令对于主函数,此指令将停止着色器执行。顶点阶段4顶点阶段分析vs_4_0 dcl_constantbuffer cb0[77], immediateIndexed dcl_constantbuffer cb1[4], immediateIndexed dcl_input v0.xyz dcl_output_siv o0.xyzw, position dcl_temps 2 0: mul r0.xyzw, v0.yyyy, cb1[1].xyzw 1: mad r0.xyzw, cb1[0].xyzw, v0.xxxx, r0.xyzw 2: mad r0.xyzw, cb1[2].xyzw, v0.zzzz, r0.xyzw 3: add r0.xyzw, r0.xyzw, cb1[3].xyzw 4: mul r1.xyzw, r0.yyyy, cb0[74].xyzw 5: mad r1.xyzw, cb0[73].xyzw, r0.xxxx, r1.xyzw 6: mad r1.xyzw, cb0[75].xyzw, r0.zzzz, r1.xyzw 7: mad o0.xyzw, cb0[76].xyzw, r0.wwww, r1.xyzw 8: ret
dcl_input指令声明着色器输入寄存器
语法:dcl_input vN[.mask][, 内插Mode]N 是标识寄存器号的整数。[.mask] 是一个可选组件掩码 (.xyzw) ,指定要使用的注册组件。此处的dcl_input v0.xyz即对应Shader内的float4 positionOS : POSITION;
dcl_output_siv指令声明包含 系统值 参数的输出寄存器。语法:dcl_output_siv oN[.masks], systemValue
所以这里的dcl_output_siv o0.xyzw, position即代表Varyings下的positionHCS接下来就是一系列的矩阵转换,从模型空间转换到齐次裁剪空间,分别乘MVP矩阵,Unity内写法为OUT.positionHCS = TransformObjectToHClip(IN.positionOS.xyz);
拆解一下这句,如下
上面注释的一句等价于下面三句。其中OptimizeProjectionMatrix(glstate_matrix_projection)是由UniversalRenderPipelineCore.cs文件下的ShaderPropertyId类传入的,用于根据平台特殊处理的矩阵。public static class ShaderPropertyId { ... public static readonly int viewMatrix = Shader.PropertyToID("unity_MatrixV"); public static readonly int projectionMatrix = Shader.PropertyToID("glstate_matrix_projection"); ... }
dcl_temps指令声明临时寄存器。语法:dcl_temps N N为临时寄存器个数每个寄存器都有一个 32 位四分量值的空间。临时和 可索引临时 寄存器的总数必须小于或等于 4096。此处的dcl_temps 2即申明了两个临时寄存器r0-r1。下面需要用到。
mul 指令语法 mul dst、src0、src1dst 是目标寄存器。
src0 是源寄存器。src1 是源寄存器。即dest.x = src0.x * src1.x;mul r0.xyzw, v0.yyyy, cb1[1].xyzw这条指令的意思为:r0.xyzw = * v0.yyyy * cb1[1].xyzw
mad乘以并添加源。语法:mad dst, src0, src1, src2dst 是目标寄存器。
src0 是源寄存器。src1 是源寄存器。src2 是源寄存器。举例:dest.x = src0.x * src1.x + src2.x;mad r0.xyzw, cb1[0].xyzw, v0.xxxx, r0.xyzw这条指令的意思为:r0.xyzw = cb1[0].xyzw * v0.xxxx + r0.xyzw
add指令两个向量相加。语法:add dst, src0, src1dst 是目标寄存器。
src0 是源寄存器。src1 是源寄存器。举例:dest.x = src0.x + src1.x;add r0.xyzw, r0.xyzw, cb1[3].xyzw这条指令的意思为:r0.xyzw = r0.xyzw + cb1[3].xyzw
分析 0: mul r0.xyzw, v0.yyyy, cb1[1].xyzw 1: mad r0.xyzw, cb1[0].xyzw, v0.xxxx, r0.xyzw 2: mad r0.xyzw, cb1[2].xyzw, v0.zzzz, r0.xyzw 3: add r0.xyzw, r0.xyzw, cb1[3].xyzw为了更加具象点,把上面这段DXBC翻译一下: 0: r0.xyzw = v0.yyyy * cb1[1].xyzw; 1: r0.xyzw = cb1[0].xyzw * v0.xxxx + r0.xyzw 2: r0.xyzw = cb1[2].xyzw * v0.zzzz + r0.xyzw 3: r0.xyzw = r0.xyzw + cb1[3].xyzw这里在原Shader内只是做了一系列的矩阵转换,但是DXBC会默认将HLSL替换为Vector4类型,包括向量,由推导可知,这里的cb1[4]是M矩阵,怎么来的呢? 由dcl_constantbuffer cb1可知,**cb1[4]**是由外部传入的参数类型我在Shader内只计算M矩阵,汇编后如下图所示:
由此,可推断出:cb1[4]正是unity_ObjectToWorld,而cb1[1]——cb1[3]分别对应M矩阵的每一列。unity_ObjectToWorld是一个4X44X44X4的矩阵,positionOS则是一个4X14X14X1的矩阵(Unity内使用列向量,矩阵左乘)那么一切都了然了,回想起矩阵乘法,两个矩阵A和B相乘,需要满足A的列数等于B的行数。a矩阵的行元素乘以每一列然后相加作为新矩阵的行元素
继续看下面: 4: mul r1.xyzw, r0.yyyy, cb0[74].xyzw 5: mad r1.xyzw, cb0[73].xyzw, r0.xxxx, r1.xyzw 6: mad r1.xyzw, cb0[75].xyzw, r0.zzzz, r1.xyzw 7: mad o0.xyzw, cb0[76].xyzw, r0.wwww, r1.xyzw依此类推,范围cb [73]——[76]则代表VP矩阵 unity_MatrixVP范围cb [61]——[64]则代表V矩阵 unity_MatrixV范围cb [57]——[60]则代表P矩阵 UNITY_MATRIX_P最后输出ret结束
到此,本次截帧分析的基础Shader已经结束。下篇分析,如果存在贴图等类型的输入分析。- End -
|