環境
Houdini Apprentice 19.5.534
python3.9 Fbx Sdk
概要
Houdiniでパーティクルを独自VATとしてExr形式のファイルに出力します
Unity上で再生できるプログラムを作ったところ、同じように動作しました
パーティクルは参考の動画をみて作成しました。バージョンが上がり多少違いましたが、同じような感じに出来ました
また動画はライフに基づいてカラーを0に近づけていますが、こちらはAlphaのアトリビュートを追加してそれに対して行っています
Alphaというアトリビュートを追加すると勝手にアルファブレンドになる模様
import OpenEXR import numpy as np import Imath import json fps = int(hou.fps()) node = hou.pwd() targetNode = hou.node(node.parm("node").eval()) startFrame = node.parm("startFrame").eval() endFrame = node.parm("endFrame").eval() imageWidth = node.parm("imageWidth").eval() imageHeight = node.parm("imageHeight").eval() outputDir = node.parm("outputDir").eval() imageSize = imageWidth * imageHeight channelNum = 4 strideTexelNum = 2 pixelType = Imath.PixelType.HALF dataType = np.float16 texels = np.zeros((imageSize, channelNum), dtype=dataType) texIndex = 0 texelIndex = 0 def writeImage(): header = OpenEXR.Header(imageWidth, imageHeight) header['channels'] = { 'R': Imath.Channel(Imath.PixelType(pixelType)), 'G': Imath.Channel(Imath.PixelType(pixelType)), 'B': Imath.Channel(Imath.PixelType(pixelType)), 'A': Imath.Channel(Imath.PixelType(pixelType)) } header['compression'] =Imath.Compression(Imath.Compression.NO_COMPRESSION) outputPath = f'{outputDir}/output{texIndex}.exr'; print(outputPath) output = OpenEXR.OutputFile(outputPath, header) output.writePixels({ 'R': texels[:,0].astype(dataType).tostring(), 'G': texels[:,1].astype(dataType).tostring(), 'B': texels[:,2].astype(dataType).tostring(), 'A': texels[:,3].astype(dataType).tostring() }) output.close() def writePoints(frame, maxPointNum): global texels, texIndex, texelIndex geo = targetNode.geometry() pointNum = len(geo.points()) texelNum = maxPointNum * strideTexelNum if (texelIndex + texelNum) > imageSize: writeImage() texIndex += 1 texelIndex = 0 texels[:,] = [0.0, 0.0, 0.0, 0.0] for point in geo.points(): pos = point.position() texels[texelIndex + 0,] = [pos.x(), pos.y(), pos.z(), 0.0] color = point.attribValue('Cd') alpha = point.attribValue('Alpha') texels[texelIndex + 1,] = [color[0], color[1], color[2], alpha] texelIndex += strideTexelNum dummyNum = maxPointNum - pointNum for i in range(dummyNum): texels[texelIndex + 0,] = [0.0, 0.0, 0.0, 0.0] texels[texelIndex + 1,] = [0.0, 0.0, 0.0, 0.0] texelIndex += strideTexelNum def main(): global texIndex, texelIndex maxPointNum = 0 frameNum = (endFrame - startFrame) + 1 for i in range(frameNum): frame = startFrame + i hou.setFrame(frame) geo = targetNode.geometry() maxPointNum = max(maxPointNum, len(geo.points())) texIndex = 0 texelIndex = 0 for i in range(frameNum): frame = startFrame + i hou.setFrame(frame) writePoints(frame, maxPointNum) if texelIndex > 0 : writeImage() texIndex += 1 paramDict = {} paramDict['texNum'] = texIndex; paramDict['fps'] = fps paramDict['frameNum'] = frameNum; paramDict['maxPointNumInFrame'] = maxPointNum; jsonData = json.dumps(paramDict, ensure_ascii=False) print(jsonData) outputPath = f'{outputDir}/output.json'; f = open(outputPath, 'w') f.write(jsonData) f.close() main()
参考
Houdini ダイナミクス基本講座 Part1:ダイナミクスシミュレーションの流れ - YouTube
プリミティブ版
HdrpVatExample/Fluid.hip at master · keijiro/HdrpVatExample · GitHub
に三角形分割、Color、Normal、UVを追加したものを使用してテスト
import OpenEXR import numpy as np import Imath import json fps = int(hou.fps()) node = hou.pwd() targetNode = hou.node(node.parm("node").eval()) startFrame = node.parm("startFrame").eval() endFrame = node.parm("endFrame").eval() imageWidth = node.parm("imageWidth").eval() imageHeight = node.parm("imageHeight").eval() outputDir = node.parm("outputDir").eval() imageSize = imageWidth * imageHeight channelNum = 4 strideTexelNum = 3 pixelType = Imath.PixelType.HALF dataType = np.float16 texels = np.zeros((imageSize, channelNum), dtype=dataType) texIndex = 0 texelIndex = 0 pointInface = 3 def writeImage(): header = OpenEXR.Header(imageWidth, imageHeight) header['channels'] = { 'R': Imath.Channel(Imath.PixelType(pixelType)), 'G': Imath.Channel(Imath.PixelType(pixelType)), 'B': Imath.Channel(Imath.PixelType(pixelType)), 'A': Imath.Channel(Imath.PixelType(pixelType)) } header['compression'] =Imath.Compression(Imath.Compression.NO_COMPRESSION) outputPath = f'{outputDir}/output{texIndex}.exr'; print(outputPath) output = OpenEXR.OutputFile(outputPath, header) output.writePixels({ 'R': texels[:,0].astype(dataType).tostring(), 'G': texels[:,1].astype(dataType).tostring(), 'B': texels[:,2].astype(dataType).tostring(), 'A': texels[:,3].astype(dataType).tostring() }) output.close() def writeFaces(frame, maxPointNum): global texels, texIndex, texelIndex geo = targetNode.geometry() texelNum = maxPointNum * strideTexelNum pointNum = len(geo.prims()) * pointInface if pointNum > maxPointNum: print(f'error pointNum:{pointNum} maxPointNum:{maxPointNum}') return if (texelIndex + texelNum) > imageSize: writeImage() texIndex += 1 texelIndex = 0 texels[:,] = [0.0, 0.0, 0.0, 0.0] for prim in geo.prims(): #print(f'prim{prim.number()}') if len(prim.vertices()) != pointInface: print('error verticesNum') break pointInPrim = [] for vertex in prim.vertices(): pointInPrim.append(vertex.point()) if len(pointInPrim) != pointInface: print('error pointInPrim') break for i in range(pointInface): point = pointInPrim[int((pointInface - i) % pointInface)] pos = point.position() #print(f'pos{index}/{point.number()}:{pos}') normal = point.attribValue('N') color = point.attribValue('Cd') alpha = point.attribValue('Alpha') uv = point.attribValue('uv') texels[texelIndex + 0,] = [pos.x(), pos.y(), pos.z(), uv[0]] texels[texelIndex + 1,] = [normal[0], normal[1], normal[2], uv[1]] texels[texelIndex + 2,] = [color[0], color[1], color[2], alpha] texelIndex += strideTexelNum #break dummyNum = maxPointNum - pointNum for i in range(dummyNum): texels[texelIndex + 0,] = [0.0, 0.0, 0.0, 0.0] texels[texelIndex + 1,] = [0.0, 0.0, 0.0, 0.0] texels[texelIndex + 2,] = [0.0, 0.0, 0.0, 0.0] texelIndex += strideTexelNum def main(): global texIndex, texelIndex frameNum = (endFrame - startFrame) + 1 geo = targetNode.geometry() maxPointNum = 0 frameNum = (endFrame - startFrame) + 1 for i in range(frameNum): frame = startFrame + i hou.setFrame(frame) geo = targetNode.geometry() maxPointNum = max(maxPointNum, len(geo.prims()) * pointInface) texIndex = 0 texelIndex = 0 for i in range(frameNum): frame = startFrame + i hou.setFrame(frame) print(f'frame:{frame}') writeFaces(frame, maxPointNum) if texelIndex > 0 : writeImage() texIndex += 1 paramDict = {} paramDict['texNum'] = texIndex; paramDict['fps'] = fps paramDict['frameNum'] = frameNum; paramDict['maxPointNumInFrame'] = maxPointNum; paramDict['strideTexelNum'] = strideTexelNum; jsonData = json.dumps(paramDict, ensure_ascii=False) print(jsonData) outputPath = f'{outputDir}/output.json'; f = open(outputPath, 'w') f.write(jsonData) f.close() main() print('finish')