python做的游戏可以导出吗_Python for RenderDoc批量导出模型和贴图
故事背景:
美術(shù)那里有需求,需要?jiǎng)e人游戲的模型,來(lái)借鑒一下,問我是否有工具可以一鍵導(dǎo)出模型。我就搜索了一下RenderDoc批量導(dǎo)出圖片,結(jié)果搜到了用C++改RenderDoc源碼的文章。讓RenderDoc批量導(dǎo)出紋理_專欄-CSDN博客?blog.csdn.net
然后看了一下官網(wǎng),看到了可以用python寫工具的(因?yàn)椴粫?huì)C++,而且好像很麻煩的樣子),我就試著用python來(lái)寫RenderDoc的工具(其實(shí)我也不會(huì)python只是這種語(yǔ)言應(yīng)該好學(xué))。
使用步驟:
1.在RenderDoc里面截一張圖。
2.運(yùn)行RenderDoc的python shell。
3.導(dǎo)出圖片與csv(因?yàn)橹荒軐?dǎo)出csv,導(dǎo)不出模型,我會(huì)在unity里把他轉(zhuǎn)為模型)
至于RenderDoc如何寫python只要看下官網(wǎng)就可以了,這里貼出地址。Python API - RenderDoc documentation?renderdoc.org
原理分析:
主要還是python部分的原理,怎么調(diào)用RenderDoc接口,官網(wǎng)雖然有解析和例子,但是并不完全,導(dǎo)出圖片的那個(gè)例子居然是導(dǎo)出output的圖片。就是fragment Shader return出去的那個(gè),完全沒找到input的圖片,我都是自己試出來(lái)的。
其實(shí)導(dǎo)出頂點(diǎn)CSV跟導(dǎo)出圖片,都用到個(gè)這個(gè)函數(shù)
controller.GetPipelineState()
從這個(gè)pipeline的state里面獲取,就是對(duì)應(yīng)RenderDoc里面的這個(gè)面板
然后用這個(gè)函數(shù)
state.GetReadOnlyResources(renderdoc.ShaderStage.Fragment)
這個(gè)里面就有所有的圖片的resourceId,可以寫個(gè)循環(huán)保存所有的圖片。
texsave = rd.TextureSave()
texsave.resourceId = resourceId
if texsave.resourceId == rd.ResourceId.Null():
return False
filename = str(int(texsave.resourceId))
texsave.mip = 0
texsave.slice.sliceIndex = 0
texsave.alpha = rd.AlphaMapping.Preserve
texsave.destType = rd.FileType.PNG
if not os.path.exists("{0}/{1}".format(folderName,eventId)):
os.makedirs("{0}/{1}".format(folderName,eventId))
outTexPath = "{0}/{1}/{2}.png".format(folderName,eventId,filename)
controller.SaveTexture(texsave, outTexPath)
同樣的vertex也是這么獲取,獲取vertex index的resourceId,跟vertex自身的resourceId。
ib.state.GetIBuffer()
vbs.state.GetVBuffers()
....
something
...
meshInput.indexResourceId = ib.resourceId # vertex index的resourceId
meshInput.vertexResourceId = vbs[attr.vertexBuffer].resourceId # vertex自身的resourceId
然后根據(jù)resourceId去BufferData里面去得到vertex
controller.GetBufferData(meshInput.indexResourceId, meshInput.indexByteOffset, 0)
源碼:只給出python源碼,C#源碼可以參考這里RenderDocMeshParserForUnity?github.com
import sys
import csv
folderName = "C:/Users/Administrator/Desktop/capMesh1"
startIndex = 475
endIndex = 759
isPrint = False
# Import renderdoc if not already imported (e.g. in the UI)
if 'renderdoc' not in sys.modules and '_renderdoc' not in sys.modules:
import renderdoc
# Alias renderdoc for legibility
rd = renderdoc
# We'll need the struct data to read out of bytes objects
import struct
import os
# We base our data on a MeshFormat, but we add some properties
class MeshData(rd.MeshFormat):
indexOffset = 0
name = ''
def pySaveTexture(resourceId,eventId,controller):
texsave = rd.TextureSave()
texsave.resourceId = resourceId
if texsave.resourceId == rd.ResourceId.Null():
return False
filename = str(int(texsave.resourceId))
# texsave.alpha = rd.AlphaMapping.BlendToCheckerboard
# Most formats can only display a single image per file, so we select the
# first mip and first slice
texsave.mip = 0
texsave.slice.sliceIndex = 0
texsave.alpha = rd.AlphaMapping.Preserve
texsave.destType = rd.FileType.PNG
if not os.path.exists("{0}/{1}".format(folderName,eventId)):
os.makedirs("{0}/{1}".format(folderName,eventId))
outTexPath = "{0}/{1}/{2}.png".format(folderName,eventId,filename)
controller.SaveTexture(texsave, outTexPath)
print("保存圖片{0}".format(outTexPath))
return True
def findIndexDrawLoop(d,index):
ret = None
if d.eventId == index:
return d
for c in d.children:
ret = findIndexDrawLoop(c,index)
if ret:
return ret
return ret
# Recursively search for the drawcall with the most vertices
def findIndexDraw(index,controller):
ret = None
for d in controller.GetDrawcalls():
if d.eventId == index:
ret = d
return ret
for c in d.children:
ret = findIndexDrawLoop(c,index)
if ret:
return ret
return ret
# Unpack a tuple of the given format, from the data
def unpackData(fmt, data):
if isPrint:
print(888)
# We don't handle 'special' formats - typically bit-packed such as 10:10:10:2
# raise RuntimeError("Packed formats are not supported!")
formatChars = {}
# 012345678
formatChars[rd.CompType.UInt] = "xBHxIxxxL"
formatChars[rd.CompType.SInt] = "xbhxixxxl"
formatChars[rd.CompType.Float] = "xxexfxxxd" # only 2, 4 and 8 are valid
# These types have identical decodes, but we might post-process them
formatChars[rd.CompType.UNorm] = formatChars[rd.CompType.UInt]
formatChars[rd.CompType.UScaled] = formatChars[rd.CompType.UInt]
formatChars[rd.CompType.SNorm] = formatChars[rd.CompType.SInt]
formatChars[rd.CompType.SScaled] = formatChars[rd.CompType.SInt]
# We need to fetch compCount components
vertexFormat = str(fmt.compCount) + formatChars[fmt.compType][fmt.compByteWidth]
# Unpack the data
value = struct.unpack_from(vertexFormat, data, 0)
# If the format needs post-processing such as normalisation, do that now
if fmt.compType == rd.CompType.UNorm:
divisor = float((2 ** (fmt.compByteWidth * 8)) - 1)
value = tuple(float(i) / divisor for i in value)
elif fmt.compType == rd.CompType.SNorm:
maxNeg = -float(2 ** (fmt.compByteWidth * 8)) / 2
divisor = float(-(maxNeg-1))
value = tuple((float(i) if (i == maxNeg) else (float(i) / divisor)) for i in value)
# If the format is BGRA, swap the two components
if fmt.BGRAOrder():
value = tuple(value[i] for i in [2, 1, 0, 3])
return value
# Get a list of MeshData objects describing the vertex inputs at this draw
def getMeshInputs(controller, draw):
state = controller.GetPipelineState()
# Get the index & vertex buffers, and fixed vertex inputs
ib = state.GetIBuffer()
vbs = state.GetVBuffers()
attrs = state.GetVertexInputs()
sampleList = state.GetReadOnlyResources(renderdoc.ShaderStage.Fragment)
for sample in sampleList:
for res in sample.resources:
print(res.resourceId)
if not pySaveTexture(res.resourceId,draw.eventId,controller):
break
meshInputs = []
# for i in ib:
# if isPri:nt
# print(i)
# for v in vbs:
# print(v)
#for attr in attrs:
# print(attr.name)
for attr in attrs:
# We don't handle instance attributes
if attr.perInstance:
raise RuntimeError("Instanced properties are not supported!")
meshInput = MeshData()
meshInput.indexResourceId = ib.resourceId # 2646
meshInput.indexByteOffset = ib.byteOffset # 0
meshInput.indexByteStride = draw.indexByteWidth # 0
meshInput.baseVertex = draw.baseVertex # 0
meshInput.indexOffset = draw.indexOffset # 0
meshInput.numIndices = draw.numIndices #頂點(diǎn)總數(shù) 18
# If the draw doesn't use an index buffer, don't use it even if bound
if not (draw.flags & rd.DrawFlags.Indexed):
meshInput.indexResourceId = rd.ResourceId.Null()
# The total offset is the attribute offset from the base of the vertex
meshInput.vertexByteOffset = attr.byteOffset + vbs[attr.vertexBuffer].byteOffset + draw.vertexOffset * vbs[attr.vertexBuffer].byteStride # 0
meshInput.format = attr.format
meshInput.vertexResourceId = vbs[attr.vertexBuffer].resourceId # 2645
meshInput.vertexByteStride = vbs[attr.vertexBuffer].byteStride # 56
meshInput.name = attr.name
meshInputs.append(meshInput)
return meshInputs
def getIndices(controller, mesh):
# Get the character for the width of index
indexFormat = 'B'
if mesh.indexByteStride == 2:
indexFormat = 'H'
elif mesh.indexByteStride == 4:
indexFormat = 'I'
# Duplicate the format by the number of indices
indexFormat = str(mesh.numIndices) + indexFormat
# If we have an index buffer
if mesh.indexResourceId != rd.ResourceId.Null():
# Fetch the data
ibdata = controller.GetBufferData(mesh.indexResourceId, mesh.indexByteOffset, 0)
# Unpack all the indices, starting from the first index to fetch
offset = mesh.indexOffset * mesh.indexByteStride
indices = struct.unpack_from(indexFormat, ibdata, offset)
# Apply the baseVertex offset
return [i + mesh.baseVertex for i in indices]
else:
# With no index buffer, just generate a range
return tuple(range(mesh.numIndices))
def printMeshData(controller, meshData,draw):
if isPrint:
print(4444)
indices = getIndices(controller, meshData[0])
csvArray = []
fileheader = []
formatxyzw = [".x",".y",".z",".w"]
if isPrint:
print("Mesh configuration:")
fileheader.append("VTX")
fileheader.append("IDX")
for attr in meshData:
if not attr.format.Special():
if isPrint:
print("\t%s:" % attr.name)
if isPrint:
print("\t\t- vertex: %s / %d stride" % (attr.vertexResourceId, attr.vertexByteStride))
if isPrint:
print("\t\t- format: %s x %s @ %d" % (attr.format.compType, attr.format.compCount, attr.vertexByteOffset))
headFormat = "{0}{1}"
for i in range(0,attr.format.compCount):
newStr = headFormat.format(attr.name,formatxyzw[i])
fileheader.append(newStr)
# We'll decode the first three indices making up a triangle
csvArray.append(fileheader)
# 寫入CSV
if not os.path.exists("{0}/{1}".format(folderName,draw.eventId)):
os.makedirs("{0}/{1}".format(folderName,draw.eventId))
outPath = "{0}/{1}/model.csv".format(folderName,draw.eventId)
csvFile = open(outPath, "w",newline='')
writer = csv.writer(csvFile)
# ##########################保存圖片#############################################
for inputIter in draw.outputs:
if not pySaveTexture(inputIter,draw.eventId,controller):
break
# ##########################保存圖片#############################################
i = 0
for idx in indices:
# for i in range(0, 3):
# idx = indices[i]
# 每個(gè)頂點(diǎn)的信息
indiceArray = []
if isPrint:
print("Vertex %d is index %d:" % (i, idx))
indiceArray.append(i)
indiceArray.append(idx)
for attr in meshData:
if not attr.format.Special():
# This is the data we're reading from. This would be good to cache instead of
# re-fetching for every attribute for every index
offset = attr.vertexByteOffset + attr.vertexByteStride * idx
data = controller.GetBufferData(attr.vertexResourceId, offset, 0)
# Get the value from the data
value = unpackData(attr.format, data)
for j in range(0,attr.format.compCount):
indiceArray.append(value[j])
# if isPri:ntWe don't go into the details of semantic matching here, just
# print both
if isPrint:
print("\tAttribute '%s': %s" % (attr.name, value))
csvArray.append(indiceArray)
i = i + 1
writer.writerows(csvArray)
csvFile.close()
print("寫入{0}成功".format(outPath))
def sampleCodePreDraw(controller,draw):
if draw.eventId >= startIndex and draw.eventId <= endIndex:
# Move to that draw
controller.SetFrameEvent(draw.eventId, True)
if isPrint:
print("Decoding mesh inputs at %d: %s\n\n" % (draw.eventId, draw.name))
# # Calculate the mesh input configuration
meshInputs = getMeshInputs(controller, draw)
# if isPri:nt# Fetch and
# print the data from the mesh inputs
printMeshData(controller, meshInputs,draw)
def sampleCodeRecursion(controller,draw):
sampleCodePreDraw(controller,draw)
for d in draw.children:
sampleCodeRecursion(controller,d)
def sampleCode(controller):
for draw in controller.GetDrawcalls():
sampleCodeRecursion(controller,draw)
def loadCapture(filename):
if isPrint:
print(222)
# Open a capture file handle
cap = rd.OpenCaptureFile()
# Open a particular file - see also OpenBuffer to load from memory
status = cap.OpenFile(filename, '', None)
# Make sure the file opened successfully
if status != rd.ReplayStatus.Succeeded:
raise RuntimeError("Couldn't open file: " + str(status))
# Make sure we can replay
if not cap.LocalReplaySupport():
raise RuntimeError("Capture cannot be replayed")
# Initialise the replay
status,controller = cap.OpenCapture(rd.ReplayOptions(), None)
if status != rd.ReplayStatus.Succeeded:
raise RuntimeError("Couldn't initialise replay: " + str(status))
return (cap, controller)
if 'pyrenderdoc' in globals():
if isPrint:
print(111)
pyrenderdoc.Replay().BlockInvoke(sampleCode)
else:
if isPrint:
print("aaaa")
rd.InitialiseReplay(rd.GlobalEnvironment(), [])
if len(sys.argv) <= 1:
if isPrint:
print('Usage: python3 {} filename.rdc'.format(sys.argv[0]))
sys.exit(0)
cap,controller = loadCapture(sys.argv[1])
sampleCode(controller)
controller.Shutdown()
cap.Shutdown()
rd.ShutdownReplay()
print("導(dǎo)出完畢!!!!")
優(yōu)化:
其實(shí)還可以做優(yōu)化,這樣導(dǎo)csv文件實(shí)在太慢了,我想到兩個(gè)方法優(yōu)化。
1.用C++直接導(dǎo)出csv,這樣是最快的。
2.可以只順序的導(dǎo)出vertex,因?yàn)楝F(xiàn)在是導(dǎo)了很多重復(fù)的vertex,就是把index跟vertex分開導(dǎo),在c#部分把他轉(zhuǎn)成三角形,可以使得程序更快,大概能快兩到三倍的樣子。
總結(jié)
以上是生活随笔為你收集整理的python做的游戏可以导出吗_Python for RenderDoc批量导出模型和贴图的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 曝三星Galaxy Z Fold4开始量
- 下一篇: 陈伯雄lisp_基于AutoLisp的A