pg08

テーマ CGとプログラム

準備: iOSアプリ The MellowRoom を App Store からDL(無料)してインストールしておく。 配信サイト

demo1 demo2 demo3 demo4

今回の内容:

演習1:

・CGソフト Blender のスクリプト機能を利用し、プログラムによる3DCGを作成する。

Blender はオープンソースのフリーソフトウェアで、無償で利用できます。

・プログラム言語 Python を使用する。

Python は、テキスト処理にに優れ、Webサービスの開発(サーバサイド)に利用されています。

また、AIの研究分野で、機械学習用のツールを利用する際のスクリプト言語としても利用されています。

オープンソースのフリーソフトウェアで、無償で利用できます。

Pythonで嫁を見つけた話 - みんなのPython第四版に寄せて http://coreblog.org/ats/thinking-about-minpy-4th/

Pythonで仕事をされている方のBlogで、Pythonやっててよかったという記事を見つけたので紹介します。

みんなのPython は大学の図書館にも第3版があります。Webアプリ編の関連書もあります。興味がある受講生は借りて勉強してみましょう。

演習2:

・ゲーム用CGにおいて、色の調整やエフェクトで重要な技術 GLSL について iOSアプリ The MellowRoom で試す。

ブラウザは Google Chrome を使う。

コードをこのWebからコピペすると Edge や InternetExplorer を使用した場合、バグります。

原因: HTML の空白文字 &nbsp が ascii文字 0x20 のspaceとしてコピペされずに、 unicodeの マルチバイトの空白文字としてコピペされるため。

Blenderの起動: 解説動画(演習1の内容の一部あり)

スタート → Blender → blender をクリック

※セキュリティ警告 「発行元が確認できませんでした。このソフトを実行しますか?」 が表示されるが、「実行」する。

※Blenderはオープンソースのソフトウェアであるので発行元が登録されていないためこのような警告が出る。

今回は、Blenderの操作方法についてはほとんど触れずに、Python スクリプトによるCG作成を中心に演習をする。

Blenderによる3DCG作成に興味のある受講者は、入門サイトを検索するか、関連資料をみて取り組んでみよう。(旧科目 デジタルエンタテインメントの 資料1 資料2

※ 資料1の12枚目のスライドに、Blenderのメニューを日本語に切り替える方法が載っています(説明中のBlenderのバージョンが古いので多少画面は異なる)

今回の演習では デフォルト設定(英語)メニューで作業を行ってください

※ ファイル → 初期設定を読み込む で英語版に戻すことができます。

参考サイト: Blender入門

演習1

BlenderのスクリーンレイアウトScripting に変更

Blenderの制御用スクリプトはプログラム言語 Python で記述できます。

ソフトとその制御用スクリプトの例としては、

Excel の VBA

Algodoo の Thyme

Webブラウザ の Javascript

Photoshop の Javascript

ゲーム Far Cry, World of Warcraft などの Lua

エディタVim の vimscript

エディタemacs の emacs lisp

など多くの例があり、作業の自動化や調整に利用されています。

以下の演習ではBlenderのスクリプト機能で、Python を利用して3DCGを作成してみます。

まず、Pythonのコードを コンソール(上の画像で青色の部分)に入力して動作を確認しましょう。

コンソールに文字入力カーソルあ表示されているので(表示されていなければ、コンソールをマウスでクリック)

キーボードから以下のコードを入力する。

1行入力して、enter キーで コードを実行する。

実行するたびに、処理結果が表示されたり、3Dビューでオブジェクトが変化する。

1+1

bpy.data.objects['Cube'].location.x += 1

bpy.data.objects['Cube'].location.y += 1

bpy.data.objects['Cube'].location.z += 1

※ コンソールのキーボード操作

上下矢印キー↓↑ で入力履歴の呼び出しが可能

同じコードを繰り返したり、以前実行したコードを編集して実行可能

コードの解説:

bpy.data.objects['Cube'] 作業中のシーンから Cube キューブという名前の付いたオブジェクトを検索して利用する。

.location オブジェクトの3D空間の座標を操作する。

.x .y .y 座標をそれぞれ x軸 y軸 z軸 に沿って操作する。

+= ⁺=記号の左側(操作対象)の値を右側の数値分増加する。

Blenderの3Dビューの 追加(add) メニューから メッシュ→ICO球(Mesh -> Ico Sphere )でICO球モデルを追加する。

次のコードでICO球モデルのZ座標を変更する。

bpy.data.objects['Icosphere'].location.z += 10

3Dビューの表示範囲をマウスで調整する。

・マウスホイール ズームイン・アウト

・ミドルボタン(ホイール)でドラッグ シーン回転

カメラ操作ボタンの確認

※数値入力PADのキーを使用する

1 3 7 フロント サイド トップ

パースの ON OFF

カメラ視点

2 4 6 8 シーン回転

HOME シーン全体を表示

解説動画(つづき)

アウトライナーパネルのシーンツリーを確認

Icosphere を選択

プロパティパネルの表示内容をオブジェクトに変更

次のコードを scriptのコンソールに入力して実行。

bpy.data.objects['Icosphere'].location = [ -10 , 0 , 0]

bpy.data.objects['Icosphere'].scale = [2,2,2]

Cubeを回転操作する。回転にはオイラー角(euler)を使用して、X軸、Y軸、Z軸の順番で回転させる。

←オイラー角の説明図(Wikipediaより)

上から順に、 約180度回転、約90度回転、約45度回転

bpy.data.objects['Cube'].rotation_euler = [0,0,3.14]

bpy.data.objects['Cube'].rotation_euler = [0,0,3.14/2]

bpy.data.objects['Cube'].rotation_euler = [0,0,3.14/4]

回転の角度指定は、 度(degree)ではなく、ラジアン(radian)を使う。 π=3.141…=180度

←ラジアンnの説明図(Wikipediaより)

マテリアル(色)を作成し、オブジェクトに設定する

※ diffuse_color 散乱光(物に光を当てたときに見える、物質の色)

color0 = bpy.data.materials.new('mycolor')

color0.diffuse_color = [1, 0, 0, 1]

# マテリアルが未設定のオブジェクトに対して、マテリアルを追加する場合。

bpy.data.objects['Icosphere'].data.materials.append( color0 )

# マテリアルが設定済みのオブジェクトに対して、マテリアルを上書きする場合。

bpy.data.objects['Cube'].material_slots[0].material = color0

スクリプトでオブジェクトを生成して、場所を移動し、色を設定する。

bpy.ops.mesh.primitive_ico_sphere_add()

bpy.context.object.location = [1, 2, 3]

bpy.context.object.data.materials.append(color0)

※ bpy.context.object は、現在操作中のオブジェクトを示す。↑の例では、新規作成したico sphere が操作対象。

演習1のつづき

解説動画(旧手順で説明)

新手順: Blender 2.80 用の操作。2019年度はこちらで進めます。

1.View Layerの Scene Collection をマウスで選択。

2.新規コレクション作成ボタンを押す。

3.Collection を非表示にする。

4.Collection 2 をマウスで選択する。

旧手順: Blender 2.79 用の操作

レイヤーを2へ切り替える

マウス操作 or 数字キー2(数値入力PADではないほう)

Python Script作成の準備

Blenderの Text Editor 画面のメニューのボタン new を押す。

以下の Python スクリプトをエディターに入力し実行する。

コンソール には入力しません。

import bpy

import math

materials = []

for i in [0, 1, 2, 3, 4, 5, 6]:

materials.append(bpy.data.materials.new('Color' + str(i)))

materials[0].diffuse_color = [1,0,0,1]

materials[1].diffuse_color = [0,1,0,1]

materials[2].diffuse_color = [0,0,1,1]

materials[3].diffuse_color = [0,1,1,1]

materials[4].diffuse_color = [1,0,1,1]

materials[5].diffuse_color = [1,1,0,1]

materials[6].diffuse_color = [1,1,1,1]

for i in [0, 1, 2, 3, 4, 5, 6]:

t = 2.0 * math.pi * i / 7

x = 1.0 * math.cos(t)

y = 1.0 * math.sin(t)

bpy.ops.mesh.primitive_ico_sphere_add()

bpy.context.object.location = [x, y, 0]

bpy.context.object.scale = [0.5, 0.5, 0.5]

bpy.context.object.data.materials.append(materials[i % 7])

スクリプトの実行には Run Script ボタンを押す。

※マウス操作で、手動でオブジェクトを削除してみる。

右クリックで削除するオブジェクトを選択 → deleteキー → delete OK を選択

オブジェクトを配置する座標の半径を変更する。

先ほどのコードを修正する。

x = 4.0 * math.cos(t)

y = 4.0 * math.sin(t)

※オブジェクトを 全選択 するには a キーを押す。 前回作成したオブジェクトが不要なら、全削除(x キー)する。

配置するオブジェクト数の増加、半径の拡大、配置間隔の角度変更

for i in [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]:

t = 2.0 * math.pi * i / 7 / 2

x = 6.0 * math.cos(t)

y = 6.0 * math.sin(t)

Pythonの文法について:

Python入門(外部サイト)

Pythonは繰り返し構文や条件分岐構文などのコードの制御範囲を「インデント」を使って表します。

例)forループ構文が繰り返すコードは code1 code2 code3 まで。code4は範囲外。

for i in [ 1,2,3]

code1

code2

code3

code4

プログラム言語の文法には、

コードの範囲を { } などのかっこを用いて表すものや、

begin end などの予約語を用いるもの、

インデントなどコードを記述したレイアウトで表すもの

があります。様々な言語について調べてみましょう。

提出物1(演習1とつづきをまとめて)

Python スクリプト と 3Dビューを収めたスクリーンショット を Webclass の第8回課題に提出

新手順:

View layer の Collection を 表示(目玉アイコンをクリック)する。

旧手順:

3Dビューは、SHIFTキー を押しながら、 キー 1 と 2 を順に押して、レイヤー1と2が表示(重なる)されている状態にしておくこと。

HOMEボタンを押して、全てのオブジェクトが表示されるようにすること。

例)

提出期限 来週まで。

ヒント:一旦、授業中に作成したCGを 仮提出 しておく。

ヒント:授業中制作のCGと混ざらないように、レイヤー3 など未使用のレイヤーに切り替えて、実験する。

以下の応用にチャレンジしてみよう。

・ 色の設定は [1,0,0,1] のほか、 [0.5, 0.5, 0.5,1] (この数値は灰色になる)など、 0.0 から 1.0 までの値が指定できる。

・ 色の数や、順番を入れ替えてみる

・ オブジェクトの変更

bpy.ops.mesh.primitive_ico_sphere_add()

bpy.ops.mesh.primitive_cube_add()

へ変更すると 球体 から 立方体に オブジェクトを変更できる。

他の種類の立体を使いたい場合は、

3Dビューメニューの Add -> Mesh からオブジェクトの種類を表示して、使用するコマンドを確認できる。

・for ループの部分をコピーして、 オブジェクトを表示する座標や サイズ を変更する

※ location = (x, y, 0) を location = (x, y, 2) と変更すると、表示位置のZ座標を変更できる。

・for ループを2重にする。

例) Python は、インデント(コードの表示位置)の高さで、ループ範囲(ブロック)を表現するので、↓のコードを使用する際は、左側の空白部分も正確に入力すること。

for z in [0, 2, 4]:

for i in [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]:

t = 2.0 * math.pi * i / 7 / 2

x = 6.0 * math.cos(t)

y = 6.0 * math.sin(t)

bpy.ops.mesh.primitive_ico_sphere_add()

bpy.context.object.location = [x, y, z]

bpy.context.object.scale = [0.5, 0.5, 0.5]

bpy.context.object.data.materials.append(materials[i % 7])

※↑このコードには、 インデントが 2レベル ある。外側が z座標を変化させるループ、 内側が円周に配置するループ。

・ ランダムの利用。 コメント欄のリンク先を参照

・range の応用。

range(0,10) で、 [0,1,2,3,4,5,6,7,8,9,10]

range(0,10,2 ) で、 [0, 2, 4, 6, 8] 2つおき

range(10,0,-1)で、 [10, 9, 8, 7, 6, 5, 4, 3, 2, 1] の逆順をリストを作成できる。

例)

for i in [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]:

for i in range(0,13):

と同じ動作。 range は丸括弧 ( ) を使うので注意。

・Python の リスト内包表記 を利用して、複雑な数列を作成し、利用する。このページのコメント欄のリンク先を参照

例) 2乗の数列の生成

[i*i/10 for i in range(0, 10)]

↑で、↓を生成

[0.0, 0.1, 0.4, 0.9, 1.6, 2.5, 3.6, 4.9, 6.4, 8.1]

以下の例では、最初のコードの着色に関する以下のコード部分は同一なので省略している。

import bpy

import math

materials = []

for i in [0, 1, 2, 3, 4, 5, 6]:

materials.append(bpy.data.materials.new('Color' + str(i)))

materials[0].diffuse_color = [1,0,0,1]

materials[1].diffuse_color = [0,1,0,1]

materials[2].diffuse_color = [0,0,1,1]

materials[3].diffuse_color = [0,1,1,1]

materials[4].diffuse_color = [1,0,1,1]

materials[5].diffuse_color = [1,1,0,1]

materials[6].diffuse_color = [1,1,1,1]

このコードよりも下の部分の元のコードを削除して、以下の例と入れ替えて動作確認をする。

作成例1)

高さ z の 値に反比例(1/z)するように半径をすぼめるコード。

for z in [1, 2, 3, 4, 5, 6, 7]:

for i in [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]:

t = 2.0 * math.pi * i / 7 / 2

x = 6.0 * math.cos(t)

y = 6.0 * math.sin(t)

bpy.ops.mesh.primitive_ico_sphere_add(location = (x/z, y/z, z*2))

bpy.context.object.scale = [0.5, 0.5, 0.5]

bpy.context.object.data.materials.append(materials[i % 7])

作成例2)

高さzに比例して半径を拡大するコード。

for z in [1, 2, 3, 4, 5, 6, 7]:

for i in [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]:

t = 2.0 * math.pi * i / 7 / 2

x = 6.0 * math.cos(t)

y = 6.0 * math.sin(t)

bpy.ops.mesh.primitive_ico_sphere_add(location = (x*z/7, y*z/7, z*2))

bpy.context.object.scale = [0.5, 0.5, 0.5]

bpy.context.object.data.materials.append(materials[i % 7])

作例3)

2種類のオブジェクト(cube ico_sphere) を使用。5色に減色。上下の段で、色を連続して使用。

for i in [0, 1, 2, 3, 4, 5, 6]:

t = 2.0 * math.pi * i / 7

x = 6.0 * math.cos(t)

y = 6.0 * math.sin(t)

bpy.ops.mesh.primitive_cube_add()

bpy.context.object.location = [x, y, 0]

bpy.context.object.data.materials.append(materials[i % 7])

c = 0

for z in [12, 10, 8, 6, 4, 2, 0]:

for i in [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]:

t = 2.0 * math.pi * i / 7 / 2

x = 4.0 * math.cos(t)

y = 4.0 * math.sin(t)

bpy.ops.mesh.primitive_ico_sphere_add()

bpy.context.object.location = [x*z/12, y*z/12, 12 - z]

bpy.context.object.scale = [0.5, 0.5, 0.5]

bpy.context.object.data.materials.append(materials[c % 5])

c += 1

より複雑な例。ランダムの利用例。

XYZ空間の一定の範囲で一定の間隔の全ての格子点について、5%の確率で四角い板を配置し、位置に応じた色を付ける。

コード先頭の import 文の並びに

import random

を追加しておくこと。

実行完了までしばらくの時間が必要。

for i in [0, 1, 2, 3, 4, 5, 6]:

materials[i].use_nodes = True

material_output = materials[i].node_tree.nodes.get('Material Output')

emission = materials[i].node_tree.nodes.new('ShaderNodeEmission')

emission.inputs['Strength'].default_value = 5.0

# link emission shader to material

materials[i].node_tree.links.new(material_output.inputs[0], emission.outputs[0])

for z in range(-120,-10,20):

for x in range(int(z),-int(z),2):

for y in range(int(z),-int(z),2):

if random.random() < 0.05:

bpy.ops.mesh.primitive_plane_add()

bpy.context.object.location = [x, y, z]

bpy.context.object.scale = [0.5, 0.5, 0.5]

bpy.context.object.data.materials.append(materials[(x+y+z) % 7])

↑をxy座標で0,0から遠ざかるにしたがって、板の発生確率を下げる調整と、ランダムに少し回転させるエフェクトを加えた例。demo1

コード先頭の import 文の並びに

import random

を追加しておくこと。

実行完了までしばらくの時間が必要。

コード random.choice([0,0,0,0.75]) は、 0 が3個、 0.75が1個の、合計4個のデータの中からランダムに1つ選んでいる。つまり、1/4の確率で、0.75、それ以外は0になる。

for i in [0, 1, 2, 3, 4, 5, 6]:

materials[i].use_nodes = True

material_output = materials[i].node_tree.nodes.get('Material Output')

emission = materials[i].node_tree.nodes.new('ShaderNodeEmission')

emission.inputs['Strength'].default_value = 5.0

# link emission shader to material

materials[i].node_tree.links.new(material_output.inputs[0], emission.outputs[0])

for z in range(-190,-10,30):

z2=z

if z2<-100: z2= -100

for x in range(z2,-z2,2):

for y in range(z2,-z2,2):

if random.random() < 0.005 + (0.15/(1+abs(x)/40+abs(y)/40)):

bpy.ops.mesh.primitive_plane_add()

bpy.context.object.location = [x, y, z+20]

bpy.context.object.scale = [0.5, 0.5, 0.5]

rx = random.choice([0,0,0,0.75])

ry = random.choice([0,0,0,0.75])

bpy.context.object.rotation_euler = [rx, ry, 0]

bpy.context.object.data.materials.append(materials[(x+y+z) % 7])

重力や摩擦など、物理属性を設定する例。 demo2

このコード例は、プログラム全体を入れ替えて利用すること。

※ 余分なオブジェクトはすべて削除しておくとよい。物理シミュレーションでアニメーションさせる際に衝突して上手くいかなくなる。

全削除方法: 3D画面で a キーで画面内オブジェクトを全選択。 x キーで削除。

import bpy

bpy.ops.mesh.primitive_plane_add()

bpy.context.object.location = [0, 0, 0]

bpy.context.object.scale = [40, 2, 0]

bpy.ops.rigidbody.object_add()

bpy.context.object.rigid_body.friction = 1

bpy.context.object.rigid_body.enabled = False

for i in range(0,22):

x = -24 + 3.0 * i

bpy.ops.mesh.primitive_cube_add()

bpy.context.object.location = [x, 0, 1.5]

bpy.context.object.scale = [0.5, 1, 1.5]

bpy.ops.rigidbody.object_add()

bpy.context.object.rigid_body.friction = 0.5

bpy.context.object.rigid_body.mass = 2

bpy.context.object.rigid_body.enabled = True

bpy.context.scene.cursor.location = [38.5, 0, 0]

bpy.ops.object.origin_set(type='ORIGIN_CURSOR')

bpy.ops.transform.rotate(value= 0.33, orient_axis="Y")

bpy.ops.object.origin_set(type='ORIGIN_CENTER_OF_MASS')

bpy.context.object.rigid_body.mass = 4

ワークスペースを animation タブに切り替える。

ドミノ倒しのアニメーションを再生するには、演習の冒頭で変更したスクリーンレイアウトを default に戻し、下部のタイムラインの再生ボタンを押す。

さらに、途中でシーンのレンダリングと物理シミュレーションが止まるので、

・シーンのフレーム数の増加(1000) ↓の画像の End: 250 を 1000 に変更

・物理シミュレーションのフレームの増加(1000)↓の画像の End: 250 を 1000 に変更

をしておく。

演習2

準備: iOSアプリ The MellowRoom を App Store からDL(無料)してインストールしておく。 配信サイト

The MellowRoom はカメラ用のエフェクトをCG用プログラム言語 GLSL で自作できるアプリ。サンプルのエフェクトが多数入っているので、サンプルのエフェクトのGLSLコードを改造したり、自作することができる。

コードの制作は、ノードタイプのビジュアルプログラミングで行う。

GLSLとは: 画像処理(CG作成、やカメラ)で、画像の全ての画素(ピクセル)の色を、プログラムにより修正・加工するための専用言語

グラフィック性能の高い製品は、このGLSLの処理能力も高性能である。ゲームの画像エフェクトなどリアルタイムの画像処理で活用されている。

GLSLは、OpenGL Shading Language の略で、C言語に似たシェーディング言語である。シェーディング言語はコンピューターの画像処理装置(グラフィックスチップ GPU)を直接プログラムでコントロールするための専用言語。

操作方法をデモするので、いっしょに操作してみましょう。

The MellowRoomのサイト

インカメラでフィルターを選択して撮影している様子。

カメラ切替は 丸矢印をタップ

フィルターは 適当に選択

スライダーの操作、画面タップを試す

ペンをタップすると、GLSLのコードを確認できる。

ノードタイプのビジュアルプログラミング

左から右へとカメラ(入力)から画面(出力)へと撮影画像のピクセルのデータが流れて処理される

C言語に似た本来のGLSLを紹介:

例) 赤色だけカメラ画像を使用し、緑と青の色は使用しない。 → 赤色だけの画像

void main()

{

vec2 uv = iScreen;

vec4 color = texture2D(iCamera, uv);

gl_FragColor = vec4(color.r,0.0, 0.0, 1.0);

}

例) RGB の色成分を GBRの順に入れ替える。 → 色の狂った画像

void main()

{

vec2 uv = iScreen;

vec4 color = texture2D(iCamera, uv);

gl_FragColor = vec4(color.g,color.b, color.r, 1.0);

}

変数 color にカメラで撮影したピクセルの色が記録されている。

color.r は red

color.g は green

color.b は blue

の各色の値になる。

次のコマンドで、画面上の色を決める。

gl_FragColor = vec4( 赤色の値, 緑色の値, 青色の値, 1.0);

次の例は、カメラで撮影した色をそそまま画面の色として設定している。

gl_FragColor = vec4( color.r, color.g, color.b, 1.0);

アプリ The MellowRoom では、上掲のようなコードを書く代わりに、ノードでプログラムを作成している。

GLSLは、処理対象画像(ゲーム画面・カメラなど)の1画素だけの色の変換方法をプログラムしている。

Full HD 画像の全領域を処理するには、

FHD (Full-HD, 1080p) 横 1920 ピクセル 縦 1080 ピクセル 2,073,600 画素

で、1フレームあたり、200万回、GLSLのコードは実行されていることになる。

1秒30フレームの動画なら、6000万回の処理に相当する。

演習2

× をタップしてコードを全消去する。

カメラとビデオを接続する。

緑のチェックをタップしてカメラ画像を確認

エフェクトなしの通常の撮影状態になる

右下隅の + をタップでノードが一覧表示される。

タップして 白黒画像化 B&W のノードを選ぶ。

この様に接続する。表示を確認する。

Split と Join を追加する。この様に接続する。表示を確認する。

カメラのピクセルの色情報を、RGBに分解し(ノードの表記ではXYZ)、R成分だけを画面に送っている。

この様に接続する。表示を確認する。

青成分と緑成分を交換して表示する。緑色の服は青く表示される。

この様に接続する。表示を確認する。

Slider を操作すると、画面が回転する。

色の変更と回転を両法適用した例

提出物2(演習2)

The MellowRoomの GLSLのノードプログラムの スクリーンショット と そのエフェクトのカメラ画像(カメラアイコンをタップして撮影) をWebclassにアップロード。

エフェクト用のGLSLは適当に修正したものや、他のエフェクトを改造したものを利用してもOK。