こんにちは、darkivyです。
g_rimlightのトゥーン化にたぶん成功したので記事にしました。
HLSLのことを何も知らない人間がよくわからないまま改造したらなんとかなっちゃったという代物ですので、改造はバックアップを取った上で自己責任でお願いします。
なお、改造にはVSCodeを使うことを推奨します。
g_rimlightの仕組み
大体光源エフェクトというのは、どこかで加算する範囲を取得し色を加算している…と勝手に思ってます。
だからColor.rgbというワードを検索してください。すると…
187行目
dotPix.r = pow(dotPix.r , RimIndex+abs(ObjRot.z*180/3.14)) ;
addColor.rgb = saturate(dotPix.r+quoGap)*saturate(mask+quoDot+DotOnly)*(RimColor+ObjXYZ)*RimPow;
result.rgb = result.rgb + addColor.rgb * Tr ;
// result.rgb = mask ; // check Gap
result.a = max(orgPix.a,result.r+result.g+result.b) ;
result = saturate(result) ;
return result;
addColorでresultに色を加算しているのがわかります。
addcolorの計算式にはmaskというのが出てきますね。
試しに、
// result.rgb = mask ; // check Gap
のところのコメントアウトを外してみましょう。(VSCodeでコメントアウトのオンオフにはCtrl+/を使います)
すると
このような画面になったと思います。つまりmaskがaddcolorで加算する色の範囲に関わっていると予測できます。
maskをトゥーン化できればg_rimlightもトゥーンになるのではないでしょうか。
トゥーンマップ
ではどうやってトゥーン化すればよいのでしょうか。
そこでこちらの記事を参考にしました。
ライティングの計算を行った後、その値をUV座標のX成分としてこの画像からライティングの値をフェッチします。
まずエフェクト用のトゥーンマップを用意します。ライティングの計算はリアル寄りに行い、うまくこのトゥーンマップのX座標に当てはめて色を決めるようです。
大事なのはこの最後のところですね。
//計算結果よりトゥーンシェーダー用のテクスチャから色をフェッチする
float4 Col = toonMap.Sample(Sampler,float2(p,0.0f));
pがリアルな陰影の結果です。これに近しいものを用意していこうと思います。
トゥーンマップには
この2つを用意します。黒が少ない方をadd1.png、黒が多い方をadd2.pngとします。
ところでこの「指定したテクスチャのX座標から陰影の色を決める」という仕様、見覚えがありませんか。
私はここでHAToonを思い出しました。
というわけでHAToonのソースコードを拝借することにしました。
HAToonからトゥーンマップ読み込み機能をお借りする
見るのは設定例のスタンダード.fxとHAHEAD.hlslです。
画像を読み込んでるっぽい場所を探すと冒頭に
#define BLENDDIFFUSE0TEXTURE "DiffuseMulSelf.png"
とあります。
そしてそのすぐ下に
DEFINE_TEXTURE_SAMPLER(BlendDiffuse0Texture, BlendDiffuse0TextureSampler, BLENDDIFFUSE0TEXTURE, Clamp, Clamp)
とあります。
ただこれをそのまま移植してもエラーが起きます。DEFINE_TEXTURE_SAMPLERが定義されてないよーって言われてしまいます。
そこでHAHEAD.hlslを見に行きましょう。
すると一番上に
#define DEFINE_TEXTURE_SAMPLER(textureName, samplerName, resourceName, uMode, vMode) \\
texture2D textureName < string ResourceName = resourceName; >; \\
sampler samplerName = sampler_state \\
{ \\
Texture = <textureName>; \\
Filter = Anisotropic; \\
AddressU = uMode; \\
AddressV = vMode; \\
MaxAnisotropy = 16; \\
};
とあります。ここでDEFINE_TEXTURE_SAMPLERを定義していたわけですね。
つまりこれらを合わせると、
g_rimlight_01.fxの14行目に
#define DEFINE_TEXTURE_SAMPLER(textureName, samplerName, resourceName, uMode, vMode) \\
texture2D textureName < string ResourceName = resourceName; >; \\
sampler samplerName = sampler_state \\
{ \\
Texture = <textureName>; \\
Filter = Anisotropic; \\
AddressU = uMode; \\
AddressV = vMode; \\
MaxAnisotropy = 16; \\
};
#define ADDTEXTURE "add1.png"
DEFINE_TEXTURE_SAMPLER(Addtexture, AddtextureSampler, ADDTEXTURE, Clamp, Clamp)
#define ADDTEXTURE2 "add2.png"
DEFINE_TEXTURE_SAMPLER(Addtexture2, AddtextureSampler2, ADDTEXTURE2, Clamp, Clamp)
と追加してください。これで画像が読み込めました。
しかも嬉しいことに「sampler」があるんですよね。samplerがなんなのかさっぱりわかりませんが…。
Maskをトゥーン化する
いよいよこの段階なんですけど結論から書いてしまいます。
210行目あたりに戻って、
dotPix.r = pow(dotPix.r , RimIndex+abs(ObjRot.z*180/3.14)) ;
float3 toondotPix=tex2D(AddtextureSampler2,float2(dotPix.r,0));
float3 toonmask=tex2D(AddtextureSampler, float2(mask,0));
addColor.rgb = saturate(toondotPix+quoGap)*saturate(toonmask+quoDot+DotOnly)*(RimColor+ObjXYZ)*RimPow;
result.rgb = result.rgb + addColor.rgb * Tr ;
result.a = max(orgPix.a,result.r+result.g+result.b) ;
result = saturate(result) ;
return result;
となるようにします。
やっていることとしては、
float3 toondotPix=tex2D(AddtextureSampler2,float2(dotPix.r,0));
float3 toonmask=tex2D(AddtextureSampler, float2(mask,0));
を追加し、addColor.rgbを
addColor.rgb = saturate(toondotPix+quoGap)*saturate(toonmask+quoDot+DotOnly)*(RimColor+ObjXYZ)*RimPow;
に書き換えています。dotpix.rも遠距離からのリムライトの見え方に関わってくるっぽいのでトゥーン化しちゃいました。
toondotPixの構文はHAToonのスタンダード.fxの
float4 value = tex2D(BlendDiffuse0TextureSampler, float2(parameters.LightNormal, 0));
をまんま真似したんですがうまくいきました。理屈はわかっていません…。
おまけ
g_rimlightをエフェクト割当のMainで非表示にし、AL_EmitterRTタブでオンにすると発光します。