C++幼女先輩

プログラミング成分多め

UNITYシェーダ研究 複数ライトの影響

コード
https://github.com/YukiMiyatake/UnityLesson/tree/Shader_6

前回まで

前回はAmbientも入れて だいたいライティング出来た
ただ、複数ライトを置いても1つ目のライトの影響しか受けない
ので、複数ライトの影響をうけるようにする

ライトの強さ

複数ライト対応するまえに、今までのライトではライトカラーの影響はうけるが
ライトの強さの影響が入っていないので追加する

ライトの種類により減衰しないライト(DirectionalLightなど)と 減衰するライト(PointLight等)があり
それらをまとめて計算するマクロが存在するので それを使う

AutoLight.cginc をIncludeし、LIGHT_ATTENUATION(i) でライトの強さが取得できる iはピクセルデータ

それをライトカラーに乗算することで、強さを反映したライト色が計算できる

float3 lightCol = _LightColor0.rgb * LIGHT_ATTENUATION(i);

ライトカラーは、ディフューズとスペキュラに影響するので、それぞれに乗算する

float3 diffuse = (NdotL*0.5 + 0.5) * lightCol;

float3 specular = pow(max(0.0, dot(H, N)), _Spec1Power) *  _Spec1Color.xyz * lightCol;  // Half vector

複数ライトに対応

UNITYでは、キャラに最も影響の強いライトは LightMode=ForwardBaseで計算するが
その他のライトは LightMode=ForwardAddで入ってくる
ので、ForwardAddのパスを増やせばいい

シェーダーの中身は 今回はほぼ、ForwardBaseのものをコピーする
ただし、Ambientは1回しか計算してはいけないので ForwardAddでは計算行わない事と
BlendModeを One Oneなどにしなければ、合成されない

BlendMode設定なし
f:id:murasame-labo:20170924035237p:plain

メインライトのみ
f:id:murasame-labo:20170924035250p:plain

ライト2個(赤ライト追加)
f:id:murasame-labo:20170924035334p:plain()

複数ライトが反映されるようになった

UNITYシェーダ研究 Ambient

コード
https://github.com/YukiMiyatake/UnityLesson/tree/Shader_5

前回のシェーダ

ディフューズとスペキュラを実装し、だいぶライティングができた
テクスチャマッピングもしている

でも、Ambientも与えていません

Ambientの設定

UNITYのバージョンにより違うけど
最近のバージョンでは
Window-Lighting-Setting から行う

f:id:murasame-labo:20170922015418p:plain

Environment Lightingのあたり

Ambientの取得

Ambinetカラーの取得は定数が用意されているので簡単だ

UNITY_LIGHTMODEL_AMBIENT.rgb;

これを計算式に加えるが
テクスチャ * Ambientの値を今までのものに足せばよいので最終的には

最終カラー = Ambientテクスチャ + Diffuseテクスチャ + スペキュラ
= (Ambient+Diffuse) * テクスチャ + スペキュラ
にした

             float3 frag(v2f i) : SV_Target
                {
                    float3 L = normalize(_WorldSpaceLightPos0.xyz);
                    float3 V = normalize(_WorldSpaceCameraPos - i.vertexW.xyz);
                    float3 N = i.normal;
                    float3 H = normalize(L + V);


                    //Ambient
                    float3 ambient = UNITY_LIGHTMODEL_AMBIENT.rgb;
                    
                    // texture albedo
                    float4 tex = tex2D(_MainTex, i.uv);

                    // Diffuse(HalfLambert)
                    float3 NdotL = dot(N, L);
                    float3 diffuse = (NdotL*0.5 + 0.5) * _LightColor0.rgb;

                    // Speculer
                // float3 specular = pow(max(0.0, dot(reflect(-L, N), V)), _Spec1Power) * _Spec1Color.xyz;  // reflection
                    float3 specular = pow(max(0.0, dot(H, N)), _Spec1Power) *  _Spec1Color.xyz;  // Half vector


                    return (ambient + diffuse) * tex + specular;
                }

カレーハッキング

これからは ニジマスの手に入るシーズンになるので、ニジマスカレーを極めようとおもう

1回作ったけど 普通に美味しいのだけど、これからさらにプラスしていく

まず前回はゴールデンカレールウを使った。普通に美味しいけど、ビーフ等にあう、濃厚なルーであった
また スパイスも自分で頑張りたいので、スパイス研究科 中村繭子パイセンや友達のアドバイスもらいつつ
自分のニジマスカレーを完成させたい

知見

一度作った時の知見であるが、野菜の多いカレーが好きだが、歯ざわりや触感が統一されてないと ん?? ってなる
例えばヤングコーン入れたがそれだけ固く ん?? だったので、煮込む野菜以外は 蒸し野菜でトッピングする

ニジマスはおろして骨などを取り除くと食べやすいが、身がくずれたり、骨のエキスが美味しいので、いまはぶつ切りで考える
将来的には、あら汁と分けたり、トッピングで乗せ身が崩れなくするなど 考えたい

デミ系のカレー好きだけど、私の求めるものではないので、ルウにと溶かす野菜は慎重にえらぶ

次への

インドのベンガル地方は川魚のカレーが多いので参考にする
川魚用のスパイス
フェンネル、クミン、ニゲラ、ブラウンマスタード、フェニュグリーク

魚はターメリックと塩とレモン汁で漬けておいて使うこと多い

酸味苦手ではあるが、酸につけて少し骨を柔らかくできるといいな

カレー  上野不忍池 サルガム

何か料理を極めたいのでカレーにしてみた
カレーは好きだし、みんな食べられる

前提として小麦アレルギーなので、ご飯にあうカレー
お米はジャポニカ米がベストだけど、場合によればインディカ米買ってもいい

今は色々なものを作るのが先 敵を知るにはまず己を知る必要がある

不忍池近くの インド&ネパール料理
こういう店は、インド料理ではなくネパールがメインのはず

とりあえず すべて入ってるコース頼む
バターチキンカレー、キーマカレー、サラダ、デザート、ナン、ライス、シシカバブ、タンドリーチキン、ラッシー

バターチキン、キーマカレーは甘い。辛口MAXだけど 私は辛さには強いので物足りないが、とにかく砂糖甘さ
デザートはインドらしく 激甘
ナン、ラッシーは甘さ控えめ

ナンは甘いのが好きだったので、残念
カレーも 味は普通だけど、好みというか目指す方向と大きく違う

そして 小麦アレルギーで速攻おなか壊し、営業に送れるという ダメなパターン・・

UNITYシェーダ研究 Blinn-Phong

コード
https://github.com/YukiMiyatake/UnityLesson/tree/Shader_4

はじめに

ディフューズを入れて 基本的なLambertモデル実装しました

次はこれにスペキュラを追加します(念のため DirectSpecularのみです)
Blinn-Phong的な事をします(定義間違ってたら教えて下さい)

スペキュラ

鏡面反射です
前回のディフューズは、ライトが面に当たった時のライティングで、View(カメラの位置)とは関係ありません
スペキュラでは、光の反射モデルなので 鏡に光源が写った時= 光の反射を直視した時のみ強烈に光ります
3DCGでは こういう事になります
光の入射角と法線のreflectionベクトル(面法線に対してライトが同じ角度で反射)と Viewベクトルの内積と正の相関関係

スペキュラの場合は、相関は Powでかけます。細かい物理的な理由は調べてないけど
スペキュラのパラメータは今回は、スペキュラ色と Powerにします

property

 Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _Spec1Power("Specular Power", Range(0, 30)) = 1
        _Spec1Color("Specular Color", Color) = (0.5,0.5,0.5,1)
    }

今回はスペキュラカラーとPowerが増えた

宣言

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

            struct v2f
            {
                float4 vertex : SV_POSITION;
                float4 vertexW: TEXCOORD0;
                float2 uv     : TEXCOORD1;
                float3 normal : TEXCOORD2;
            };

            uniform sampler2D _MainTex; uniform float4 _MainTex_ST;
            uniform float _Spec1Power;
            uniform float4 _Spec1Color;

念のため 今回はフラグメントシェーダで計算します。一部頂点シェーダで計算して補完してもいいものがありますが

スペキュラのパラメータ以外に フラグメントシェーダに vertexWを送ってます
vertexには ビューポート変換まで行うので、World座標での位置情報が必要なためです

頂点シェーダ

         v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.vertexW = mul(unity_ObjectToWorld, v.vertex);

                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                o.normal = UnityObjectToWorldNormal(v.normal);
                return o;
            }

vertexWに対して World座標系への変換をかけています

フラグメントシェーダ

         float3 frag (v2f i) : SV_Target
            {
                float3 L = normalize(_WorldSpaceLightPos0.xyz);
                float3 V = normalize(_WorldSpaceCameraPos -i.vertexW.xyz);
                float3 N = i.normal;


                // texture albedo
                float4 tex = tex2D(_MainTex, i.uv);

                // Diffuse(HalfLambert)
                float3 NdotL = dot(N, L);
                float3 diffuse = (NdotL*0.5 + 0.5) * _LightColor0.rgb ;

                // Speculer
                float3 specular = pow(max(0.0, dot(reflect(-L, N), V)), _Spec1Power) * _Spec1Color.xyz;  // reflection

    
                return diffuse*tex + specular;
            }

Vは ViewVectorです。  _WorldSpaceCameraPosは World座標系でのカメラのポジションで World座標系でのポジションをマイナスすると、ピクセルからカメラに向かうベクトルになります

dot(reflect(-L, N), V) これが、反射ベクトルと Viewベクトルの内積部分
reflectというのは組み込み関数で この場合は 法線を中心として 反射したライトベクトルが取れる
それを maxで 0未満(裏面)の計算を無効にし
スペキュラの強さで累乗しスペキュラ色を乗算

スペキュラは加算なので単純にディフューズに掛け合わせる

f:id:murasame-labo:20170913102938p:plain

カメラを動かすと スペキュラのライティングだけ変わります
ディフューズはカメラ位置とは無関係なのもわかると思います

ハーフベクトル

上記の計算は 正確な反射ベクトルを計算した
具体的には reflect(-L,N)の部分だが、反射ベクトルは多少計算が重い
R = L - (2V・N) N
L - 2 * dot( V*2, N ) * N

スペキュラは ハーフベクトルを使って近似が出来る事が広く知られているのでそれを実装する

ハーフベクトルとは、ライトベクトルとViewベクトルの間のベクトルであり、それと法線の内積で近似出来る

ハーフベクトルは normalize(L+V); で表される
reflectより圧倒的に計算量が低い

float3 H = normalize(L+V);

// Speculer
// float3 specular = pow(max(0.0, dot(reflect(-L, N), V)), _Spec1Power) * _Spec1Color.xyz;  // reflection
float3 specular = pow(max(0.0, dot(H, N)), _Spec1Power) *  _Spec1Color.xyz;  // Half vector

f:id:murasame-labo:20170913105709p:plain

計算間違えてるのか、ライトの影響パワーが大きく異なるが、スペキュラはかかった

UNITYシェーダ研究 Lambertモデル実装

前回は 新規で作ったUNITシェーダの説明したので
簡単なシェーダ作ります

コードはココ
https://github.com/YukiMiyatake/UnityLesson/tree/Shader_3

基本シェーダ(Albedo)

先ほどのシェーダからFOGを削除しシンプルにします
テクスチャは・・・雰囲気欲しいからつけておく

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

        Pass
        {
            CGPROGRAM
           #pragma vertex vert
           #pragma fragment frag
            
           #include "UnityCG.cginc"

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

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
            
            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                return o;
            }
            
            fixed4 frag (v2f i) : SV_Target
            {
                // sample the texture
                fixed4 col = tex2D(_MainTex, i.uv);
                return col;
            }
            ENDCG
        }
    }
}

f:id:murasame-labo:20170911092726p:plain

テクスチャ(Albedo)のみですね

Lambert

最も簡単なライティングモデルの ランバート反射を作ります
先ほどのAlbedoはライトの影響をうけなかったけど今度はうけます

ランバート反射は、面にライトが当たる角度により影響の強さが変わる
具体的には 法線とライトベクトルの内積を乗算する

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

        Pass
        {
            Tags{
                "LightMode" = "ForwardBase"
            }

            Cull Back

            CGPROGRAM
           #pragma vertex vert
           #pragma fragment frag
            
           #include "UnityCG.cginc"
           #include "Lighting.cginc"

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

            struct v2f
            {
                float4 vertex : SV_POSITION;
                float2 uv     : TEXCOORD0;
                float3 normal : TEXCOORD1;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
            
            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                o.normal = UnityObjectToWorldNormal(v.normal);
                return o;
            }
            
            float3 frag (v2f i) : SV_Target
            {
                // sample the texture
                float4 col = tex2D(_MainTex, i.uv);

                // Diffuse
                float3 lightDir = normalize(_WorldSpaceLightPos0.xyz);
                float3 NdotL = dot( i.normal, lightDir);
                float3 diffuse = max(0.0, NdotL ) * _LightColor0.rgb * col.xyz;
                return diffuse;
            }
            ENDCG
        }
    }
}

LightMode

Tags{
  "LightMode" = "ForwardBase"
}

これを付けないと安定しなかったので付けた
LightModeにより呼ばれるパスが変わる

ForwardBaseは Forwardレンダリングのメインのライトのパス
ForwardAddは Forwardレンダリングのメイン以外のライトがそれぞれ呼ばれる
Deferredは Deferredレンダリングのパス
ShadowCasterは 影のパス
など

構造体

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

            struct v2f
            {
                float4 vertex : SV_POSITION;
                float2 uv     : TEXCOORD0;
                float3 normal : TEXCOORD1;
            };

法線が必要なので NORMALが追加

シェーダ中身

         v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                o.normal = UnityObjectToWorldNormal(v.normal);
                return o;
            }
            
            float3 frag (v2f i) : SV_Target
            {
                // sample the texture
                float4 col = tex2D(_MainTex, i.uv);

                // Diffuse
                float3 lightDir = normalize(_WorldSpaceLightPos0.xyz);
                float3 NdotL = dot( i.normal, lightDir);
                float3 diffuse = max(0.0, NdotL ) * _LightColor0.rgb * col.xyz;
                return diffuse;
            }

o.normal = UnityObjectToWorldNormal(v.normal);
法線がローカル座標系なので World座標系に変換する

WorldSpaceLightPos0、LightColor0 はビルトイン定数(Lighting.cginc のinclude必要)
WorldSpaceLightPos0: 0番目のライトのポジション(ディレクショナルライトは方向) LightColor0: 0番目のライトの色

float3 NdotL = dot( i.normal, lightDir);
ここで 法線とライトの内積を計算する

ディフューズは ライトと法線の内積ライトカラーとテクスチャカラーを掛ければ求められる
内積は負の場合(裏面)があるので その場合は0にしておく

f:id:murasame-labo:20170911160322p:plain

ライトを動かせば ライティングも変わるようになった
でも少し暗い

またSphereを置けばわかりやすいが、陰のつき方が90度でわかれている
実際のオブジェクトは光の回析や吸収反射などで もっとゆるやかに出るべきである

ハーフランバート

先ほどのランバートだと、ライティングが90度で終わる
f:id:murasame-labo:20170911162221p:plain

もう少しゆるやかにしたいのでハーフランバートを適用してみる 先ほどのランバート反射では ライトと法線の内積が -1~1 であったが  0~1の範囲にリマップすれば ゆるやかになる

//              float3 diffuse = max(0.0, NdotL ) * _LightColor0.rgb * col.xyz;
                float3 diffuse = (NdotL*0.5 + 0.5) * _LightColor0.rgb * col.xyz;

f:id:murasame-labo:20170911162420p:plain

もう少しピーキーにしたいなら 先ほどの内積を2乗する

//              float3 diffuse = max(0.0, NdotL ) * _LightColor0.rgb * col.xyz;
//             float3 diffuse = (NdotL*0.5 + 0.5) * _LightColor0.rgb * col.xyz;
                float3 diffuse = pow(NdotL*0.5 + 0.5, 2) * _LightColor0.rgb * col.xyz;

f:id:murasame-labo:20170911162635p:plain

他にも float3 diffuse = (NdotL*0.75 + 0.25) * _LightColor0.rgb * col.xyz;

とか色々と考えれるので、好みで使い分けよう

f:id:murasame-labo:20170911172540p:plain

UNITYシェーダー研究 UNITシェーダを読む

今回のソース

https://github.com/YukiMiyatake/UnityLesson/tree/Shader_2

前回のあらすじ

Surfaceシェーダを新規で作る 中野シスターズに適用
Surfaceシェーダの解説

たいした内容じゃなくてすみません

でも 初歩からやりたいの

UNITシェーダを適用する

本格的なシェーダーを作るには UNITシェーダは欠かせないので Surfaceシェーダと同じように作る
今度は名前を Unlit/yUnit にする

Shader "Unlit/yUnit"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
    }
    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;
            
            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
            {
                // sample the texture
                fixed4 col = tex2D(_MainTex, i.uv);
                // apply fog
                UNITY_APPLY_FOG(i.fogCoord, col);
                return col;
            }
            ENDCG
        }
    }
}

今度は明らかに、レンダリング結果が変わった

f:id:murasame-labo:20170911092726p:plain

シェーダーの中身

今回は前回と違い、テクスチャしかパラメーターがありません!

Pass

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

UNITシェーダでは 複数のPassを持つことができる
輪郭線とか影とか
今回は1個

pragmaは 前回はSurfaceでサーフェースシェーダを指定したが、今回は vertex、fragmentと2つのシェーダを指定している
もちろん、それらの後に続くのは エントリポイントである
今回は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;

UnityCG.cginc をincludeしているが、色々と便利なマクロや関数等が入ったヘッダなので使う

構造体は今回2個
頂点毎の入力(attribute)の appdata
フラグメントシェーダに渡す Varyingの v2f

そして シェーダーの引数 uniformの _MainTex

シェーダー中身

         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
            {
                // sample the texture
                fixed4 col = tex2D(_MainTex, i.uv);
                // apply fog
                UNITY_APPLY_FOG(i.fogCoord, col);
                return col;
            }

頂点シェーダ

UnityObjectToClipPosの定義を見てみよう

// Tranforms position from object to homogenous space
inline float4 UnityObjectToClipPos(in float3 pos)
{
    // More efficient than computing M*VP matrix product
    return mul(UNITY_MATRIX_VP, mul(unity_ObjectToWorld, float4(pos, 1.0)));
}
inline float4 UnityObjectToClipPos(float4 pos) // overload for float4; avoids "implicit truncation" warning for existing shaders
{
    return UnityObjectToClipPos(pos.xyz);
}

オブジェクトのローカル座標からビューポート座標に変換している

TRANSFORM_TEX

// Transforms 2D UV by scale/bias property
#define TRANSFORM_TEX(tex,name) (tex.xy * name##_ST.xy + name##_ST.zw)

なんと、トークンマクロで

TRANSFORM_TEX(v.uv, MainTex); は
v.uv.xy *
MainTex_ST.xy + MainTex_ST.zw
になる。
MainTex_ST.xyにTiling、zwにOffsetの値が入ってくるようだ

UNITY_TRANSFER_FOG

        // SM3.0 and PC/console: calculate fog distance per-vertex, and fog factor per-pixel
        #define UNITY_TRANSFER_FOG(o,outpos) o.fogCoord.x = (outpos).z

ビューポート座標系でのZ座標をFOGのパラメータに入れている。距離によるFOG。ここは さらっと読み飛ばしてもいい

結論からいうと、ローカル座標をビューポート座標に変換し、テクスチャのUVを補正して、Zフォグのパラメータを渡しているだけのようだ

フラグメントシェーダ

// sample the texture
fixed4 col = tex2D(_MainTex, i.uv);
// apply fog
UNITY_APPLY_FOG(i.fogCoord, col);
return col;

非常にシンプルで、テクスチャマッピングして、FOGを適用しているだけのようだ
ライトの計算も何も入っていない(実際DirectionalライトをON/OFFしても変化しない)