頭と尻尾はくれてやる!

パソコンおやじのiPhoneアプリ・サイト作成・運営日記


Metalのステンシルバッファでマスクしてみる

Metalでステンシルバッファを使ってみようとしたのよ。Metalではもちろん、OpenGLでも使ったことなかったのでなかなか苦しんでるところだけどとりあえず最低限の動作をさせてみようとしたんだ。
わかりやすそうなところで、マスクをやってみたのよ。



↑これはいつも通りの(?)ロボット、もう一つは平面を用意してロボットの周りを回る。これはまだステンシルバッファは使っていなくて単に両方描いてるだけね。
この平面の部分のみロボットを描く、みたいなことをやってみる。

毎回の描画ループではこんな感じ :
1.ステンシルバッファの値は0にクリア
2.平面の描画時はステンシルバッファに128を書き込む
3.ロボットの描画時はステンシルバッファの値をみて128なら表示する

その前に下準備をせねば、、、

下準備(1) MTLRenderPassDescriptor オブジェクトの設定

ステンシルバッファ用のメモリ確保などをしておくよ。コードはこんな感じで。

-(void)setupRenderPassDescriptor
 {
    self.renderPassDescriptor = [MTLRenderPassDescriptor renderPassDescriptor];
    id  drawable = [_view.metalLayer nextDrawable];

    MTLTextureDescriptor *textureDescriptor = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatStencil8
                                                                                        width: drawable.texture.width
                                                                                       height: drawable.texture.height
                                                                                    mipmapped: NO];
    textureDescriptor.textureType =  MTLTextureType2D;
    id  stencilTexture = [device newTextureWithDescriptor: textureDescriptor];

    MTLRenderPassStencilAttachmentDescriptor *stencilAttachment = _renderPassDescriptor.stencilAttachment;
    stencilAttachment.texture = stencilTexture;
    stencilAttachment.loadAction = MTLLoadActionClear;
    stencilAttachment.storeAction = MTLStoreActionDontCare;
    stencilAttachment.clearStencil = 0;
}
_viewってのは表示するUIViewオブジェクトで
@property(nonatomic , weak) CAMetalLayer *metalLayer;
ってプロパティを持ってる(AppleのMetal関連のサンプルコードのままだけど)。

なるほど、ステンシルバッファもデプスバッファみたいにサイズ指定して ’テクスチャ’ として作るのかあ!と地味に感動したわ。上のコードだとstencilTextureはローカル変数だけど、インスタンス変数にしといたらこれを他のシェーダに突っ込んで、、、とかできるんだろうな。
ところで、デプスバッファの方だけど、今回は平面とロボットの前後関係は問わないとしてるのでデプスバッファのpixel formatは MTLPixelFormatInvalid ってことで。こうしておくと自分でメモリ確保なんかしなくてもよきに計らってくれるし、ロボットはちゃんと表示される。

下準備(2) 平面オブジェクトの設定

下のコードは平面のオブジェクトまわりの設定ね。
{
    MTLRenderPipelineDescriptor *pipelineStateDescriptor = [[MTLRenderPipelineDescriptor alloc] init] ;
    pipelineStateDescriptor.label = @“pipeline for plane";
    pipelineStateDescriptor.sampleCount = 1;
    pipelineStateDescriptor.vertexFunction = vertexProgram;
    pipelineStateDescriptor.fragmentFunction = nil;
    pipelineStateDescriptor.colorAttachments[0].pixelFormat = MTLPixelFormatBGRA8Unorm;
    //pipelineStateDescriptor.colorAttachments[0].writeMask = MTLColorWriteMaskNone;
    pipelineStateDescriptor.depthAttachmentPixelFormat = _depthPixelFormat;
    pipelineStateDescriptor.stencilAttachmentPixelFormat = _stencilPixelFormat;

    NSError *pipelineError;
    self.renderPipelineState = [_device newRenderPipelineStateWithDescriptor:pipelineStateDescriptor   error: &pipelineError];
}
長いので半分に分けとくわ。
最初はMTLRenderPipelineStateオブジェクトの設定。面白いのはフラグメントシェーダがnilでいいんだわ。平面は画面に表示させる必要がないので MTLColorWriteMaskNone としてるけど、これなくても表示されないな。マスク用オブジェクトを表示させないない方法は他にもあるみたい(デプステストで必ず失敗させるとか)。

depth,stencilのpixel formatが引数になってるけど、下準備(1)でセットしたMTLRenderPassDescriptor オブジェクトの内容と合わせないと実行時にエラーが出ると思う。

下はこの続きね。
{
    MTLStencilDescriptor *stencilDescriptor = [[MTLStencilDescriptor alloc] init];
    stencilDescriptor.stencilCompareFunction = MTLCompareFunctionAlways;
    stencilDescriptor.depthStencilPassOperation = MTLStencilOperationReplace;

    MTLDepthStencilDescriptor *desc = [[MTLDepthStencilDescriptor alloc] init];//—(A)
    desc.frontFaceStencil = stencilDescriptor;
    desc.backFaceStencil = stencilDescriptor;
    self.depthStencilState = [_device newDepthStencilStateWithDescriptor:desc];
}
上のコードの(A)の部分なんだけど MTLDepthStencilDescriptor オブジェクトの設定で depthCompareFunction プロパティを設定しないとデフォルトで MTLCompareFunctionAlways なので、depthテストは常にパスすることになるよね。
MTLStencilDescriptor オブジェクトの設定で stencilCompareFunction プロパティを MTLCompareFunctionAlways にしとけば平面部分についてはstencilテストも常にパスすることになる。

depthStencilPassOperation プロパティ(これはdepthテストもstencilテストの両方がパスした時の挙動を設定する)これを MTLStencilOperationReplace にする。この〜Replaceってのはステンシルバッファの値を参照値に置き換えるのよ。平面がある部分のdepthテストもstencilテストも常にパスする設定なので、結局平面のある部分のステンシルバッファの値が参照値に書き換えられることになる(平面のないところは0のママ)。

下準備(3) ロボットオブジェクトの設定

次に、ロボットの方ね。ロボットの方も平面に似たような設定するんだけど、ロボットはステンシルバッファの値を見て参照値と同じなら表示するような設定にしてる。
{
    MTLStencilDescriptor *stencilDescriptor = [[MTLStencilDescriptor alloc] init];
    stencilDescriptor.stencilCompareFunction = MTLCompareFunctionEqual;
    stencilDescriptor.stencilFailureOperation = MTLStencilOperationKeep;
    stencilDescriptor.depthFailureOperation = MTLStencilOperationKeep;
    stencilDescriptor.depthStencilPassOperation = MTLStencilOperationKeep;

    MTLDepthStencilDescriptor *desc = [[MTLDepthStencilDescriptor alloc] init];
    desc.depthCompareFunction = MTLCompareFunctionLess;
    desc.depthWriteEnabled = YES;
    desc.frontFaceStencil = stencilDescriptor;
    desc.backFaceStencil = stencilDescriptor;

    self.depthStencilState = [_device newDepthStencilStateWithDescriptor:desc];
}
stencilCompareFunction プロパティを MTLCompareFunctionEqual としてるのでロボットを描こうとした部分のステンシルバッファの値と参照値が同じならパスする、という設定。このstencilテストで結果がどうあれステンシルバッファの値を変更する必要はないから〜Operationはどれも〜Keepになってる。
depthテストはいつも通り。


下準備は以上のような感じ。毎フレーム、コールされる部分で今までと違うのは参照値を教えてあげることくらいかな。
{
    [renderEncoder setStencilReferenceValue: 128];
}
こんな感じでセットすることができる。参照値としてるけど、基準値とか比較値とか見るところによっていろいろだったりする。


以上の結果がこんな感じ。



わかりにくいけど、まあ意図通り動いてる。ふー。


参考になったページ
wgld.org | WebGL: ステンシルバッファ |
Tsurumura Seisakusho: OpenGl ステンシルバッファ
ステンシル バッファー テクニック (Direct3D 9)


煙の粒子の運動方程式

particleで煙っぽいのを表現してみようとしてるんだけど、噴出口みたいなのがあってそこから出た煙は上部へ、、、っていうのを実現するにはどうしたらいいんだろ?
粒子は最初は噴出時の方向で飛んで行くものの水平成分は減衰して最後は上方向のみになる、ってイメージだよな。
ということでparticleの運動は速度に比例する抵抗を受けるとしてやってみた。

放物運動に関する運動方程式の解説
↑これの [E] 速度に比例する空気抵抗を持つ放物運動 って部分。速度に比例する場合だと式を出しておけるみたいよ(実際は速度の二乗の方に近いみたいだけど、解を求めるのがえらい大変らしい)。

上のリンク先のは平面だけどz方向も考慮して結局座標はこんな感じの式になった。

粒子の運動方程式の解

U0,V0,W0は各方向の初速、Vtは終端速度、gは重力加速度だったのでgだけど、実際には煙に働く上向きの加速度。
いろいろやり方あるだろうけど、シェーダ内で計算があまり重複しない方がいいかなってことでこのp1からp5の係数をparticleごとにシェーダに送ってみた。なんだか面倒だよね、、、きっともっと上手い方法があるんじゃないかな、って気がしてならないけど、ともかくこんな感じになったんだ。



これで1000個のparticleだけど、iPhone 6だと普通に60FPSで表示されてる。さすがだな。
パラメータがたくさんあるのでテキトウな感じの煙だけど、いろいろと調整すればいろんな煙を表現できるんじゃないかなあ。


数式を画像出力してくれるサービスを使ってみた

数式画像作成ツール - 物理のかぎしっぽ
↑ちょっと数式をブログに書こうとして何かいいWebサービスないかな、と探して見つけたサイトがこちら。

え?何?LaTeXって何?
って状態なので参考にしてみたページがこのあたり :
LaTeX - コマンド一覧

粒子の運動方程式

↑できたpng画像がこちら。左寄せにする方法がわからなかったのが残念だけど綺麗なもんだね。


\ x(t) = p_1(1-e^{p_2t} ) \
\ y(t) = p_3(1-e^{p_2t} )-p_5t \
\ z(t) = p_4(1-e^{p_2t} ) \
\ \
\ p1 = \frac{V_t U_0}{g} \
\ p2 = -\frac{g}{V_t} \
\ p3 = -\frac{V_t(V_0+V_t)}{g} \
\ p4 = \frac{V_t W_0}{g} \
\ p5 = V_t \

↑メモがてら残しておくけど書き込んだのはこんなの。

便利なサービスがあるもんだな。ありがたや、ありがたや。


Metalのブレンディング

前にparticleで雪っぽいものをやってみたんだけど、次にMetalで煙を表現してみようと思ったんだよ。
煙だと雪と違って発火点のような起点があって、、、みたいな話もあるんだけど、今回はそれ以前のブレンディングの話。

というのもいじっててなぜかフラグメントシェーダ内で色のアルファ値をゼロにしてても透明にならないってことがあって何が悪いのかな、、、と調べてたのよ。
WebGLだけど
wgld.org | WebGL: ブレンドファクター |
↑ここの話はとても参考になったわ。

具体的にはコードはこんな感じに。ええ、Objective-Cです。
MTLRenderPipelineDescriptor のオブジェクトが pipelineStateDescriptor ね。
    pipelineStateDescriptor.colorAttachments[0].blendingEnabled = YES;
    pipelineStateDescriptor.colorAttachments[0].rgbBlendOperation = MTLBlendOperationAdd;
    pipelineStateDescriptor.colorAttachments[0].alphaBlendOperation =MTLBlendOperationAdd;
    pipelineStateDescriptor.colorAttachments[0].sourceRGBBlendFactor = MTLBlendFactorSourceAlpha;//MTLBlendFactorOne;
    pipelineStateDescriptor.colorAttachments[0].sourceAlphaBlendFactor = MTLBlendFactorOne;
    pipelineStateDescriptor.colorAttachments[0].destinationRGBBlendFactor = MTLBlendFactorOneMinusSourceAlpha;
    pipelineStateDescriptor.colorAttachments[0].destinationAlphaBlendFactor = MTLBlendFactorOne;//MTLBlendFactorOneMinusSourceAlpha;
二箇所ほどコメントで消してるのがあるけど消す前がAPPLEのサンプルコード MetalShaderShowcase のparticle関連部分での設定。これだとアルファ値ゼロでも透明にならなかったので先のリンク先記事を参考にこのように修正、かつ、シェーダ内でRGBとアルファ値を別々に計算。その結果がこんなの。



いやこれまだ途中で、particleは単に落下してるだけなんだけど、すでに噴出したparticleは現在の起点の位置に影響しない、というのは一応できてる。
次は煙らしく上へ行くようにしないと。


Metalで二つのshaderを使う

Metalのお話ね。あ、昨日の早朝のWWDC keynoteでMetal for OS Xなるものが発表されたみたいだけど、ここでやってるのはiOSね。

頭と尻尾はくれてやる! / SwiftでBulletはやめにする
↑そんなわけでSwiftではなくObjective-CでMetalを扱う練習をやってるんだけど、ようやくSwiftでやってたことができてきたかなーってとこ。


↑例えばこれはPhong shader。


で、本題なんだけど、上のPhong shaderで描いたロボットのところに雪を降らせてみるってのをやろうとしたのよ。
雪ってのはparticleを使う、、、ってのはAPPLEのサンプルコードもあるしまあOKなのよ。


↑particleを使うとこんな感じ。ちなみにこれはまだSwiftだったけど。


このparticleを使う場合のshaderとロボット用のPhong shaderは当然別なので MTLRenderPipelineDescriptor オブジェクトもそれぞれ作ることになる。従ってここから作られる MTLRenderPipelineState オブジェクトも二つある。でも、MTLRenderPassDescriptor オブジェクトは一つでいい(最初これも二つ作って変なことになってたわ)。




↑ということで無事表示されたロボットと雪。

二つのshaderを扱う場合、MetalDeferredLightingってAPPLEのサンプルコードが超絶参考になったわ。


  TopPage  



Copyright ©頭と尻尾はくれてやる!. Powered by FC2 Blog. Template by eriraha.