前言

  本来要肝 Houdini 教程的,但是最近一直不在状态(:з」∠)
  然后工作上遇到这个需求,本来是打算从 RenderDoc 导出 csv 文件,然后读取 csv 文件在 Maya API 将相关信息写到模型里面。
  但是 RenderDoc 的 ID 序号和 Maya 的 FaceVertex 无法关联到一起。
  在 Maya 里面顺序被打乱了。

  由于这个问题不急,所以也就搁置了,没有进行下一步的处理。
  直到最近在 KM 上找到了一篇 RenderDoc 插件开发的文章,瞬间觉得可以调用 RenderDoc API 来解决这个问题。
  于是才有了本篇文章的探索。

  最后的实现效果已经开源在 Github 上 链接

RenderDoc 是啥

  RenderDoc 是业内大名鼎鼎的 截帧软件, 用来分析游戏渲染数据的利器。不过我不是图形向 TA , 使用上还是一知半解(:з」∠)
  RenderDoc 是结余 Qt 框架开发,内置 Python API ,可以通过 Python 脚本实现插件开发。

alt

Github链接
官网链接
文档链接

插件开发

  RenderDoc 的文档里面有很详细关于插件开发的内容 链接

1
2
3
4
# windows
%APPDATA%\qrenderdoc\extensions
# linux
~/.local/share/qrenderdoc/extensions

  在上面的路径下创建目录
  然后添加一个 __init__.pyextension.json 的文件

1
2
3
4
5
6
7
8
9
{
"extension_api": 1,
"name": "Extension name for users",
"version": "1.0",
"minimum_renderdoc": "1.2",
"description": "A longer description of your extension.\n\nIt can contain multiple lines",
"author": "Your name <your@email.com>",
"url": "url/to/repository"
}

extension_api 固定为 1

  RenderDoc 会读取 extension.json 的信息显示在 Extension Mananger 的面板上。

alt

  启用插件之后就可以通过 API 扩展界面功能了

alt

RenderDoc 调试 Python 代码

alt

  RenderDoc 有 Python Shell 面板
  这里编写代码有完整的 renderdoc 环境, 方便测试代码。
  毕竟上面的插件流程测试代码都需要 reload 一下,非常不方便

RenderDoc 使用 Qt 编写界面

  虽然 RenderDoc 自带 PySide 模块
  但是 Qt 界面开发上并没有 Maya 那么灵活
  用 Maya 的传统思路构建 UI 会让 UI 一闪而过,即便使用 global 变量也不行。

1
2
3
4
5
6
from PySide2 import QtWidgets
dialog = QtWidgets.QDialog()
dialog.show()
# 下面注释的代码会导致 RenderDoc 卡死
# dialog.exec_()
# QtWidgets.QMessageBox.warning(None, "title", "msg")

alt

  RenderDoc 的主线程不能被 PySide 创建的界面所阻塞,否则就会导致 RenderDoc 卡死。
  RenderDoc Python API 提供了 Mini-Qt Helper

1
2
3
4
5
6
7
8
from PySide2 import QtWidgets
dialog = QtWidgets.QDialog()
manager = pyrenderdoc.Extensions()
mqt = manager.GetMiniQtHelper()
mqt.ShowWidgetAsDialog(dialog)

# 也可以通过下面的代码生成一个警告窗口
# manager.MessageDialog("msg", "title")

alt
alt

  这里使用全局变量 pyrenderdoc 获取 Helper ,然后调用 Helper 显示 Dialog
  不过有个缺点这是个 阻塞 Dialog , RenderDoc 的界面就无法响应了。


  关于 RenderDoc 一些内置变量和库需要进行区分

renderdoc 是自身的 API 模块
qrenderdoc 是Qt相关的 API 模块

  pyrenderdoc 则是默认自带的全局变量,属于 CaptureContext 类型,可以在文档中查阅相关的信息。 文档链接

  根据上面的文档上面, RenderDoc 推荐在 Python 环境下使用 pyrenderdoc.Replay().BlockInvoke(myCallback) 来进行处理操作。
  BlockInvoke 会调用非 UI 现成进行处理,避免 UI 无响应,并且只有激活的 controller 下才会触发。
  也就是没有截帧数据的情况下, BlockInvoke 的函数是不会被调用到的。
  但是这样会导致 BlockInvoke 的调用函数不在 Qt 线程里面,如果触发 Qt 相关的界面 API 依然不行。

1
2
3
4
manager = pyrenderdoc.Extensions()
def myCallback(controller):
manager.MessageDialog("FBX Ouput Sucessfully", "Congradualtion!~")
pyrenderdoc.Replay().BlockInvoke(myCallback)

  比较神奇的情况就出现了,我也不太搞懂,理论上上面的代码无法执行的,但是在代码调试界面是可以执行的。
  但是如果在 插件环境 下执行这个代码就会卡死,使用需要慎重。

2021-4-10 更新

  最近抽空来修复 renderdoc2fbx 工具的 一些 BUG ,想要用 Qt Designer 写个界面接入到 renderdoc 里面。
  我发现上面的测试还是差了一步,我只是生成界面,却没有生成按钮到界面里面。
  结果 renderdoc 的 API 需要用 GetMiniQtHelper 的 API 来创建组件。
  单纯使用 PySide2 库创建组件并不可行,因为 renderdoc 的 Python 和 PySide2 都是经过魔改的。

  打开 renderdoc 的安装目录可以看到 PySide2 模块 以及 Python 都是缺失的。
  python.exe 都找不到,在renderdoc 下 import socket 库还会报错。

1
2
3
4
Traceback (most recent call last):
File "script.py", line 1, in <module>
File "socket.py", line 49, in <module>
ModuleNotFoundError: No module named '_socket'

  因此在 renderdoc 创建界面只能使用 IMiniQtHelper 类里面提供的方法。

1
2
3
4
manager = pyrenderdoc.Extensions()
mqt = manager.GetMiniQtHelper()
print(dir(mqt))
['AddGridWidget', 'AddWidget', 'ClearContainedWidgets', 'CloseCurrentDialog', 'CreateButton', 'CreateCheckbox', 'CreateComboBox', 'CreateGridContainer', 'CreateGroupBox', 'CreateHorizontalContainer', 'CreateLabel', 'CreateOutputRenderingWidget', 'CreateRadiobox', 'CreateSpinbox', 'CreateTextBox', 'CreateToplevelWidget', 'CreateVerticalContainer', 'FindChildByName', 'GetChild', 'GetNumChildren', 'GetParent', 'GetSpinboxValue', 'GetWidgetName', 'GetWidgetText', 'GetWidgetType', 'GetWidgetWindowingData', 'InsertWidget', 'IsWidgetChecked', 'IsWidgetEnabled', 'SetComboOptions', 'SetSpinboxBounds', 'SetSpinboxValue', 'SetWidgetBackgroundColor', 'SetWidgetChecked', 'SetWidgetEnabled', 'SetWidgetFont', 'SetWidgetName', 'SetWidgetReplayOutput', 'SetWidgetText', 'ShowWidgetAsDialog', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__int__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'acquire', 'append', 'disown', 'next', 'own', 'this', 'thisown']

  基于上面的 API 和文档,返回都是 QWidget 类型,简单的界面编写可以如下参考。

1
2
3
4
5
6
7
8
9
10
11
12
13
manager = pyrenderdoc.Extensions()
mqt = manager.GetMiniQtHelper()

container = mqt.CreateToplevelWidget("Test Widget")
vertical = mqt.CreateVerticalContainer()
edit = mqt.CreateTextBox(True,lambda *args:None)
# NOTE 点击按钮关闭界面
button = mqt.CreateButton(lambda *args:mqt.CloseCurrentDialog(True))
mqt.SetWidgetText(button,"Button")
mqt.AddWidget(vertical,edit)
mqt.AddWidget(vertical,button)
mqt.AddWidget(container,vertical)
mqt.ShowWidgetAsDialog(container)

alt

  因此在 renderdoc 用 Python 写界面会有诸多限制,我这里开发了一个属性输入窗口,倒是能够符合我的需求。

alt

  虽然有诸多限制,不过还有一些功能可以使用,比如 QSettings

2021-4-14 更新

  获取的 QWidget 组件可以转换为 Qt 的对应组件然后用 PySide2 的 API 进行交互。
  比如说上面通过 mqt.CreateComboBox 生成下来选项, renderdoc 的 API 无法修改显示的 index
  可以将获取的 QWidget 传入到 QtWidgets.QComboBox ,通过 setCurrentText 进行修改。

1
2
3
4
5
6
7
manager = pyrenderdoc.Extensions()
mqt = manager.GetMiniQtHelper()

combo = mqt.CreateComboBox(False, lambda:None)
combo = QtWidgets.QComboBox(combo)
combo.addItems(["unity", "unreal"])
combo.setCurrentText("unreal")

  还是可以基于 Qt 的 API 进行修改,灵活性上并没有想象那么差。

2021-4-17附注: 这个用法只能在 插件环境下生效 , RenderDoc 自带的 Python shell 不起作用。

FBX 导出插件编写

  关于扩展的相关命令可以在 Extension Manager 相关的 API 文档里面找到 链接

注册 Mesh Viewer 菜单

1
2
3
pyrenderdoc.Extensions().RegisterPanelMenu(
qrenderdoc.PanelMenu.MeshPreview, ["Export FBX Mesh"], callback
)

alt

  上面的代码可以在 Mesh Viewer 的插件按钮下扩展一个 menu item ,触发相应的函数功能。
  具体编写方式可以参照官方文档 链接

1
2
3
def register(version: str, ctx: qrd.CaptureContext):
# as above ...
ctx.Extensions().RegisterWindowMenu(qrd.WindowMenu.Tools, ["My extension"], menu_callback)

alt

  官方文档的案例是扩展到 Tools 菜单下

解析 模型 数据

  官方提供了很详细的数据解析案例 链接
  官方使用 struct 库解析二进制数据,从中提取出我们需要的信息。

alt

  基于官方提供的数据就可以获取到 Vertex Normal UV Tangent VertexColor 的数据了。

  接下来就是将这些数据更具 FBX ASCII 的形式组装到文本里面。
  具体操作我是从 Maya 导出一个简单的面片 ASCII FBX 去测试的。

  其中比较坑的地方是, FBX 组装位置信息是有一个规则来区分三角面和四边面的。
  在网上查到了不错文章解决了我这个头疼的问题 https://www.codenong.com/cs105411312/

alt

  所以 PolygonVertexIndex 按照这个规范去构建数据就不会导致模型显示不出来。
  另外考虑到导出都是单个模型, 也不带材质,所以我导出的时候也将 FBX 无关的信息统统清理掉了。

总结

  以上就是 RenderDoc 开发 FBX 导出工具的记录。

2021-4-17 性能优化

  官方给出的 API 需要用 Python 进行 2进制解码获取模型数据。
  但是这个获取方案效率实在是太低了。
  经过我的摸索,可以通过 PySide2 模块获取 renderdoc UI 里面的数据。 链接
  直接获取出 QTableView 里面的信息,这样导出速度可以提升数十倍。
  另外使用插件的 Python 调用可以直接使用 PySide2 构建组件,但是在 Python Shell 下却不起作用。