こちらで実装されているファーシェーダの仕組みをUnityに移植してみました。
参考にしたほうがWebGLで実装されていて、動きもあってだいぶふさふさな感じです。
これをUnityでやれないかとあれこれ調べて、なんとか同じような感じになるようにしてみました。
だいぶ力技部分もありますが・・w
(余談ですが、WebGLコンテンツを参考にする場合はFirefoxのシェーダエディタが超絶便利です)
ちなみに作ったサンプルはGitHubに上げてあります。
(動くサンプルもあります)
[2014.09.24 追記]
バンプマッピングによるライティングを追加してみました。(サンプル)
まずざっくり概要から
そもそもファーシェーダってなにかというと、3Dでふさふさな「ファー」を表現するシェーダです。
こちらのファーシェーダーの記事が概念を理解しやすいと思います。
実装方法や実現方法のアプローチはいくつかありますが、上記で書かれているのも今回やってみたのも同じアプローチのものです。
層を重ねる
記事の図を見てもらえれば分かりますが、ひとつのモデルを少しずつ膨らませて、何層にも分けてレンダリングするアプローチです。
要は 毛の断面図を何層も重ねたら毛っぽくなるよね ってことです。
記事では、バンプマッピングを使って毛にもライティングを施しさらにリアルにしていますが、今回はそこまではやっていません。
テクセルから色をどう取るか
ちなみに今回は以下のテクスチャを使いました。
このテクスチャは毛の断面ではありません。
なので断面になるようにする工夫が必要です。
そこで、以下のような断面っぽいテクスチャを別途用意します。
さて、これをどう使うのか。
毛っぽく見せるわけですから、最初のテクスチャから色を取るところと取らないところを決めればいいわけです。
そうすれば毛の断面図のようなテクスチャとして使えるわけです。
ちなみに余談ですが、テクセルは「テクスチャのピクセル」でテクセルです。
フラグメントシェーダ
結論から言うと、 上記ふたつ目のテクスチャの中のテクセルが「ある閾値」以下の場合に破棄する という実装を行います。
(フラグメントシェーダで計算中のピクセルを破棄するにはdiscard
を使います)
以下がフラグメントシェーダのコードです。
やってること自体はほんとにシンプルですね。
// フラグメントシェーダへの入力構造体
struct vert2frag {
float4 position : POSITION;
float2 uv : TEXCOORD0;
float2 uv2 : TEXCOORD1;
};
// ...中略...
// フラグメントシェーダ
float4 frag(vert2frag i) : COLOR {
float4 map = tex2D(_SubTex, i.uv2);
if (map.a <= 0.0 || map.b < FUR_OFFSET) {
discard;
}
float4 color = tex2D(_MainTex, i.uv);
color.a = 1.1 - FUR_OFFSET;
return color;
}
フラグメントシェーダではふたつのテクスチャを参照しています。
_MainTex
と_SubTex
です。
_MainTex
は普通のテクスチャです。上記の画像では上部の色がついたほうです。
そしてキモとなるのが_SubTex
で、白黒のほうのテクスチャです。
まず最初にサブテクスチャから、現在計算中のテクセルを取り出します。
float4 map = tex2D(_SubTex, i.uv2);
そしてその取り出したテクセル情報から、レンダリングすべきかどうかを判断する処理を行います。
if (map.a <= 0.0 || map.b < FUR_OFFSET) {
discard;
}
アルファが0以下か、あるいは青成分(map.b
)が特定の値以下だったらピクセルをレンダリングしない(discard
)という意味です。
FUR_OFFSET
はファーのオフセットで、今回は60層に分けてレンダリングしているので「層 / 60」の値が、層ごとに渡ってきます。
(例えば30層目だったら「30 / 60 = 0.5」)
バーテックスシェーダ
フラグメントシェーダは数行からなるだいぶシンプルな実装でした。
バーテックスシェーダのほうは若干複雑です。
まずはコードから。
// 頂点シェーダへの入力構造体
struct vertInput {
float4 vertex : SV_POSITION;
float4 normal : NORMAL;
float2 texcoord : TEXCOORD0;
float2 texcoord2 : TEXCOORD1;
};
// ...中略...
uniform float4 _Gravity;
vert2frag vert(vertInput v) {
const float spacing = 0.35;
vert2frag o;
float3 forceDirection = float3(0.0);
float4 position = v.vertex;
// Wind
forceDirection.x = sin(_Time.y + position.x * 0.05) * 0.2;
forceDirection.y = cos(_Time.y * 0.7 + position.y * 0.04) * 0.2;
forceDirection.z = sin(_Time.y * 0.7 + position.y * 0.04) * 0.2;
float3 displacement = forceDirection + _Gravity.xyz;
float displacementFactor = pow(FUR_OFFSET, 3.0);
float4 aNormal = v.normal;
aNormal.xyz += displacement * displacementFactor;
float4 n = normalize(aNormal) * FUR_OFFSET * spacing;
float4 wpos = float4(v.vertex.xyz + n.xyz, 1.0);
o.position = mul(UNITY_MATRIX_MVP, wpos);
o.uv = v.texcoord;
o.uv2 = v.texcoord2 * 20.0;
return o;
}
まず、バーテックスシェーダへの入力として以下の情報を受け取ります。
// 頂点シェーダへの入力構造体
struct vertInput {
float4 vertex : SV_POSITION;
float4 normal : NORMAL;
float2 texcoord : TEXCOORD0;
float2 texcoord2 : TEXCOORD1;
};
頂点情報として位置と法線、ふたつのテクスチャのUV座標が渡ってきているわけですね。
(: SV_POSITION
などはセマンティクスと呼ばれています。この辺りは話がそれるので割愛します)
層のレンダリング
コードにはforceDirection
なる変数がありますが、このあたりは実際に動かした時に風にふかれているような演出をしているだけなのでなくても動きます。
キモとなる部分は以下の部分です。
float4 n = normalize(aNormal) * FUR_OFFSET * spacing;
float4 wpos = float4(v.vertex.xyz + n.xyz, 1.0);
o.position = mul(UNITY_MATRIX_MVP, wpos);
o.uv = v.texcoord;
o.uv2 = v.texcoord2 * 20.0;
最初のn
が、実際にモデルをふくらませるための係数になってます。
(aNormal
は上記の風を考慮した状態なので、単純にv.normal
を使っても動きます)
法線情報に、ファーのオフセットとspacing
という係数を掛けたものを、実際の頂点位置(v.vertex
)に足しているわけですね。
このspacing
の値を変えるとファーの層の間隔が変化します。(サンプルでは0.35)
注意点
最初、どちらもfloat4
なのでそのまま足していました。
float4 wpos = v.vertex + n;
しかし、こうしてしまうとw
の値がおかしくなり、モデルの形状がおかしくなってしまうので、xyz
のみ計算して、w
の値を1.0
にする必要があります。
UV座標を計算
最後の部分がUV座標に関する計算です。
といっても、メインのテクスチャはそのまま、サブテクスチャのみ20倍しているだけですね。
この20倍している部分は別になくても問題ありません。単に、使用している断面のテクスチャの細かさを調整しているだけなので。
なので10倍や30倍などと値を変えると毛の雰囲気も変わります。
ちなみに5倍くらいにしたやつだとこんな感じになります↓(ふさふさ感割増)
複数Passで層をレンダリング
今回移植するにあたって悩んだところが、ひとつのシェーダでどうやって層をレンダリングさせるか、でした。
参考にしたWebGLの実装は、事前にモデルを60個生成して、それぞれレンダリングすることで実現していました。
ただ、Unityの場合はそうした実装ができません。
しかしUnityにはPass
というコマンドがあり、このPass
がまさにひとつのモデルをレンダリングする役割を持っています。
なので、今回の実装が力技だと書いたのは、このPass
を単純に60回書いているからです。
Passの例
Pass {
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#define FUR_OFFSET 0.016
#include "FurHelper.cginc"
ENDCG
}
// 以下、0.016ずつ増えるPassを60回書く
この、#define FUR_OFFSET 0.016
となっている部分が微妙に異なるPass
を60回書いている、というわけですね。(他にいい方法あったら教えて下さいorz)
(もちろん、実際は60回分のPass
を書き出してくれるプログラムを別に作ってますが・・)
ちなみにこのPass
を複数回書くヒントはUnityのForumで話されていたものを参考にしました。
だいぶ冗長な書き方ではありますが、とりあえずUnity上で動くものが作れたのでよしとしますw
Unity AssetStoreに並びました
Simple Fur Shader | Unity Asset Store
※ AssetStoreのものはさらに、 通常のライティングとバンプマッピングによるライティングを施したもの となっています。
KAYACのLPでも使ってます
KAYACのWebGLサイト制作募集のサイト。
ここでもファーシェーダが使われています。実装方法はほぼ同じ。
こちらのサイトではさらにいくつかのシェーダを切り替えてロゴの見た目を変えられるようになってます。
iOS8なら実際に動いているところが見れるので見てみてください( *'-')