本日はShader勉強枠です。
〇法線方向に展開する。
3Dオブジェクトはメッシュによって構成されています。Unityの場合メッシュは数多くのポリゴンの集合によって形成されています。
このポリゴン単体は立体ではなく空間上に形成された面になりますが、その面の垂線(垂直方向に引いた線)が法線になります。
今回Shader側でメッシュを法線方向に展開します。これによって何ができるかですが、太った見た目のキャラクターなどが3Dモデル自体を弄ることなくShaderとマテリアルによって行えます。

今回も前回扱ったDiffuse LocalSlice CullOffShaderを基に追加していきます。
●Diffuse LocalSlice CullOff
//シンタックスShaderの名前と格納場所
Shader "Example/Diffuse LocalSlice CullOff"{
Properties{
_MainTex("Texture",2D) = "white"{}
_BumpMap("Bumpmap", 2D) = "bump"{}
_RimColor("Rim Color", Color) = (0.26,0.19,0.0)
//リムpowerのレンジ defaultでは3に設定される。
_RimPower("Rim Power", Range(0.5,8.0)) = 3.0
//SecondryTexture
_Detail("Detail", 2D) = "gray"{}
_SlicePower1("SlicePower1",Range(0,1)) = 0.1
_SlicePower2("SlicePower2",Range(0,10)) = 5
_SlicePower3("SlicePower3",Range(0,1)) = 0.5
}
//Shaderの中身サブシェーダーではレンダリングパスの一覧を定義し、任意のオプションとしてすべてのパスに共通の State (状態)を設定します。追加で、サブシェーダーの特定の Tag を設定できます。
SubShader{
//RenderingType
Tags{"RenderType" = "Opaque"}
//メッシュの裏表両面を描画
Cull Off
CGPROGRAM
#pragma surface surf Lambert
//Input構造体はテクスチャ座標として扱うものを記述します。
struct Input {
float2 uv_MainTex;
float2 uv_BumpMap;
float3 viewDir;
float2 uv_Detail;
//ワールド座標を使用しないためコメントアウト
//float3 worldPos;
};
sampler2D _MainTex;
sampler2D _BumpMap;
float4 _RimColor;
float _RimPower;
sampler2D _Detail;
float _SlicePower1;
float _SlicePower2;
float _SlicePower3;
void surf(Input IN, inout SurfaceOutput o) {
o.Albedo = tex2D(_MainTex, IN.uv_MainTex).rgb;
//_Detailのrgbをo.Albedoに乗算
o.Albedo *= tex2D(_Detail, IN.uv_Detail).rgb * 2;
clip(frac((IN.uv_MainTex.x + IN.uv_MainTex.y * _SlicePower1) * _SlicePower2) - _SlicePower3);
o.Normal = UnpackNormal(tex2D(_BumpMap, IN.uv_BumpMap));
//rim = 1.0 -viewDirの正規化したものとノーマルマップの内積(0~1の値を取る)
half rim = 1.0 - saturate(dot(normalize(IN.viewDir), o.Normal));
//Emissionに代入 propertyのRimColorの値(RGB)× rim^_RimPower= 0~1の値を取る 参考https://docs.microsoft.com/en-us/windows/win32/direct3dhlsl/dx-graphics-hlsl-pow
o.Emission = _RimColor.rgb * pow(rim, _RimPower);
}
ENDCG
}
Fallback "Diffuse"
}
〇頂点シェーダーを扱う
ここでは今までのShaderとは違うアプローチを行います。
今まではオブジェクト自体の形状を変化させることなくノーマルマップや見た目や一部を描画しないことによって変化を与えてきました。
これはサーフェースシェーダーと言います。
SubShader{
CGPROGRAM
#pragma surface surf Lambert
struct Input {
};
void surf(Input IN, inout SurfaceOutput o) {
}
ENDCG
}
Fallback "Diffuse"
#pragma surface surf Lambert
としてサーフェースシェーダーをコンパイル命令しています。
サーフェースシェーダーのコンパイルは
//#pragma surface (サーフェースシェーダーの関数名、ここではsurf)(使用するライティングモデル) #pragma surface surfacefunction lightModel [optionalparams]
のように記述します。
ここまではsurfという関数名を使ってLumbertというライティングモデルを使用していました。
今回はオブジェクトの3Dモデルのメッシュ自体の法線情報を扱います。 これは頂点シェーダーと呼ばれるものを用います。
頂点シェーダーはoptionalparamsで追加することができます
#pragma surface surf Lambert vertex:vert
このように表記することで頂点シェーダーを用いることができます。
●頂点シェーダーの関数を追加する
次にサーフェースシェーダーの関数surfと同様頂点シェーダーの関数を追加します。
void vert(inout appdata_full v)
{
}
この引数のinout appdata_full vは頂点シェーダーを用いる時の決まり事のようです。
処理を記述します。
void vert(inout appdata_full v) {
v.vertex.xyz += v.normal * _Amount;
}
このv.vetex.xyzは頂点、v.normalは法線ベクトルになります。 _Amountは変数です。
つまり
void vert(inout appdata_full v) {
//頂点=法線方向×_Amount
v.vertex.xyz += v.normal * _Amount;
}
という意味になります。
サーフェースシェーダーのsurf関数同様数値を扱うために直前で_Amountを宣言します。
float _Amount;
void vert(inout appdata_full v) {
//頂点=法線方向×_Amount
v.vertex.xyz += v.normal * _Amount;
}
Propetiesに_Amountを追加します。
Properties{
_Amount("Extrusion Amount", Range(-1,1)) = 0.5
}
これで法線方向にメッシュを展開するShaderが完成しました。

以上でオブジェクトを法線方向に展開するShaderが完成しました。
//シンタックスShaderの名前と格納場所
Shader "Example/Diffuse expansion"{
Properties{
_MainTex("Texture",2D) = "white"{}
_BumpMap("Bumpmap", 2D) = "bump"{}
_RimColor("Rim Color", Color) = (0.26,0.19,0.0)
//リムpowerのレンジ defaultでは3に設定される。
_RimPower("Rim Power", Range(0.5,8.0)) = 3.0
//SecondryTexture
_Detail("Detail", 2D) = "gray"{}
_SlicePower1("SlicePower1",Range(0,1)) = 0.1
_SlicePower2("SlicePower2",Range(0,10)) = 5
_SlicePower3("SlicePower3",Range(0,1)) = 0.5
_Amount("Extrusion Amount", Range(-1,1)) = 0.5
}
//Shaderの中身サブシェーダーではレンダリングパスの一覧を定義し、任意のオプションとしてすべてのパスに共通の State (状態)を設定します。追加で、サブシェーダーの特定の Tag を設定できます。
SubShader{
//RenderingType
Tags{"RenderType" = "Opaque"}
Cull Off
CGPROGRAM
//関数surfでサーフェースシェーダーをLambertのライティングモデルで頂点シェーダーも扱う
#pragma surface surf Lambert vertex:vert
//Input構造体はテクスチャ座標として扱うものを記述します。
struct Input {
float2 uv_MainTex;
float2 uv_BumpMap;
float3 viewDir;
float2 uv_Detail;
//ワールド座標を使用しないためコメントアウト
//float3 worldPos;
};
float _Amount;
void vert(inout appdata_full v) {
//頂点=法線方向×_Amount
v.vertex.xyz += v.normal * _Amount;
}
sampler2D _MainTex;
sampler2D _BumpMap;
float4 _RimColor;
float _RimPower;
sampler2D _Detail;
float _SlicePower1;
float _SlicePower2;
float _SlicePower3;
void surf(Input IN, inout SurfaceOutput o) {
o.Albedo = tex2D(_MainTex, IN.uv_MainTex).rgb;
//_Detailのrgbをo.Albedoに乗算
o.Albedo *= tex2D(_Detail, IN.uv_Detail).rgb * 2;
clip(frac((IN.uv_MainTex.x + IN.uv_MainTex.y * _SlicePower1) * _SlicePower2) - _SlicePower3);
o.Normal = UnpackNormal(tex2D(_BumpMap, IN.uv_BumpMap));
//rim = 1.0 -viewDirの正規化したものとノーマルマップの内積(0~1の値を取る)
half rim = 1.0 - saturate(dot(normalize(IN.viewDir), o.Normal));
//Emissionに代入 propertyのRimColorの値(RGB)× rim^_RimPower= 0~1の値を取る 参考https://docs.microsoft.com/en-us/windows/win32/direct3dhlsl/dx-graphics-hlsl-pow
o.Emission = _RimColor.rgb * pow(rim, _RimPower);
}
ENDCG
}
Fallback "Diffuse"
}