goo blog サービス終了のお知らせ 

日々適当

hibitekitou

UnityのShaderメモ

cg |2015-02-05

Unityのシェーダには三種類あるらしい。

Fixed Function Shaders
UnityのシェーダであるShaderLabで記述する。古い環境で使用される事を想定しているものらしいので、無視してみる。

Surface Shaders
ライティングとシャドウが適用されるシェーダ。ShaderLabとCg,HLSLで記述。

Vertex Shaders, Fragment Shaders
ShaderLabとCg,HLSLで記述される。Vertex Shadersは名前の通り頂点位置を計算する。画面上の位置がこれで決定されて、ラスタライズしピクセルの並びが決定される。その後、そのピクセルを操作するためのシェーダが Fragment Shaders(フラグメントシェーダ)。ピクセルシェーダとも呼ばれる。

ということで、全てのシェーダにはShaderLabが必要となる。ShaderLabのフォーマットは以下の通り。

Shader "MyShader" {
    Properties {
        // Unityからシェーダに渡すパラメータを記述
    }
    SubShader {
        //シェーダーを記述する。
    }
    SubShader {
        // 複数のハードウェアをサポートする時はSubShaderを必要なだけ連ねる
    }
    Fallback "Diffuse"
}

最初の"MyShader"がシェーダ名。Unity内で表示されるパスもここでまとめて指定する。myShaders/MyShader とか。
最後のFallbackはSubShaderで記述されたシェーダが動作しなかった場合、とりあえずこれで動作するようにしとけと指定する所。

PropertiesにはUnityからシェーダに渡す値を記述するけど、渡す値は7種類の書き方があるらしい。

  • 実数値(最大・最小値有り) name ("display name", Range (min, max)) = number
  • カラー値 name ("display name", Color) = (number,number,number,number)
  • 2Dテクスチャ name ("display name", 2D) = "name" { options }
  • 長方形テクスチャ name ("display name", Rect) = "name" { options }
  • キューブマップテクスチャ name ("display name", Cube) = "name" { options }
  • 実数値 name ("display name", Float) = number
  • 4Dベクトル name ("display name", Vector) = (number,number,number,number)

シェーダ(SubShader内)からは[name]で参照される。UnityのGUIからは(マテリアルインスペクタからは)display name として表示される。等号の後ろの値はデフォルト値。
デフォルト値について、テクスチャ(2D, Rect, Cube)は空の文字列か、"white", "black", "glay", "bump" が内蔵のデフォルトとして用意されている。
サンプルのコードを見ていると、nameにはアンダースコアを頭に付けたものを使用しているんだけど、そんなルールなのかね?
2Dテクスチャと長方形(Rect)テクスチャの違いは、前者は画像の縦横が2^nになっている物で、後者はそうじゃないものという事らしい。

テクスチャにはoptionsが設定できる。利用できる値は

  • TexGen texgenmode
  • LightmapMode

の二種類らしい。TexGenはテクスチャの座標生成モード。texgenmodeはObjectLinear 、EyeLinear 、SphereMap 、CubeReflect 、CubeNormal のいずれか。
LightmapModeを記述すると、ライトマップパラメータの影響を受けるようになる。


続いてSubShader。この中にパスとサブシェーダタグを記述する。パスは以下の通り。

SubShader {
    Pass {
        //
    }
}

どのようにレンダリングエンジンにレンダリングされるかを指定する感じらしい。Fixed Function Shadersではこの中身のみをみるとのこと。だから単純なものはここだけで記述可能かな。

Shader "Custom/SimplePassOnly" {
    Properties {
        _Diffuse ("Diffuse Color", Color) = (1, 1, 1, 1)
        _Specular ("Spacular Color", Color) = (0.5, 0.5, 0.5, 1)
        _Emission ("EmissionColor", Color) = (0, 0, 0, 1)
        _MulColor ("Mult Color" , Color) = (1.0, 0.0,0.0,1.0)
        _MainTex ("Texture", 2D) = "white" { }
    }
    SubShader {
        Tags { "Queue" = "Geometry" } //Queueは描画の順番を判定する。Geometryはデフォルト値。
        Pass {
                Material {
                        Diffuse [_Diffuse]
                        Ambient ( 0.0, 0.0, 0.0, 1.0  ) //値を直接しているする時とプロパティを参照する時、()と[]の違いがある
                        Specular [_Emission]
                        Shininess 0.1 //0から1の範囲
                        Emission [_Emission]
                }
                Lighting On //スペキュラーが適用されるか否か
                SeparateSpecular On
                Cull Back //この設定は表面描画。とれる値は→(Back , Front , Off)
                ZWrite On //深度バッファへの書き込み(On, Off)
                ZTest LEqual // 深度テスト。奥行きを基準とした評価をし、合格したら描画される。LEqual以外はあまり使われない。
                //Offset 0, -1 書き込む深度値をずらす時に使う、ということらしい。
                SetTexture[_MainTex]{
                        ConstantColor [_MulColor]
                        combine Texture * Constant //_MainTexに_MulColorを乗算で乗せている。
                }
                //AlphaTest Less 0.5 アルファの値を見て、描画を決定する。テクスチャのアルファチャンネルで穴を開けるとかに利用。
                //Blend SourceBlendMode DestBlendMode 後で勉強する。
                //Fog{ } 後で勉強する。
        }
    }
    Fallback "Diffuse"
}

まぁしかし、これだと当然出来ることも限られてくるわけで、そうするとサーフェースシェーダに手を出さざる得なくなるらしい。
SubShaderに CGPROGRAM~ENDCG を書いておいて、そこに挟まれた範囲がシェーダとなる。さらにそのシェーダがサーフェースシェーダであることを示すために、#pragma surface という指示を与えてやるようだ。

#pragma surface surfaceFunction lightModel [optionalparams]

surfaceFunction、lightModel、 [optionalparams] を適切に指定してやる。
surfaceFunctionはシェーダの関数名。lightModelはシェーディングモデルの指定かな。"Lambert"か"BlinnPhong"となる。[optionalparams]はオプションなんだけど、マニュアルには19個程も記述されてた。サーフェイスシェーダの記述 [Unity リファレンスマニュアル]

CGPROGRAM
#pragma surface surf Lambert
void surf( Input IN, inout SurfaceOutput o ) {
}
ENDCG

というわけで、基本的にはこんな形。void surf( Input IN, inout SurfaceOutput o ) { } に渡すInputや出力するSurfaceOutputを指定する。これらは構造体でいろいろ決まりごとがある模様。

Shader "Custom/SimplePassOnly" {
    Properties {
        _BaseColor ("Base" , Color) = (1.0, 0.0, 0.0, 1.0)
        _MainTex ("Main Texture", 2D) = "white" { }
        _SubTex ("Sub Texture", 2D) = "white" { }
    }
    SubShader {
        Tags { "Queue" = "Geometry" } //Queueは描画の順番を判定する。Geometryはデフォルト値。

        CGPROGRAM
        #pragma surface surf Lambert
        
        float4 _BaseColor; //Propertiesで設定した色を取得
        sampler2D _MainTex; //Propertiesで設定したテクスチャを取得
        sampler2D _SubTex;  
        
        //入力の構造体
        struct Input {
            float2 uv_MainTex; //Propertiesで設定したテクスチャを取得。uvXXXXという形式で取得するみたい。
            float4 screenPos; //スクリーンの座標系?
        };
        void surf( Input IN, inout SurfaceOutput o ) {
            //oが出力の構造体
            o.Albedo = half3(_BaseColor) * tex2D( _MainTex, IN.uv_MainTex ).rgb ; //アルベドを設定。拡散反射光であり、いわゆるディフューズと同義かね。
            float2 screenUV = IN.screenPos.xy / IN.screenPos.w; //よく分からないけど、wで割ることで、ビューポートに対して0から1に収まる座標系に変換されるらしい。
            screenUV *= float2(2,3); //その上で繰り返す。
            o.Emission = tex2D( _SubTex, screenUV ).rgb * 0.5; //その座標系で貼られたテクスチャをエミッションに適用。
        }
        ENDCG
    }
    Fallback "Diffuse"
}

んじゃ、これを半透けにしましょう、って時。あるいはテクスチャのアルファチャンネルで抜きましょうって時。
この場合、Tags { "Queue" = "Transparent" } とする必要があるのと、#pragma surface surf Lambert alpha とする必要がある。alphaを追加してアルファ ブレンディングモードにしている。その上で、o.Alphaに適切な値を指定する。

Shader "Custom/SimplePassOnly" {
    Properties {
        _BaseColor ("Base" , Color) = (1.0, 0.0, 0.0, 1.0)
        _MainTex ("Main Texture", 2D) = "white" { }
        _SubTex ("Sub Texture", 2D) = "white" { }
    }
    SubShader {
        Tags { "Queue" = "Transparent" } //描画するタイミングを半透けのもんに適したものに指定。

        CGPROGRAM
        #pragma surface surf Lambert alpha //アルファブレンディングモード
        
        float4 _BaseColor; //Propertiesで設定した色を取得
        sampler2D _MainTex; //Propertiesで設定したテクスチャを取得
        sampler2D _SubTex;  
        
        //入力の構造体
        struct Input {
            float2 uv_MainTex; 
            float3 worldRefl; 
        };
        void surf( Input IN, inout SurfaceOutput o ) {
            half4 color = tex2D( _MainTex, IN.uv_MainTex ) ; //テクスチャのカラー(rgba)を取得
            o.Albedo = color.rgb *  half3(_BaseColor) ; //Propertiesで設定した色をテクスチャに乗算してる。
            o.Emission = tex2D( _SubTex, IN.worldRefl ).rgb * 0.5; //反射っぽい表現。
            o.Alpha = color.a; //透明度の指定。この場合、テクスチャのアルファチャンネルから。
        }
        ENDCG
    }
    Fallback "Diffuse"
}

とは言え、裏面が描画されていないから、ちょっと不自然である。裏面も描画させるには、裏面を描いてから表面を描くようにするそうで。

Shader "Custom/SimplePassOnly" {
    Properties {
        _BaseColor ("Base" , Color) = (1.0, 0.0, 0.0, 1.0)
        _MainTex ("Main Texture", 2D) = "white" { }
        _SubTex ("Sub Texture", 2D) = "white" { }
    }
    SubShader {
        Tags { "Queue" = "Transparent" } 

        //裏面描画*****************************************************
        Cull Front
        CGPROGRAM
        #pragma surface surf Lambert alpha 
        
        float4 _BaseColor; 
        sampler2D _MainTex; 
        sampler2D _SubTex;  
        
        struct Input {
            float2 uv_MainTex; 
            float3 worldRefl; 
        };
        void surf( Input IN, inout SurfaceOutput o ) {
            half4 color = tex2D( _MainTex, IN.uv_MainTex ) ; 
            o.Albedo = color.rgb *  half3(_BaseColor) ; 
            o.Emission = tex2D( _SubTex, IN.worldRefl ).rgb * 0.5; 
            o.Alpha = color.a; 
        }
        ENDCG

        //表面描画*****************************************************
        Cull Back
        CGPROGRAM
        #pragma surface surf Lambert alpha 
        
        float4 _BaseColor; 
        sampler2D _MainTex; 
        sampler2D _SubTex;  
        
        struct Input {
            float2 uv_MainTex; 
            float3 worldRefl; 
        };
        void surf( Input IN, inout SurfaceOutput o ) {
            half4 color = tex2D( _MainTex, IN.uv_MainTex ) ; 
            o.Albedo = color.rgb; 
            o.Emission = tex2D( _SubTex, IN.worldRefl ).rgb * 0.5; 
            o.Alpha = color.a; 
        }
        ENDCG
    }
    Fallback "Diffuse"
}

こちらは反射が不自然なので、反射表現のためにCubeMapをしてみる。Properties でCubeMapの取り口を作るのは、

_Cube ("Cubemap", CUBE) = "" {}

となる。でもって、先のコードの出力部分の tex2D( _SubTex, IN.worldRefl ).rgb * 0.5 部分を texCUBE (_Cube, IN.worldRefl).rgb にする。

Shader "Custom/SimplePassOnly" {
    Properties {
        _BaseColor ("Base" , Color) = (1.0, 0.0, 0.0, 1.0)
        _MainTex ("Main Texture", 2D) = "white" { }
        _SubTex ("Sub Texture", 2D) = "white" { }
        _Cube ("Cubemap", CUBE) = "" {} //キューブマップの取り口
    }
    SubShader {
        Tags { "Queue" = "Transparent" } 

        //裏面描画
        Cull Front
        CGPROGRAM
        #pragma surface surf Lambert alpha 
        
        float4 _BaseColor; 
        sampler2D _MainTex; 
        samplerCUBE _Cube; //Propertiesで設定したキューブマップを取得
        
        //入力の構造体
        struct Input {
            float2 uv_MainTex; 
            float3 worldRefl; //ワールド座標系?
        };
        void surf( Input IN, inout SurfaceOutput o ) {
            half4 color = tex2D( _MainTex, IN.uv_MainTex ) ; 
            o.Albedo = color.rgb *  half3(_BaseColor) ; 
            o.Emission = texCUBE (_Cube, IN.worldRefl).rgb * 0.5; //反射っぽい表現。キューブマップ。
            o.Alpha = color.a; //透明度の指定。この場合、テクスチャのアルファチャンネルから。
        }
        ENDCG

        //表面描画
        Cull Back
        CGPROGRAM
        #pragma surface surf Lambert alpha 
        
        float4 _BaseColor; 
        sampler2D _MainTex;  
        samplerCUBE _Cube;
        
        //入力の構造体
        struct Input {
            float2 uv_MainTex; 
            float3 worldRefl; 
        };
        void surf( Input IN, inout SurfaceOutput o ) {
            half4 color = tex2D( _MainTex, IN.uv_MainTex ) ; 
            o.Albedo = color.rgb; 
            o.Emission =texCUBE (_Cube, IN.worldRefl).rgb * 0.5; 
            o.Alpha = color.a; 
        }
        ENDCG
    }
    Fallback "Diffuse"
}

だいぶ自然になったかなというところ。
とりあえずはこんな具合ですかなぁ。

コメント ( 0 )|Trackback ( )
 
コメント
 
コメントはありません。
コメントを投稿する
ブログ作成者から承認されるまでコメントは反映されません
 
名前
タイトル
URL
コメント
コメント利用規約に同意の上コメント投稿を行ってください。

数字4桁を入力し、投稿ボタンを押してください。