Render Monkey 1.6 でお絵かきツールを作ろう

添付写真のような絵が描けます。rfxファイル(XML)を以下からダウンロードできます。名前を付けて保存して、Render Monkey に食わせてください。

http://cvs.sourceforge.jp/cgi-bin/viewcvs.cgi/*checkout*/gslib/GSLib/SandBox/RenderMonkey/Paint.rfx?rev=HEAD&content-type=text/plain

操作方法は

  • 左ドラッグ:ペイント
  • 右クリック:クリア

※Render Monkey のツールバーにある Mouse Input をクリックしないと正しく動作しません

このページを見ている人のほとんどは、シェーダーなんて当然使えるはずなので、ディフューズやスペキュラーの計算は他所に譲りまして、ここでは、Render Monkey のトリッキーな使い方を紹介させていただきます。こいつを通じて、さくっとRender Moneky に慣れてもらえればと思います。いや、やっぱり入門にはきついかも。
というわけで、2D Paint を作ってみようと思います。マウス入力を受け取り、塗り塗りします。恐るべきことに、マウスの位置やマウスボタンの押下情報がシェーダーコンスタントとして入力可能です(FX Composerでもできるようです)。

まず、レンダーターゲットテクスチャーを作ります。

  1. Screen-Aligned Quad を作ったら、Single Pass を Draw と名前を変えてください。名前を変えるには、クリックして F2 です。
  2. Screen-Aligned Quad を右クリック
  3. Add Texture -> Add Renderable Texture
  4. renderTextureをダブルクリック
  5. サイズを512×512に
  6. Auto-Generate Mip Map を off
  7. Format を A8R8G8B8 に

つぎに、各種変数を定義します。変数は、シェーダーコンスタントとして、すぐさま利用可能です。

  1. Drawを右クリック
  2. Add Variable -> Float -> Predefined の中から、以下のものを選択
    1. fMouseButtonStateLeft
    2. fMouseButtonStateRight
    3. fMouseCoordsXY_NDC
    4. fViewportDimensionsInverse

Drawパスのレンダリングターゲットを変更します。

  1. Drawを右クリック
  2. Add Render Target -> renderTexture
  3. Draw 以下の renderTexture をダブルクリック(2重丸みたいなアイコン)
  4. Enable color clear を off

renderTexture にはペイントされたデータが入ることになります(Enable color clear が on だと、毎回ターゲットがクリアされるので、画像が残りません)。次に、レンダリングステートを設定します。描画はちょっと変わった加算ブレンドを使います。

  1. Drawを右クリック
  2. Add Render State Block
  3. Render State をダブルクリックし、以下のように変更
    1. GL_AlphaEnable TRUE
    2. GL_BlendEnable TRUE
    3. GL_BlendEquation ADD
    4. GL_BlendSourceRGB ONE
    5. GL_BlendSourceAlpha ONE
    6. GL_BlendDestRGB SRC_ALPHA
    7. GL_BlendDestAlpha ONE

最後に、シェーダーを以下のように変更してください。

// vertex shader
varying vec2 texCoord;
uniform vec2 fViewportDimensionsInverse;

void main(void)
{
   gl_Position = vec4( gl_Vertex.xy, 0.0, 1.0 );
   gl_Position = sign( gl_Position );

   // Texture coordinate for screen aligned (in correct range):
   texCoord = (gl_Position.xy + vec2( 1.0 ) ) * 0.5 * ( vec2( 512.0 ) * fViewportDimensionsInverse );
}

僕のカードではレンダリングターゲットは2乗サイズが必須なので、サイズを512にし、fViewportDimensionsInverseで実際の画面の逆数をもらい、値を補正することにしました。

// fragment shader
uniform sampler2D Texture0;
uniform vec2 fMouseCoordsXY_NDC;
uniform float fMouseButtonStateRight;
uniform float fMouseButtonStateLeft;

varying vec2 texCoord;

void main(void)
{
   vec2 invYMouseXY = vec2( fMouseCoordsXY_NDC.x, 1.0 - fMouseCoordsXY_NDC.y );
   if ( fMouseButtonStateRight > 0.0 ) {
      //   clear canvas
      gl_FragColor = vec4( 0.0 );
   } else {
      float intensity = ( 1.0 - length( invYMouseXY - texCoord ) * 20.0 ) * fMouseButtonStateLeft;
      gl_FragColor =
         vec4(
            intensity, intensity, intensity, 1.0
         );
   }
}

fragment program の if ( fMouseButtonStateRight > 0.0 ) { は右クリックでキャンバスをクリアするためのモノです。Render State で GL_BlendDestRGB を SRC_ALPHA に指定したのはこのためです。
fMouseCoordsXY_NDC にはマウスの座標が [ 0.0 - 1.0 ] で入ってきます。テクスチャーと上下関係が逆になるので、invYMouseXY を作っています。後は、マウスカーソルの位置と現在打ち込もうとしている座標が近いほど高いインテンシティーを設定します。最後に、fMouseButtonStateLeft とかけ算することで、左クリック時のみ色を塗ることが可能になります。

あとは、これをディスプレーに表示します。最初のやり方で、Screen-Aligned Quad をもう一度作ります。そして、描画しているパスをコピーして貼り付け、名前を Display に変えてください。

Textureを追加します。

  1. Display を右クリック
  2. Add Texture Object
  3. Texture0 の中の baseMap を右クリック
  4. Reference Node -> renderTexture

後は、シェーダーを以下のように変更してください。

// vertex shader
varying vec2  texCoord;
uniform vec2 fViewportDimensions;

void main(void)
{
   gl_Position = vec4( gl_Vertex.xy, 0.0, 1.0 );
   gl_Position = sign( gl_Position );
    
   // Texture coordinate for screen aligned (in correct range):
   texCoord = (gl_Position.xy + vec2( 1.0 ) ) / vec2( 2.0 ) * ( fViewportDimensions / vec2( 512.0 ) );
}
// fragment shader
uniform sampler2D Texture0;

varying vec2 texCoord;

void main(void)
{
    gl_FragColor = texture2D( Texture0, texCoord );
}

Render Monkey 1.6 でしょうもないテクスチャービューアを作ろう

Render Monkey Paint


※添付画像は次の「ペイントツール」の画像です。あしからず。

FX Composer, Render Monkey 等シェーダーツールがたくさんあるのですが、どうも参考資料が少ないので、遊んでみます。Render Monkey を選んだ理由は、単に僕の家のビデオカードATI であることと、よりツールっぽい(書くべきコード量が少なく、シェーダーを書く以外はほとんどマウス操作)ところです。レンダーターゲットのサポートはRender Monkeyの方が早かったので、こちらの方が使い慣れているというのも大きいです。どちらのツールにも言えることなのですが、見た目と裏腹に、使い方がとっても「素朴」です。MSあたりがXNAでとんでもないツールを作ってくれることを密かに期待しています。

Render Monkey は ATI のツールです。ATIディベロッパーサイトに行って、ダウンロードしてきてください。DirectX9cが必要ですので、そっちのインストールもお忘れなく。

現在、皆さんに説明する気満点なのですが、ただ、「はてな」がしょうもないので、Render Monkeyのスクリーンショットを多用した説明ができません。

まずは、テクスチャーを表示します。お料理番組のように3分後の大根をとってくる感覚で、テクスチャーをすでに画面いっぱいに表示したエフェクトを取ってくることができます。

  1. ウインドウ右にある「Workspace」のEffect Workspaceを右クリック
  2. Add Effect Group -> Effect Group w/OpenGL Effect

これだけで、丸いオブジェクトが表示されるはずです。GL と書いたアイコンの Effect1 というエフェクトをさっさと削除してください。左クリックした後キーボードの「Delete」ボタンを押してください。いよいよテクスチャーを表示します。

  1. 先ほどできた Effect Group を右クリック
  2. Add Effect -> OpenGL -> Screen-Aligned Quad

やったー。これで終わりです。あとは、Screen-Aligned Quad の下にある base をいじればテクスチャーが編集差し替えも可能です。楽しんでください。ただ、これって、上下逆なんです。シェーダーを見てみましょう。右にあるWorkspaceツリービューにはScene-Aligned Quad 以下がこんな感じになっているはずです。

  • Screen-Aligned Quad
    • Single Pass
      • Vertex Program
      • Fragment Program

ここで Vertex Program をダブルクリックしてください。

varying vec2  texCoord;

void main(void)
{
   gl_Position = vec4( gl_Vertex.xy, 0.0, 1.0 );
   gl_Position = sign( gl_Position );
    
   // Texture coordinate for screen aligned (in correct range):
   texCoord = (vec2( gl_Position.x, - gl_Position.y ) + vec2( 1.0 ) ) / vec2( 2.0 );
}

gl_Position.y が逆だべさ。ということで、以下のように直します。

varying vec2  texCoord;

void main(void)
{
   gl_Position = vec4( gl_Vertex.xy, 0.0, 1.0 );
   gl_Position = sign( gl_Position );
    
   // Texture coordinate for screen aligned (in correct range):
   texCoord = (gl_Position.xy + vec2( 1.0 ) ) / vec2( 2.0 );
}

完。

最小の仕様、十分な品質

未熟だ。

アジャイル開発手法、特に XP の特徴である「未来に向けた設計をしない」というプラクティスをどうも、長い間誤解していたようだ。
「必要最小限の実装でとどめる」ことを信奉し始めたきっかけは、ずるずると締め切りなどお構いなしに、自分の作りたいものを作り続けて、それがじゃまされると烈火のごとく怒ったり、無口になったりする人を多く見たからだ。彼らは優秀なチームにも、そうではないチームにも存在して、至極もっともなことを言っては、「品質」だとか「こだわり」だとかいう言葉を振り回す。それは、言い訳にすぎない。特に大きなチームになると彼らは増える傾向にあるようだ。チーム全体が危機に陥っても、罪悪感など感じていないように見える。そのくせ、空気を読むことだけは上手で、黙々と作業を続けるふりをしている。本当は、彼らはエディターやUMLツールと向き合う時間よりも、Webブラウザと向き合う時間の方が長いのに。そして、うまくいかず追いつめられると「難しい」、「自分にはできない」と居直る。
彼らの気持ちはよくわかる。彼らは僕の心にかつて住んでいたし、未だに隠れて生き延びているからだ。だからこそ、僕は彼らが嫌いなんだと思う。納得いくまでものが作りたければ、世捨てでもしていつまでも一人でやっていればいい。予算があり、締め切りがあるのだ。「未来に向けた設計をしない」は自分に対する戒めでもある。

だが、これが裏目に出た。品質を生むのは実装量であり、時間であると思う。品質を生むには技量とそれを得るための労力が必要だ。何かの機能があって、それが最低限何とか使える程度で止める必要がある場合、僕はいつも悩んできた。ある時はその実装を指示し、別の時はそれを止めさせたりした。僕のこういった場合における指示はいつも一貫性を欠いていた。どこまでも作りすぎてはいけない。しかし、品質は確保しなければならない。それは、小さなクラスや関数でも言えることだし、開発用の小さなツールでも言えることだ。しかし、これらユーザーの目に触れないところは特に品質を犠牲にしていた。使いづらかったり、穴があったりしたのだ。

XPが言わんとする「未来に向けた設計をしない」の意図するところは、「最小の仕様、十分な品質」であると思う。事前にスパイクや設計を入念に行い、オンサイト顧客から要望をきちんと聞き、「最小限の仕様」を絞り込む。これによって作りすぎ防止を確保した上で、「見積もり」を行う。それをオンサイト顧客に提示し、オンサイト顧客がタスクの優先度を決定する。ユーザーによる要求があり、なおかつ作りすぎの懸念がなくなった状態で、「十分な品質」を実現する。

iPod shuffle はこの好例だと思う。

  • 最小の仕様
    • シャッフル再生しかない
    • ボタンは数個しかない
    • 液晶ディスプレーもない
  • 十分な品質
    • デザイン
    • 「シンプルさ」として売りにすらなっている少ないボタン
    • 軽さ
    • バッテリーの持ち

ターゲット層にとっての優先度をよく調べ、仕様を絞り込んだ上での品質の追求。おそらく、ユーザーにふれることのない関数、クラス、ツールにこれほどまでの品質は必要ないと考えられる。用途、場面、状況に応じた「十分な品質」が必要なのだと思う。「未来に向けた設計をしない」というプラクティスはそれを否定するものではない。

こんなことに、今頃気づいてしまった。どこかの本に書いてあるのだろうか?最近、この分野の勉強が足りないことに気づいた。

reallocエミュレート

http://cvs.sourceforge.jp/cgi-bin/viewcvs.cgi/gslib/GSLib/SandBox/Lua/Lua.cpp?rev=1.1&content-type=text/vnd.viewcvs-markup

の冒頭にある、my_realloc, my_free がそれにあたります。malloc を使って realloc を実装するにはどうするのか調べるために、作ってみました。コメントにある通り、VC++.net のヘルプを見て作ってみました。

あと、lua 本体ですが、面倒だったので本体全部をプロジェクトにぶっこんでしまいました。
先ほどの my_realloc, my_free を lua に呼び出させるには、

http://cvs.sourceforge.jp/cgi-bin/viewcvs.cgi/gslib/GSLib/SandBox/Lua/lua/lmem.c?rev=1.1&content-type=text/vnd.viewcvs-markup

にあるように、l_realloc, l_free を強引に書き換えてしまえば動くみたいです。#ifndef l_free とあるので、強引な書き換えなしにもっとエレガントにうまく行きそうですが、面倒だったので、これでやめておきます。

これで、lua のメモリ確保は制御下におくことができました。