まずはシーンの構築と問題の確認をします
※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がそれのようです。
BillboardTree ShaderのSubshaderの中身がこちら
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"
と描画順を普通の半透明にしてみると、今度はこんな見た目になってしまいます。
では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してみました。
どうでしょう、アンチエイリアスが入るところで水面下が黒いアウトラインとして見えてしまっています。
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しているのはそのままだと色で見るときにわからなくなるから便宜上です。
こちらが結果です。
どうもvertexShaderに渡されるv.vertexの情報はBillboardのpivot位置というか原点のようです。四隅ではないんですね。
ひとまず、Billboardの原点からの相対距離情報と、ワールド座標のObjectの位置は所得できたので、これでなんとかしてみます。
#解決
VertexShader
の中でv.vertex
にv.texcoord1
の座標を合体すれば四隅の頂点座標が作り出せます。カメラからの角度を考慮しないと正しい数値にはなりませんが、今回は水面だけ取りたいので回転は無視しました。
あとは、fragmentShader
の中でピクセルのワールド座標がY<0
なら水面下ということでアルファを0.2にしてしまいます。
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
}
どうでしょう。 それなりに目立たなくなったと思いませんか?
#今後の課題
・カメラの回転いれたほうがやっぱいい?
・水面0がソースに書かれてしまっているので、外から渡す方法は?
・半透明0.2も、できたら水面の透明度から持ってきたい。 上と同じで外から渡す方法は?
Shader.SetGlobalFloat
とか使うのが早いのかな?