KLabGames Creative Blog

KLabは、多くのスマートフォン向けゲームを開発・提供しています。このブログでは、KLabのクリエイターがスマートフォン向けのゲームを制作・運営していく中で培った様々な技術や挑戦とそのノウハウについて紹介していきます。

はじめに

初めまして。クリエイティブ部のがなさまと申します。
この記事はKLab Creative Advent Calendar 2019 の 3日目の記事になります。

技術書専門の同人誌即売会「技術書典6」において今回も当社エンジニア有志で「KLab Tech Book Vol.4」を発行しました。
この同人誌の表紙制作とポストカード6種の制作を、社内イラストレーター有志

  • キャライラストレーター5名(東京4名・大阪1名)

  • 背景デザイナー2名(東京2名)

総勢6名でKLabの「どぶろく制度」(標準労働時間の10%以内であれば、上司の承認なしで本当にやりたかった研究など、好きなことに時間を費やすことができる制度)を使い取り組みましたので、ご紹介いたします。

今回は1つのIPのような作品として非常に品質の高いアウトプットが出来たのではないかと感じており、通常業務以外で「気軽な本気」で「気軽なやりたい!」を実践でき、自分たちで「楽しい!」を生み出すことができたお話しが出来ればと思います。

image9

「自由に好きなもの作っていいよ」は楽しくなかった

「KLab Tech Book」は2019年4月で4冊目となり、表紙の制作取り組みは今回で3回目となります。
初回、vol1の表紙はエンジニアで用意していましたが、vol2から社内イラストチームが参加できないかという相談を受け、イラストレーターの有志を募って取り組み始めました。
そこから半年後にvol3も制作し、進行する上でイラストレーター達から「何描いて欲しいかのオーダーないの?」という質問と疑問をもらいました。

私はイラストレーター達の通常業務の息抜きになればと良いなという想いから「B5サイズのイラストにしてくれればOK!」「自由に好きに描いていいよ!」と、いわゆる”丸投げ”をしていました。
しかしイラストレーター達から「うーん…なんか、何やって欲しいのかが見えなくて、あまり遣り甲斐が見いだせない…」といった話が出てきたのです。

え?自由に描くの、楽しくないの?そうなの???

自由に描くなら趣味で描く。
会社で取り組む制作については、しっかりと一定のプロダクトを生み出したい!
だってプロですもの。
私はイラストレーター達にしっかりオーダーを提案しなければいけない、と気づきました。

楽しい!は自分達で生み出す

まず毎回「KLab Tech Book」を進行している、ほぼエンジニアしかいないチャット部屋でキャラクター設定募集を募り、エンジニア陣の妄想を聞き出しリスト化しました。
何人くらい興味を持ってくれるかな~と思いながらも、そのリストをつけて社内告知したところ、なんと5人も挙手してくれたのです!しかも内2名は前回も参加してくれたメンバーでした。

進行はそのエンジニアばかりのチャット部屋で行い、
リスト化された妄想がイラストレーター達によって具現化され、キャラデザイン案がどんどん投稿されてゆきました。
イラストが投稿されるたびに、そのチャット部屋にいるあらゆる職種のメンバーから、ダイレクトに感想が飛び交い、通常業務にはあまり見られないフランクなやりとりであっという間にキャラクターが生み出されていったのです。

イラストメンバー同士もバラバラのプロジェクトや拠点に所属する為、初期はお互いの自己紹介から始まり、互いに何が行えるのかを伺っていましたが、週次で30分ほど集まる形にして認識合わせなどを行いました。
私はスケジュールだけは都度切っておりましたが、メンバー間でどんどんと話が進んでゆき、イラストのテイストや誰が何を分担するや、目指すクオリティなどが決まってゆくではありませんか。

image11

image14

image8

少ない時間の中、みんなのやる気に「大丈夫?頑張り過ぎてない?」と心配しつつも、クオリティの高いアウトプットが続々と作られ、
盛り上がってゆくチャット部屋…!
途中から背景デザイナーも合流し学園のデザインまでも出来てきました。
(結局お蔵入りしてましたが、途中、なぜか3Dモデルも出来てました…)

image12

面白いことに、楽しんで作っていることは伝染してゆくのか、
社内で「何か面白いことやってるんだって?」と、
いろいろと聞かれるようにもなっていました。

アウトプットご紹介

執筆者募集から印刷データ納品まで約6週間。
制作したアウトプットのご紹介となります。

image10

image13

image1
image6
image5
image7
image2
image3
image4

まとめ

最後に、参加メンバーの感想をご紹介します。

  • ぶっつけ本番で作業分担&絵柄やクオリティ統一できたのは凄い…他案件の方々と技術協力もできて良い制作でした

  • キャラデザ等普段やらない作業をやりつつも、各々得意分野で分担してクオリティ高く制作できたので満足度高かったです

  • 背景アリのキャラメインのイラストが作成でき、KLabのイラストレーターの合作という点でとても良いものになってると思います!

  • 合作(表紙)と個人技(ポストカード)の両方があったのでプロジェクトの垣根を超えた作業が新鮮だった。あと個人のテイストを知れたのもかなり良かった

  • 案だしの段階から否定的な意見がなく、活発且つお互いを尊重して作業できたと思う

  • 今回は特にみなさんの意見を取り入れながらの作業だったのとキャラの設定もあり、レイアウトなどのイメージがしやすかったです

  • 他案件のデザイナーの方やエンジニアの方々など、普段あまり関わらないような方と一緒に作業ができて楽しかったです!良い機会になりました

こういうことやろうよ!という提案に、とても前向きに取り組んでくれる当社のクリエイティブメンバーが私は大好きです。
これからも通常業務以外にも、もっと「気軽な本気」で「気軽なやりたい!」を実践でき、自分たちで「楽しい!」を生み出してゆくKLabでありたいと思います。

そして今年の秋にはKLabtechbook vol5も無事発行しております。
こちらの取り組みはまた別の機会できっと触れることができると思いますので、どうぞ
お楽しみに!

明日の記事はnaoieさんのフォトスキャンのお話です!

はじめに

この記事は KLab Creative Advent Calendar 2019 の 2日目の記事になります。

こんにちは。テクニカルアーティストグループの fo-ta です。
去年のアドベントカレンダー では Shader のことを書かせて頂きました。
今回は半透明の前後関係(描画順)について書かせて頂きます。

概要

半透明のオブジェクトはなぜ見た目の破綻が起こるのか、具体的に Unity ではどう改善できるのかを紹介します。

クリエイティブブログですので、デザイナーさん、アーティストさん向けにわかり易さを重視しています。そのため、省略した書き方も取り入れています。ところどころ「正確には違う」点があるかと思いますが、ご了承ください。

難しい話はいいから実例だけ知りたい!という方はここまでスキップしちゃいましょう。

ざっくり座学編

深度

そもそも、なぜ3DCGは前後関係が正しく見えるんでしょうか。

「手前にあるんだから手前に見えて当たり前」。現実世界ではそうですが、CGである以上はちゃんと仕組みがあります。

その仕組とは、深度情報です。

image15

Z深度、深度マップ、デプスマップなどの呼び方をされる、上図の様な白黒画像を見たことがある人も多いでしょう。映像制作で被写界深度をかける時などにも使いますよね。この情報を利用します。
すでに手前にレンダリングされている場合はレンダリングしない、そうでなければレンダリングして深度情報も更新する、と言う流れで正確な前後関係になっています。

このように、深度情報を用いて正確な前後関係でレンダリングする手法をZバッファ法と呼びます。

Zバッファ法の弱点

そんなZバッファ法にも弱点があります。

手前にレンダリングされた半透明オブジェクトの深度情報により、奥のオブジェクトがレンダリングされない、ということが起こる点です。

image11

この様な理由から、半透明オブジェクトは奥のオブジェクトから順番にレンダリングすることで前後関係を解決しようとします。

この順番のことを「描画順」と呼びます(レンダリング=描画)。

描画順の決定方法

描画順はいくつかの要素を元に決定されます。ここでは代表的な例を紹介します。

オブジェクトの位置

オブジェクトの位置がよりカメラから遠いと先、近いと後の描画順になります。つまりより遠くにあるオブジェクトから順に描画されます。
ここでいう「オブジェクトの位置」は、オブジェクトの中心座標が基準になります。ツールハンドルの切り替えボタンを Center にするとその位置がわかります。

image3

image16

通常の Mesh の場合は中心位置が自動的に算出されます。
SkinnedMesh の場合は Bounds の Center の位置が中心座標です。これは Unity 上で編集することができます。

image4

image12

Render Queue

マテリアルの Render Queue の数字が小さいと先、大きいと後の描画順になります。

image1

これはオブジェクトの位置よりも優先されるため、奥にあるのに手前に描画する、と言うトリッキーなことも可能です。

※ Render Queue に使用する値はゲーム全体を考慮して設計すべきです。必ず担当のエンジニアさんと相談しましょう。

頂点番号

上記2つはオブジェクト単位ですが、1つのオブジェクト内のではポリゴンの頂点番号が小さいと先、大きいと後の描画順になります。

image8

Maya ではコンポーネントエディタ、 Houdini では SceneView や GeometrySpreadsheet で確認することができます。

実践編

実際に制作の現場で遭遇しそうな例を2つほどご紹介します。
(今回は Unity 2018.4 を使用しています。)

斬撃エフェクト編

image2

剣で攻撃する時などに使用する斬撃エフェクトです。

このように厚みのあるエフェクトを作成する際、ポリゴンの両面を描画する Shader を使用すると破綻が発生します。

角度を変えて見た際に、奥にあるはずのポリゴンが手前に描画されてしまうのです。

このような場合は、両面にポリゴンを用意し、 片面描画の Shader を使用すると解消できます。
両面にポリゴンを用意する際の注意として、前述の頂点番号を整える必要があります。頂点番号が小さい方が先にレンダリングされるので、内側が小さく、外側が大きくなるようにします。
複製&法線反転で内側と外側のメッシュを用意したら、 Maya では内側、外側の順メッシュを選択して結合、 Houdini であれば内側、外側の順で Merge ノードに接続すると意図した順の頂点番号になります。

Unity でポリゴンを片面のみ描画する Shader を設定します。

image5

例えばプリセットの Particle/Standard Ulit Shader の場合、 Two Sided のチェックを外すと片面描画になります。

無事、破綻を解消できました。

竜巻編

より多くのポリゴンが重なる場合、もう一歩工夫が必要です。

左が破綻したもの、右が整理したものです。

破綻している方では、大きな竜巻の内側ポリゴンが小さな竜巻よりも手前に描画されてしまっています。また、記事冒頭の画像のように視点を変えると前後関係も変わってしまうこともあります。
大きい竜巻の内外と小さい竜巻の内外、複数のポリゴンがちぐはぐな順番で描画されて起こる破綻です。

では、どのような順番で描画されればうまくいくでしょうか。

image18

①大きい竜巻の内側、②小さい竜巻の内側、③小さい竜巻の外側、④大きい竜巻の外側の順番で描画されれば大丈夫そうです。

内側の竜巻は斬撃の際と同様に内側、外側の順番で頂点番号を整理します。
外側の竜巻は、 Render Queue で順序付けします。メッシュを分割し、それぞれ別のマテリアルをあてましょう。

image7

負荷の増加について

描画順を整えるためにアセット数や描画負荷が上がってしまうことがままあります。
例えばポリゴンを両面作ると、当然ながらポリゴン数が倍増します。マテリアルを増やすことでドローコールが増えることもあります。

見た目のためなら何を犠牲にしても構わない!…なんてことはありません
見えなかったり目立たない部分は妥協し、目立つ場所は整えると言った、ほど良い落とし所を探ることが重要です。

その他

今回は詳細な説明を省略しますが、この他の半透明描画に関係するトピックスをいくつか紹介します。機会があれば別の記事で詳しくご紹介しようと思います。

複数マテリアル

image6

1つのメッシュにQueueが同じ複数のマテリアルを割り当てた場合、上から順に描画されます。これを利用して、竜巻の例でメッシュを1つにすることができます。

ZTest

ZTest という仕組みを利用すると、そのオブジェクトを描画する際、すでに書き込まれている深度情報を無視することができます。全画面エフェクトなど、最前面に表示したい場合に有効です。
ON / OFF の単純な切り替えではないことと、他の要素との兼ね合いがやや難しいため、今回は省略しています。

加算合成

半透明(アルファ合成)と比べ、加算合成同士は前後関係が破綻していてもバレにくいです。加算はその名の通り色の計算がシンプルな足し算のため、どちらが先でも結果が同じになるためです。

ディザ合成

不透明を格子状にパンチ抜きして描画することで擬似的に半透明にする技術です。不透明で描画するので深度情報を利用できるため、前後の破綻が起きません。
解像度が低いと不透明だとバレること、パンチ抜きの処理がモバイル端末と相性がわるいことなどから今回は省略しています。

最後に

エンジニアさん向けのこの手の情報は結構あるんですが、アーティストさん、デザイナーさん向けの記事ってあまり見ないな、と思ったことが今回のきっかけです。
描画順の破綻と日々戦っている人達に、少しでも解決のヒントになれれば幸いです。

なお、もし要望があれば、他の実践例もご紹介したいと考えています。

image10

この記事はユニティちゃんライセンス条項の元に提供されています。

こんにちは、atsushiです。

この記事はKLab Creative Advent Calendar 2019 の1日目の記事になります。

アドベントカレンダーとは、12月1日からクリスマスまでの25日間、毎日一人ずつブログ記事を書いてバトンを繋いでいくという技術界隈のちょっとしたお祭りのようなものです。

2018年に引き続き、今年もなんとか作成することが出来ました!

このカレンダーでは、KLabの3D・2D・UI/UX・エフェクト・サウンドから、TA・R&D・マネジメントに至るまで様々なクリエイティブの話題について触れて行ければと思います。
無事完走できるように暖かく見守っていてください!

さて、まず私からは「KLabの”情報発信”」について、自分なりの目線でお話いたします。

KLabの”情報発信”について

KLabには情報発信をしたいスタッフを積極的に支援しよう!という文化があります。

そして、その文化をより推進するために「情報発信促進制度」「クリエイティブ広報グループ・技術広報グループ」という二つの存在が大きな役割を果たしています。

これら2つについて簡単にご紹介します。

情報発信促進制度

情報発信促進制度とはその名の通り、従業員による情報発信活動を促進し、成長を支援する制度です。

この制度を使うことで、CEDECやUnite等のカンファレンスで発表・登壇をしたい場合や、勉強会やイベントを企画・開催・運営したいとなった場合に、それを実現するために掛かった時間は業務をしたものとみなされ、活動費用も支援してもらえるようになります。

クリエイティブ広報グループ・技術広報グループ

クリエイティブ広報グループ・技術広報グループとは、アーティスト・エンジニアそれぞれの分野に特化した広報組織のことです。

この組織は今年設立されたばかりで、特徴は所属しているメンバーの殆どが普段現場で実際にゲーム開発をしている現役のアーティストやエンジニアということです。

現場の目線で「こういうのがあったらいいのにな」と思うような勉強会・イベント等の企画を自分たちの手でそのまま実現でき、さらにそれを広報活動に繋げることが出来るという強みがあります。

制度・組織が新設されてどう変わった?

これらの制度や組織が出来たことで、KLabは情報発信をしたい・興味がある人にとってとても活動がしやすい環境になったと感じており、実際に社内の情報発信に対する熱量は明らかに上がってきております。

とくに今年はCEDEC、Unite、GDCなど数多くのカンファレンスに当社から多くのスタッフが登壇することができ、社内でも有志による勉強会が月に1回以上開催されるようになりました。

また、昨年末のアドベントカレンダーを始める為に立ち上げたこのKLabGames Creative Blogも、実はあれから今日に至るまで毎月1回以上は必ず更新されていたりします。

(ふと思い出したときにでもいいので是非覗いてみてください!)

まとめ

ありがたいことにこれらの活動がきっかけでKLabのお仕事に興味を持ってくださる方が増えたり、実際に入社してくださる方も増えてきたように感じてます。

情報発信による効果はアクセス数等の数値だけでは測れない部分もあるので、こういった実際のリアクションがあるとやっててよかったなと思えますね!

ここまでご覧いただきありがとうございました。明日の記事はfo-taによるデザイナーのための半透明描画順です!お楽しみに!

こんにちは。はじめまして。
KLabのテクニカルアーティストGに所属している、こかろまと申します。

去年の KLab Creative Advent Calender 2018 以来のブログ執筆となります。

今回の内容はMaya+Pythonでツールを書き始めた方を想定しています。

はじめに

ユーザーにとって「扱いやすいツール」とはどういった要素があげられるでしょうか。

作る方によって意見は変わってくるかと思いますが、私の場合は特に「ツールがユーザーにとって直観的であるかどうか(=ツールの直観性)」が重要だと考えています。

ここでいう「ツールの直観性」とは、使用するユーザーが普段利用しているツールの挙動に合わせることであったり、アプリケーションに最低限備わっているユーザーインターフェイスにできる限り合わせることとして位置づけています。

ユーザーインターフェイスの部品の一例としては、普段オペレーティングシステムで
使っている「ダイアログ」や...

image1

アプリケーションによって異なりますが、「Ctrl+Z」のundo(元に戻す)・
「Ctrl+Y」のredo(やり直し)などのショートカットであったり...

そこで、今回は社内のツール実装方針として共有する目的も兼ねて、
Maya+Pythonにおける「undo(元に戻す)」「redo(やり直し)」についてお話ししたいと思います。

Maya+Pythonにおけるundo・redoの挙動

Maya+Pythonでツールを実装するにあたって、undo・redoで躓く点は
大きく分けて下記の二つになると思います。

  • ユーザーが求めるundoができないケース
  • そもそもundo・redoできないケース

それぞれの対処方法についてお話ししていきます。

ユーザーが求めるundoができないケース

Mayaでツールを実装する際に考慮していないと陥りやすいケースです。

「シーン上にあるpCube1オブジェクトを選択する」だけのツールを例に挙げてみます。分かりやすいようにバリデータなども一切組んでいません。image2

# select_tool.py
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

import maya.cmds as cmds

class SelectTool(object):

   def __init__(self, select_object_name=’’):
       self.select_object = select_object_name

   def execute(self):
       cmds.select(self.select_object, r=True)
       return

def main():
    _select_tool = SelectTool('pCube1')
    _select_tool.execute()

Maya+PythonにおいてMayaのdagノードを触るようなツールを実装する際には
方針によって様々ですが、基本以下のいずれかを利用するかと思います。

  • maya.cmds
  • maya.mel
  • pymel
  • openmaya(1.0 / 2.0)

このうち、「maya.cmds」「maya.mel」は各コマンドにundo・redo機能が実装されており、「一つのコマンドごとに」mayaのコマンド履歴の待ち行列に溜まっていきます。

「pymel」も内部的に「maya.cmds」「maya.mel」を利用しているため、同様の挙動となります。

つまり、上記のような「シーン上にあるpCube1オブジェクトを選択する」だけのツールの場合、特にツール側に考慮は必要なくundo(pCube1選択前に戻る)・redo(pCube1選択後に戻る)することができます。

image5

ですが、ツールは往往にして複雑になっていくものです。

たとえば、選択しているオブジェクトすべてを「Object_」というPrefixを付け、
後ろに数字を3桁の連番ごとにリネームするようなツールが必要になったとします。

その場合、大雑把ですが下記のような実装になると思います。

# rename_tool.py
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

import maya.cmds as cmds


class RenameTool(object):

   def __init__(self):
       pass

   @staticmethod
   def validate():
       if not cmds.lockNode(q=True, l=True):
           return False

       return True
  
   def execute(self):
       _selection_dag_nodes = cmds.ls(sl=True, sn=True, fl=True, dag=True, tr=True)

       for _index, _value in enumerate(_selection_dag_nodes ):
           if self.validate() is False:
               continue

           _rename = 'object_{0:03d}'.format(_index)
           cmds.select(_value, r=True)
           cmds.rename(_rename)

       return

ツール自体は問題なく、シーン上の選択可能なdagノードがリネームされます。

また、undo・redoも行うことができます。

ただし、一点このツールには問題があります。

選択しているノードが大量にある場合のundo・redoの挙動です。

たとえば、50個くらいリネームすることができるdagノードを選択している場合に
上記のツールを実行します。

その後「あ、間違えてツール実行しちゃった!戻さなきゃ」とユーザーがundoを行うとします。

発行したコマンド回数分undoを行わないとツール実行前に戻れません。

今回の例では1つのdagノードあたりの実行順にすると「cmds.lockNode」「cmds.ls」「cmds.select」「cmds.rename」の4回分コマンドを発行しているので、ツール実行前の状態に戻す場合、1つのdagノードあたり合計4回undoを行う必要がありますが、mayaで設定されている待ち行列以上にundoすることはできません。

※正確にはコマンド単位ではなく、用意されている関数によって異なります。
Autodesk社のMayaコマンドリファレンスサイトに『「元に戻す」が可能』と記載があるものが待ち行列追加対象のものとなります。下記はlsコマンドのリファレンスです。

image6-1

出典:Autodesk Maya Help:Python コマンド:ls 

余談ですが、mayaの待ち行列の回数はMayaの『プリファレンス』⇒『元に戻す』項目より設定することができます。

インストール時点ではデフォルトで50に設定されています。

image4

ツールによってコマンドが発行される回数次第で、ツール実行前の履歴を消してしまうこともありえます。

こういったツールは、「ユーザーにとって扱いやすいツール」とはいえません。

解決策

昨今のmayaでは待ち行列に登録する範囲を設定するチャンクが用意されています。

maya.cmds.undoInfoのopenChunk, closeChunkがそれにあたります。

openChunk関数でチャンクを開き、closeChunkでチャンクを閉じる間に処理を書くことで、その間の処理を1回の待ち行列として定義することができます。

今回のツールでは、シーン上のオブジェクトを回すイテレータを実行している「execute関数」に上記のチャンク範囲を適用することで、シーン上のオブジェクト数分のコマンド発行から「ツールを実行する」単位となり、undo・redoの回数を1回に抑えることができます。

# rename_tool.py
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

import maya.cmds as cmds


class RenameTool(object):

   def __init__(self):
       pass

   @staticmethod
   def validate():
       if not cmds.lockNode(q=True, l=True):
           return False

       return True
   
   def execute(self):
        cmds.undoInfo(openChunk=True)

        _selection_dag_nodes = cmds.ls(sl=True, sn=True, fl=True, dag=True, tr=True)
        for _index, _value in enumerate(_selection_dag_nodes ):
            if self.validate() is False:
                continue

            _rename = 'object_{0:03d}'.format(_index)
            cmds.select(_value, r=True)
            cmds.rename(_rename)

        cmds.undoInfo(closeChunk=True)

        return

ツールを実行し、リネームが確認できた後、Ctrl+Zでundoしてみます。

今度は1回ですべて戻す(=ツール実行前に戻る)ことができました!

image3

上記でもチャンクを一つにまとめることはできますが、せっかくPythonを使っているので、本処理をデコレータとして定義しておき、共通ライブラリとして扱えるようにしておくとツールや関数ごとに定義する必要がないので便利です。

# undo_wrapper.py
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

import maya.cmds as cmds

def undo_chunk(function):
   def wrapper(*args, **kwargs):
       cmds.undoInfo(ock=True)
       function(*args, **kwargs)
       cmds.undoInfo(cck=True)

   return wrapper
# rename_tool.py
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

import maya.cmds as cmds

from . import undo_wrapper


class RenameTool(object):

   def __init__(self):
       pass

   @staticmethod
   def validate():
       if cmds.lockNode(q=True, l=True):
           return False

       return True
   
   @undo_wrapper.undo_chunk
   def execute(self):
       _selection_dag_nodes = cmds.ls(sl=True, sn=True, fl=True, dag=True, tr=True)
       for _index, _value in enumerate(_selection_dag_nodes ):
           if self.validate() is False:
               continue

           _rename = 'object_{0:03d}'.format(_index)
           cmds.select(_value, r=True)
           cmds.rename(_rename)

       return

そもそもundo・redoできないケース

頂点すべてに処理を加える、UVを展開する、スキンウェイトを調整するなど、
処理自体が重いツールを実装する場合、maya.cmdsやpymelでは実行速度に期待ができないのもあって、弊社では基本的にopenmaya for Python APIを利用しています。

openmaya for Python API では、undo・redo機能は提供されていません。

そもそも処理したことが待ち行列にスタックされないため、undoを行っても
openmaya for Python APIを実行する前の履歴が実行され、処理したことはそのまま残り続けます。

そのため、undo・redoが必須な場合はすべて自前で実装する必要があります。

解決策

openmayaでundo・redoを考慮に入れる場合、MPxCommandのプロキシクラスを継承したクラス単位で処理を実装する必要があります。

また、一種のコマンドとして実装する必要があるため、プラグイン化する必要があります。

プラグイン化については今回は省きますが、Autodesk社公式リファレンスが非常に参考になります。

選択しているオブジェクトの頂点カラーをすべて赤(1.0, 0.0, 0.0, 1.0)で
塗りつぶす簡単なプラグインを例に挙げます。

今回は簡単に説明するためにdagノードの名前単位で頂点カラー情報を保持していますが、
厳密にアンドゥ・リドゥを実装する際にはMTypeID等で制御するべきです。

 # vertex_color_fill_plugin.py
# coding: utf-8
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

import re
import _ctypes

import maya.cmds as cmds
import maya.api.OpenMaya as OpenMaya


class VertexColorFillTool(OpenMaya.MPxCommand):
   kPluginCmdName = 'VertexColorFillTool'

   def __init__(self):
       OpenMaya.MPxCommand.__init__(self)
       self.before_calculate_vertex_color_dict = {}
       self.selection_list = []

   @staticmethod
   def initialize():
       return VertexColorFillTool()

   @staticmethod
   def validate():
       if not cmds.ls(sl=True):
           print('error : no selection')
           return False

       _color_set_list = cmds.polyColorSet(q=True, acs=True)
       if not _color_set_list:
           print('error : color set no exists')
           return False

       return True

   def get_active_vtx_selection_list(self):
       _vertex_list = self.get_select_vtx_list()
       if not _vertex_list :
           cmds.error(u'not selected dag object or vtx')
           return

       cmds.select(_vertex_list , r=True)

       return OpenMaya.MGlobal.getActiveSelectionList()

   @staticmethod
   def get_select_vtx_list():
       _vtx_list = cmds.polyListComponentConversion(tv=True)
       _vtx_list = cmds.ls(_vtx_list, fl=True)
       return _vtx_list

   def doIt(self, args):
       """
       コマンド実行時(関数実行) 関数
       :param args:
       :return:
       """
       if not self.validate():
           return

       self.selection_list = self.get_active_vtx_selection_list()

       for _selection_object in xrange(self.selection_list.length()):
           _vertex_colors = []
           _dag_path, _ = self.selection_list.getComponent(_selection_object)
           _target_mesh = OpenMaya.MFnMesh(_dag_path)

           # 頂点カラーの実行前の状態を保持
           self.before_calculate_vertex_color_dict[_selection_object] = _target_mesh.getVertexColors()
           _vertex_list = cmds.ls(self.selection_list.getSelectionStrings(_selection_object), fl=True)

           _vertex_colors = [(1.0, 0.0, 0.0, 1.0) for i in _vertex_list]

           _target_mesh.setVertexColors(_vertex_colors, xrange(_target_mesh.numVertices))
       return

   def redoIt(self):
       """
       待ち行列の実態(リドゥ) 基本処理を再度実行すればOK
       :return:
       """
       self.doIt()
       return

   def undoIt(self):
       """
       待ち行列の実態(アンドゥ) 実行前の値を書き込むことで元に戻す(ように見せる)
       :return:
       """
       for _selection_object in xrange(self.selection_list.length()):
           _dag_path, _ = self.selection_list.getComponent(_selection_object)
           target_mesh = OpenMaya.MFnMesh(_dag_path)

           target_mesh.setVertexColors(self.before_calculate_vertex_color_dict[_selection_object],
                                       xrange(target_mesh.numVertices))
       return

   def isUndoable(self):
       return True


def maya_useNewAPI():
   """
   プラグインに渡されるオブジェクトの型を示すための定義。openmaya for Python API 2.0以降のみ必須。
   :return:
   """
   pass


def initializePlugin(mobject):
   _open_maya_plugin = OpenMaya.MFnPlugin(mobject)
   try:
       _open_maya_plugin.registerCommand(VertexColorFillTool.kPluginCmdName, VertexColorFillTool.initialize)
   except TypeError:
       print('error initiliaze plugin')


def uninitializePlugin(mobject):
   _open_maya_plugin = OpenMaya.MFnPlugin(mobject)
   try:
       _open_maya_plugin .deregisterCommand(VertexColorFillTool.kPluginCmdName)
   except:
       print('error uninitiliaze plugin')

# 実行側のコード(スクリプトエディタなど)
import maya.cmds as cmds
cmds.loadPlugin(’vertex_color_fill_plugin’)
cmds.VertexColorFillTools()

プラグインとしてロードし、実行した結果が下記のとおりです。

各dagノードが元々持っていた頂点カラーに戻せていることが確認できます。

image7

あくまで上記のクラスを継承した際にはundoやredo等、ユーザ操作に対応した
継承元のコールバック関数が呼び出されるだけなので、

  • doIt関数「実行前の状態を記憶しておき、目的の処理を行い、場合に応じて実行後の処理状態を記憶する」
  • undoIt関数「doIt関数の際に記憶した、実行前の状態に戻すための処理を行う」
  • redoIt関数「実行後の処理状態に戻すための処理を行う」
    =今回のツールではdoIt関数を再度呼び出すことと結果が同じとなるため、doIt関数を呼び出すことでredo処理とする

上記3つの実装方針に基づいてundo・redoを実装することになります。

そのため、処理次第ではundo・redoではない処理を実装することもできてしまいます。

細心の注意を払って実装しないと、うっかりデータを破壊するような処理になりかねません。

また、プラグインの都合上Maya上でunInitialize(プラグインマネージャでプラグインのロードを外す等)されてしまうと当然そのツールで保持していた待ち行列は破棄されてしまうため、注意が必要です。

まとめ

ツールやユーザーのニーズによってundo・redoの範囲の要件は変わります。

また、undo・redoがそもそも必要ないケースもあるかと思います。

openmaya for Python API を扱う必要があるツールにundo・redoが必須要件としてツール完成後に加わる場合、単なるPythonのパッケージ群のツール設計』から『Mayaでのプラグイン設計が必須のツール設計』と、実装方式や再設計に係る工数が大きく変わることもあるため、必ず初期のツール設計段階の要件の一つとして洗い出しを行うことを強く推奨します。

今回は「ユーザーにとって扱いやすいツール」の一例として、undo・redoについて
お話しさせていただきました。

本記事がMayaでツールを作る人の手助けになれば幸いです。

image2

こんにちは。クリエイティブR&Dグループ所属のsasaと申します。

セクション内では主にVFXを担当しており、現在はHoudiniというツールの技術検証・研鑽・実践を行っています。

「R&Dグループってなんぞや?」という方はコチラ↓

http://klabgames.creative.blog.jp.klab.com/archives/14399176.html

Pyro基礎編のPyroとは?

今回は表題の通りPyroを使った基礎Tipsとして炎作成のテクニックを動画チュートリアル形式でご紹介したいと思います。

Pyroが何か分からないという方もいるでしょうからまずはPyroについて説明いたします。

Pyroとは主にHoudiniでは炎や煙のシミュレーションに使う機能の事を指します。

Houdini16.5から17になった際に一部の仕様が変更になって、以前の学習リソースが「使えないわけではないが初心者のうちはチンプンカンプンでPyro学習が中々難しい。また、ルックやマテリアル設定、レンダリングまで一貫した資料が中々無い」という状況に陥りがちで現状少々学習が難しい状態になっています(自分もHoudini17になる辺りにHoudiniを本格的に始めたので仕様の違いに苦労しました)。

Introduction

Houdini基礎編のチュートリアルの後にいきなりシミュレーションのチュートリアルは少し飛躍しているかもしれないとも思いましたが、上述した理由からHoudini17.5仕様のPyroのシミュレーションからレンダリング・コンポまでの基礎Tipsを作ろうと思い今回のチュートリアルの作成に至った次第です。

注意点なのですが今回はPyroやシミュレーションで説明する事が多くHoudiniの基本的な部分からの解説だと説明量が膨大になるので、前回の記事(※下記参照)の内容位までは履修してHoudiniの基礎がある程度分かっている方向けの内容になります。

勿論動画チュートリアルですので内容を理解はしていなくても手順をなぞれば同じものはできるのですが、ご自身で思い描いたビジュアルを作るにはやはりツールやCG・シミュレーションの根本的な理解が必要になります。

とはいえ「まずは御託はいいからPyroでの炎の作り方を教えてくれ。話はそれからだ」というお気持ちも重々理解できます。ですので本チュートリアルをPyroの最初の一歩として皆様方のステップアップの足掛かりにでもして頂ければ幸いです。

※前回の記事↓

http://klabgames.creative.blog.jp.klab.com/archives/19665739.html

今回やること

image1

今回は上記画像のような炎素材を作成します。

エミッター作成からコンポジットまでの各工程を行います。基礎編なのであまり複雑にならないような構成且つ私がやりやすいと思っているフローを紹介致します。

これは前回の記事にも書きましたがHoudiniには良くも悪くもアプローチに多種多様な選択肢があるので、私のやり方を紹介することで「こういう考え方でやっている人もいる」ということを知って頂き、その視点を軸にすることで他のアプローチを比較検討しやすくなるだろうという思いがあります。

また、前回の記事同様基本的に動画チュートリアルですのでブログ上のテキストは要点等の捕捉という形式になっております。

PyroBasicTutorial:Fire01

こちらが今回のチュートリアル動画となります。各チャプターへのショートカットは動画の説明欄に記載しています。

Houdiniのバージョンは17.5、コンポに使用しているAfterEffectsのバージョンはCC2019です。

簡単なショートカットキーやグローバル変数は上述した前回の記事まででおおよそ説明しておりますので動画内ではほぼ説明をしていませんが、念のために幾つか下記に記載致します。

ショートカットキー 内容
tab tabメニューの表示
U 上の階層に上がる
I 下の階層に潜る
R 選択したノードのディスプレイフラグを立てる
Y + ドラッグ ワイヤーの切断
Alt + ドラッグ 複製
Ctrl+B 選択中のビューを最大化及び元に戻す
Space + F 選択したジオメトリ、オブジェクトをビュー全体に表示
Space + A 全ジオメトリをビュー全体に表示
Space + H グリッドを中心に表示

グローバル変数 内容
$F 現行フレーム
$HIP 現行のシーンファイルを含んだディレクトリ
$HIPNAME 拡張子なしの現行シーンファイル名
$OS 現行オペレータ(ノード)の名前

Tutorialの補足

動画内にテキストで必要な補足はいれているのですが、それ以外にも気になりそうな事だったり、「これってどういう意味だろう?」と感じそうな箇所だったり、追加で別途説明をした方が良さそうだと感じた箇所を下記にまとめました。

チュートリアル動画を履修中、必要に応じて適宜ご確認いただければ幸いです。

Introduction

ネットワークエディタは各工程毎にネットワークボックス(灰色半透明のボックス)を縦長に配置して区切り、左から右に作業フローを展開していっています。

ネットワークエディタはご自身のお好きな形で配置して頂いて大丈夫ですが、上記の「左から右に作業フローを展開する」というようなルールを決めておくと後日ご自身で確認したり他の人にシェアする時に分かりやすく理解しやすいのでオススメです。

炎を青系の色合いでレンダリングしている理由ですが、これは動画内にも記載した通り色幅が多くてコンポジットで調整しやすいからです。

何故コンポジットで調整しやすい形が良いかというと、炎の色味は案件やディレクター・クライアントの意向で千差万別なのでコンポジットで柔軟に対応できるようにしておいた方が良い場合が多くオススメなのでこの手法で作業を行っています。

CreateBaseObject

switchSOPでオブジェクトの位置を切り替えていますが、ここは普通にキーフレームでも大丈夫です。今回のような場合はswitchSOPを使うと後から値を修正したい場合にキーフレームやアニメーションを修正しなくて良いので今回はこの手法で作業を行っています。

前回のチュートリアルでも記載致しましたが、最後に繋いだ参照用のnullの名前を大文字にしておくと、引用時のリストのトップに表示されるようになり後の作業時に探しやすくなります。

CreatePyroFire_Part1

ここは、シェルフや各種チュートリアルでHoudini16.5と17系でノード構成が違っていて間違いやすく戸惑う部分だと思います。とはいえ、実は16.5でも17系のような構成でベースとなるボリュームを作ることはできたので、17系ではこれがHoudini推奨の手法のようです。

今後また仕様が変更となる事も想定されますが、17系ではベースのボリュームを作るのに

  1. PyroSourceで読み込んだジオメトリ等をpoint化。計算に必要なアトリビュートも付与
  2. AttributeNoiseで付与したアトリビュートにノイズを当ててディテール調整
  3. VolumeRasterizeAttributesでVDB(ボリューム)化

という作業フローが主体だというのを知識の下地として知っておくと、バージョンアップで仕様変更があったとしても理解や学習が捗りやすいかと存じます。

CreatePyroFire_Part2&Part3

シミュレーションの肝の部分なので色々と説明していますが、ここはご自身で色々とパラメータを調整して結果を確認して頂くのが学習の一番の近道かと存じます。

CreateCache1

キャッシュを取る方法は後の火花作成の時のように他にも色々とあるのですが、ここは分かりやすさ重視でシェルフのものをそのまま使用しています。

一旦VDB化してキャッシュサイズを少し軽くするテクニックなどもあるのですが、今回は脱線しそうだったのでそのままキャッシュを取っています。

CreateSpark

シミュレーション情報からvelocityを拾う手法や、timeshiftで疑似モーションブラーを作るテクニックは便利なので火花作成を通じて紹介していますが、火花自体作らなくても良いとお考えの場合は火花作成及びそのキャッシュ作成の工程を飛ばして頂いても問題ございません。

Up-ResContainer

FumeFXでいうWaveletTurbulenceのようなもので、簡単な手順で低解像度のキャッシュから高解像度のキャッシュを作ることが出来ます。

炎でUp-resContainerを使う場合はheatとdensityのアトリビュート(スカラーフィールド)が無いと上手くいかない事が多いです(Houdiniの公式ヘルプにはこれらの要素は必須と書かれています)。ですので後々density要素をビジュアルで使わない場合でもこの段階では今回のようにデータとして持たせておく必要があります。

Up-resContainerの要であるGasUpResソルバはWaveletTurbulence同様にシュルシュルという独特な滑らかさのディテールが付きやすいです。色味と同じく炎のディテールも案件やディレクター・クライアントの意向で千差万別なので、この独特な滑らかさをだしたくない場合は素直に通常のシミュレーションでDivisionSizeを0.01以下にするなどしてクオリティを詰めていく必要があります。

LoadCache&MaterialSettings

fileのloadタイプが「AllGeometry」である事に疑問を感じた方は「PackedDiskPrimitive」にして頂いても大丈夫です。前回の記事でpack関係には軽く触れているのですが、今回のチュートリアル中には説明しておらずこの段階でいきなりpackについて触れても混乱の元になるだろうと感じたので今回は「AllGeometry」のまま進行しています。

Lighting&Rendering

SkyLightは単純に調整が楽で使っているだけなので他のライトを使用して頂いても問題ございません。

Mantra周りの説明は文章に起こすとやけに長くなるのですが、やっている事は普通のレンダリング設定なので実際に触りながら適宜テキストを確認して頂けると理解しやすいかと存じます。

Composite

使用しているaepは以下からダウンロードすることができます。一般的な常識の範囲内で学習ソースとしてご自由にお使いください。

blog03_cmp_01.rar をダウンロード

Creative Commons License
This work by KLab Inc. is licensed under a Creative Commons Attribution-NonCommercial 4.0 International License.

aep内はソースのデータを置き換えればそれ以降のコンポ全てが自動で変化・反映されるようになっており、これはHoudiniの非破壊的なプロシージャルな作法とよく似ています。AfterEffects自体はプロシージャルを前提としたツールではありませんが、このようにプロシージャルの考え方を踏襲すると効果的にシーンを構築することができます。

終わりに

チュートリアルお疲れさまでした。

如何だったでしょうか? 少しでも皆様のHoudiniの勉強のお役に立てたのなら幸いです。

私自身、丁度Houdini16.5~Houdini17になる辺りからHoudiniを始めたので「あれ?色んなチュートリアルで使っているFluidSourceっていうノードがHoudini17にないぞ?」に始まり、今まで使っていたFumeFXやVrayVolumeGridとの違いでディテールの出し方からマテリアル・レンダリングに至るまで満足なクオリティをだせるようになるのに色々と四苦八苦しました。PyroはHoudini17以降の仕様の資料がまだ潤沢とはいえず同じような悩みを抱えている方は多いかと思われます。ですので当時の私と同じような壁に差し当たった方の力になれたのならこれ以上の喜びはありません。

記事中でも言及しましたがHoudiniはツールの進化が早いので今後Houdini16.5→17の時のように推奨手法が更新される可能性は十分にあると思います。

ただ、そうなったとしてもそれまでのアプローチを知っているか否かというのは理解や学習の際に影響を及ぼす要素かと思いますので、ツールの進化を恐れずに現時点でのアプローチをしっかり知っておくのは重要なことだと存じます。

冒頭や前回の記事にも書きましたがHoudiniには無数のアプローチがあり、私が今回やった方法も一例に過ぎません。特に私はFumeFXからの転向者ですので今回のチュートリアルはFumeFXっぽい考え方で進行している所が少々あります。ですが、まずは一例を知ることでそこから多種多様なアプローチを比較検討することができるようになるので、一つの指標として本チュートリアルを活用して頂けますと幸甚に存じます。

また、「こういう事を知りたい」「こういう内容を記事に取り上げてほしい」といった要望も募集中です。何かございましたらコメントフォームの方へ書き込みをお願いいたします。

最後までお目通し頂きありがとうございました。

image1

はじめに

こんにちは!KLab株式会社クリエイティブ部 サウンドグループのいっちーです。
2019年7月26日『KLab×gumi×TACSYSTEM』ソーシャルゲームサウンドMeetup!
を開催した話をさせていただきます。

「ソーシャルゲームサウンドMeetup!」って何?

ソーシャルゲーム業界で活躍するサウンドクリエイターたちが
会社の枠を超えて集まり、ソーシャルゲームサウンドならではの考え方や解決方法について発表する勉強会及び交流会となっています。

ボイスをテーマに選んだ理由

今回は日頃から業務の中で接することが多いボイス周辺の話を
「ソーシャルゲームのボイスの話~収録・整音のながれ~」
と題してmeetupを開催させていただきました!

他社の方、または社内サウンドメンバーと話をしていると
「ある一つの作業」について
少しづつイメージが違うことがあります。
「ボイス整音」もそんな作業の一つでした。

「普段よく口にしている作業だからと言って
作業に対する段取りや内容が、みんな同じ認識とは限らない!」
これは最近よく思うことです。

それは作業環境や
使用しているツール
会社の方針によるものだったりしたのかもしれません。

どれが正解というより
そもそも同じ作業に対してでも、
人が違えば少しずつイメージが違うということ理解した上で
仕事を依頼する時(社内外問わず)は
成果物のイメージ(クオリティライン)の共有を密に行うことが大事!

ということを自戒の念をこめて行った勉強会でもありました☆

各社が普段行っている作業内容や段取りを公開することで
何か良い部分があれば取り入れていただき、
他スタッフと作業する時は話し合い、認識を揃えた上で
お互い気持ちよく作業に取り組める環境が当たり前になることが目標です!

開催当日

前半はトークセッション

▼そもそも整音って何 ?
▼収録~整音のながれ
などについて株式会社gumi様とKLabサウンドチームのメンバーが
トークセッション形式で行いました!

後半は整音の実演!

各社の整音方法を発表しあいます。
いつもと違い人に見られている特別な環境下ですが
普段の作業と同じようなスピード感で行う、タイムトライアル形式でも行いました。

▼①解説:中村 航(株式会社gumi)

▼②解説:宮内 俊成(KLab株式会社)#1

▼③解説:宮内 俊成(KLab株式会社)#2

▼④解説:解説:因 正博(タックシステム株式会社)#1

▼⑤解説:解説:因 正博(タックシステム株式会社)#2

まとめ

ご来場いただいた方からは
「普段、他人の作業を見ることがないので面白かった!」
「勉強になりました!ぜひまた見たいです!」と
ポジティブな意見を沢山いただくことが出来たので、有意義な会に出来たのではないかと思います。

今回も多くの社内外の方に助けられながらイベントを行うことが出来ました。
今後もこれまでの反省を生かしつつ、より良い会にしていけるように精進します!
次回内容も下記サウンドtwitterなどでお知らせしますので興味を持っていただければ幸いです☆

▼当日の様子

image2

下記に今回meetup内容も含め、過去に発表した内容の資料をアップしていますので、ご興味ある方はぜひ読んでいただければと思います☆

ソーシャルゲームサウンドMeetup!



KLab sound team が関わるイベント情報や技術TipsはTwitter上でも公開しています!
@klabsoundteam