おもちゃラボ

Unityで遊びを作ってます

【ARKit】ARでAmbient Occlusion(AO)を使う

ARKitやVuforiaなど、ARのアプリケーションでアンビエントオクルージョン(Ambient Occlusion)の効果を使いたくても、ポストエフェクトが使えなかったり、処理負荷が大きくなりすぎる傾向があります。

ここでは、ARKItやVuforiadでも簡単にAOの効果を実現する方法を紹介します。

f:id:nn_hokuson:20190709201717j:plain:w650

モデルにLightmap Staticを設定する

ポストエフェクトを使わずにAOを表現するにはライトマップをベイクするのが一般的です。ライトマップをベイクするには、インスペクタから「Static」に指定します。

ただ、単純に「Static」にチェックしてしまうと、「動かないオブジェクト」とみなされ、ARで表示でされなくなってしまいます。

そこで「Static▼」の「▼」をクリックして「Lightmap Static」を指定します。これでライトマップに対してのみ静的なオブジェクトとみなされます。

f:id:nn_hokuson:20190709200112j:plain:w350

ライトをベイクする

続いてAmbient Occlusionの設定とライトマップのベイクを行います。

まずはヒエラルキーウインドウからDirectional Lightを選択して、インスペクタからModeを「Baked」または「Mixed」に設定します。Bakedはライトは無視してベイクしたテクスチャのみを使用します。一方、Mixedは非Staticオブジェクトに対しては通常通りライティングします。

また、Shadow Typeは「Soft Shadows」になっていることを確認しておきましょう。Shadow Typeが設定されていないと、正しく影の計算が行われません。

f:id:nn_hokuson:20190709200253p:plain:w350

次に「Window」→「Rendering」→「Lighting Settings」を選択して、Lightingのパネルを開きます。「Ambient Occlusion」にチェックを入れて、「Indirect Contribution」と「Direct Contribution」を3程度に設定してみてください。

f:id:nn_hokuson:20190709155623j:plain:w400

Ambient Occlusionの設定項目であるDirectとIndirectについては、次の記事も合わせて参考にしてみてください。

nn-hokuson.hatenablog.com

最後にLightingパネルの一番下にある「Generate Lighting」ボタンをクリックしてベイクを開始します。1分程度でベイクが終了し、AOの効果がかかったモデルが表示されます。

f:id:nn_hokuson:20190709155654j:plain:w400

エラーが出る場合

次のように、「Mesh doesn't have albed UVs」や「Mesh doesn't have UV's suitable for lightmapping」といったエラーが出る場合、モデルに正しくUV座標が設定されていない可能性があります。

f:id:nn_hokuson:20190709201427p:plain

BlenderやMayaなど3DCGソフトを使ってUV展開を行い、正しくUV座標を設定してください。BlenderでUV展開をする方法は次の記事で紹介しています。

nn-hokuson.hatenablog.com

【Unity】広重のような雨のエフェクトを作る

歌川広重の描いたような雨をUnityで降らせる方法を紹介します。地面に雨粒があたったときは、そこから波紋が広がるようにします。完成版は↓こんなかんじです〜

ちなみに歌川広重の超有名な雨の絵はこんな感じ。「名所江戸百景 大はしあたけの夕立」ってやつです。臨場感というか質感がすごいですね。

f:id:nn_hokuson:20190704210353j:plain:w300 
出展:wikipedia

ここでは次の流れで雨のエフェクト作っていきます。まずは雨粒が落下するパーティクルを作成し、次に波紋のパーティクルを作ります。最後に地面に雨粒があたったときに波紋が広がるようにします。

雨粒を作る

Unityで雨を作るにはParticle Systemを使います。生成するパーティクルには次のテクスチャを使用します。

f:id:nn_hokuson:20190704211549p:plain:w150

まずはパーティクルに使うマテリアルを作ります。プロジェクトウィンドウで右クリックして、「Create」→「Material」を選択してください。名前はrainにしておきます。

f:id:nn_hokuson:20190704203640p:plain:w200

次に作成したマテリアルを選択して、Shaderを「Particles/Standard Unlit」に設定、Rendering Modeは「Fade」、Albedoにはrainのテクスチャをセットします。

f:id:nn_hokuson:20190704203854p:plain:w400

ヒエラルキーウインドウで「Create」→「Effect」→「Particle System」を選択してパーティクルを作成します。雨のテクスチャが表示されるように[Render]のMaterialにrainマテリアルをセットしてください。また、雨が垂直に表示されるようにRender Modeは「Vertical Billboard」を選択します。

f:id:nn_hokuson:20190704204016p:plain:w400

雨が振り続けるように[Emission]のRate over Timeを「300」に設定します。また、雨を広範囲に降らせるため、[Shape]ののShapeを「Circle」、Radiusを「14」に設定します。

f:id:nn_hokuson:20190704204216p:plain:w300 f:id:nn_hokuson:20190704204420p:plain:w300

まだ雨粒が外側に広がって全然雨っぽくないですね・・・

f:id:nn_hokuson:20190704204257j:plain:w500

雨を下方向に降らせるための設定をしていきます。DurationとStart Lifetimeは「2」に設定します。雨粒がに円の外周に向かって広がらないようにStart Speedを「0」にします。雨粒を縦方向に伸ばすために3D Start Sizeを(1,3,1)にします。最後に雨粒が下方向に落下するようにGravity Modifireを「1」に設定します。

f:id:nn_hokuson:20190704204601p:plain:w400

ぐっと雨っぽくなりました。

f:id:nn_hokuson:20190704204641j:plain:w500

次は地面を設置して地面にあたったときは雨粒が消えるようにしましょう。ヒエラルキーウインドウでPlaneを生成して、いい感じに配置してください。

f:id:nn_hokuson:20190704204954j:plain:w500

パーティクルが地面にあたったら消えるようにします。[Collision]のTypeを「World」に設定し、Lifetime Lossを「1」にします。

f:id:nn_hokuson:20190704204846p:plain:w400

これで地面に当たると雨のパーティクルが消えるようになりました。

 f:id:nn_hokuson:20190704205007j:plain:w300

波紋を作る

雨粒が地面にあたったときに広がる波紋のパーティクルを作ります。波紋のパーティクルは1つだけ作っておいて、雨粒が地面にあたったときに生成されるように、雨粒のパーティクルのSub Emitterに設定します。

波紋には次の円のテクスチャを使用しました。

f:id:nn_hokuson:20190704211721p:plain:w150

まずは波紋に使うテクスチャをインポートして、雨粒と同じように波紋のマテリアルを作成してください。

f:id:nn_hokuson:20190704205111p:plain:w200

波紋用のパーティクルを新しく作成し、そこに波紋のマテリアルを設定します。波紋は地面と平行に広げたいのでRender Modeは「Horizontal Billboard」に設定します。

f:id:nn_hokuson:20190704205153p:plain:w400

地面から波紋のパーティクルが湧き出てきます・・・

f:id:nn_hokuson:20190704205213p:plain:w400

波紋はすぐ消えるようにDurationとStart Lifetimeを「0.8」に設定します。また波紋が上方向に広がらないようにStart Speedを「0」にします。

f:id:nn_hokuson:20190704205348p:plain:w400

波紋の生成個数が1個になるように[Emission]のRate over Timeを「0」、BurstsのCountを「1」に設定します。また、常に中心にパーティクルが生成されるように[Shape]のRadiusは「0.01」に設定してください。

f:id:nn_hokuson:20190704205455p:plain:w300 f:id:nn_hokuson:20190704205523p:plain:w300

波紋が徐々に広がって消えるようにします。波紋を広げるには[Size over Lifetime]を線形に増加するグラフに設定して、sizeを「3」に設定しました。

f:id:nn_hokuson:20190704205606p:plain:w400

また、波紋が徐々に消えるようにするため、[Color over Lifetime]で右端の透明度を「0」に設定します。

f:id:nn_hokuson:20190704205551p:plain:w400

これで徐々に透明になりながら広がる波紋ができました。

f:id:nn_hokuson:20190704205628g:plain:w400

波紋のパーティクルをSub Emitterに指定

最後に雨粒が地面にあたったときに波紋が生成されるように、波紋のパーティクルを雨のパーティクルのSub Emitterに設定します。

雨のパーティクルの[Sub Emitter]のドロップダウンリストを「Collision」に変更して、右の欄に波紋のパーティクルを設定します。

f:id:nn_hokuson:20190704205754p:plain:w400

波紋のパーティクルが雨粒パーティクルの子要素になっていればOKです。

f:id:nn_hokuson:20190704205902p:plain:w250

これで雨粒が地面にあたったら波紋が表示されるようになりました。実行して確かめてみてください。

f:id:nn_hokuson:20190704210017j:plain:w600

【Unityシェーダ】テクスチャを円形にトランジションさせる

uGUIのImageを使うと、画像のトランジションを簡単に実現することができます。この記事では円形のトランジションを通常のテクスチャやスプライトでも行えるようなシェーダを作ります。

円に沿って表示する方法

テクスチャの一部を表示するにはdissolveシェーダでやったのと同じ方法を使います。

nn-hokuson.hatenablog.com

dissolveシェーダではグレースケールのノイズ画像を用意し、しきい値を超えているところは描画、しきい値以下の部分はdiscardします。そのしきい値を変えることで徐々に消えていく動きを実現しました。

f:id:nn_hokuson:20190702184349j:plain:w400

今回はノイズ画像ではなく、次のようなグレースケールの画像を用意します。この画像は0度のところが黒色で時計回りに明るくなり、360度のところで白色になります。
f:id:nn_hokuson:20190702184430p:plain:w300
この画像をしきい値で二値化します。例えば、明度が0.1未満の部分は白、明度が0.1以上の部分は黒色とすると次のような画像になります。
f:id:nn_hokuson:20190702184438p:plain:w350
このしきい値を徐々に変化させて行くことで、扇状に白色の部分が増えていきます。
f:id:nn_hokuson:20190702184820p:plain:w550
作成した画像をマスクとして使います。マスクの白色部分はテクスチャを表示し、黒色部分のピクセルは破棄することでuGUIのように環状にテクスチャを表示できるようになります。
f:id:nn_hokuson:20190702185555p:plain:w550

円形切り取りシェーダ

それでは円形トランジションシェーダを作っていきましょう。プロジェクトウィンドウで右クリックして「Create」→「Shader」→「Unlit Shader」を選択し、ファイル名を「RadialFIll」にします。

また、作成したシェーダを右クリックして「Create」→「Material」を選択し、シェーダをアタッチしたマテリアルを作成します。
f:id:nn_hokuson:20190702184640p:plain:w200
ファイルが作成できたら、次のプログラムを入力してください。

Shader "Unlit/RadialFIll"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _Threshold ("Threshold", Range(0,1)) = 0
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            // make fog work
            #pragma multi_compile_fog

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                UNITY_FOG_COORDS(1)
                float4 vertex : SV_POSITION;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
            float _Threshold;

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                UNITY_TRANSFER_FOG(o,o.vertex);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {                
                fixed4 col = tex2D(_MainTex, i.uv);

                // 平面上の角度を求める
                fixed2 uv = 0.5 - i.uv;
                float rad = atan2(uv.y, uv.x);                
                
                // -π〜πを0〜1の値に変換する
                float value = (rad + 3.14)/(2 * 3.14);

                // しきい値以下なら表示しない
                if ( value < _Threshold ) discard;

                return value;
            }
            ENDCG
        }
    }
}

主にフラグメントシェーダのプログラムを修正しています。まずはテクスチャの中心を原点にするためにuv座標を0.5引いています。これによりuv座標は-0.5から0.5の値で変化します。

f:id:nn_hokuson:20190702184711p:plain:w400

次に平面上の座標から角度を求め、その角度にしたがって明度を0〜1に設定します。平面上の角度はatan2関数によって求められます。atan2は-π〜πの範囲の値を返すため、これを明度になおすため、0〜1にスケールします

次にスケールした値がしきい値よりも大きい場合には描画、小さい場合にはdiscardしています。しきい値はインスペクタから入力できるようにPropertiesブロックの中で_Threshold変数を記述しています。またvertexシェーダの上でも宣言するのを忘れないようにしてください。

スタート位置や回転方向を変える

uGUIのFillを使えば、どこから表示をスタートするのか(Top, Bottom, Left, Right)を選択することができます。これを実現するにはatan2に指定するuv座標を次のように設定します。

スタートの角度 指定方法
0度(右) atan2(uv.y, uv.x)
90度(上) atan2(-uv.x, uv.y)
180度(左) atan2(-uv.y, -uv.x)
270度(下) atan2(uv.x, -uv.y)

f:id:nn_hokuson:20190702184735g:plain

また、時計周りで表示させたい場合はatan2の第一引数の正負をひっくり返すだけです。具体的には次のように指定してください。

0度(右) atan2(-uv.y, uv.x)
90度(上) atan2(uv.x, uv.y)
180度(左) atan2(uv.y, -uv.x)
270度(下) atan2(-uv.x, -uv.y)

実行結果

シェーダプログラムができたら保存して、マテリアルを平面オブジェクトにアタッチしてください。インスペクタからThresholdのスライダを動かすと、それに合わせてテクスチャが円形にトランジションします。
f:id:nn_hokuson:20190702185903j:plain:w500