こんばんは 予告通り 「三つ目時 」=テレビ東京標準時の木曜夜7時半の更新です。
だれもわからないだろうけどw
今回はノーマル法線とノーマルマップの合成に関してのはなしです。
●たっきゅんのガ☆チンコ開発日記 http://takkun.nyamuuuu.net/blog2/archives/1627
● ISO-IEC 18026 - Clause 8, Spatial reference frames
のほうから画像だけお借りしました 不都合があればお申し出ください
--------------------------------------------------------------------------------------------------------------------------------------------------------
ノーマルマップはオブジェクトの法線ベクトルをテクスチャ画像のカラー値に置き換えたものです。
接空間 (Tangent Space) ポリゴン面の法線に対して垂直に接する面の座標空間です。グーローシェーディングがかかっている場合はポリゴン面上の法線はポリゴンを構成する頂点の法線がグラデーションで補完された値になります。
図のようにノーマル法線方向をZ軸 ポリゴンのUV空間 U方向をX軸 V方向をY軸と見立てます。
ノーマルマップは X軸成分をRカラー値、Y軸成分をGカラー値に置き換えて ポリゴン上のポイントではテクスチャのRカラー値によって法線はTangent方向へ傾くように計算され Gカラー値によってBinormal方向に傾くように計算されます。 それによってポイントあたりの法線方向が歪められてライト計算に反映されると凹凸が表現できるという仕組みです。
テクスチャカラーのRGB成分は整数値の0~255または0~1で定義されていますので 0.5を中心としてそれより小さければマイナスの値 大きければプラスの値とみなして扱います。
Unityのシェーダで接空間 (Tangent Space)データを使用する場合はシェーダー入力の構造体にappdata_tanを指定します。 これで取得できるのはモデルのAssetデータの変換時に計算されたタンジェント値です。
struct appdata_tan {float4 vertex : POSITION;
float4 tangent : TANGENT;
float3 normal : NORMAL;
float4 texcoord : TEXCOORD0;
};
もちろんappdata_fullですべてのデータを取得しても構いませんがデータはなるべく使用するものだけにしたほうが軽量化出来ますので そこら辺はケースバイケースで判断してください。
binormalは以下の式で計算出来ます。
float3 binormal = cross( IN.normal, IN.tangent.xyz ) * IN.tangent.w;
あまりにざっくりなので解説しますと 2つのベクトルに対して外積(cross)をとることで直行するベクトルを算出することができます。(INはシェーダinput構造体を表します)
上の式ではnormalとtangentをcross(外積)とることでbinomal(副法線)を算出しています。ベクトルのw成分は
vector(ベクトルの方向(x,y,z)、ベクトルの大きさw)で定義されるベクトルの大きさ成分wをかけています。
cross(外積)を用いた2つのベクトルに直行するベクトルを求める式は たとえばポリゴンを自前で生成した時に
法線を求める用途などにも使われます。 あとポリゴン面の表裏判定などですかね。
これが法線計算の基本ですが これを理解していると例えば 波のようにポリゴンをうねらせたりといったモデルを
変形させた時にシェーダー内部でノーマルを計算させたり 自前でポリゴン生成をするときに法線の計算をしたりという応用ができます。
●TANGENTの計算式
| float3 tangent; float3 c1 = cross(Normal, float3(0.0, 0.0, 1.0)); if (length(c1)>length(c2)) tangent = normalize(tangent); |
●BINORMALの計算式
| float3 binormal; binormal = cross(Normal, tangent); binormal = normalize(binormal); |
補足>ノーマルベクトルをタンジェント空間に投影するコードが以下のようになっています ノーマルを再計算させたい時に参考にしてみてください
ノーマルベクトルとポリゴンのタンジェント空間座標(UVZ)の内積をとって角度変換し0~1の範囲に丸めているだけです。
float3 normalTS; normalTS.x = dot(normal, Input.binormal); normalTS.y = dot(normal, Input.tangent); normalTS.z = dot(normal, Input.normal); normalTS = normalize(normalTS); return float4(normalTS * 0.5 + 0.5, 1); |
< ここまでが基本の解説でした>
● NormalMap - Polycount Wiki http://wiki.polycount.com/NormalMap#TSNM
これを読めば大丈夫じゃないかというのが PolyCount Wiki のノーマルマップの解説です 英語ですけど解説図が多いのでほぼ眺めるだけでも内容は理解できると思います。
このサイトを紹介すればよかっただけのような気もしますね・・・
■今回のお題 ノーマルマップの合成で質感のクオリティアップを図ります。
●メリットとしては
モデル全体を覆うノーマルマップとデティール用のノーマルマップそれぞれが あまり大きなテクスチャを使用しなくてもモデルのデティールアップが図れることが挙げられます。たとえば壁などで細かい質感を出すために高解像度テクスチャを使用しなくてもわりとラフなテクスチャでも十分見栄えのする質感表現が可能になります。
ノーマルマップ自体は多少重た目なのですが、シェーダ内でLOD命令を使用してカメラからの距離に応じてシェーダの式を切り替えることで軽量化できるかと思います。 ビルドインシェーダーのウォーターシェーダ を参考にしてみてください。
■オーバーレイを使用する合成の例
オーバーレイの式を利用したノーマルマップの合成ですがオーバーレイはカラーの数値が
0.5を境に合成式を変化させます。 以前パーティクルのアイデア1で加算と乗算を重ねあわせましたが
それとほぼ同じです。たとえば背景に対してブレンド式を適用すればカラー部分を強調したままの
加算が再現できます。
BlendOverlayf(base, blend) (base < 0.5 ? (2.0 * base * blend) : (1.0 - 2.0 * (1.0 - base) * (1.0 - blend)))
| float4 norm = tex2D(_BumpMap, IN.uv_BumpMap); float4 norm2 = tex2D(_BumpMap2, IN.uv_BumpMap2); dest = norm2<0.5 ? 2*norm*norm2 : 1-2*(1-norm)*(1-norm2); dest = lerp(norm2, dest, _Opacity); o.Normal = UnpackNormal(dest); |
例) フォトショップのオーバーレイモードを使用してノーマルマップを調整することができます
NVIDIA のフォトショップ用ノーマルマップフィルタでノーマルマップを生成した場合すこし薄めのノーマルマップが仕上がると思います。 この場合フォトショップでレイヤーを使用してノーマルマップの深さをコントロールすることが出来て
下図のようにオーバーレイモードで同じノーマルマップをレイヤーに複製して重ねます。レイヤーのオペーシティ(濃度)をコントロールしてノーマルの深さをコントロールします。
もちろん部分的にレイヤー切り貼りで重ねれば すこしコントラストを強めたい部分だけに作用します。
- Normal map process tutorial by Ben "poopinmymouth" Mathis includes an example of painting out wavy lines in a baked normal map.
■ノーマライズを使用する合成の例
図はUDK(アンリアルエディターのシェーダーツリー)のノーマルマップをブレンドするサンプルです
内容は2つのマップのノーマル成分を加算してノーマライズするだけという単純なものですが オーバーレイ計算の式では条件判定に三項演算子を使用していました 以前解説したようにシェーダーはif条件式が苦手なのでできれば避けたいところです。ノーマライズ版のほうが多少軽めかもしれません。ただノーマライズもそんなに軽い関数ではないので状況によって実装して比べてみるしか無いですかね。
| float4 norm = tex2D(_BumpMap, IN.uv_BumpMap); float4 norm2 = tex2D(_BumpMap2, IN.uv_BumpMap2); o.Normal = normalize(float3(norm.xy + norm2 .xy, norm.z)); |
補足> normalize, dot, inversesqrt などのオペレータはUnityが最適なコードに変換するので自作のものを使用しないこと。 pow, exp, log, cos, sin, tan などの計算関数は非常に重たいのでなるべくテクスチャ参照(例えばカラーカーブをテクスチャで用意したもの)などを利用することがあげられています。
● Unity - Optimizing Graphics Performance
その他の方法については以下のリンクに詳しい情報が掲載されていますので参照してみてください
●Blending in Detail - Self Shadow http://blog.selfshadow.com/publications/blending-in-detail/
●シェーダを実装した画像
■ 『 Diffuse Detail Bump.shader 』
| Shader "custom/Diffuse Detail Bump"{ Properties { _Color ("Main Color", Color) = (1,1,1,1) _MainTex ("Base (RGB)", 2D) = "white" {} _DetailMap ("Detail (RGB)", 2D) = "gray" {} _BumpMap ("Normalmap", 2D) = "bump" {} _DetailBumpMap ("Normalmap(Detail)", 2D) = "bump" {} _DetailScale("DetailScale", Range (0.01, 1)) = 0.4 } SubShader { Tags { "RenderType"="Opaque" } LOD 250 CGPROGRAM #pragma surface surf Lambert #include “UnityCG.cginc” sampler2D _MainTex; sampler2D _DetailMap; sampler2D _BumpMap; sampler2D _DetailBumpMap; fixed4 _Color; half _DetailScale; struct Input { float2 uv_MainTex; float2 uv_DetailMap; float2 uv_DetailBumpMap; }; void surf (Input IN, inout SurfaceOutput o) { // デティールマップ(カラー)をテクスチャカラーと合成する fixed4 c = tex2D(_MainTex, IN.uv_MainTex) * _Color; c.rgb *= tex2D(_DetailMap,IN.uv_DetailMap).rgb*2; // 2つのノーマルマップから法線ベクトルをフェッチして合成する fixed4 Normal1 = tex2D(_BumpMap, IN.uv_MainTex); fixed4 Normal2 = tex2D( _DetailBumpMap ,IN.uv_DetailBumpMap) * float4 (_DetailScale,_DetailScale,0,0); //アウトプットノーマルに法線ベクトルをセットする o.Normal = UnpackNormal (Normal1+Normal2); // アウトプットカラーをセットする o.Albedo = c.rgb; o.Alpha = c.a; } ENDCG } Fallback "Diffuse" } |
● fixed4 Normal2 = tex2D( _DetailBumpMap ,IN.uv_DetailBumpMap) * float4 (_DetailScale,_DetailScale,0,0);
画像の例ではこの式の部分を以下の例のように書き換えてあります
例)
#include “UnityCG.cginc”
float4 _DetailBumpMap_ST;
……………….
float2 UVCoord = IN.uv_MainTex * _DetailBumpMap_ST.xy
Normal2 = tex2D( _DetailBumpMap , UVCoord) * float4 (_DetailScale,_DetailScale,0,0);
UVがメインテクスチャと同じものを使用する場合はテクスチャスロットのoffsetの数値を使用してこのように
記述します。
”テクスチャ名+_ST”のUnityマクロの使用法は以前の”パーティクルのアイデア2”の記事を参照してください。
Tips>
ノーマルマップは基本的にRとGのチャンネルしか使用しないためBやAlphaチャンネルは空いた状態ですので、テクスチャの枚数を圧縮したい場合はこの空きチャンネルを利用することが出来ます。 一般にはAO(アンビエントオクルージョンマップ)やマスクテクスチャなどをいれるようです。 スマホなどのテクスチャ枚数が処理速度に影響が出やすいものはなるべくあいているチャンネルを利用してテクスチャ枚数を減らすことでメモリ効率がよくなります。
記事が長くなりすぎましたので ノーマルマップ生成方法やツールの使用法などの解説はまた後日にします。
その気になれば10年後も可能とい(ry
もうすこし内容をつめようと思ってたんですけどねー すこし中途半端な感じになってしまいました次回への課題にします。
ではまた






























