タイトルの通りUnityでディスプレイスメントシェーダーを実装しました.
シェーダースクリプト
Shader "Custom/Displace" { Properties { _MainTex ("Texture", 2D) = "white" {} _DisplaceMap ("DisplaceMap", 2D) = "white" {} _OffsetStrength("OffsetStrength", Range(0,10)) = 1 _NormalMap("NormalMap", 2D) = "white" {} _Spec("SpacularStrength", Range(0,10)) = 1 } 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; float3 normal : NORMAL; float4 tangent : TANGENT; }; struct v2f { float2 uv : TEXCOORD0; float4 vertex : SV_POSITION; float3 viewDir: TEXCOORD1; float3 lightDir: TEXCOORD2; }; sampler2D _MainTex; sampler2D _DisplaceMap; sampler2D _NormalMap; float _OffsetStrength; float _Spec; v2f vert (appdata v) { v2f o; // VS内で使えるテクスチャサンプリング関数 float4 _offset = tex2Dlod(_DisplaceMap, float4(v.uv, 0, 0)); // rgbaをサンプリングしたのちrチャネルを抽出 // 画像が4チャネルでもグレースケールならr=g=b float offset = _offset.r; // モデル空間での法線に沿って頂点を変形させる v.vertex += float4( v.normal * offset * _OffsetStrength, 0 ); o.vertex = UnityObjectToClipPos(v.vertex); o.uv = v.uv; // ライトとビューから頂点へのベクトルをオブジェクト空間にしたのち // タンジェントスペースに変換する TANGENT_SPACE_ROTATION; o.lightDir = mul(rotation, ObjSpaceLightDir(v.vertex)); o.viewDir = mul(rotation, ObjSpaceViewDir(v.vertex)); return o; } fixed4 frag(v2f i) : SV_Target { // 正規化 i.lightDir = normalize(i.lightDir); i.viewDir = normalize(i.viewDir); // sample Normal in Tangent Space float3 normal = UnpackNormal( tex2D(_NormalMap, i.uv) ); normal = normalize(normal); float3 halfDir = normalize(i.lightDir + i.viewDir); float4 diff = saturate( dot(normal, i.lightDir) ); float3 spec = pow( max(0, dot(normal, halfDir)), _Spec * 128.0 ); // sample Albedo float4 tex = tex2D(_MainTex, i.uv); // add diffuse and specular effects fixed4 col; col.rgb = tex.rgb * diff + spec*tex.rgb; return col; } ENDCG } } }
なお、拡散反射と鏡面反射の部分は下記のサイトを参考にしました.
頂点テクスチャフェッチ(VTF)とは
通常テクスチャのサンプリングはフラグメントシェーダー内で行いますが、バーテックスシェーダー内でサンプリングを行うことを頂点テクスチャフェッチ(Vertex Texture Fetch)というらしいです. 特別な手続きは特に必要なく、VTF用の関数を用いるだけです.
// VS内で使えるテクスチャサンプリング関数 float4 _offset = tex2Dlod(_DisplaceMap, float4(v.uv, 0, 0));
tex2D() ではなく tex2Dlod() を利用することだけ覚えておきましょう.
ここで注意なのがFS内で使用するtex2D()とは異なり、 uv座標をfloat4で指定します .
Standardシェーダーとの比較
上記の自作シェーダーとUnityにデフォルトで備わっているStandardシェーダーをオブジェクトに適用してその見栄えを比較してみましょう.
テクスチャにはambientCGのTiles 107を利用しました.
公平な比較をするために、自作シェーダーで対応しているAlbedo、Normal Map、Height Mapの3テクスチャを適用した場合の比較になります. またStandardシェーダーのテクスチャ以外の値はデフォルトです.
思いのほか自作シェーダーもよい感じですね. しかし、自作シェーダーとなるといずれ光の表現などに限界を感じることでしょう.
Standard Assetsのシェーダーを使おう
さて、これまでディスプレイスメントシェーダーを自作してきたわけですが、大きな問題が2つあります.
- ディスプレイスメントしたことによる法線の再計算をしていない
- ポリゴンの頂点数が少ないときにうまくディスプレイスメント出来ない
1に関してはNormal Mapを使ったりすればよいですし、再計算を実装することもそこまで難しくはありません. 2に関してもUnityにはテッセレーションに関する便利な機能があります. しかしながら、これらの機能を盛り込んだディスプレイスメントシェーダーがStandard Assets内に存在するので、特に理由がない限りはこれを使うべきでしょう.