LoginSignup
1
0

More than 3 years have passed since last update.

UnityのTerrainに生やしたTreeのBillboardがおかしくなるのを何とかしてみる

Last updated at Posted at 2019-09-13

まずはシーンの構築と問題の確認をします

※Unity 2017~2019では同じ

普通にTerrainを作り、普通にTreeを適当に作ってみました。
TerrainもTreeもテクスチャは無しです。
水面はPlaneにStandardのTransparentにしたテクスチャを、これも適当にセットしてあります。

↑)TerrainのBillboard Startを20にしてみると比較的近場からBillboardになります。
さあ、水面との関係がおかしくなっているのがわかります。

現状の確認

どうしてこうなってしまうのか確認するために、TreeのBillboardで使っているShaderを調べます。

・Treeの構造

Prefabの中にMaterialが含まれています。この二つのシェーダーが使われているのでしょうか。
・Hidden/Nature/Tree Creater Bark Optimized
・Hidden/Nature/Tree Creator Leaves Optimized

結論から言うと、描画されているときは、こちらのシェーダーだけで描画しているわけではありません。
ここは、FrameDebugを見てみましょう。Billboardは最後のDrawMeshがそれのようです。


・Hidden/TerrainEngine/BillboardTree
こちらのシェーダーが描画しているようです。
シェーダーの中身を見ていきます。

BillboardTree ShaderのSubshaderの中身がこちら

BillboardTree.shader
    SubShader {
        Tags { "Queue" = "Transparent-100" "IgnoreProjector"="True" "RenderType"="TreeBillboard" }

        Pass {
            ColorMask rgb
            Blend SrcAlpha OneMinusSrcAlpha
            ZWrite Off Cull Off

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile_fog
            #include "UnityCG.cginc"
            #include "TerrainEngine.cginc"

            struct v2f {
                float4 pos : SV_POSITION;
                fixed4 color : COLOR0;
                float2 uv : TEXCOORD0;
                UNITY_FOG_COORDS(1)
                UNITY_VERTEX_OUTPUT_STEREO
            };

            v2f vert (appdata_tree_billboard v) {
                v2f o;
                UNITY_SETUP_INSTANCE_ID(v);
                UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);
                TerrainBillboardTree(v.vertex, v.texcoord1.xy, v.texcoord.y);
                o.pos = UnityObjectToClipPos(v.vertex);
                o.uv.x = v.texcoord.x;
                o.uv.y = v.texcoord.y > 0;
                o.color = v.color;
                UNITY_TRANSFER_FOG(o,o.pos);
                return o;
            }

            sampler2D _MainTex;
            fixed4 frag(v2f input) : SV_Target
            {
                fixed4 col = tex2D( _MainTex, input.uv);
                col.rgb *= input.color.rgb;
                clip(col.a);
                UNITY_APPLY_FOG(input.fogCoord, col);
                return col;
            }
            ENDCG
        }
    }

ぱっと見、Transparentのプライオリティをいじっている以外、おかしなところはありません。
試しにTagの中で "Queue" = "Transparent" と描画順を普通の半透明にしてみると、今度はこんな見た目になってしまいます。
billboard2_.png

ではZTestさせてみたらどうでしょう。

            ColorMask rgb
            Blend SrcAlpha OneMinusSrcAlpha
            ZWrite On
            Cull Off
                fixed4 col = tex2D( _MainTex, input.uv);

                if (col.a == 0)discard;

こんな感じでZ情報を書き込み、FragmentShaderの頭でアルファが0ならClipしてみました。
billboard2.png

どうでしょう、アンチエイリアスが入るところで水面下が黒いアウトラインとして見えてしまっています。
if (col.a == 0)discard; この行を消すと、もっと露骨に表示されてしまいますが、Billboardの描画が半透明よりも前に書かれると、非表示の箇所のピクセル分も、Zが書き込まれているため、水面の描画がされません。 まあそりゃそうですね。

↓Billboardの問題はつまり、半透明同士の重なりの問題というわけです。
UnityでOIT(Order Independent Tranceparency)

しかし、この重なり順の解決のためにあちこち改造するのは嫌なので、
もう少しシンプルに、せめて目立たなくするだけでもできないか検討してみました。

まずは調査

appdata_tree_billboardの中身はどう定義されているのでしょうか、TerrainEngine.cgincを見てみましょう。

struct appdata_tree_billboard {
    float4 vertex : POSITION;
    fixed4 color : COLOR;           // Color
    float4 texcoord : TEXCOORD0;    // UV Coordinates
    float2 texcoord1 : TEXCOORD1;   // Billboard extrusion
    UNITY_VERTEX_INPUT_INSTANCE_ID
};

texcoordにはUV、texcoord1にはBillboardの為の情報が含まれていそうです。
texcoord1の中身をhalf3(texcoord1.x, texcoord1.y, 0)という形にしてカラーで表示してみました。


※状況を理解しやすくするために、新しく地面の底にも枝が伸びる木を作ってみました。

texcoord1の中身は、Terrainの地面に接点する地点を中心としたピクセル座標のようです。
色がすぐに振り切ってしまっているので、uv的な0-1空間ではなくUnityScaleの座標でしょうか。

次は、v.vertexの中身をみてみましょう。頂点はきちんと来ているのでしょうか。
half3(v.vertex.y, v.vertex.y, v.vertex.y) * 0.1;
こんな感じで頂点のy座標だけ取り出してみました。 1/10しているのはそのままだと色で見るときにわからなくなるから便宜上です。
billboard3_.png
こちらが結果です。
どうもvertexShaderに渡されるv.vertexの情報はBillboardのpivot位置というか原点のようです。四隅ではないんですね。

ひとまず、Billboardの原点からの相対距離情報と、ワールド座標のObjectの位置は所得できたので、これでなんとかしてみます。

解決

VertexShaderの中でv.vertexv.texcoord1の座標を合体すれば四隅の頂点座標が作り出せます。カメラからの角度を考慮しないと正しい数値にはなりませんが、今回は水面だけ取りたいので回転は無視しました。

あとは、fragmentShaderの中でピクセルのワールド座標がY<0なら水面下ということでアルファを0.2にしてしまいます。

BillboardTree.shader
Shader "Hidden/TerrainEngine/BillboardTree" {
    Properties {
        _MainTex ("Base (RGB) Alpha (A)", 2D) = "white" {}
    }

    SubShader {
        Tags { "Queue" = "Transparent+10" "IgnoreProjector"="True" "RenderType"="TreeBillboard" }

        Pass {
            ColorMask rgb
            Blend SrcAlpha OneMinusSrcAlpha
            ZWrite Off
            Cull Off

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile_fog
            #include "UnityCG.cginc"
            #include "TerrainEngine.cginc"

            struct v2f {
                float4 pos : SV_POSITION;
                fixed4 color : COLOR0;
                float2 uv : TEXCOORD0;
                float4 worldPos : TEXCOORD2;//worldPos
                UNITY_FOG_COORDS(1)
                UNITY_VERTEX_OUTPUT_STEREO
            };

            v2f vert (appdata_tree_billboard v) {
                v2f o;
                UNITY_SETUP_INSTANCE_ID(v);
                UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);

                //pixelのworld座標を所得 (texcoord1側の処理が正確ではないけど、今回はyが取れればいいので)
                o.worldPos = half4(mul(unity_ObjectToWorld, v.vertex).xyz, 0)
                    + half4(mul(unity_ObjectToWorld, half4(v.texcoord1.x, v.texcoord1.y, 0, 0)).xyz, 0);

                TerrainBillboardTree(v.vertex, v.texcoord1.xy, v.texcoord.y);
                o.pos = UnityObjectToClipPos(v.vertex);
                o.uv.x = v.texcoord.x;
                o.uv.y = v.texcoord.y > 0;
                o.color = v.color;
                UNITY_TRANSFER_FOG(o,o.pos);
                return o;
            }

            sampler2D _MainTex;
            fixed4 frag(v2f input) : SV_Target
            {
                fixed4 col = tex2D( _MainTex, input.uv);
                if (col.a == 0)discard;

                col.rgb *= input.color.rgb;
                //clip(col.a);
                UNITY_APPLY_FOG(input.fogCoord, col);

                if (input.worldPos.y < 0) {
                    col.a = 0.2;
                }

                return col;
            }
            ENDCG
        }
    }

    Fallback Off
}

billboard4.png

どうでしょう。 それなりに目立たなくなったと思いませんか?

今後の課題

・カメラの回転いれたほうがやっぱいい?
・水面0がソースに書かれてしまっているので、外から渡す方法は?
・半透明0.2も、できたら水面の透明度から持ってきたい。 上と同じで外から渡す方法は?
Shader.SetGlobalFloatとか使うのが早いのかな?

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0