おもちゃラボ

Unityで遊びを作ってます

【Unityシェーダ入門】画面をセピア色にするポストエフェクトを作る

今回はスクリーンをセピア調にするポストエフェクトシェーダを作ります。ちなみに・・・Wikipediaによるとセピアとはイカスミのことらしいです。

セピア(sepia)とは、イカ墨のこと。また、イカ墨由来の黒褐色をも意味する。かつてモノクロ写真などにこの色のインクが用いられたために、古い写真は褪色し淡い褐色になった。

ゲーム画面をセピア色に変換(ポストエフェクト)した結果は次のようになります。

f:id:nn_hokuson:20170508224217j:plain

ポストエフェクトを使って画面をグレースケール変換する方法は、こちらの記事で紹介しているので参考にしてみて下さい。

nn-hokuson.hatenablog.com

描画する画像をフックする

カメラに映った画像を取得してポストエフェクトをかけるために、画面をレンダリングする途中で画像をフックして画像処理します。

f:id:nn_hokuson:20170508233734p:plain

プロジェクトビューで「PostEffect.cs」というファイルを作成し、次のプログラムを入力してください。

using UnityEngine;
using System.Collections;

public class PostEffect : MonoBehaviour {

	public Material sepia;

	void OnRenderImage(RenderTexture src, RenderTexture dest)
	{
		Graphics.Blit (src, dest, sepia);
	}
}

上記の「ポストエフェクトで画面をグレースケール化する」のページでも説明していますが、OnRenderImageメソッドはレンダリングが完了した後に呼び出されるメソッドで、この中でBlitメソッドを使ってポストエフェクトをかけています。Blitメソッドはsrc画像に第三引数で指定したポストエフェクトをかけてdest画像に書き込みます。

スクリプトが保存できたら、PostEffect.csをカメラオブジェクトにアタッチしてください。

f:id:nn_hokuson:20170508230018j:plain:w550

Unityでセピアシェーダとマテリアルを作る

画面をセピア色にするためのシェーダを作ります。プロジェクトビューで「右クリック」→「Create」→「Shader」→「Standard Surface Shader」を選択し、作成したファイル名をSepiaに変更します。続いてこのシェーダファイルをアタッチするマテリアルも作成しましょう。monoToneのシェーダをを選択した状態で「右クリック」→「Create」→「Material」を選択して「Custom_Sepia」を作成します。

f:id:nn_hokuson:20170508225601p:plain:w180
Sepia.shaderを開いて次のスクリプトを入力してください。

Shader "Custom/sepia" {
    Properties {
    	_Darkness("Dark", Range(0, 0.1)) = 0.04
    	_Strength("Strength", Range(0.05, 0.15)) = 0.05
        _MainTex("MainTex", 2D) = ""{}
    }

    SubShader {
        Pass {
            CGPROGRAM

            #include "UnityCG.cginc"

            #pragma vertex vert_img
            #pragma fragment frag

            sampler2D _MainTex;
            half _Darkness;
            half _Strength;

            fixed4 frag(v2f_img i) : COLOR {
                fixed4 c = tex2D(_MainTex, i.uv);
                half gray = c.r * 0.3 + c.g * 0.6 + c.b * 0.1 - _Darkness;
                gray = ( gray < 0 ) ? 0 : gray;

                half R = gray + _Strength;
                half B = gray - _Strength;

                R = ( R > 1.0 ) ? 1.0 : R;
                B = ( B < 0 ) ? 0 : B;
                c.rgb = fixed3(R, gray, B);
                return c;
            }

            ENDCG
        }
    }
}

上記のプログラムでははセピア変換処理はフラグメントシェーダ(fragメソッドの中)で行っています。このフラグメントシェーダでは、次の3ステップで画像をセピア色に変換しています。

  1. 画像のグレースケール変換
  2. 明度を修正
  3. グレースケール値から赤成分を足し、青成分を減らす

ステップ2で使用する画像を暗くする量(Darkness)と、Step3で使用するセピア色の強さ(Strength)はUnityのインスペクタからも変更できるように、プロパティの部分で宣言しています。

グレースケール化に関しては下記のページを参考にして下さい。

nn-hokuson.hatenablog.com

画像をセピア色に変換するアルゴリズムは、各ピクセルの赤成分を足して、青成分を引くだけです。数式としては非常に簡単ですが、案外それっぽい見た目になりますよ〜!

カメラにセピア用マテリアルをセットする

作成したマテリアルを、カメラにアタッチしたPostEffectスクリプトのSepia欄にドラッグ&ドロップします。これで、画面をセピア色にするポストエフェクトをかける準備ができました。

f:id:nn_hokuson:20170508231327j:plain:w550

実行結果は次のようになります。ポストエフェクトの効果で画面がセピア色になっていますね。

f:id:nn_hokuson:20170508233259j:plain

先ほど、シェーダファイルで「画面の明度」と「セピア色の強さ」はプロパティに設定したので、インスペクタから調整可能です。

f:id:nn_hokuson:20170508233020p:plain:w300

パラメータを調節することで同じセピア色でも色々な表情を作れるので、自分のゲームに合った色合いを見つけてみてくださいね〜!

f:id:nn_hokuson:20170508232951j:plain

【Unityシェーダ入門】ステンシルバッファを使って隠れた部分を描く

プレイヤがゲームステージに配置されている障害物などの後ろ側に回り込むと、見えなくて操作しづらくなってしまいます。そこで、障害物によって隠れた部分は影だけ描いたりします。この記事では、Unityで隠れた部分を影で描画する方法を紹介します。

f:id:nn_hokuson:20170502184709p:plain:w550

今回の記事は次のようになります。

ステンシルバッファの原理

ステンシルバッファといっても、特殊なものではなく、一般的な画像と同じと考えて問題ありません。画像と違うのは、ステンシルバッファは画面に表示するためのものではなく、描画する際に使用する「裏方の値」ということです。

では、どのようにしてスプライトの裏側の画像を描画するかを説明します。

まずは画面上にブロックを描くと同時に、ステンシルバッファには「1」を書き込みます。これによりブロックの位置のステンシルバッファは「1」になります。

f:id:nn_hokuson:20170502181425p:plain:w550

続いて、ユニティちゃんを描画する際には、ステンシルバッファを参照します。描画しようとしている位置のステンシルバッファの値が「1」だった場合は、そのピクセルを黒く塗りつぶし、ステンシルバッファの値が「0」だった場合は、ユニティちゃんのテクスチャを描画します。

f:id:nn_hokuson:20170502182031p:plain:w550

実際にはステンシルバッファの値をif文で分けることは出来ないので、Stencilプロパティを利用して2回描画します。詳しくは下のプログラムを参考にしてみて下さい。

ステンシルバッファ用のシェーダを作る

ここでは、ブロック用のシェーダとユニティちゃん用のシェーダを作ります。プロジェクトビューで右クリックし、「Create」→「Shader」→「Standard Surface Shader」を選択、「Unitychan」という名前で保存します。同様に「Block」シェーダも作成しましょう。

f:id:nn_hokuson:20170502182746p:plain:w150

作成した「Unitychan」シェーダを選択した状態で、右クリック→「Create」→「Material」でシェーダに対応するマテリアル(Custom_Unitychan)を作ります。同様に「Custom_Block」も作りましょう。

f:id:nn_hokuson:20170502182757p:plain:w150

それぞれのマテリアルをユニティちゃんとブロックにアタッチして、それぞれのテクスチャをセットして下さい。

ブロックのシェーダを作る

では、まずBlockシェーダから見ていきましょう。

Shader "Unlit/Block"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            Stencil
            {
                Ref 1
                Comp Always
                Pass Replace
            }

            CGPROGRAM
            #pragma vertex vert_img
            #pragma fragment frag
            
            #include "UnityCG.cginc"

            sampler2D _MainTex;

            fixed4 frag (v2f_img i) : COLOR
            {
                fixed4 col = tex2D(_MainTex, i.uv);
                return col;
            }
            ENDCG
        }
    }
}

Blockシェーダの中では、ブロックを描画する位置のステンシルバッファを「1」で塗りつぶすように設定しています。ステンシルバッファの操作はStencilのプロパティを使用します。Stencilのプロパティの宣言は次のようになります。
 

Ref ステンシルに書き込む値
Comp 使用する比較関数
Pass 比較関数が真のときにの操作

 
ここでは、「Ref 1」としてステンシルバッファを「1」で塗りつぶすことを宣言しています。また、「Comp Always」「Pass Replace」としてブロックを描画する位置は常にステンシルバッファを強制的に「1」にすることを指定しています。

ブロックを描画し終わった時点でステンシルバッファは次のようになります。

f:id:nn_hokuson:20170502183109p:plain:w280

ユニティちゃんのシェーダを作る

続いて、ユニティちゃんのシェーダを作ります。

Shader "Unlit/Unitychan"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        Tags { "RenderType"="Transparent"
                "Queue" = "Transparent"
                "IgnoreProjector"="True" }
        LOD 100
        Blend SrcAlpha OneMinusSrcAlpha 
        ZWrite Off
        Cull off

        Pass
        {
            Stencil
            {
                Ref 1
                Comp Equal
            }

            CGPROGRAM
            #pragma vertex vert_img
            #pragma fragment frag
            #include "UnityCG.cginc"

            sampler2D _MainTex;

            fixed4 frag (v2f_img i) : SV_Target
            {
                float alpha = tex2D(_MainTex, i.uv).a;
                fixed4 col = fixed4(0,0,0,alpha);
                return col;
            }
            ENDCG
        }

        Pass
        {
            Stencil
            {
                Ref 0
                Comp Equal
            }

            CGPROGRAM
            #pragma vertex vert_img
            #pragma fragment frag
            #include "UnityCG.cginc"

            sampler2D _MainTex;

            fixed4 frag (v2f_img i) : SV_Target
            {
                fixed4 col = tex2D(_MainTex, i.uv);
                return col;
            }
            ENDCG
        }
    }
}

ユニティちゃんシェーダの方はSubShaderの中に2つのPassがあります。このPassブロック一つが「塗り」の1回に相当します。つまりPassが2つあるので、一度塗って、乾いてから更に色を乗せる二度塗りになります。

上にも書いたように、ステンシルバッファの値を元にif文で分けることは出来ません。次のように「影」と「テクスチャ」の描画を分けて2度塗りすることでオブジェクトに隠れた部分も描画します。

  1. 影の部分を描画する
  2. ユニティちゃんのテクスチャ部分を描画する

まずは1Pass目では「Ref 1」「Comp Equal」としてステンシルバッファが「1」の部分だけを描画します。ステンシルバッファが「1」の部分とはブロックを描画した部分でしたね。

f:id:nn_hokuson:20170502183818p:plain:w550

描画はフラグメントシェーダの中で行っています。1Pass目のフラグメントシェーダでは、テクスチャの透明度を参照して黒く塗りつぶしています。

続いて2Pass目では「Ref 0」「 Comp Equal」としてステンシルバッファが「0」の部分だけを描画します。ステンシルバッファが「1」の部分とはブロック以外の部分です。

f:id:nn_hokuson:20170502184134p:plain:w550

フラグメントシェーダの中では、単純にユニティちゃんのテクスチャを描画しているだけです。

このように、ステンシルバッファを使って2度塗りをすることで、オブジェクトの後ろに回り込んだときは影を描画する「背景透過シェーダ」を作ることが出来ました。

f:id:nn_hokuson:20170502184545g:plain

【Unityシェーダ入門】シェーダを使って世界に雪を降らせよう

季節はどんどん夏に向かっていっていますが・・・今回はシェーダを使ってUnityで雪を降らせる方法を紹介したいと思います。

f:id:nn_hokuson:20170425190313j:plain

雪を積もらせるアルゴリズム

深さのある雪を積もらせる場合は色々と計算が大変ですが、表面に薄っすらと積もらせるだけであれば、テクスチャの色を変えるだけでそれっぽく見せることができます。

こんな、新緑(?)の風景が・・・

f:id:nn_hokuson:20170425192310j:plain:w450

こうなります!

f:id:nn_hokuson:20170425192258j:plain:w450

ただ、テクスチャの色を変えると言っても、テクスチャを真っ白にするだけでは画面一面真っ白になってしまうだけです。

屋根の裏側や葉っぱの裏側には雪を積もらせないようにするには、オブジェクトの法線と雪が降ってくるベクトル(大体上向き)の内積を取ります。

この内積の値が1に近ければば面が上を向いていると判断してテクスチャの色を白色にします。そうでない部分は面が横や下方向を向いているので、オリジナルのテクスチャ色を使用します。

f:id:nn_hokuson:20170425190605p:plain:w550

雪のシェーダプログラム

まずは雪用のシェーダファイルと、それに対応するマテリアルを作ります。プロジェクトビューで右クリックし、「Create」→「Shader」→「Standard Surface Shader」を選択、「Snow」という名前で保存します。作成したSnowシェーダを選択した状態で、右クリック→「Create」→「Material」でシェーダに対応するマテリアル(Custom_Snow)を作ります。

f:id:nn_hokuson:20170425191007p:plain:w150

作成したマテリアルは3Dモデルにアタッチして、テクスチャを設定しておきましょう。作成する雪のシェーダプログラムは次のようになります。

Shader "Custom/Snow" {
    Properties {
        _Color ("Color", Color) = (1,1,1,1)
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
        _Glossiness ("Smoothness", Range(0,1)) = 0.5
        _Metallic ("Metallic", Range(0,1)) = 0.0
        _Snow("Snow", Range(0,2))= 0.0
    }
    SubShader {
        Tags { }
        LOD 200
        
        CGPROGRAM
        #pragma surface surf Standard fullforwardshadows 
        #pragma target 3.0
        sampler2D _MainTex;

        struct Input {
            float2 uv_MainTex;
            float3 worldNormal;
        };

        half _Glossiness;
        half _Metallic;
        fixed4 _Color;
        half _Snow;
    
        void surf (Input IN, inout SurfaceOutputStandard o) {
            float d = dot(IN.worldNormal, fixed3(0, 1, 0));
            fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
            fixed4 white = fixed4(1,1,1,1);
            c = lerp(c, white, d*_Snow);

            o.Albedo = c.rgb;
            o.Metallic = _Metallic;
            o.Smoothness = _Glossiness;
            o.Alpha = 1;
            }
        ENDCG
    }
    FallBack "Diffuse"
}

まずは雪の積もらせ方をインスペクタから設定できるようにPropertiesにSnowパラメータ(0.0〜2.0)を追加しています。

surfメソッドの中ではまず、上方向のベクトルと面の法線ベクトルの内積をとって、面の上向き具合を調べています。この値とインスペクタから入力したSnowパラメータをかけた値が「雪積もり度」になります。

この「雪積もり度」により、上向きの面ほど雪が積もって白くなります。次の図からも上向きの面(樽や窓の桟)に雪が積もっていることがわかると思います。

f:id:nn_hokuson:20170425191422j:plain

実際には「雪積もり度」の値をもとにして、「設定されたテクスチャの色」と「雪の白色」の間の値をlerpメソッドで補間します。当然「雪積もり度」が大きいほど白色に近づき、「雪積もり度」が小さいほどテクスチャの元来の色に近づきます。

f:id:nn_hokuson:20170425191819j:plain:w500

木の裏側や屋根の裏側は、法線ベクトルと雪ベクトルの内積がマイナスになるので雪がつもりません。

f:id:nn_hokuson:20170425191727j:plain:w500

実行結果と雪の表現の参考資料

作成したSnowシェーダを実行した結果がこちらになります。ここではインスペクタからSnowのパラメータを0.0→1.0に変化させています。

f:id:nn_hokuson:20170425193625g:plain

アサシンクリードでも似たような手法を使っているようです。

【GDC 2013】「アサシンクリードIII」グラフィックス技術解説 - GAME Watch

雪がハラハラ舞うようにするには次のページが参考になりそうです。

qiita.com

雪の上に足跡をつけるアルゴリズムは「いけにえと雪のセツナ」の資料や・・・・

「いけにえと雪のセツナ」グラフィック解説(第1回・フロー編) – 株式会社ロジカルビート

http://japan.unity3d.com/unite/unite2016/files/DAY2_1030_room2_Domae.pdf

こちらの記事が参考になりそうです〜

kikikiroku.session.jp

全然参考にならない参考資料

「その気になればね、砂漠に雪を降らすことだって、余裕でできるんですよ」

あまり関係ないですが、伊坂幸太郎の「砂漠」がとても好きです。雪つながりということで・・・笑

[asin:4101250251:detail]