身体に雪が降りかかったようなシェーダーを作るUnityで始めるシェーダー入門(終)

Unityを使ってシェーダーを作る方法を学ぶ連載。最終回は、3Dキャラクターの身体に雪が降りかかったような見栄えにするシェーダーを作成する。

» 2018年07月05日 05時00分 公開
[薬師寺国安PROJECT KySS]

 Unityを使ってシェーダーを作る方法を学ぶ連載「Unityで始めるシェーダー入門」。連載第1回ではシェーダーの概要と作り始めるまでの環境構築を紹介した。

 最終回は、3Dキャラクターの身体に雪が降りかかったような見栄えにするシェーダーを作成する。実際に作成するのは図1のようなものだ。Inspectorから雪の色や雪が身体に占める割合を指定できるようにしてみよう。

図1 3Dキャラクターの身体に降りかかった雪

 以前の連載でインポートしておいた、3Dキャラクター「Ethan」をScene上に1体配置しておこう。カメラは3Dキャラクターの位置を調整して図2のような表示になるようにする。

図2 Scene画面上に3Dキャラクターを1体配置した。Game画面でも確認できる

「SnowShader」の中身を確認

 新しいScene画面を開き、「Shaders」フォルダを選択して、マウスの右クリックで表示されるメニューから「Create」→「Shader」→「Standard Surface Shader」と選択する。新しく作成されたShaderには「SnowShader」と名前を付けておこう。

 次に、「Materials」フォルダを選択して、マウスの右クリックで表示されるメニューから「Create」→「Material」と選択する。新しく作成されたMaterialには「SnowMaterial」と名前を付けておこう。

 先ほど作成したSnowMaterialを選択して、Inspectorを表示させてみよう。Shaderの位置に先ほど作成したSnowShaderが「Custom/SnowShader」として関連付けられている。通常は、Standard Surface Shaderとして作成したシェーダーはCustomというグループ名付きで表示される。

 SnowShaderの中身は、これまでの連載通り、Standard Surface Shaderのコードが記述されている。このコードをリスト1のように変更する。

Shader "Custom/SnowShader" {
    Properties{
        _MainColor("Main Color", Color) = (1.0,1.0,1.0,1.0)
        _Snow("Level of snow", Range(1, -1)) = 1
        _SnowColor("Color of snow", Color) = (1.0,1.0,1.0,1.0)
        _SnowDirection("Direction of snow", Vector) = (0,1,0)
        _SnowDepth("Depth of snow", Range(0,1)) = 0
    }
        SubShader{
        Tags{ "RenderType" = "Opaque" }
        LOD 200
    
        CGPROGRAM
        #pragma surface surf Lambert vertex:vert
    
    
    float _Snow;
    float4 _SnowColor;
    float4 _MainColor;
    float4 _SnowDirection;
    float _SnowDepth;
    
    struct Input {
        float3 Dummy;
    };
    
    void vert(inout appdata_full v)
    {
        float4 sn = mul(UNITY_MATRIX_IT_MV, _SnowDirection);
        if (dot(v.normal, sn.xyz) >= _Snow)
            v.vertex.xyz += (sn.xyz + v.normal) * _SnowDepth * _Snow;
    }
    
    void surf(Input IN, inout SurfaceOutput o)
    {
        if (dot(WorldNormalVector(IN, o.Normal), _SnowDirection.xyz) >= _Snow)
            o.Albedo = _SnowColor.rgb;
        else
            o.Albedo = _MainColor;
            o.Alpha = 1;
    }
    ENDCG
    }
        FallBack "Diffuse"
}
リスト1 SnowShaderのコード

 3行目では、Mainのカラーを選択できるようにしている。

 4行目では、「Level of snow」をスライダーで指定できるようにしている。規定値は1だ。

 5行目では、Snowの色を選択できるようにしている。

 6行目では、Snowの方向を指定している。

 7行目では、Snowの深度をスライダーで指定できるようにしている。規定値は0だ。

 14行目では、#pragmaディレクティブに「vertex:関数名」を追加している。ここでは、関数名は「vert(ヴェール)」なので、「vertex:vert」と記述している。

 17〜21行目ではProperties内のプロパティ名の型を宣言している。vertやsurf関数内での使用を可能としている。

 23〜25行目のInput構造体は、今回は不要だが、指定をしていないとエラーになるので、ダミーのデータを宣言している。

 27〜32行目はvert関数の中身だ。vert関数では、変数「v」を、「appdata_full」構造体として指定している。appdata_full構造体は位置、接線、法線、4つのテクスチャ座標と色を表す。

 vert関数内の29行目では、mul関数で法線をワールド座標に変換している。「UNITY_MATRIX_IT_MV」は、シェーダーで使用可能な定義済みの値で、モデル(ワールド)からビューの逆マトリクス(IT = Inverse Transposed)を表している。つまり、逆マトリクスであるので、「ビュー座標系からワールド座標系に変換」していることになる。

 さらにmul関数では、法線をワールド座標に変換していることになる。変換したワールド座標を、_SnowDirectionに掛けて_SnowDirectionの位置を移動していると解釈すればいいだろう。

 マトリクスとは「行列」という意味になる。この辺りは数学的知識が必要で大変に複雑だ。ここでは、29行目の記述で、「ビュー座標系からワールド座標系に変換して、_SnowDirectionに掛けて位置を移動している」という程度に理解しておけばいいだろう。

 マトリクスの詳細について知りたい場合は、下記のURLを参照するといい。

 30〜31行目では、法線と移動した_SnowDirectionの位置の内積が、Level of snowのスライダーの値と等しいか、大きければ、Snowの深度とLevel of snowの値を乗算して、_SnowDirectionの頂点座標と法線座標を加算したものに乗算し続けている。

 34〜41行目のsurf関数では、雪の方向はワールド座標で表され、オブジェクトの法線は通常、モデル自体には相対的になっている。モデルを回転しても、法線は変更されない。これを修正するには、法線をオブジェクト座標からワールド座標に変換する必要がある。これを行うには、WorldNormalVector関数を使用して行う。

 法線オブジェクト座標からワールド座標に変換した内積(dot)が、Level of snowの値に等しいか大きかった場合は、o.Albedoにそのまま雪の色を指定している。そうではない場合はo.AlbedoにMain Colorを指定している。透明度は不透明としている。

SnowMaterialのInspectorを設定する

 SnowMaterialのInspectorを開き、Main Color、Color of snowの色を指定してScene上のEthenの上にドラッグ&ドロップする。すると、Ethanは全身が黄緑色で表示される。ここで、Level of snowのスライダーをゆっくりと動かしてみよう(図3)。

図3 Snow MaterialのInspectorを表示して設定した

 -0.02ほどの値にすると図4のように表示される。

図4 図3のInspectorを設定したEthan

 Ethanの上にうっすらと雪がふりかかっているように見える。この状態で、図3のDepth of snowのスライダーを動かしてみよう。値を最大の1まで持っていくと、図5のように、Ethanが少し細くなったように表示される。

図5 Level of snowの値が-0.02、Depth of snowの値が1の場合

 ここの処理が、リスト1の30〜31行目の処理で実現していると思われる。_Snowプロパティで表される、Level of snowの値が-0.02、_SnowDepthプロパティで表されるDepth of snowの値が1の場合の図が図5だ。

 では、Level of snowの値が0.1、Depth of snowの値が0.56に指定するとどのように表示されるだろう。図6のように、雪の占める割合は減り、今度は太ったような感じで表示される。

図6 Level of snowの値が0.1、Depth of snowの値が0.56の場合

 Level of snowの値と、Depth of snowの値をスライダーからいろいろ変更してみると、いろいろな表示になり、30〜31行目のコードが、どのように動作しているのかが、自然と分かってくるのではないだろうか。

終わりに

 今回で「Unityで始めるシェーダー入門」は最終回だ。できるだけ、簡単なサンプルをもとに解説を進めていったのではあるが、筆者にとっても大変に難しくて、困難極めた解説であった。

 なかなか、コードの意味が理解できない部分もあったと思うが、「シェーダーとはどんなものであるのか」「どのように記述するのか」といった基本のキについては理解していただけたのではないだろうか。

 今回のシェーダー入門が、シェーダーのとっかかりになってくれれば、筆者としては満足だ。長い間のお付き合いありがとうございました。

参考書籍

著者プロフィール

薬師寺 国安(やくしじ くにやす) / 薬師寺国安事務所

薬師寺国安事務所代表。Visual Basicプログラミングと、マイクロソフト系の技術をテーマとした、書籍や記事の執筆を行う。

1950年生まれ。事務系のサラリーマンだった40歳から趣味でプログラミングを始め、1996年より独学でActiveXに取り組む。

1997年に薬師寺聖とコラボレーション・ユニット「PROJECT KySS」を結成。

2003年よりフリーになり、PROJECT KySSの活動に本格的に参加。.NETやRIAに関する書籍や記事を多数執筆する傍ら、受託案件のプログラミングも手掛ける。

Windows Phoneアプリ開発を経て、現在はWindowsストアアプリを多数公開中。

Microsoft MVP for Development Platforms - Client App Dev (Oct 2003-Sep 2012)。

Microsoft MVP for Development Platforms - Windows Phone Development(Oct 2012-Sep 2013)。

Microsoft MVP for Development Platforms - Client Development(Oct 2013-Sep 2014)。

Microsoft MVP for Development Platforms-Windows Platform Development(Oct 2014-Sep 2015)。


Copyright © ITmedia, Inc. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。