图形引擎实战:基于rdc文件的世界场景还原流程介绍

RenderDoc 是一款基于帧捕捉的开源图形调试器,我们经常用它来排查一些GPU渲染、资源规格等问题,本文将介绍如何利用rdc文件,来导出数据进行性能分析。

  • 如何导出单个模型:介绍了如何提取Mesh信息、如何生成fbx文件以及贴图导出的内容
  • 找到变换矩阵:通过找到vp矩阵或者vp矩阵的逆,将模型从local space转换到world space,或是从Clip Space转到World Space(在转换过程中会有误差)
  • 进行批量导出:该如何进行批量导出,批量导出时需要注意到的一些坑

单个模型导出

在RenderDoc的Mesh Viewer界面中,可以看到网格的相关信息,根据这些信息可以生成并导出fbx模型文件。 同时,在Texture View界面可以看到输入的贴图信息,我们需要将其批量导出成tga格式。

提取Mesh信息

在RenderDoc的Mesh Viewer界面中,可以发现有VS Input和VS output两组信息(可能某些地方不叫这个名字,但是大致形式差不多),如下图所示。

RenderDoc提供了相应的接口让我们可以拿到VS Input和VS output中的数据,其中,VS Input指的是输入进顶点着色器的模型信息,VS Output指的是经过MVP变换之后的模型信息。关于mesh信息的导出可以参考官方文档中的示例代码

生成FBX文件

参考官方文档的示例代码可以获取mesh信息。 在获取了mesh信息之后,我们可以使用FBX SDK来创建FBX文件(想要了解FBX文件结构可以参考这篇文章)。下面是根据Mesh信息创建fbx文件的代码示例。

def SaveAsFbx(DataFrame, saveName):  
    fbxName = os.path.basename(saveName).split(".")[0]  
    fbxManager = FbxManager.Create()  
    fbxScene = FbxScene.Create(fbxManager, "")  
    rootNode = fbxScene.GetRootNode()  
    FbxSystemUnit.ConvertScene(FbxSystemUnit.m, fbxScene)  

    # Mesh node创建  
    newNode = FbxNode.Create(fbxScene, fbxName)  
    FbxNode.AddChild(rootNode, newNode)  
    mesh = FbxMesh.Create(fbxScene, fbxName + "_Mesh")  
    newNode.SetNodeAttribute(mesh)  

    DataFrame_change = DataFrame.drop_duplicates(subset=["IDX"], keep="first", inplace=False)  
    DataFrame_change = DataFrame_change.sort_values(by="IDX", ascending=True) 
    # -----------------添加Mesh----------------#  
    mesh.InitControlPoints(len(DataFrame_change))  
    pos = []  
    idx_binding_info = []  
    for i in range(0, len(DataFrame_change)):  
        pos.append(np.array([DataFrame_change.iloc[i][attribute_export[0] + ".x"],  
                             DataFrame_change.iloc[i][attribute_export[0] + ".y"],  
                             DataFrame_change.iloc[i][attribute_export[0] + ".z"]]))  
        # 重组位置数据  
        vertexPos = FbxVector4(DataFrame_change.iloc[i][attribute_export[0] + ".x"],  
                               DataFrame_change.iloc[i][attribute_export[0] + ".y"],  
                               DataFrame_change.iloc[i][attribute_export[0] + ".z"])  
        mesh.SetControlPointAt(vertexPos, i)  
        idx_binding_info.append(int(DataFrame_change.iloc[i]["IDX"]))  

    # 重构三角面  
    for j in range(0, int(vertexCount / 3.0)):  
        # 将vertex与point相互绑定  
        mesh.BeginPolygon(j)  
        mesh.AddPolygon(idx_binding_info.index(int(DataFrame.iloc[j * 3]["IDX"])))  
        mesh.AddPolygon(idx_binding_info.index(int(DataFrame.iloc[j * 3 + 1]["IDX"])))  
        mesh.AddPolygon(idx_binding_info.index(int(DataFrame.iloc[j * 3 + 2]["IDX"])))  
        mesh.EndPolygon()  

    # ----------------添加Normal----------------#  
    if attribute_export[1] + ".x" in DataFrame_change:  
        # 创建Normal Layer  
        normalLayer = mesh.GetLayer(0)  
        if normalLayer is None:  
            mesh.CreateLayer()  
            normalLayer = mesh.GetLayer(0)  

        # 创建layerElementNormal  
        layerElementNormal = FbxLayerElementNormal.Create(mesh, "")  
        layerElementNormal.SetMappingMode(FbxLayerElement.EMappingMode(1))  # EMappingMode.eByControlPoint  
        layerElementNormal.SetReferenceMode(FbxLayerElement.EReferenceMode(0))  # EReferenceMode.eDirect  

        # 从DataFrame_change中获取Normal数组  
        normalArray = []  
        for i in range(0, len(DataFrame_change)):  
            normalArray.append(np.array([DataFrame_change.iloc[i][attribute_export[1] + ".x"],  
                                         DataFrame_change.iloc[i][attribute_export[1] + ".y"],  
                                         DataFrame_change.iloc[i][attribute_export[1] + ".z"],  
                                         DataFrame_change.iloc[i][attribute_export[1] + ".w"]]))  

        # 将Normal数组传入layerElementNormal中  
        for i in range(0, len(DataFrame_change)):  
            vertexNormal = FbxVector4(normalArray[i][0], normalArray[i][1], normalArray[i][2], normalArray[i][3])  
            layerElementNormal.GetDirectArray().Add(vertexNormal)  

        normalLayer.SetNormals(layerElementNormal)  

    # ----------------添加Vertex Color----------------#  
    # 创建Vertex Color Layer  
    if attribute_export[3] + ".x" in DataFrame_change:  
        vcolorLayer = mesh.GetLayer(0)  
        if vcolorLayer is None:  
            mesh.CreateLayer()  
            vcolorLayer = mesh.GetLayer(0)  

        # 创建layerElementVcolor  
        layerElementVcolor = FbxLayerElementVertexColor.Create(mesh, "")  
        layerElementVcolor.SetMappingMode(FbxLayerElement.EMappingMode(1))  # EMappingMode.eByControlPoint  
        layerElementVcolor.SetReferenceMode(FbxLayerElement.EReferenceMode(0))  # EReferenceMode.eDirect  

        # 从DataFrame_change中获取Normal数组  
        vcolorArray = []  
        for i in range(0, len(DataFrame_change)):  
            vcolorArray.append(np.array([DataFrame_change.iloc[i][attribute_export[3] + ".x"],  
                                         DataFrame_change.iloc[i][attribute_export[3] + ".y"],  
                                         DataFrame_change.iloc[i][attribute_export[3] + ".z"],  
                                         DataFrame_change.iloc[i][attribute_export[3] + ".w"]]))  
        # 将Vertex Color数组传入layerElementVcolor中  
        for i in range(0, len(DataFrame_change)):  
            vertexColor = FbxColor(vcolorArray[i][0], vcolorArray[i][1], vcolorArray[i][2], vcolorArray[i][3])  
            layerElementVcolor.GetDirectArray().Add(vertexColor)  
        normalLayer.SetVertexColors(layerElementVcolor)  

    # ----------------添加UV0----------------#  
    # 创建UV0 Layer  
    if attribute_export[2] + ".x" in DataFrame_change:  
        uv0Layer = mesh.GetLayer(0)  
        if uv0Layer is None:  
            mesh.CreateLayer()  
            uv0Layer = mesh.GetLayer(0)  

        # 创建layerElementUV0  
        layerElementUV0 = FbxLayerElementUV.Create(mesh, attribute_export[2])  
        layerElementUV0.SetMappingMode(FbxLayerElement.EMappingMode(1))  # EMappingMode.eByControlPoint  
        layerElementUV0.SetReferenceMode(FbxLayerElement.EReferenceMode(0))  # EReferenceMode.eDirect  

        # 从DataFrame_change中获取TEXCOORD0数组  
        uv0Array = []  
        for i in range(0, len(DataFrame_change)):  
            uv0Array.append(np.array([DataFrame_change.iloc[i][attribute_export[2] + ".x"],  
                                      DataFrame_change.iloc[i][attribute_export[2] + ".y"]]))  
        # 将uv0Array传入layerElementUV0中  
        for i in range(0, len(DataFrame_change)):  
            vertexUV0 = FbxVector2(uv0Array[i][0], uv0Array[i][1])  
            layerElementUV0.GetDirectArray().Add(vertexUV0)  
        uv0Layer.SetUVs(layerElementUV0)  

    # ----------------FBX导出----------------#  
    saveScene(saveName, fbxManager, fbxScene, pAsASCII=True)  
    fbxManager.Destroy()  
    del fbxManager, fbxScene, DataFrame

贴图导出

与此同时,我们也需要导出当前DrawCall下所使用的贴图资源,可以参照RenderDoc官网的示例代码对贴图进行导出。 在贴图命名正确的情况下,可以根据命名规律找出Diffuse、Normal等贴图。然后导出模型时通过FBX SDK来创建模型材质来绑定这些贴图,这样子在后续模型进入Unity后,模型会自动关联到这些贴图,而不至于是白模。(在Unreal引擎做的游戏所截的帧中得到的rdc文件,通常贴图没有一个可识别的命名,这种就无能为力了)

世界坐标还原

单个Local Space的模型导出是简单的,但是想要还原整个世界场景,还需做进一步处理。 想要还原世界坐标,就需要将模型空间坐标或是屏幕空间坐标转换到世界空间。在进行转换之前,我们先简单了解一下MVP变换。

MVP变换介绍

在图形流水线中,MVP变换指的是一系列坐标空间变换,这些转换通过将模型变换(Model Transform)、视图变换(View Transform)和投影变换(Projection Transform)相结合来实现。这三种变换共同组成了MVP变换,它们将3D场景中的对象转换到一个二维图像上,以便在屏幕上渲染。下面详细介绍每个组成部分:

模型变换(Model Transform)

  • 用于将对象从模型空间(对象自己的局部坐标系统)转换到世界空间(场景的全局坐标系统)。
  • 它包括平移(Translation)、旋转(Rotation)和缩放(Scale)操作。

视图变换(View Transform)

  • 将对象从世界空间转换到视图空间(也称为摄像机空间或眼睛空间)。
  • 在视图空间中,观察点(通常是虚拟摄像机)位于原点,所有对象都相对于这个观察点进行定位。
  • 这个变换通常由摄像机的位置和方向决定。

投影变换(Projection Transform)

  • 将视图空间中的3D坐标转换到剪裁空间(Clip Space),并最终通过透视除法转换为归一化设备坐标(Normalized Device Coordinates, NDC)。
  • 这个变换定义了视锥体(View Frustum),它决定了哪些部分的场景被渲染到屏幕上。
  • 投影变换可以是透视投影(Perspective Projection)或正交投影(Orthographic Projection)。

引擎中的MVP变换

以Unity为例。在Unity中,我们可以通常可以直接拿到MVP变换矩阵来进行MVP矩阵变换。下面是Lit的部分源码

struct Attributes  
{  
    ...
    float4 positionOS : POSITION;  
    ...
};

struct Varyings  
{  
    ...
    float4 positionCS : SV_POSITION;  
    ...
};

VertexPositionInputs GetVertexPositionInputs(float3 positionOS)  
{  
    VertexPositionInputs input;  
    input.positionWS = TransformObjectToWorld(positionOS);  
    input.positionVS = TransformWorldToView(input.positionWS);  
    input.positionCS = TransformWorldToHClip(input.positionWS);  

    float4 ndc = input.positionCS * 0.5f;  
    input.positionNDC.xy = float2(ndc.x, ndc.y * _ProjectionParams.x) + ndc.w;  
    input.positionNDC.zw = input.positionCS.zw;  

return input;  
}

Varyings LitPassVertex(Attributes input)  
{
    ...
    VertexPositionInputs vertexInput = GetVertexPositionInputs(input.positionOS.xyz);
    ...
#if defined(REQUIRES_WORLD_SPACE_POS_INTERPOLATOR)  
    output.positionWS = vertexInput.positionWS;  
#endif
    output.positionCS = vertexInput.positionCS;  
    return output;
}

不难发现,输入到顶点着色器(VS Input)的位置坐标POSITION是处于模型空间,对应的是变量positionOS,最后输出到SV_POSITION的是裁剪空间坐标positionCS。 而在Lit的顶点着色器中,positionCS经过了世界空间、视图空间、裁剪空间的转换,但并没有计算NDC空间的坐标(所以后续我们也不需要进行NDC空间的转换)。 所以,RenderDoc中的VS Input通常代表的是模型空间下的Mesh信息,VS Output通常代表的是裁剪空间下的Mesh信息(未经过NDC空间转换)。

利用变换矩阵还原世界坐标

现在我们已经知道了,VS Input中POSITION通过MVP变换(不包括NDC变换)可以得到VS Output中的SV_POSITION。 那么想要得到模型的世界空间坐标,我们可以有两种做法 - 利用M矩阵进行还原(从模型空间转换到世界空间) - 利用VP矩阵进行还原(从屏幕空间转换到世界空间) 但无论是利用M矩阵还是VP矩阵还原,我们本质上是要拿到平移旋转缩放的信息。这是因为我们后续需要做的操作是拿到了模型空间的模型后,先将模型导入到引擎中,然后通过改变模型的平移旋转缩放值的方式来对场景进行还原。

第一种方法得到的世界空间坐标会更加准确,但缺点是M矩阵在很多情况下很难提取,并且大多数的模型的M矩阵会不一样。第二种方法得到世界空间坐标会有很小的误差(在计算逆矩阵时所导致的误差),尽管VP矩阵在一些情况下提取起来也不方便,但是优点在于在同一帧的情况下,场景中的物体所使用的的VP矩阵通常是不变的,所以此时我们可以人工找到VP矩阵的位置,以此来对世界空间的坐标进行还原。

利用M矩阵还原

在某些情况下,我们截帧是可以截到带有标识的M矩阵的,如下图所示。

此时可以准确定位到M矩阵所在的CBuffer位置,拿到M矩阵信息。 但是在很多情况下,我们很难准确定位M矩阵的位置,这时就需要反编译DXBC的代码反推出M矩阵的位置,在这里我尝试过这种做法,实现起来难度很大,且识别准确率不高,不是很推荐。 在能够顺利拿到M矩阵的情况下,就可以对M矩阵进行平移旋转缩放信息的提取。下面是提取平移旋转缩放信息的示例代码。

def extractPosition(matrix):  
    position = [matrix[0, 3], matrix[1, 3], matrix[2, 3]]  
    return position

def extractRotation(matrix):  
    forward = [matrix[0, 2], matrix[1, 2], matrix[2, 2]]  
    upwards = [matrix[0, 1], matrix[1, 1], matrix[2, 1]]  
    return lookRotation(forward, upwards)

def extractScale(matrix):  
    s1 = magnitude(matrix[0, 0], matrix[1, 0], matrix[2, 0], matrix[3, 0])  
    s2 = magnitude(matrix[0, 1], matrix[1, 1], matrix[2, 1], matrix[3, 1])  
    s3 = magnitude(matrix[0, 2], matrix[1, 2], matrix[2, 2], matrix[3, 2])  
    return scale

def magnitude(e, b, c, d):  
    e = e * e  
    b = b * b  
    c = c * c  
    d = d * d  
    s = e + b + c + d  
    return math.sqrt(s)

def lookRotation(forward, up):  
    forward = normalize(forward)  
    vector = normalize(forward)  
    vector2 = normalize(np.cross(up, vector))  
    vector3 = np.cross(vector, vector2)  
    m00 = vector2[0]  
    m01 = vector2[1]  
    m02 = vector2[2]  
    m10 = vector3[0]  
    m11 = vector3[1]  
    m12 = vector3[2]  
    m20 = vector[0]  
    m21 = vector[1]  
    m22 = vector[2]  
    num8 = (m00 + m11) + m22  
    quaternion = FbxQuaternion(0.0, 0.0, 0.0, 1)  
    if num8 > 0:  
        num = float(math.sqrt(num8 + 1.0))  
        quaternion.SetAt(3, num * 0.5)  
        num = 0.5 / num  
        quaternion.SetAt(0, (m12 - m21) * num)  
        quaternion.SetAt(1, (m20 - m02) * num)  
        quaternion.SetAt(2, (m01 - m10) * num)  
        return quaternion  
    if (m00 >= m11) and (m00 >= m22):  
        num7 = float(math.sqrt(((1.0 + m00) - m11) - m22))  
        num4 = 0.5 / num7  
        quaternion.SetAt(0, 0.5 * num7)  
        quaternion.SetAt(1, (m01 + m10) * num4)  
        quaternion.SetAt(2, (m02 + m20) * num4)  
        quaternion.SetAt(3, (m12 - m21) * num4)  
        return quaternion  
    if m11 > m22:  
        num6 = float(math.sqrt(((1.0 + m11) - m00) - m22))  
        num3 = 0.5 / num6  
        quaternion.SetAt(0, (m10 + m01) * num3)  
        quaternion.SetAt(1, 0.5 * num6)  
        quaternion.SetAt(2, (m21 + m12) * num3)  
        quaternion.SetAt(3, (m20 - m02) * num3)  
        return quaternion  
    num5 = float(math.sqrt(((1.0 + m22) - m00) - m11))  
    num2 = 0.5 / num5  
    quaternion.SetAt(0, (m20 + m02) * num2)  
    quaternion.SetAt(1, (m21 + m12) * num2)  
    quaternion.SetAt(2, 0.5 * num5)  
    quaternion.SetAt(3, (m01 - m10) * num2)  
    return quaternion

拿到平移旋转缩放信息后,我们可以将其写入fbx文件中,以便导入unity中时transform会带有此信息。

def SaveAsFbx(DataFrame, saveName):  
    ...
    newNode.LclScaling.Set(FbxDouble3(scale[0], scale[1], scale[2]))  
    newNode.LclTranslation.Set(FbxDouble3(position[0], position[1], position[2]))  
    newNode.LclRotation.Set(FbxDouble3(rotation[0], rotation[1], rotation[2]))
    ...

利用VP矩阵还原

同样的,在某些情况下,我们是可以截帧可以截到带有标识的VP矩阵或是VP矩阵的逆。如下图所示。

而大部分情况下呢,我们是很难直接找到VP矩阵的位置的。 但由于VP矩阵在同一帧的情况下(或者说在一个rdc文件内),矩阵的值通常是相等的,所以这时我们可以通过阅读DXBC源码,判断VP矩阵所存的CBuffer位置。 如下面的在DXBC源码里,VP矩阵被存到了cb2[17]、cb2[18]、cb2[19]、cb[20]。

vs_5_0
      dcl_globalFlags refactoringAllowed
      dcl_constantbuffer cb0[51], immediateIndexed
      dcl_constantbuffer cb1[7], immediateIndexed
      dcl_constantbuffer cb2[10], immediateIndexed
      dcl_constantbuffer cb3[21], immediateIndexed
      dcl_input v0.xyz
      dcl_input v1.xyzw
      dcl_input v2.xyz
      dcl_input v3.xy
      dcl_input v4.xy
      dcl_input v5.xyzw
      dcl_output_siv o0.xyzw, position
      dcl_output o1.xyzw
      dcl_output o2.xyzw
      dcl_output o3.xyzw
      dcl_output o4.xyzw
      dcl_output o5.xyzw
      dcl_output o6.xyzw
      dcl_temps 5
   //这里是乘以M矩阵,模型空间到世界空间的变换
   0: mul r0.xyzw, v0.yyyy, cb2[1].xyzw
   1: mad r0.xyzw, cb2[0].xyzw, v0.xxxx, r0.xyzw
   2: mad r0.xyzw, cb2[2].xyzw, v0.zzzz, r0.xyzw
   3: add r0.xyzw, r0.xyzw, cb2[3].xyzw
   //这里是将上述结果乘以VP矩阵,世界空间到裁剪空间的变换
   4: mul r1.xyzw, r0.yyyy, cb3[18].xyzw
   5: mad r1.xyzw, cb3[17].xyzw, r0.xxxx, r1.xyzw
   6: mad r1.xyzw, cb3[19].xyzw, r0.zzzz, r1.xyzw
   7: mad r0.xyzw, cb3[20].xyzw, r0.wwww, r1.xyzw
   8: mov o0.xyzw, r0.xyzw
   9: eq r1.x, cb0[49].y, l(0)
  10: movc r1.xy, r1.xxxx, v3.xyxx, v4.xyxx
  11: mad o1.zw, r1.xxxy, cb0[50].xxxy, cb0[50].zzzw
  12: mad o1.xy, v3.xyxx, cb0[45].xyxx, cb0[45].zwzz
  13: dp3 r1.y, v2.xyzx, cb2[4].xyzx
  14: dp3 r1.z, v2.xyzx, cb2[5].xyzx
  15: dp3 r1.x, v2.xyzx, cb2[6].xyzx
  16: dp3 r1.w, r1.xyzx, r1.xyzx
  17: rsq r1.w, r1.w
  18: mul r1.xyz, r1.wwww, r1.xyzx
  19: mul r2.xyz, v1.yyyy, cb2[1].yzxy
  20: mad r2.xyz, cb2[0].yzxy, v1.xxxx, r2.xyzx
  21: mad r2.xyz, cb2[2].yzxy, v1.zzzz, r2.xyzx
  22: dp3 r1.w, r2.xyzx, r2.xyzx
  23: rsq r1.w, r1.w
  24: mul r2.xyz, r1.wwww, r2.xyzx
  25: mul r3.xyz, r1.xyzx, r2.xyzx
  26: mad r3.xyz, r1.zxyz, r2.yzxy, -r3.xyzx
  27: mul r1.w, v1.w, cb2[9].w
  28: mul r3.xyz, r1.wwww, r3.xyzx
  29: mov o2.y, r3.x
  30: mul r4.xyz, v0.yyyy, cb2[1].xyzx
  31: mad r4.xyz, cb2[0].xyzx, v0.xxxx, r4.xyzx
  32: mad r4.xyz, cb2[2].xyzx, v0.zzzz, r4.xyzx
  33: add r4.xyz, r4.xyzx, cb2[3].xyzx
  34: mov o2.w, r4.x
  35: mov o2.x, r2.z
  36: mov o2.z, r1.y
  37: mov o3.x, r2.x
  38: mov o4.x, r2.y
  39: mov o3.z, r1.z
  40: mov o4.z, r1.x
  41: mov o3.w, r4.y
  42: mov o4.w, r4.z
  43: mov o3.y, r3.y
  44: mov o4.y, r3.z
  45: mul r0.y, r0.y, cb1[6].x
  46: mul r1.xzw, r0.xxwy, l(0.5000, 0.0000, 0.5000, 0.5000)
  47: mov o5.zw, r0.zzzw
  48: add o5.xy, r1.zzzz, r1.xwxx
  49: mad r0.x, v5.w, l(-2.0000), l(1.0000)
  50: mad o6.w, cb0[42].x, r0.x, v5.w
  51: mov o6.xyz, v5.xyzx
  52: ret

通常来说,存储这些矩阵的CBuffer都是可以在RenderDoc的Pipeline State界面的Vertex Shader下面找到。 在定位好CBuffer的位置后,可以是用RenderDoc提供的API在CBuffer中提取矩阵信息。下面是用来提取CBuffer的示例代码。

def get_constant_buffer(self, buffer_name):
    buffer_index = -1  
    buffer_name = buffer_name.strip()  
    for i in range(0, len(self.shader_reflection.constantBlocks)):  
        cb_name = self.shader_reflection.constantBlocks[i].name.strip()  
        if cb_name == buffer_name:  
            buffer_index = i  
    if buffer_index == -1:  
        print("没有找到Cbuffer")  
        return None  
    pipeState = self.state.GetGraphicsPipelineObject()  
    entry = self.state.GetShaderEntryPoint(rd.ShaderStage.Vertex)  
    cbuffer = self.state.GetConstantBuffer(rd.ShaderStage.Vertex, buffer_index, 0)  
    cbuffer_vars = self.controller.GetCBufferVariableContents(pipeState, self.shader_reflection.resourceId,  
                                                              rd.ShaderStage.Vertex,  
                                                              entry, buffer_index, cbuffer.resourceId, 0, 0)  
    cbuffer_vars_value = [] 
    for i in cbuffer_vars:  
        cbuffer_vars_value.append(list(i.value.f32v[0:4]))  
    return cbuffer_vars_value

在拿到VP矩阵后,还需要计算VP矩阵的逆。然后用positionCS乘以VP矩阵的逆可以得到positionWS。最后用positionWS乘以positionOS的逆可以得到M矩阵,从而可以计算出平移缩放旋转值(上一小节有介绍)。

批量导出

批量导出的话,我们可以让用户输入EventID或是ActionID的起始ID和终止ID。然后通过遍历这些ID,通过ID来查找Action,找到Action后用单个模型导出的逻辑进行导出。

在批量导出的过程中需要注意的是,在DX11或DX12平台下截的帧,需要对使用DrawIndexedInstanced和DrawIndexedInstancedIndirect的Action进行特殊处理,因为这两者都在一个Action(或者说是DrawCall)下画了多个不同位置的同一个mesh,也就是说,他们的mesh信息相同,但是平移旋转缩放信息可能不同。

在使用M矩阵进行还原时,就要注意这一个Action下就会要用到多个不同的M矩阵(提取的时候会很麻烦)。在使用VP矩阵进行还原时,会发现RenderDoc提供了各个不同Instance的VS Output的信息,通过RenderDoc提供的API进行获取即可。

欢迎加入我们!

感兴趣的同学可以投递简历至:CYouEngine@cyou-inc.com

#我的求职思考##搜狐畅游##游戏引擎##图形引擎实践#
全部评论

相关推荐

8 3 评论
分享
牛客网
牛客企业服务