タイトルの通り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;
float4 _offset = tex2Dlod(_DisplaceMap, float4(v.uv, 0, 0));
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);
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
);
float4 tex = tex2D(_MainTex, i.uv);
fixed4 col;
col.rgb = tex.rgb * diff + spec*tex.rgb;
return col;
}
ENDCG
}
}
}
なお、拡散反射と鏡面反射の部分は下記のサイトを参考にしました.
blog.applibot.co.jp
頂点テクスチャフェッチ(VTF)とは
通常テクスチャのサンプリングはフラグメントシェーダー内で行いますが、バーテックスシェーダー内でサンプリングを行うことを頂点テクスチャフェッチ(Vertex Texture Fetch)というらしいです. 特別な手続きは特に必要なく、VTF用の関数を用いるだけです.
float4 _offset = tex2Dlod(_DisplaceMap, float4(v.uv, 0, 0));
tex2D() ではなく tex2Dlod() を利用することだけ覚えておきましょう.
ここで注意なのがFS内で使用するtex2D()とは異なり、 uv座標をfloat4で指定します .
docs.microsoft.com
docs.microsoft.com
Standardシェーダーとの比較
上記の自作シェーダーとUnityにデフォルトで備わっているStandardシェーダーをオブジェクトに適用してその見栄えを比較してみましょう.
テクスチャにはambientCGのTiles 107を利用しました.
ambientcg.com
公平な比較をするために、自作シェーダーで対応しているAlbedo、Normal Map、Height Mapの3テクスチャを適用した場合の比較になります. またStandardシェーダーのテクスチャ以外の値はデフォルトです.
思いのほか自作シェーダーもよい感じですね. しかし、自作シェーダーとなるといずれ光の表現などに限界を感じることでしょう.
Standard Assetsのシェーダーを使おう
さて、これまでディスプレイスメントシェーダーを自作してきたわけですが、大きな問題が2つあります.
- ディスプレイスメントしたことによる法線の再計算をしていない
- ポリゴンの頂点数が少ないときにうまくディスプレイスメント出来ない
1に関してはNormal Mapを使ったりすればよいですし、再計算を実装することもそこまで難しくはありません. 2に関してもUnityにはテッセレーションに関する便利な機能があります. しかしながら、これらの機能を盛り込んだディスプレイスメントシェーダーがStandard Assets内に存在するので、特に理由がない限りはこれを使うべきでしょう.
assetstore.unity.com
参考
docs.unity3d.com
tsumikiseisaku.com
www.wwwmaplesyrup-cs6.work