執筆中
はじめに
前回(OBJ編)に引き続き、今回はマテリアルをUSDの世界に取り込んでみましょう。
マテリアルをUSDへ変換
シーンはScene Import
が提供する仕組みを利用してトランスレータープラグインをpythonで実装できましたが、マテリアルも同様にpython(C++も)で変換できる仕組みが用意されています。概要は以下のドキュメントに記載されています。
https://www.sidefx.com/docs/houdini/solaris/shader_framework.html
トランスレーターの実装は以下にざっくりとした説明が記載されています。
マテリアルもpythonでトランスレーターが書けることがわかったので、トランスレーターを実装する準備を始めましょう。
カスタムマテリアル(HDA)を作成する
カスタムマテリアルの作成は先ほどのドキュメントに手順が記載されています。
- FileメニューからNewAssetを選択
- VOPsを選択しNetworkTypeをUSD Surface Shaderに設定
- HDAを生成
- HDAが生成されたらNodeタブを開きRenderMaskに識別子を設定
- 今回は識別子に CUSTOMUSD と設定します(識別子は任意です)
- パラメータを設定。今回はベースカラーテクスチャ用に baseColor を設定しました
Pythonトランスレーターとマテリアルの関連は Render Mask に設定されている識別子を使用します。これ重要です!
トランスレーターを実装してみる
実装したトランスレーターの仕様は
- UsdPreviewSurface
- ファイルテクスチャをdiffuseColorへ設定
シンプルな仕様ですが、UsdPreviewSurfaceの生成とテクスチャの接続に対応していますので、diffuseColor以外のテクスチャも同様の実装で対応できます。コードをみていきましょう
import re
from husd.shadertranslator import ShaderTranslator as TranslatorBase
from husd.shadertranslator import ShaderTranslatorHelper as HelperBase
from pxr import Sdf, UsdShade
class CustomShaderTranslatorHelper( HelperBase ):
def __init__(self, translator_id,
usd_stage, usd_material_path, usd_time_code):
HelperBase. __init__( self, translator_id,
usd_stage, usd_material_path, usd_time_code )
def usdRenderContextName(self, shader_node, shader_node_output_name):
""" Returns the name of the render context used in the USD material
output name. In this case, a univarsal USD render context name.
"""
return UsdShade.Tokens.universalRenderContext
def createUsdShader(self, shader_node, shader_node_output_name,
usd_parent_schema):
""" Creates and configures a single USD shader primitive
based on the given Houdini shader node.
Returns a USD shader schema for the created USD primitive.
"""
# create the USD shader
usd_shader_path = self.usdShaderPrimitivePath(shader_node,
usd_parent_schema)
# create st reader
stReader = UsdShade.Shader.Define(self.usdStage(), usd_shader_path.AppendPath('streader'))
stReader.CreateIdAttr('UsdPrimvarReader_float2')
stReader.CreateInput('varname', Sdf.ValueTypeNames.Token).Set("st")
# create texture
filename = shader_node.parm('basecolor').eval()
diffuseTextureSampler = UsdShade.Shader.Define(self.usdStage(), usd_shader_path.AppendPath('texture'))
diffuseTextureSampler.CreateIdAttr('UsdUVTexture')
diffuseTextureSampler.CreateInput('file', Sdf.ValueTypeNames.Asset).Set(filename)
diffuseTextureSampler.CreateInput('st', Sdf.ValueTypeNames.Float2).ConnectToSource(stReader.ConnectableAPI(), 'result')
diffuseTextureSampler.CreateOutput('rgb', Sdf.ValueTypeNames.Float3)
# create preview shader
usd_shader = self.defineUsdShader( shader_node, usd_shader_path )
usd_shader.SetShaderId( 'UsdPreviewSurface' )
# connect diffuse textire
usd_shader.CreateInput('diffuseColor', Sdf.ValueTypeNames.Color3f).ConnectToSource(diffuseTextureSampler.ConnectableAPI(), 'rgb')
return usd_shader
################################################################################
class CustomShaderTranslator( TranslatorBase ):
def __init__(self):
TranslatorBase.__init__( self )
self.myPattern = re.compile( r'\bCUSTOMUSD\b' )
def matchesRenderMask(self, render_mask):
return bool( self.myPattern.search( render_mask ))
def renderContextName(self, shader_node, shader_node_output_name):
# Standard and USD preview shaders don't have render context name,
# since they are applicable to all renderers.
return UsdShade.Tokens.universalRenderContext
def shaderTranslatorHelper(self, translator_id,
usd_stage, usd_material_path, usd_time_code):
return CustomShaderTranslatorHelper(translator_id,
usd_stage, usd_material_path, usd_time_code)
standard_translator = CustomShaderTranslator()
def usdShaderTranslator():
return standard_translator
ポイントとなるのは トランスレーター機能を提供する ShaderTranslator 、ShaderTranslatorHelper クラス と トランスレーターを登録する usdShaderTranslator です。
ShaderTranslator
CustomShaderTranslator はトランスレーター本体で ShaderTranslator を継承しています。
class CustomShaderTranslator( TranslatorBase ):
def __init__(self):
TranslatorBase.__init__( self )
self.myPattern = re.compile( r'\bCUSTOMUSD\b' )
マテリアルノードとの関連は Render Mask で行います。
def matchesRenderMask(self, render_mask):
return bool( self.myPattern.search( render_mask ))
UsdPreviewSurface では UsdShade.Tokens.universalRenderContext を返せばいいらしい
def renderContextName(self, shader_node, shader_node_output_name):
# Standard and USD preview shaders don't have render context name,
# since they are applicable to all renderers.
return UsdShade.Tokens.universalRenderContext
トランスレーターは ShaderTranslatorHelper へ実装していきます
def shaderTranslatorHelper(self, translator_id,
usd_stage, usd_material_path, usd_time_code):
return CustomShaderTranslatorHelper(translator_id,
usd_stage, usd_material_path, usd_time_code)
ShaderTranslatorHelper
CustomShaderTranslatorHelperを実装していきます。
class CustomShaderTranslatorHelper( HelperBase ):
def __init__(self, translator_id,
usd_stage, usd_material_path, usd_time_code):
HelperBase. __init__( self, translator_id,
usd_stage, usd_material_path, usd_time_code )
def usdRenderContextName(self, shader_node, shader_node_output_name):
""" Returns the name of the render context used in the USD material
output name. In this case, a univarsal USD render context name.
"""
return UsdShade.Tokens.universalRenderContext
トランスレーターをcreateUsdShaderへ実装します。
def createUsdShader(self, shader_node, shader_node_output_name,
usd_parent_schema):
""" Creates and configures a single USD shader primitive
based on the given Houdini shader node.
Returns a USD shader schema for the created USD primitive.
"""
ShaderTranslatorHelper.usdShaderPrimitivePath関数でShader Primを生成します。
# create the USD shader
usd_shader_path = self.usdShaderPrimitivePath(shader_node,
usd_parent_schema)
テクスチャ向けのPrimを作成していきます。
# create st reader
stReader = UsdShade.Shader.Define(self.usdStage(), usd_shader_path.AppendPath('streader'))
stReader.CreateIdAttr('UsdPrimvarReader_float2')
stReader.CreateInput('varname', Sdf.ValueTypeNames.Token).Set("st")
# create texture
filename = shader_node.parm('basecolor').eval()
diffuseTextureSampler = UsdShade.Shader.Define(self.usdStage(), usd_shader_path.AppendPath('texture'))
diffuseTextureSampler.CreateIdAttr('UsdUVTexture')
diffuseTextureSampler.CreateInput('file', Sdf.ValueTypeNames.Asset).Set(filename)
diffuseTextureSampler.CreateInput('st', Sdf.ValueTypeNames.Float2).ConnectToSource(stReader.ConnectableAPI(), 'result')
diffuseTextureSampler.CreateOutput('rgb', Sdf.ValueTypeNames.Float3)
UsdPreviewSurfaceを生成し、テクスチャをdiffuseColorへ接続します。
# create preview shader
usd_shader = self.defineUsdShader( shader_node, usd_shader_path )
usd_shader.SetShaderId( 'UsdPreviewSurface' )
# connect diffuse textire
usd_shader.CreateInput('diffuseColor', Sdf.ValueTypeNames.Color3f).ConnectToSource(diffuseTextureSampler.ConnectableAPI(), 'rgb')
return usd_shader
#usda 1.0
(
doc = """Generated from Composed Stage of root layer
"""
framesPerSecond = 24
metersPerUnit = 1
timeCodesPerSecond = 24
)
def Xform "grid1" (
customData = {
int[] HoudiniPrimEditorNodes = [21]
}
kind = "component"
)
{
matrix4d xformOp:transform:xform = ( (1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 0, 0, 1) )
uniform token[] xformOpOrder = ["xformOp:transform:xform"]
def Mesh "mesh_0" (
apiSchemas = ["MaterialBindingAPI"]
)
{
float3[] extent = [(-5, 0, -5), (5, 0, 5)]
int[] faceVertexCounts = [4] (
customData = {
int64 HoudiniDataId = 5583945335354111658
}
)
int[] faceVertexIndices = [0, 1, 3, 2] (
customData = {
int64 HoudiniDataId = 5583945335354111658
}
)
rel material:binding = </materials/CustomUSDShader1>
uniform token orientation = "leftHanded"
point3f[] points = [(-5, 0, -5), (5, 0, -5), (-5, 0, 5), (5, 0, 5)] (
customData = {
int64 HoudiniDataId = 31
}
interpolation = "vertex"
)
texCoord2f[] primvars:st = [(-4.9999995, 5), (4.9999995, 5), (4.9999995, -5), (-4.9999995, -5)] (
customData = {
int64 HoudiniDataId = 39
}
interpolation = "faceVarying"
)
int[] primvars:st:indices = None
uniform token subdivisionScheme = "none"
}
}
def HoudiniLayerInfo "HoudiniLayerInfo" (
customData = {
int HoudiniCreatorNode = 19
int[] HoudiniEditorNodes = [19, 20, 16, 21, 24]
string HoudiniSaveControl = "Placeholder"
bool HoudiniTreatAsSopLayer = 0
}
)
{
}
def Scope "materials"
{
def Material "CustomUSDShader1" (
customData = {
int[] HoudiniPrimEditorNodes = [25]
}
)
{
token outputs:surface.connect = </materials/CustomUSDShader1/CustomUSDShader1.outputs:surface>
def Shader "CustomUSDShader1"
{
uniform token info:id = "UsdPreviewSurface"
color3f inputs:diffuseColor.connect = </materials/CustomUSDShader1/CustomUSDShader1/texture.outputs:rgb>
token outputs:surface
def Shader "streader"
{
uniform token info:id = "UsdPrimvarReader_float2"
token inputs:varname = "st"
float2 outputs:result
}
def Shader "texture"
{
uniform token info:id = "UsdUVTexture"
asset inputs:file = @C:/Users/hideki/Desktop/USDLogoHeader.png@
float2 inputs:st.connect = </materials/CustomUSDShader1/CustomUSDShader1/streader.outputs:result>
float3 outputs:rgb
}
}
}
}
おわり
OBJ編、マテリアル編で、「あ~この情報がLOPへ持っていけたらいいのになぁ 」というケースで助けになればと思い記事にしました。改めてSolaris(LOP)良くできているなと思いました。
もしSolarisの習得に壁を感じている方(特にTA)は、USDをPython APIやusda(USDテキスト形式)を見てからSolarisへ戻ると理解しやすいかもしれないです。
なんだか駆け足で申し訳ないです・・・・
githubへHDAとPythonコードをアップしました。
https://github.com/K240/ac2021usd