Triplanar Mapping

下記チュートリアルで紹介されているTriplanar Mappingをやってみます。 www.ronja-tutorials.com

Triplanar Mappingとは

Triplanar Mappingとはどういうものかと言うと、上方向、横方向からテクスチャを貼り付けるようなイメージです。 モデルのUVを参照せず、world座標を参照するため、他のモデルとの境界線を目立たなくさせる事ができます。 地形などを作るのに向いていそうです。


頂点shader

まず、頂点shaderの処理。UVではなくworld空間の座標を参照するようにします。

また、3方向から投影するテクスチャをどれだけ混ぜるか? の指標とするため、 world空間でのモデルの法線を求めます。

struct appdata{
    float4 vertex : POSITION;
    float3 normal : NORMAL;
};

struct v2f{
    float4 position : SV_POSITION;
    float3 worldPos : TEXCOORD0;
    float3 normal : NORMAL; // world空間での法線
};

v2f vert(appdata v){
    v2f o;
    o.position = UnityObjectToClipPos(v.vertex);

    // 頂点のworld座標を求める
    float4 worldPos = mul(unity_ObjectToWorld, v.vertex);
    o.worldPos = worldPos.xyz;

    // world空間での法線を求める
    o.normal = UnityObjectToWorldNormal(v.normal);
    return o;
}

world空間でのモデルの法線の算出について、なぜその計算になるのか? についてはこちらが詳しかったです。 raytracing.hatenablog.com

fragment shader

次にfragment shaderの処理。

fixed4 frag(v2f i) : SV_TARGET{
    // TRANSFORM_TEXを使ってテクスチャのrepeatなどを考慮
    float2 uv_front = TRANSFORM_TEX(i.worldPos.xy, _MainTex);
    float2 uv_side = TRANSFORM_TEX(i.worldPos.zy, _MainTex);
    float2 uv_top = TRANSFORM_TEX(i.worldPos.xz, _MainTex);

TRANSFORM_TEXをする事でtexutreのoffset, tilingが反映されます。 UnityのTRANSFORM_TEXマクロ | 生存日記

// 先程作ったworld座標ベースのuvでtextureを読みます。
fixed4 col_front = tex2D(_MainTex, uv_front);  // xy平面から貼り付けるtexture
fixed4 col_side = tex2D(_MainTex, uv_side); // zy平面から貼り付けるtexutre
fixed4 col_top = tex2D(_MainTex, uv_top); // 上 (xz平面) から貼り付けるtxture

3方向から投影するテクスチャをどれだけ混ぜるか? の指標となる重みを定義します。 法線はマイナスの値も取るので、ここは絶対値を使います。 マイナスの値を採用すると、下方向を向いた面が黒くなってしまいます。

// world座標の法線をもとにした重みを定義
float3 weights = i.normal;
// 法線が単純に縦方向を向いているのか? 横方向を向いているのか? の判断だけに使うので、絶対値を使う。
weights = abs(weights);
// 全部足して1になるよう調整する。暗くなるのを避けるため。
weights = weights / (weights.x + weights.y + weights.z);

// それぞれの重みを影響させる
col_front *= weights.z;
col_side *= weights.x;
col_top *= weights.y;

// 3方向を合成
fixed4 col = col_front + col_side + col_top;

// 外部propertyでの色調整を反映
col *= _Color;
return col;

ここで、上方向からのテクスチャを草に入れ替えてみます。 上方向からのテクスチャをpropertyとして持たせ、反映させるようコードを修正します。

_MainTexTop ("Texture", 2D) = "white" {}

//...

sampler2D _MainTexTop;
float4 _MainTexTop_ST;

//...
float2 uv_top = TRANSFORM_TEX(i.worldPos.xz, _MainTexTop);

上方向を向いている部分に草が生えました。 f:id:ssr_maguro:20200121193402p:plain

しかし、なんだか草部分がボンヤリしていますね。

期待している状態はこんな感じです。 f:id:ssr_maguro:20200121193800p:plain

このための修正をしていきます。

より方向の影響を強くする

係数とpowを使って重みを際立たせます。

powを使うとどう値が変化するのかは下記にて。 ssr-maguro.hatenablog.com

//...

_Sharpness("Blend Sharpness", Range(1, 64)) = 1

//...

float _Sharpness;

//...

weights = abs(weights);
// powを使って重みをくっきりさせる
weights = pow(weights, _Sharpness);
weights = weights / (weights.x + weights.y + weights.z);

//...

完成、ブラッシュアップ

これで期待した絵になりました。

しかしまだ色々問題があり、下から見ても草が生えていたりします。

f:id:ssr_maguro:20200121195711p:plain

次はこういった問題を解消し、草が生える範囲を調整できたりするようにしてみます。 続く。

他の参考URL

catlikecoding.com medium.com