1. はじめに
高校生と学生に、Arnold Pythonを使って、CGとPythonを体験しようと、いろいろ検証を始めました。Antigravity CLIとAntigravity2.0を併用しながら検証しているところ。便利なのはRenderingしてTIFF画像を出力してくれるところです。Pov-Rayのスクリプトは、いろいろなリンクを参考にさせていただきました。
レガシーにリスペクトと感謝。
ありがとうございます。
2. Pov-RayのシーンをAntigravity CLIを使ってArnold Pythonに変換する
プロジェクトフォルダをつくり、ターミナルから、agyを起動する。
その前に、ArnoldのPATHを設定しておく。
$env:PATH="C:\Arnold-7.4.5.2-windows\bin;" + $env:PATH
$env:PYTHONPATH="C:\Arnold-7.4.5.2-windows\python;" + $env:PYTHONPATH
agyに教えておくといいです。
Antigravity2.0に移行した場合は、プロンプトで
移行前と同じように、C:\Arnold-7.4.5.2-windows\python をPYTHONPATHに、
C:\Arnold-7.4.5.2-windows\bin をPATHに追加してArnoldを実行してください。
以下は、変換するためのプロンプトです。これまで、サンプルとして試したPythonファイルを参照してもらいながら、シーンを変換していきました。
> Pov-RayシーンをArnold pythonに変換してください。
マテリアルの指定: 「POV-Rayの pigment や finish を見つけた場合、Arnoldでは必ず standard_surface
ノードを使用し、base_color や specular パラメータにマッピングすること。」
ライトの置換: 「POV-Rayの area_light は、Arnoldの quad_light に変換すること。」
座標系の注意: 「POV-Rayは左手座標系(Y-up)ですが、Arnoldの環境に合わせる際、Z軸の反転などが必要な場合は適宜計算して出力すること。」
画面サイズ: 640 x 480
"driver_tiff"を使用します。
高校生向けに解説コメントを追加します
また、これまで生成したpyファイルを参照して、
以下を変換してみてください。001.pyで保存してください。
## 001
camera{
location <0,0,-3>
look_at <0,0,0>
}
light_source {
<100,100,-100>
color rgb <1,1,1>
}
object {
sphere {<0 , 0 , 0> , 1}
texture {
pigment { rgb <1 , 0 , 0> }
}
}
# 001.py
# Arnold 7 Python API
# POV-Rayシーンから Arnold (Python API) への変換スクリプト
#
# 【高校生向け解説】
# このスクリプトは、3Dグラフィックス(CG)プログラミングの第一歩である「赤い球体を1個配置する」シーンです。
# 高校の数学や物理で習う「ベクトル」「座標系」「物理法則(逆二乗の法則)」が、
# プログラミングの中でどのように使われているかを詳しく解説します!
#
# ここで学べるポイント:
# 1. 右手系と左手系の座標変換(Z軸の反転)
# - POV-Rayは「左手系座標(Z軸の奥がプラス)」、Arnoldは「右手系座標(Z軸の手前がプラス)」です。
# - POV-Rayのカメラ位置が <0, 0, -3> のため、ArnoldではZ座標の符号を反転し (0, 0, 3) に配置します。
# 2. カメラの視界を計算する「ベクトル外積(Cross Product)」
# - 関数 `look_at` は、カメラの位置(eye)、見つめる点(target)、上方向(up)から、カメラの「向き」を表す3次元の基底ベクトル(u, v, w)を計算します。
# - 二つのベクトルに垂直な新しいベクトルを作る「外積(up x w)」を使って、カメラの右方向(u)や上方向(v)を数学的に求めています。
# 3. 光の明るさと「逆二乗の法則」
# - 物理の世界では、点光源から出る光の強さは「距離の2乗」に反比例して弱くなります。
# - ライトが (100, 100, 100) に置かれており、シーンの中心(原点)からの距離は d = √30000 ≒ 173.2 です。
# - Arnoldは物理的に正しい計算を行うため、そのままでは光が届きません。元の明るさ 1.0 に見合うよう、距離の2乗(d^2 = 30000.0)を掛け算して intensity = 30000.0 に補正しています。
# 4. 物体の質感(マテリアル)と「拡散反射」
# - `standard_surface` は現実の素材を表現する万能なシェーダーです。
# - `specular`(反射光)を 0.0 に設定することで、つや消し(マット)のざらざらした赤(ベースカラー: 1.0, 0.0, 0.0)を表現しています。
# 5. CGの最適化手法「インスタンス(クローン)配置」
# - 形状のデータ(mesh_sphere)を直接レンダリングするのではなく、それは非表示(visibility=0)にし、
# - 代わりに位置情報だけを持った「分身(ginstance)」を配置します。将来的に何千個もの物体を置くときに、メモリを大幅に節約するための基本テクニックです。
from arnold import *
import math
# --- 便利関数の定義 ---
# 4x4の変換行列を作成する関数です。
def make_matrix(vals):
return AtMatrix(*vals)
# 位置 (tx, ty, tz) への移動を表現する行列を作ります。
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
)
# カメラの位置と向きからビュー変換行列を作る関数です。(高校数学の「ベクトルの外積」と「正規化」の応用)
def look_at(eye, target, up):
# ① 視線ベクトル 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
# ② カメラの右方向のベクトル u を求めます(上方向と視線ベクトルの外積)
# 外積を使うと、2本のベクトルに対して垂直な第3のベクトルを作ることができます。
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
# ③ カメラの実際の真上方向のベクトル v を求めます(w と u の外積)
vx = wy*uz - wz*uy
vy = wz*ux - wx*uz
vz = wx*uy - wy*ux
# これら3つのベクトルと位置情報から、3次元空間のビュー行列を作成して返します
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
)
# --- ① レンダリングエンジンの初期化 ---
universe = AiBegin()
AiMsgSetConsoleFlags(universe, AI_LOG_ALL)
# --- ② レンダリング出力の設定 ---
# 画像のギザギザ(ジャギー)を滑らかにするフィルターを設定します
filt = AiNode(universe, "gaussian_filter", "myfilter")
# レンダリング結果を TIFF 画像として書き出すためのドライバーを設定します
driver = AiNode(universe, "driver_tiff", "mydriver")
AiNodeSetStr(driver, "filename", "001.tif")
# レンダリングの解像度(640x480 ピクセル)と、計算の細かさ(AA_samples = 4)を設定します
options = AiUniverseGetOptions(universe)
AiNodeSetInt(options, "xres", 640)
AiNodeSetInt(options, "yres", 480)
AiNodeSetInt(options, "AA_samples", 4)
# 間接光(物体同士の照り返しなど)の計算回数(深さ)を設定します
AiNodeSetInt(options, "GI_diffuse_depth", 3)
AiNodeSetInt(options, "GI_specular_depth", 3)
out_arr = AiArrayAllocate(1, 1, AI_TYPE_STRING)
AiArraySetStr(out_arr, 0, "RGBA RGBA myfilter mydriver")
# 最終的なレンダー出力として設定
AiNodeSetArray(options, "outputs", out_arr)
# --- ③ カメラの設定 ---
# POV-Ray定義: location <0,0,-3> look_at <0,0,0>
# 右手系へのZ軸反転により、カメラは (0.0, 0.0, 3.0) に置きます
camera = AiNode(universe, "persp_camera", "/camera")
AiNodeSetFlt(camera, "fov", 50.0) # 視野角を 50度 に設定
AiNodeSetFlt(camera, "near_clip", 0.01)
AiNodeSetFlt(camera, "far_clip", 100000.0)
# ビュー変換行列を設定
camera_matrix = look_at((0.0, 0.0, 3.0), (0.0, 0.0, 0.0), (0.0, 1.0, 0.0))
AiNodeSetMatrix(camera, "matrix", camera_matrix)
AiNodeSetPtr(options, "camera", camera)
# --- ④ ライト(光源)の設定 ---
# POV-Ray定義: light_source { <100,100,-100> color rgb <1,1,1> }
# Z反転によりライトは (100.0, 100.0, 100.0) になります
# 逆二乗の物理減衰(距離 d = √30000 ≒ 173.2)を補正するため、
# 強度(intensity)を 30000.0 に設定しています
light = AiNode(universe, "point_light", "light")
AiNodeSetRGB(light, "color", 1.0, 1.0, 1.0)
AiNodeSetFlt(light, "intensity", 30000.0)
AiNodeSetFlt(light, "exposure", 0.0)
light_matrix = make_transform_matrix(100.0, 100.0, 100.0)
AiNodeSetMatrix(light, "matrix", light_matrix)
# --- ⑤ マテリアル(色と質感)の設定 ---
# POV-Ray定義: pigment { rgb <1,0,0> }
# 赤色でつや消しのマテリアルを作ります
shader_red = AiNode(universe, "standard_surface", "shader_red")
AiNodeSetFlt(shader_red, "base", 1.0)
AiNodeSetRGB(shader_red, "base_color", 1.0, 0.0, 0.0) # 鮮やかな赤
# specularを 0.0 にすることで、プラスチックや金属のようなハイライト(反射光)のない、
# つや消し(マット)な粘土のような質感にします
AiNodeSetFlt(shader_red, "specular", 0.0)
# --- ⑥ ジオメトリ(形状)の設定 ---
# POV-Ray定義: sphere { <0,0,0>, 1 }
# 原点に半径 1.0 の球体を定義します
mesh_sphere = AiNode(universe, "sphere", "mesh_sphere")
AiNodeSetFlt(mesh_sphere, "radius", 1.0)
# マスター形状ノード自体は画面に直接映らないよう非表示(visibility = 0)にします
AiNodeSetByte(mesh_sphere, "visibility", 0)
# 実体を表示するためのインスタンス(分身)を作成します
identity = make_matrix([1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1])
inst_sphere = AiNode(universe, "ginstance", "/sphere")
AiNodeSetByte(inst_sphere, "visibility", 255) # カメラや光に対して表示
AiNodeSetByte(inst_sphere, "sidedness", 255)
AiNodeSetBool(inst_sphere, "receive_shadows", True) # 影を受ける
AiNodeSetBool(inst_sphere, "self_shadows", True) # 自分自身に影を落とす
# 分身の参照元(node)としてマスター球体を設定し、赤い質感(shader)と位置(matrix)を設定します
AiNodeSetPtr(inst_sphere, "node", mesh_sphere)
AiNodeSetPtr(inst_sphere, "shader", shader_red)
AiNodeSetMatrix(inst_sphere, "matrix", identity)
# --- ⑦ シーンの出力とレンダリング ---
# レンダリングする前に、確認用のテキスト形式シーンファイル (.ass) を書き出します
params = AiParamValueMap()
AiParamValueMapSetBool(params, "binary", False)
AiSceneWrite(universe, "001_ascii.ass", params, None)
AiParamValueMapDestroy(params)
# レンダリングを開始して、001.tif を書き出します
render_session = AiRenderSession(universe)
AiRender(render_session)
AiRenderSessionDestroy(render_session)
# 終了処理(メモリ解放など)を行います
AiEnd()
3. 繰り返しを使う。
# 002_2.py
# Arnold 7 Python API
# POV-Rayシーンから Arnold (Python API) への変換スクリプト
#
# 【高校生向け解説】
# 今回のシーン「002_2」では、制御構文である「ループ(繰り返し)処理」を使って、
# 横一列に並んだ5つの黄色い金属球を配置します。
#
# ここで学べるポイント:
# 1. プログラミングにおける繰り返し(ループ)処理の応用
# - POV-Rayの `#while` ループを Python の `for` ループ(または `while` ループ)に翻訳します。
# - カウンタ変数(MyValue)を -2 から 2 まで 1 ずつ変化させながら、球体を配置します。
# - これにより、同じ形状定義(球体)を使って、少しずつ位置をずらしたクローン(インスタンス)を
# スマートに量産することができます。
# 2. 右手系座標(Arnold)とZ軸反転
# - POV-Rayは「左手系座標(Z軸の奥方向がプラス)」、Arnoldは「右手系座標(Z軸の手前方向がプラス)」です。
# - カメラの配置座標が POV-Ray で <0.0, 1.0, -5.0> なので、ArnoldではZ座標の符号を反転して
# (0.0, 1.0, 5.0) に配置します。
# 3. 物理ベースレンダリングにおける光の減衰(逆二乗の法則)
# - POV-Rayのデフォルトのライトは、どんなに遠くに置いても光が衰えません(非物理的)。
# - Arnoldは現実世界の物理法則に従うため、光は進むにつれて「距離の2乗に反比例して暗く」なります。
# - 今回のライトは (100, 100, -100) という非常に遠い場所にあります(原点からの距離 d = √30000 ≒ 173.2)。
# - 元の光の明るさ(1.0)を維持するために、距離の2乗(d^2 = 30000.0)を掛け算して
# ライトの明るさ(intensity)を 30000.0 に設定しています。
# 4. 金属質感(metalness = 0.8)
# - 黄色い塗料のベースカラー (1.0, 0.85, 0.0) に対し、指示通り `metalness` を 0.8 に設定します。
# - これにより、天球(スカイドーム)の青空の色合いが球体の表面にシャープに写り込み、
# 美しい黄金のような金属光沢を放ちます。
from arnold import *
import math
# --- 便利関数の定義 ---
# 4x4の行列を作成する関数です。
def make_matrix(vals):
return AtMatrix(*vals)
# 位置 (tx, ty, tz) への移動を表現する行列を作ります。
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
)
# カメラの位置と向きのビュー変換行列を作る関数です。
def look_at(eye, target, up):
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)
if w_len > 0.0001:
wx /= w_len
wy /= w_len
wz /= w_len
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)
if u_len > 0.0001:
ux /= u_len
uy /= u_len
uz /= u_len
vx = wy*uz - wz*uy
vy = wz*ux - wx*uz
vz = wx*uy - wy*ux
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
)
# --- ① レンダリングエンジンの初期化 ---
universe = AiBegin()
AiMsgSetConsoleFlags(universe, AI_LOG_ALL)
# --- ② レンダリング出力の設定 ---
filt = AiNode(universe, "gaussian_filter", "myfilter")
# 指示通り、TIFF画像を書き出すための "driver_tiff" ドライバーを設定します。
driver = AiNode(universe, "driver_tiff", "mydriver")
AiNodeSetStr(driver, "filename", "002_2.tif") # 保存される画像ファイル名
# options設定(アスペクト比 4:3 の 640x480 ピクセルで設定)
options = AiUniverseGetOptions(universe)
AiNodeSetInt(options, "xres", 640) # 横幅
AiNodeSetInt(options, "yres", 480) # 縦幅
AiNodeSetInt(options, "AA_samples", 4)
out_arr = AiArrayAllocate(1, 1, AI_TYPE_STRING)
AiArraySetStr(out_arr, 0, "RGBA RGBA myfilter mydriver")
# 最終的なレンダー出力先を設定
AiNodeSetArray(options, "outputs", out_arr)
# --- ③ カメラの設定 ---
# POV-Ray定義:
# camera {
# location <0.0, 1.0, -5.0>
# look_at <0.0, 0.0, 0.0>
# }
# Z反転により、カメラ位置は (0.0, 1.0, 5.0) に配置します。
# 視野角(fov)はバランスの良い標準的な 60度 に設定します。
camera = AiNode(universe, "persp_camera", "/camera")
AiNodeSetFlt(camera, "fov", 60.0)
AiNodeSetFlt(camera, "near_clip", 0.01)
AiNodeSetFlt(camera, "far_clip", 10000.0)
# ビュー変換行列を計算して適用します。
camera_matrix = look_at((0.0, 1.0, 5.0), (0.0, 0.0, 0.0), (0.0, 1.0, 0.0))
AiNodeSetMatrix(camera, "matrix", camera_matrix)
AiNodeSetPtr(options, "camera", camera)
# --- ④ 背景(skydome_light)の設定 ---
# 指示された通りのパラメータでスカイドーム環境光を設定します。
# 美しい青空を表現するため、(0.2, 0.4, 0.8) の色を設定します。
skydome = AiNode(universe, "skydome_light", "myskydome")
AiNodeSetRGB(skydome, "color", 0.2, 0.4, 0.8) # 背景および環境光の色
AiNodeSetFlt(skydome, "intensity", 0.6) # 明るさの強さ
AiNodeSetInt(skydome, "samples", 2) # ノイズ防止のサンプル数
# --- ⑤ ライトの設定 ---
# POV-Ray定義:
# light_source {
# <100, 100, -100>
# color rgb 1.0
# }
# Z反転によりライトの位置は (100.0, 100.0, 100.0) になります。
# 距離の2乗(d^2 = 100^2 + 100^2 + 100^2 = 30000.0)を掛けて
# 物理減衰による暗さを補正し、元の強度 1.0 に見合う明るさにします。
light = AiNode(universe, "point_light", "light1")
AiNodeSetRGB(light, "color", 1.0, 1.0, 1.0)
AiNodeSetFlt(light, "intensity", 30000.0)
AiNodeSetMatrix(light, "matrix", make_transform_matrix(100.0, 100.0, 100.0))
# --- ⑥ マテリアル(色と質感)の設定 ---
# 黄色(Yellow)をベースにした金属マテリアルを作成します。
# 赤 1.0, 緑 0.85, 青 0.0 の割合で混ぜて、少し温かみのある鮮やかな黄色にしています。
# 指示に従って "metalness" (金属度) を 0.8 に設定します。
shader_yellow_metal = AiNode(universe, "standard_surface", "shader_yellow_metal")
AiNodeSetFlt(shader_yellow_metal, "base", 1.0)
AiNodeSetRGB(shader_yellow_metal, "base_color", 1.0, 0.85, 0.0) # 黄色
AiNodeSetFlt(shader_yellow_metal, "metalness", 0.8) # 金属度 0.8
AiNodeSetFlt(shader_yellow_metal, "specular", 1.0) # 反射の強さ 1.0
AiNodeSetFlt(shader_yellow_metal, "specular_roughness", 0.1) # 表面の粗さ(少しシャープな反射)
# --- ⑦ ジオメトリ(形状)の設定とループ処理 ---
# POV-Rayでの繰り返し処理:
# #declare MyValue = -2;
# #while ( MyValue <= 2 )
# object {
# sphere { <0, 0, 0>, 0.5 }
# translate <MyValue, 0, 0>
# }
# #declare MyValue = MyValue + 1;
# #end
#
# このループ処理により、X座標が -2, -1, 0, 1, 2 の合計5箇所に球体を配置します。
# 効率的な描画のため、共通のマスター球体(sphere)を1つ作り、
# 各座標にはクローン(ginstance)を配置してトランスフォーム(移動行列)を設定します。
# マスター球体の定義
sphere_base = AiNode(universe, "sphere", "sphere_base")
AiNodeSetVec(sphere_base, "center", 0.0, 0.0, 0.0)
AiNodeSetFlt(sphere_base, "radius", 0.5)
AiNodeSetByte(sphere_base, "visibility", 0) # マスター自体はレンダリングに映らないよう非表示にします
# Pythonのwhileループを使って5つのクローンを作成・配置します
my_value = -2.0
counter = 1
while my_value <= 2.0:
# クローン(インスタンス)を作成
inst_name = f"/sphere_{counter}"
inst = AiNode(universe, "ginstance", inst_name)
AiNodeSetByte(inst, "visibility", 255)
AiNodeSetByte(inst, "sidedness", 255)
AiNodeSetBool(inst, "receive_shadows", True)
AiNodeSetBool(inst, "self_shadows", True)
# 接続設定
AiNodeSetPtr(inst, "node", sphere_base)
AiNodeSetPtr(inst, "shader", shader_yellow_metal)
# 平行移動行列を計算(X座標を my_value だけ移動)
trans_matrix = make_transform_matrix(my_value, 0.0, 0.0)
AiNodeSetMatrix(inst, "matrix", trans_matrix)
# ループ変数の更新
my_value += 1.0
counter += 1
# --- ⑧ シーンの出力とレンダリング ---
# アスキー形式の .ass ファイルを書き出します(デバッグやシーン確認用)
params = AiParamValueMap()
AiParamValueMapSetBool(params, "binary", False)
AiSceneWrite(universe, "002_2_ascii.ass", params, None)
AiParamValueMapDestroy(params)
# レンダリングの実行
render_session = AiRenderSession(universe)
AiRender(render_session)
AiRenderSessionDestroy(render_session)
# Arnoldの終了処理
AiEnd()
4. チェッカーボードを使う
# 002_3.py
# Arnold 7 Python API
# POV-Rayシーンから Arnold (Python API) への変換スクリプト
#
# 【高校生向け解説】
# 今回のシーン「002_3」では、地面(水平な床)と壁(垂直な奥壁)という2枚の平面をチェッカー模様で描き、
# その手前に7個の赤い球体を一列に並べます。
#
# ここで学べるポイント:
# 1. 空間における平面の方程式と変換
# - 地面は POV-Rayで法線 <0, 1, 0>(真上向き)の平面です。Y = 0 の位置にあります。
# - 奥壁は POV-Rayで法線 <0, 0, 1>(Zの正の方向向き)の平面です。Z = 1 の位置にあります。
# - Z軸反転により、Arnoldでは奥壁は Z = -1.0 の位置になり、カメラ(Z = 6.0)から見て奥側になります。
# 2. 最新のArnold APIに適合するUVマッピングの工夫
# - 非推奨(Deprecated)となった `u_frequency` などのアトリビュートを使わずに、
# ポリゴンに割り当てるUV座標自体の範囲を [0.0, 40.0] に拡張する手法を採用しました。
# これにより、40x40ユニットの平面に対して、1マスが1ユニットのチェッカー模様が自動的かつ綺麗に配置されます。
# 3. ループ(繰り返し)処理による球体の配置
# - 変数 MyValue を -3 から 3 まで 1 ずつ変化させながらループ処理を行います。
# - X座標が -3, -2, -1, 0, 1, 2, 3 の計7箇所に赤い球体を並べます。
# 4. 「すべて」のオブジェクトへの金属質感(metalness = 0.8)の適用
# - 指示に基づき、赤い球体だけでなく、地面と奥壁のチェッカー模様にも `metalness = 0.8` を適用します。
# - これにより、地面に球体が写り込んだり、球体に奥壁のチェッカー模様が鏡のように反射したりする、
# 非常にリッチな金属光沢の空間が生み出されます。
# 5. 環境光(skydome_light)と反射の効果
# - 金属の反射を美しく見せるために、指定のスカイドームライト(青空色: 0.2, 0.4, 0.8)を追加しています。
# - この環境光がないと、金属は反射する対象がなくて真っ黒になってしまいます。
from arnold import *
import math
# --- 便利関数の定義 ---
# 4x4の行列を作成する関数です。
def make_matrix(vals):
return AtMatrix(*vals)
# 位置 (tx, ty, tz) への移動を表現する行列を作ります。
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
)
# カメラの位置と向きのビュー変換行列を作る関数です。
def look_at(eye, target, up):
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)
if w_len > 0.0001:
wx /= w_len
wy /= w_len
wz /= w_len
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)
if u_len > 0.0001:
ux /= u_len
uy /= u_len
uz /= u_len
vx = wy*uz - wz*uy
vy = wz*ux - wx*uz
vz = wx*uy - wy*ux
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
)
# --- ① レンダリングエンジンの初期化 ---
universe = AiBegin()
AiMsgSetConsoleFlags(universe, AI_LOG_ALL)
# --- ② レンダリング出力の設定 ---
filt = AiNode(universe, "gaussian_filter", "myfilter")
# 指示通り、TIFF画像を書き出すための "driver_tiff" ドライバーを設定します。
driver = AiNode(universe, "driver_tiff", "mydriver")
AiNodeSetStr(driver, "filename", "002_3.tif") # 保存される画像ファイル名
# options設定(アスペクト比 4:3 の 640x480 ピクセルで設定)
options = AiUniverseGetOptions(universe)
AiNodeSetInt(options, "xres", 640) # 横幅
AiNodeSetInt(options, "yres", 480) # 縦幅
AiNodeSetInt(options, "AA_samples", 4)
out_arr = AiArrayAllocate(1, 1, AI_TYPE_STRING)
AiArraySetStr(out_arr, 0, "RGBA RGBA myfilter mydriver")
# 最終的なレンダー出力先を設定
AiNodeSetArray(options, "outputs", out_arr)
# --- ③ カメラの設定 ---
# POV-Ray定義:
# camera {
# perspective
# location <0.0, 4, -6>
# look_at <0.0, 2, 0>
# angle 60
# }
# Z反転により、カメラ位置は (0.0, 4.0, 6.0) に配置し、注視点は (0.0, 2.0, 0.0) です。
# 視野角(fov)は angle に指定された 60.0 度に設定します。
camera = AiNode(universe, "persp_camera", "/camera")
AiNodeSetFlt(camera, "fov", 60.0)
AiNodeSetFlt(camera, "near_clip", 0.01)
AiNodeSetFlt(camera, "far_clip", 10000.0)
# ビュー変換行列を計算して適用します。
camera_matrix = look_at((0.0, 4.0, 6.0), (0.0, 2.0, 0.0), (0.0, 1.0, 0.0))
AiNodeSetMatrix(camera, "matrix", camera_matrix)
AiNodeSetPtr(options, "camera", camera)
# --- ④ 背景(skydome_light)の設定 ---
# 指示された通りのパラメータでスカイドーム環境光を設定します。
# 青空のグラデーション光を表現するため、(0.2, 0.4, 0.8) の色を設定します。
skydome = AiNode(universe, "skydome_light", "myskydome")
AiNodeSetRGB(skydome, "color", 0.2, 0.4, 0.8) # 背景および環境光の色
AiNodeSetFlt(skydome, "intensity", 0.6) # 明るさの強さ
AiNodeSetInt(skydome, "samples", 2) # ノイズ防止のサンプル数
# --- ⑤ ライトの設定 ---
# POV-Ray定義:
# light_source { <100, 100, -100> rgb 1 }
# Z反転によりライトの位置は (100.0, 100.0, 100.0) になります。
# 距離の2乗(d^2 = 100^2 + 100^2 + 100^2 = 30000.0)を掛けて
# 物理減衰による暗さを補正し、元の強度 1.0 に見合う明るさにします。
light = AiNode(universe, "point_light", "light1")
AiNodeSetRGB(light, "color", 1.0, 1.0, 1.0)
AiNodeSetFlt(light, "intensity", 30000.0)
AiNodeSetMatrix(light, "matrix", make_transform_matrix(100.0, 100.0, 100.0))
# --- ⑥ マテリアル(色と質感)の設定 ---
# 1. 地面および壁用のチェッカー模様テクスチャノード
# 黒色 (0, 0, 0) と白色 (1, 1, 1) を交互に並べます。
checker_tex = AiNode(universe, "checkerboard", "checker_tex")
AiNodeSetRGB(checker_tex, "color1", 0.0, 0.0, 0.0) # 黒色
AiNodeSetRGB(checker_tex, "color2", 1.0, 1.0, 1.0) # 白色
# 2. 地面・壁用のチェッカーマテリアル(すべて金属質感)
# 指示に従い `metalness = 0.8` を適用します。反射が美しく映り込むようにします。
shader_checker_metal = AiNode(universe, "standard_surface", "shader_checker_metal")
AiNodeSetFlt(shader_checker_metal, "base", 1.0)
AiNodeLink(checker_tex, "base_color", shader_checker_metal)
AiNodeSetFlt(shader_checker_metal, "metalness", 0.8) # すべて質感に metalness 0.8 を追加
AiNodeSetFlt(shader_checker_metal, "specular", 1.0) # 反射強度
AiNodeSetFlt(shader_checker_metal, "specular_roughness", 0.2) # 反射の粗さ(少しボケた反射)
# 3. 赤い金属球用のマテリアル
# 赤色 (1.0, 0.0, 0.0) をベースカラーにし、`metalness = 0.8` を適用します。
shader_red_metal = AiNode(universe, "standard_surface", "shader_red_metal")
AiNodeSetFlt(shader_red_metal, "base", 1.0)
AiNodeSetRGB(shader_red_metal, "base_color", 1.0, 0.0, 0.0) # 赤色
AiNodeSetFlt(shader_red_metal, "metalness", 0.8) # すべて質感に metalness 0.8 を追加
AiNodeSetFlt(shader_red_metal, "specular", 1.0) # 反射強度
AiNodeSetFlt(shader_red_metal, "specular_roughness", 0.1) # 表面の粗さ(シャープな反射)
# --- ⑦ ジオメトリ(形状)の設定 ---
# 1. 地面(polymesh)の配置
# POV-Ray定義: plane { <0, 1, 0>, 0 } translate <0.5, 0, 1>
# XZ平面(高さ Y=0)に配置し、Xに 0.5、Zに 1.0(Z反転で -1.0)平行移動させます。
# 40x40ユニットの範囲をカバーするポリゴンを作成します。
mesh_ground = AiNode(universe, "polymesh", "mesh_ground")
AiNodeSetBool(mesh_ground, "smoothing", False)
AiNodeSetArray(mesh_ground, "nsides", AiArray(1, 1, AI_TYPE_UINT, 4))
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))
# ローカル座標系での頂点位置(原点中心)
AiNodeSetArray(mesh_ground, "vlist",
AiArray(4, 1, AI_TYPE_VECTOR,
AtVector(-20.0, 0.0, -20.0), AtVector(20.0, 0.0, -20.0),
AtVector(-20.0, 0.0, 20.0), AtVector(20.0, 0.0, 20.0)
))
AiNodeSetArray(mesh_ground, "nlist",
AiArray(1, 1, AI_TYPE_VECTOR, AtVector(0.0, 1.0, 0.0)))
# UV座標(0.0〜40.0に設定して、1マス1ユニットのチェッカーを再現)
uvlist_ground = AiArray(4, 1, AI_TYPE_VECTOR2,
AtVector2(0.0, 0.0), AtVector2(40.0, 0.0),
AtVector2(0.0, 40.0), AtVector2(40.0, 40.0)
)
AiNodeSetArray(mesh_ground, "uvlist", uvlist_ground)
AiNodeSetArray(mesh_ground, "uvidxs", AiArray(4, 1, AI_TYPE_UINT, 0, 1, 3, 2))
AiNodeSetPtr(mesh_ground, "shader", shader_checker_metal)
# 地面インスタンスの生成と配置(平行移動: X=0.5, Z=-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_checker_metal)
AiNodeSetMatrix(inst_ground, "matrix", make_transform_matrix(0.5, 0.0, -1.0))
# 2. 奥壁(polymesh)の配置
# POV-Ray定義: plane { <0, 0, 1>, 0 } translate <0.5, 0, 1>
# XY平面(Z=0)に配置し、Xに 0.5、Zに 1.0(Z反転で -1.0)平行移動させます。
# 40x40ユニットの範囲をカバーするポリゴンを作成します。
mesh_wall = AiNode(universe, "polymesh", "mesh_wall")
AiNodeSetBool(mesh_wall, "smoothing", False)
AiNodeSetArray(mesh_wall, "nsides", AiArray(1, 1, AI_TYPE_UINT, 4))
# カメラを向くように、右ねじの法則で法線が -Z を向くような時計回りのインデックスにします。
AiNodeSetArray(mesh_wall, "vidxs", AiArray(4, 1, AI_TYPE_UINT, 0, 2, 3, 1))
AiNodeSetArray(mesh_wall, "nidxs", AiArray(4, 1, AI_TYPE_UINT, 0, 0, 0, 0))
# ローカル座標系での頂点位置(原点中心のXY平面)
AiNodeSetArray(mesh_wall, "vlist",
AiArray(4, 1, AI_TYPE_VECTOR,
AtVector(-20.0, -20.0, 0.0), AtVector(20.0, -20.0, 0.0),
AtVector(-20.0, 20.0, 0.0), AtVector(20.0, 20.0, 0.0)
))
# 法線は手前(カメラ方向)の -Z を指すように設定
AiNodeSetArray(mesh_wall, "nlist",
AiArray(1, 1, AI_TYPE_VECTOR, AtVector(0.0, 0.0, -1.0)))
# UV座標(0.0〜40.0に設定して、1マス1ユニットのチェッカーを再現)
uvlist_wall = AiArray(4, 1, AI_TYPE_VECTOR2,
AtVector2(0.0, 0.0), AtVector2(40.0, 0.0),
AtVector2(0.0, 40.0), AtVector2(40.0, 40.0)
)
AiNodeSetArray(mesh_wall, "uvlist", uvlist_wall)
AiNodeSetArray(mesh_wall, "uvidxs", AiArray(4, 1, AI_TYPE_UINT, 0, 2, 3, 1))
AiNodeSetPtr(mesh_wall, "shader", shader_checker_metal)
# 奥壁インスタンスの生成と配置(平行移動: X=0.5, Z=-1.0)
inst_wall = AiNode(universe, "ginstance", "/wall")
AiNodeSetByte(inst_wall, "visibility", 255)
AiNodeSetByte(inst_wall, "sidedness", 255)
AiNodeSetBool(inst_wall, "receive_shadows", True)
AiNodeSetBool(inst_wall, "self_shadows", True)
AiNodeSetPtr(inst_wall, "node", mesh_wall)
AiNodeSetPtr(inst_wall, "shader", shader_checker_metal)
AiNodeSetMatrix(inst_wall, "matrix", make_transform_matrix(0.5, 0.0, -1.0))
# 3. 7個の赤い球体(Red Spheres)のループ配置
# POV-Rayでの繰り返し処理:
# #declare MyValue = -3;
# #while ( MyValue <= 3 )
# sphere { <0, 0, 0>, 0.5 translate <MyValue, 0.5, 0> }
# ...
# X座標が -3.0 から 3.0 まで 1.0刻みで球体を並べます。
# マスター球体の定義
sphere_base = AiNode(universe, "sphere", "sphere_base")
AiNodeSetVec(sphere_base, "center", 0.0, 0.0, 0.0)
AiNodeSetFlt(sphere_base, "radius", 0.5)
AiNodeSetByte(sphere_base, "visibility", 0) # マスター自体は非表示
# ループ処理によるクローン作成
my_value = -3.0
counter = 1
while my_value <= 3.0:
# クローン(ginstance)を作成
inst_name = f"/sphere_{counter}"
inst = AiNode(universe, "ginstance", inst_name)
AiNodeSetByte(inst, "visibility", 255)
AiNodeSetByte(inst, "sidedness", 255)
AiNodeSetBool(inst, "receive_shadows", True)
AiNodeSetBool(inst, "self_shadows", True)
# 接続設定
AiNodeSetPtr(inst, "node", sphere_base)
AiNodeSetPtr(inst, "shader", shader_red_metal)
# 平行移動行列を設定(X座標 = my_value, Y座標 = 0.5, Z座標 = 0.0)
trans_matrix = make_transform_matrix(my_value, 0.5, 0.0)
AiNodeSetMatrix(inst, "matrix", trans_matrix)
# ループ変数の更新
my_value += 1.0
counter += 1
# --- ⑧ シーンの出力とレンダリング ---
# アスキー形式の .ass ファイルを書き出します(シーン構造の確認用)
params = AiParamValueMap()
AiParamValueMapSetBool(params, "binary", False)
AiSceneWrite(universe, "002_3_ascii.ass", params, None)
AiParamValueMapDestroy(params)
# レンダリングの実行
render_session = AiRenderSession(universe)
AiRender(render_session)
AiRenderSessionDestroy(render_session)
# Arnoldの終了処理
AiEnd()
Arnoldでは、CSG集合演算はできないぽい。
Pov-RayとRenderManではできる。
いろいろ検証です。ありがとうございます。


