[Unity]Phongシェーディング

Shaders Laboratory」に置いてあるシェーダーに1行づつコメントつけていくシリーズの第2弾。

今回はPhongシェーディングです。

Phongシェーディングは各点ごとに明るさを計算する処理は重めだが、
その分計算結果が高精度な陰影手法だそうです。

/*
	フォンシェーディング
	ピクセルごとに色の補完を行う高精度のシェーディング
*/
Shader "PhongShader"
{
    Properties
    {
        // _MainTexという変数を宣言、インスペクター上ではMain Textureという名前の2Dテクスチャで、デフォルトはwhiteというテクスチャ
        _MainTex( "Main Texture", 2D ) = "white" {}

        // 次のヘッダー属性までをグループ化する、Ambientは環境光
        [Header(Ambient)]

        // _Ambientという変数を宣言、インスペクター上ではIntensityという名前の0~1範囲の数値
        _Ambient( "Intensity", Range( 0.0, 1.0 ) ) = 0.1

         // _AmbColorという変数を宣言、カラー値
        _AmbColor( "Color", color ) = ( 1.0, 1.0, 1.0, 1.0 )

		// Diffuseは拡散光
        [Header(Diffuse)]
        _Diffuse( "Val", Range( 0.0, 1.0 ) ) = 1.0
        _DifColor( "Color", color ) = ( 1.0, 1.0, 1.0, 1.0 )

		// Specularは鏡面反射光、光の入射角とは逆方に反射する光
        [Header(Specular)]

        // 次の変数をトグル化する
        [Toggle]
		_Spec( "Enabled?", Float ) = 0.0
        _Shininess( "Shininess", Range( 0.1, 10 ) ) = 1.0
        _SpecColor( "Specular color", color ) = ( 1.0, 1.0, 1.0, 1.0 )

		// Emissionは放射光、自身が発する光
        [Header(Emission)]
        _EmissionTex( "Emission texture", 2D ) = "gray" {}
        _EmiVal( "Intensity", float) = 0.0

		// HDR属性を指定すると、次のパラメータをHDRで制御でする
        [HDR]
		_EmiColor( "Color", color ) = ( 1.0, 1.0, 1.0, 1.0 )
    }

    SubShader
    {
        Pass
        {
            Tags
			{
				// 普通のモデルを描画するとレンダラーに伝える
				"RenderType" = "Opaque"

				// 描画順番の指定、Geometryは不透明のモデル
				// とても重要な設定で半透明モデルをGeometryに設定すると多くの場合正常に描画されない
				"Queue" = "Geometry"

				// 環境光、メインのディレクショナルライト、頂点/SH ライト、ライトマップが適用される
				"LightMode" = "ForwardBase"
			}

			// CGPROGRAM~ENDCGの区間はCg言語での記述だよという宣言
            CGPROGRAM

			// 頂点シェーダーの指定
            #pragma vertex vert

			// フラグメントシェーダーの指定
            #pragma fragment frag

	        // 一部分だけ処理が違うシェーダーを生成する(詳しくはバリアントで検索すると良い)
	        #pragma shader_feature __ _SPEC_ON

			// Unity標準のヘルパー関数等のインクルード
	        #include "UnityCG.cginc"

			// 頂点データの定義
	        struct v2f
			{
	            float4 pos : SV_POSITION;
	            float2 uv : TEXCOORD0;
	            fixed4 light : COLOR0;
	        };

	        fixed4 _LightColor0;

	        // Diffuse
	        fixed _Diffuse;
	        fixed4 _DifColor;

	        // Specular
	        fixed _Shininess;
	        fixed4 _SpecColor;

	        // Ambient
	        fixed _Ambient;
	        fixed4 _AmbColor;

			// 頂点シェーダーの定義
	        v2f vert( appdata_base v )
	        {
	            v2f o;

	            // ワールド座標系への変換
	            float4 worldPos = mul( unity_ObjectToWorld, v.vertex );

	            // ビューとプロジェクトにより変換して、実際の描画座標を得る
	            o.pos = mul( UNITY_MATRIX_VP, worldPos );

	            // 正規化したライトの方向得る
	            float3 lightDir = normalize( _WorldSpaceLightPos0.xyz );

	            // ワールド座標系を打ち消してローカル空間での法線を得る
	            float3 worldNormal = normalize( mul( v.normal, (float3x3)unity_WorldToObject ) );

	            // カメラから頂点への方向を得る
	            float3 viewDir = normalize( _WorldSpaceCameraPos.xyz - worldPos.xyz );

	            // 環境光を計算
	            fixed4 amb = _Ambient * _AmbColor;

	            // 拡散光を計算(ライトの向きと法線が向かいあう程に強く色を反射させる)
	            fixed4 NdotL = max( 0.0, dot( worldNormal, lightDir ) * _LightColor0 );
	            fixed4 dif = NdotL * _Diffuse * _LightColor0 * _DifColor;

				// 環境光と拡散光を合成する
	            o.light = dif + amb;

	            // 鏡面反射光の計算、入射角とは反対方向に反射する
#if _SPEC_ON
	            float3 refl = reflect( -lightDir, worldNormal );
	            float RdotV = max( 0.0, dot( refl, viewDir ) );
	            fixed4 spec = pow( RdotV, _Shininess ) * _LightColor0 * ceil( NdotL ) * _SpecColor;
	            o.light += spec;
#endif

				// UV座標を渡す
	            o.uv = v.texcoord;

	            return o;
	        }

	        sampler2D _MainTex;

	        // 放射光
	        sampler2D _EmissionTex;
	        fixed4 _EmiColor;
	        fixed _EmiVal;

			// フラグメントシェーダー
	        fixed4 frag( v2f i ) : SV_Target
	        {
				// テクスチャの色を取得
	            fixed4 c = tex2D( _MainTex, i.uv );

				// 頂点シェーダーで計算した環境光、拡散光、鏡面反射光を合成する
	            c.rgb *= i.light;

	            // 放射光を合成
	            fixed4 emi = tex2D( _EmissionTex, i.uv ).r * _EmiColor * _EmiVal;
	            c.rgb += emi.rgb;

	            return c;
	        }

	        ENDCG
	    }
    }
}

コメントを記載しているうちに気づいたがピクセルごとに光の計算をしていない…
転載元を見ると頂点ごとに陰影計算を行って処理速度を軽くしたPhongな模様。

一応、それに気づけただけでも進展という感じがします。

投稿者: yametai2kin

在宅勤務か個人アプリ開発者も目指してます。 ゲームプログラマー歴20年の現役です。

コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト /  変更 )

Google フォト

Google アカウントを使ってコメントしています。 ログアウト /  変更 )

Twitter 画像

Twitter アカウントを使ってコメントしています。 ログアウト /  変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト /  変更 )

%s と連携中

<span>%d</span>人のブロガーが「いいね」をつけました。