0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Arnold Python APIを使ってみる

0
Posted at

Antigravity CLIを使って、Arnold Python APIのサンプルを参照しながら、シーンを構築してみました。検証しながら進めていきます。ターミナルでの確認、レンダリング実行まだまだ慣れないところです。

円柱を格子状に配置したシーンを作成する

主な変更点

  1. 出力形式を TIFF に変更:
    • driver_exr の代わりに driver_tiff ノードを生成し、出力ファイル名を primitives04.
    tif に変更しました。
    • sRGB 色空間 ( color_space )
    を設定して、一般的な画像ビューアで正しく表示されるようにしています。
  2. わかりやすい解説コメントの追加:
    • 3D CG の基本概念(4x4 行列、カメラ座標の計算、平行光源や環境光の違い)
    • Arnold のレンダリング設定(ピクセルサイズや光の反射回数)
    • メモリ節約技術である「テンプレート(マスター)とコピー(インスタンス)による配置」
    • シェーダーパラメータ( metalness や specular_roughness )の物理的な意味
    • 2重ループによる格子状配置の仕組み

動作確認

• レンダリング画像: primitives04.tif
作成したスクリプトを実行し、以下の出力ファイルが正常に生成されることを確認しました:

• アスキー形式 ASS: primitives04_ascii.ass

"AA_samples"やcamera positioned at (7, 7, -7)やAiNodeSetRGB(shader_ground、"base_color", 0.3, 0.3, 0.3)に変更しました。当初生成のシーンは、円柱の反射が黒で、地面にも影が出ていませんでした。考えられる原因は何でしょうか。

初期のシーンで「円柱の反射が真っ黒になってしまう現象」および「地面に影が出ない現象」が発生していた場合、Arnoldレンダラーの設定やノードのパラメータにおいて、以下のようないくつかの原因が考えられます。
──────

1. 円柱の反射(鏡面反射)が黒くなる原因

Arnold の金属( metalness 1.0)などの反射は、周囲の光や物体をレイトレーシング(光線の追跡)によって計算して映し出します。これが黒くなる場合、以下の設定が疑われます。

• 原因A: 反射の計算回数( GI_specular_depth )が 0 になっている
• Arnold のオプション ( options ) にある GI_specular_depth(鏡面反射の最大バウンス回数)が 0に設定されていると、光が金属表面で反射した先の計算が行われず、反射部分がすべて真っ黒になります。
• 原因B: 空間を照らす環境光( skydome_light などの背景)がない
• 金属の反射は、周囲の景色や空の色を映し出すことで表現されます。もし背景やskydome_light (ドーム状の環境光)が存在しない、または強さ ( intensity ) が 0の場合、反射する対象が「何もない真っ黒な宇宙空間」と同じ状態になり、反射面が黒く見えてしまいます。
• 原因C: 反射率・粗さシェーダーパラメータの設定不備
• standard_surface シェーダーで、 metalness(金属度)を高めているにもかかわらず、反射のウェイト( specular )が 0になっていたり、反射の色(specular_color)が黒になっていたりすると、反射が表現されません。

2. 地面に影が出ない原因

ライトから発せられた光が円柱に遮られて地面に影を作るはずが、影が出ない場合は、光線(レイ)の設定やオブジェクトの影の受け設定がオフになっている可能性があります。

• 原因A: 地面(受影オブジェクト)の receive_shadows が False になっている
• 地面インスタンスの receive_shadows パラメータが False(オフ)になっていると、他の物体が遮った光による影を受け取らなくなります。
• 原因B: ライトの cast_shadows が False になっている
• メインライト( distant_light など)の cast_shadows 属性が False(オフ)になっていると、そのライトは「影を作らない光」となり、すべての物体を透過して照らすため影ができません。

• 原因C: 円柱の visibility (可視性マスク)で影用光線(Shadow Ray)がオフになっている
• Arnold の visibility パラメータはビットマスクで制御されます。もしこれが 0などの低い値で影(Shadow
Ray)に対して非可視に設定されていると、光線が円柱を素通りしてしまい、地面に影が落ちなくなります。(※通常、インスタンスのvisibility は 255 に設定してすべての光線に反応させます)。
• 原因D: 光源の位置や方向(ライトの角度)が不適切
•ライトの向きが「地面の下から上に向かって照らしている」状態になっていたり、カメラの真後ろから完璧
に水平に照らしているために影がカメラの死角(物体の真後ろ)に隠れてしまっているなど、配置上の理由
で影が見えなくなっている場合があります。

確認・解決のためのチェックポイント

  1. オプションの確認:
    # 反射の深度が 1 以上になっているか
    AiNodeSetInt(options, "GI_specular_depth", 3)

  2. 地面・オブジェクトの設定確認:
    # 影を受け取る・落とす設定が True になっているか
    AiNodeSetBool(inst_ground, "receive_shadows", True)
    AiNodeSetByte(inst_cyl, "visibility", 255) # 255 で全てのレイを可視化

  3. ライトの設定確認:
    # ライトがオブジェクトの上方に位置し、地面に向けて照らしているか
    key_light_matrix = look_at((8.0, 12.0, 4.0), (0.0, 0.0, 0.0), (0.0, 1.0, 0.0))

# primitives04.py
# Arnold 7 Python API
# 5x5 grid of identical capped cylinders viewed from camera (10, 10, -10) looking at (0, 0, 0).
# 出力を TIFF 形式に変更し、高校生向けに解説コメントを追加したスクリプト。

import math
# Arnoldレンダラーをコントロールするためのライブラリを読み込みます。
from arnold import *

# --- 行列(マトリクス)を作る関数 ---
# 3Dグラフィックスでは、物体の位置、傾き(回転)、大きさを「4x4の数字の表(行列)」で表現します。
def make_matrix(vals):
    return AtMatrix(*vals)

# カメラやライトが「どこから(eye)」「どこを向いて(target)」「どちらが上か(up)」を指定して、
# その向きに合わせた変換行列(カメラの目線を表す行列)を計算する関数です。
def look_at(eye, target, up):
    # カメラの向きの基準となる3つの直交する方向(ベクトル)を計算します。
    # 1. カメラが見ている方向(奥向きの軸 w)
    wx = eye[0] - target[0]
    wy = eye[1] - target[1]
    wz = eye[2] - target[2]
    w_len = math.sqrt(wx*wx + wy*wy + wz*wz)
    wx /= w_len
    wy /= w_len
    wz /= w_len
    
    # 2. カメラの右方向(右向きの軸 u): 上方向ベクトルとwの外積を計算します。
    ux = up[1]*wz - up[2]*wy
    uy = up[2]*wx - up[0]*wz
    uz = up[0]*wy - up[1]*wx
    u_len = math.sqrt(ux*ux + uy*uy + uz*uz)
    ux /= u_len
    uy /= u_len
    uz /= u_len
    
    # 3. カメラの上方向(上向きの軸 v): wとuの外積を計算します。
    vx = wy*uz - wz*uy
    vy = wz*ux - wx*uz
    vz = wx*uy - wy*ux
    
    # 計算した3つの軸とカメラの位置(eye)を組み合わせて、4x4の行列を作って返します。
    return AtMatrix(
        ux,  uy,  uz,  0.0,
        vx,  vy,  vz,  0.0,
        wx,  wy,  wz,  0.0,
        eye[0], eye[1], eye[2], 1.0
    )

# 物体の位置(tx, ty, tz)と倍率(sx, sy, sz)を指定して、配置用の行列を作る関数です。
def make_transform_matrix(tx, ty, tz, sx=1.0, sy=1.0, sz=1.0):
    return AtMatrix(
        sx,  0.0, 0.0, 0.0,
        0.0, sy,  0.0, 0.0,
        0.0, 0.0, sz,  0.0,
        tx,  ty,  tz,  1.0
    )

# --- Arnoldの初期設定 ---
# Arnoldのレンダリングエンジン(頭脳)を起動します。
universe = AiBegin()
# レンダリング中の進捗やエラーなどのログ情報をすべて画面(コンソール)に出力するように設定します。
AiMsgSetConsoleFlags(universe, AI_LOG_ALL)

# --- フィルターの設定 ---
# 出来上がった画像のピクセルのギザギザ(ジャギー)を滑らかにするためのフィルター(Gaussianフィルター)を作ります。
filt = AiNode(universe, "gaussian_filter", "myfilter")

# --- 画像出力用ドライバーの設定 ---
# ★ 出力を "driver_tiff"(TIFF画像)に変更しました。
# これにより、レンダリング結果が高画質なTIFF形式(拡張子 .tif)で保存されます。
driver = AiNode(universe, "driver_tiff", "mydriver")
AiNodeSetStr(driver, "filename", "primitives04.tif")
AiNodeSetStr(driver, "color_space", "sRGB") # 標準的なディスプレイで綺麗に見える色空間(sRGB)を指定します

# --- レンダリング全体のオプション設定 ---
options = AiUniverseGetOptions(universe)
AiNodeSetInt(options, "xres", 800) # 生成する画像の横幅(ピクセル数)
AiNodeSetInt(options, "yres", 600) # 生成する画像の縦幅(ピクセル数)
AiNodeSetInt(options, "AA_samples", 4) # アンチエイリアス(綺麗さ)のレベル。数値を上げると滑らかになりますが時間がかかります。
AiNodeSetInt(options, "GI_diffuse_depth", 3) # 間接光(光の跳ね返り)を計算する回数。部屋の明るさに影響します。
AiNodeSetInt(options, "GI_specular_depth", 3) # 金属などの反射の跳ね返りを計算する回数。

# 出力方法の登録:「RGBA」チャンネルを「上記のフィルター」と「上記のドライバー」を使って書き出すように設定します。
out_arr = AiArrayAllocate(1, 1, AI_TYPE_STRING)
AiArraySetStr(out_arr, 0, "RGBA RGBA myfilter mydriver")
AiNodeSetArray(options, "outputs", out_arr)

# --- カメラの設定 ---
# 遠近感のあるリアルな見え方をする「透視投影カメラ(persp_camera)」を作ります。
camera = AiNode(universe, "persp_camera", "/camera")
AiNodeSetFlt(camera, "fov", 45.0) # 視野角(レンズの広さ)。45度は標準的な画角です。
AiNodeSetFlt(camera, "near_clip", 0.01) # これより近いものは映さない(手前の限界)
AiNodeSetFlt(camera, "far_clip", 100000.0) # これより遠いものは映さない(奥の限界)

# カメラを座標 (7, 7, -7) に置き、原点 (0, 0, 0) を見つめるように設置します。
camera_matrix = look_at((7.0, 7.0, -7.0), (0.0, 0.0, 0.0), (0.0, 1.0, 0.0))
AiNodeSetMatrix(camera, "matrix", camera_matrix)
# 作成したカメラをレンダリングで使うメインカメラとして登録します。
AiNodeSetPtr(options, "camera", camera)

# --- ライト(照明)の設定 ---
# 1. スカイドームライト(全体を優しく照らす光)
# 空全体から包み込むように均一な光を当て、影が真っ暗になるのを防ぎます。
skydome = AiNode(universe, "skydome_light", "myskydome")
AiNodeSetRGB(skydome, "color", 1.0, 1.0, 1.0) # 白色の光
AiNodeSetFlt(skydome, "intensity", 0.8) # 光の強さ
AiNodeSetInt(skydome, "samples", 3) # 光の綺麗さ(ノイズを減らす設定)

# 2. ディスタントライト(太陽光のように平行に進む強い光)
# 鋭い影や、キラッとしたハイライトを作るためのメイン照明(キーライト)です。
key_light = AiNode(universe, "distant_light", "keylight")
AiNodeSetRGB(key_light, "color", 1.0, 0.9, 0.8) # 少し温かみのある(夕日のような)色
AiNodeSetFlt(key_light, "intensity", 1.5) # 強めの光
# 右上奥 (8, 12, 4) から原点 (0, 0, 0) に向かって光を差し込ませます。
key_light_matrix = look_at((8.0, 12.0, 4.0), (0.0, 0.0, 0.0), (0.0, 1.0, 0.0))
AiNodeSetMatrix(key_light, "matrix", key_light_matrix)

# --- シェーダー(質感・見た目)の設定 ---
# standard_surface は、設定次第で金属、プラスチック、ガラスなど様々な質感を表現できる万能なシェーダーです。

# 1. 金(ゴールド)の質感
shader_gold = AiNode(universe, "standard_surface", "shader_gold")
AiNodeSetFlt(shader_gold, "base", 1.0)
AiNodeSetRGB(shader_gold, "base_color", 1.0, 0.766, 0.336) # 金色のベースカラー
AiNodeSetFlt(shader_gold, "metalness", 1.0) # 1.0 にすると「金属」の質感になります(反射が強く、周囲を映し込む)
AiNodeSetFlt(shader_gold, "specular_roughness", 0.08) # 表面の粗さ。0に近いほどピカピカした鏡面になり、大きいほどザラザラになります。

# 2. 銅(コッパー / ローズゴールド)の質感
shader_copper = AiNode(universe, "standard_surface", "shader_copper")
AiNodeSetFlt(shader_copper, "base", 1.0)
AiNodeSetRGB(shader_copper, "base_color", 0.95, 0.64, 0.54) # 銅色のベースカラー
AiNodeSetFlt(shader_copper, "metalness", 1.0) # 金属
AiNodeSetFlt(shader_copper, "specular_roughness", 0.1) # 少しだけザラザラ

# 3. 青い陶器(ツヤのあるプラスチックのような)の質感
shader_blue = AiNode(universe, "standard_surface", "shader_blue")
AiNodeSetFlt(shader_blue, "base", 1.0)
AiNodeSetRGB(shader_blue, "base_color", 0.1, 0.45, 0.9) # 鮮やかな青色
AiNodeSetFlt(shader_blue, "metalness", 0.0) # 0.0 なので「非金属(プラスチックや陶器など)」になります。
AiNodeSetFlt(shader_blue, "specular_roughness", 0.05) # かなりツルツル
AiNodeSetFlt(shader_blue, "specular_IOR", 1.5) # 屈折率(光の反射の強さを決める物理的な値。1.5はガラスやプラスチックの標準的な値です)

# 4. 地面の質感
shader_ground = AiNode(universe, "standard_surface", "shader_ground")
AiNodeSetFlt(shader_ground, "base", 1.0) 
AiNodeSetRGB(shader_ground, "base_color", 0.85, 0.85, 0.85) 
AiNodeSetFlt(shader_ground, "specular_roughness", 0.6) 
AiNodeSetFlt(shader_ground, "metalness", 0.0)

# 変形させない(元の状態のまま)時に使う基本の行列(単位行列)を定義します。
identity = make_matrix([1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1])

# --- ジオメトリ・マスター(金型・3Dモデルのテンプレート) ---
# メモリを節約するため、オリジナルの形状(マスター)を1つだけ作り、それをコピー(インスタンス)して配置します。
# マスター自身は画面に映らないように `visibility` を 0(非表示)に設定します。

# 1. シリンダー(円柱の胴体部分)のテンプレート
# ※ Arnoldの基本の円柱は、上下のフタが開いた「中空のチューブ」のような形状です。
mesh_cylinder = AiNode(universe, "cylinder", "mesh_cylinder")
AiNodeSetVec(mesh_cylinder, "bottom", 0.0, 0.0, 0.0) # 底面の中心座標
AiNodeSetVec(mesh_cylinder, "top", 0.0, 1.0, 0.0) # 天面の中心座標
AiNodeSetFlt(mesh_cylinder, "radius", 0.7) # 半径 0.5
AiNodeSetByte(mesh_cylinder, "visibility", 0) # 非表示

# 2. 天面のフタ用の円盤(ディスク)
disk_top = AiNode(universe, "disk", "disk_top")
AiNodeSetVec(disk_top, "center", 0.0, 1.0, 0.0) # 天面の位置に配置
AiNodeSetVec(disk_top, "normal", 0.0, 1.0, 0.0) # 上を向いた円盤
AiNodeSetFlt(disk_top, "radius", 0.7)
AiNodeSetByte(disk_top, "visibility", 0)

# 3. 底面のフタ用の円盤(ディスク)
disk_bottom = AiNode(universe, "disk", "disk_bottom")
AiNodeSetVec(disk_bottom, "center", 0.0, 0.0, 0.0) # 底面の位置に配置
AiNodeSetVec(disk_bottom, "normal", 0.0, -1.0, 0.0) # 下を向いた円盤
AiNodeSetFlt(disk_bottom, "radius", 0.7)
AiNodeSetByte(disk_bottom, "visibility", 0)


# --- 5×5のグリッド状に円柱を並べる ---
# テンプレートをコピーして、色を変えながら配置します。
grid_size = 5 # 縦5列、横5列
spacing = 1.3 # 円柱同士の間隔
cyl_scale_r = 0.5  # 半径を元のテンプレートの 0.5 倍(0.25)にする
cyl_scale_h = 1.2  # 高さを 1.2 にする

# 使用する3つの質感をリストにしておきます。
shaders = [shader_gold, shader_copper, shader_blue]

# 2重ループ(行と列)を使って、格子状に並べていきます。
for row in range(grid_size):
    for col in range(grid_size):
        # 原点 (0, 0, 0) を中心にして、綺麗に並ぶように座標を計算します。
        tx = (col - (grid_size - 1) / 2.0) * spacing
        tz = (row - (grid_size - 1) / 2.0) * spacing
        ty = 0.0
        
        # 斜め方向にストライプ模様になるように、使う質感(インデックス)を計算します。
        shader_idx = (row + col) % 3
        shader = shaders[shader_idx]
        
        # 配置・大きさを表す行列を作ります。
        mat = make_transform_matrix(tx, ty, tz, cyl_scale_r, cyl_scale_h, cyl_scale_r)
        
        # 各オブジェクトにユニークな名前をつけるための末尾の文字列です(例: _0_0, _0_1)
        inst_name_suffix = f"_{row}_{col}"
        
        # A. 円柱胴体パーツのコピー(インスタンス)を作って配置
        inst_cyl = AiNode(universe, "ginstance", f"/inst_cyl{inst_name_suffix}")
        AiNodeSetByte(inst_cyl, "visibility", 255) # 255 = 画面に表示する
        AiNodeSetByte(inst_cyl, "sidedness", 255) # 表裏両面を描画する
        AiNodeSetBool(inst_cyl, "receive_shadows", True) # 他の物体の影を受ける
        AiNodeSetBool(inst_cyl, "self_shadows", True) # 自分自身の影を作る
        AiNodeSetPtr(inst_cyl, "node", mesh_cylinder) # コピー元としてシリンダーのテンプレートを指定します
        AiNodeSetPtr(inst_cyl, "shader", shader) # 質感を指定します
        AiNodeSetMatrix(inst_cyl, "matrix", mat) # 配置する場所・大きさを設定します
        
        # B. 天面のフタパーツのコピーを作って配置
        inst_top = AiNode(universe, "ginstance", f"/inst_top{inst_name_suffix}")
        AiNodeSetByte(inst_top, "visibility", 255)
        AiNodeSetByte(inst_top, "sidedness", 255)
        AiNodeSetBool(inst_top, "receive_shadows", True)
        AiNodeSetBool(inst_top, "self_shadows", True)
        AiNodeSetPtr(inst_top, "node", disk_top)
        AiNodeSetPtr(inst_top, "shader", shader)
        AiNodeSetMatrix(inst_top, "matrix", mat)
        
        # C. 底面のフタパーツのコピーを作って配置
        inst_bot = AiNode(universe, "ginstance", f"/inst_bot{inst_name_suffix}")
        AiNodeSetByte(inst_bot, "visibility", 255)
        AiNodeSetByte(inst_bot, "sidedness", 255)
        AiNodeSetBool(inst_bot, "receive_shadows", True)
        AiNodeSetBool(inst_bot, "self_shadows", True)
        AiNodeSetPtr(inst_bot, "node", disk_bottom)
        AiNodeSetPtr(inst_bot, "shader", shader)
        AiNodeSetMatrix(inst_bot, "matrix", mat)


# --- 地面(フラットな板)の作成 ---
# 平らな板(ポリゴンメッシュ)を作成します。
mesh_ground = AiNode(universe, "polymesh", "mesh_ground")
AiNodeSetBool(mesh_ground, "smoothing", False) # カクカクした面として扱う設定
AiNodeSetArray(mesh_ground, "nsides", AiArray(1, 1, AI_TYPE_UINT, 4)) # 4角形ポリゴンが1つ
AiNodeSetArray(mesh_ground, "vidxs", AiArray(4, 1, AI_TYPE_UINT, 0,1,3,2)) # 頂点を結ぶ順番(インデックス)
AiNodeSetArray(mesh_ground, "nidxs", AiArray(4, 1, AI_TYPE_UINT, 0,0,0,0)) # 法線の割り当て
# 地面の4つの端(頂点)の座標(X, Y, Z)を定義します。Y=0.0の高さで15x15の大きさです。
AiNodeSetArray(mesh_ground, "vlist",
    AiArray(4, 1, AI_TYPE_VECTOR,
        AtVector(-15.0,0.0,-15.0), AtVector(15.0,0.0,-15.0),
        AtVector(-15.0,0.0, 15.0), AtVector(15.0,0.0, 15.0),
    ))
# 地面が真上(Y方向プラス)を向いていることを定義します。
AiNodeSetArray(mesh_ground, "nlist",
    AiArray(1, 1, AI_TYPE_VECTOR, AtVector(0,1,0)))

# 地面のインスタンスを作成し、原点位置(変形なし)に配置します。
inst_ground = AiNode(universe, "ginstance", "/ground")
AiNodeSetByte(inst_ground, "visibility", 255)
AiNodeSetByte(inst_ground, "sidedness", 255)
AiNodeSetBool(inst_ground, "receive_shadows", True)
AiNodeSetBool(inst_ground, "self_shadows", True)
AiNodeSetPtr(inst_ground, "node", mesh_ground)
AiNodeSetPtr(inst_ground, "shader", shader_ground)
AiNodeSetMatrix(inst_ground, "matrix", identity)

# --- シーンのエクスポート ---
# 計算の前に、設定された3D空間の情報(シーン)をテキスト形式のASSファイル(primitives04_ascii.ass)として保存します。
# これにより、正しく設定できているかを後で確認できます。
params = AiParamValueMap()
AiParamValueMapSetBool(params, "binary", False) # テキスト形式で保存する設定
AiSceneWrite(universe, "primitives04_ascii.ass", params, None)
AiParamValueMapDestroy(params)

# --- レンダリングの実行 ---
# レンダリングセッションを作成し、光のシミュレーション(レイトレーシング)を開始して画像を描画します。
render_session = AiRenderSession(universe)
AiRender(render_session)
# レンダリングが終わったら、使用したメモリなどの資源を解放します。
AiRenderSessionDestroy(render_session)

# Arnoldのエンジンを終了します。これでプログラムの処理がすべて完了します。
AiEnd()

primitives04.png

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?