最近項目有個新需求锚贱,在AR場景中,點擊場景的任意位置可以獲取到點擊位置物體表面的位置橄维,傳統(tǒng)的都是用碰撞來做训堆,給不同對象添加BoxCollider做葵,然后發(fā)射線护蝶,即可华烟。不過我們希望碰撞點的位置能在物體的表面,而不是包圍盒的表面持灰,打個比方盔夜,一把椅子,它的包圍盒中有很大一塊區(qū)域是空的堤魁,因為包圍盒的計算是將所有頂點都包含在盒子中喂链,所以碰撞點只能落在包圍盒上,看起來離椅子還有很遠的距離妥泉,如果使用mesh collider椭微,對于一個場景中任意位置都支持的話,模型太多盲链,性能消耗不起蝇率。
后來經(jīng)群友提示,可以將頂點的位置(XYZ)作為顏色(RGB)渲染到RT上刽沾,再從RT上采樣獲取RGB值本慕,將RGB值轉換為世界坐標,這是一種非常取巧的方式侧漓,也可以說是歪門邪道间狂,哈哈,不過能實現(xiàn)功能就好火架。
下面說下核心的思路和代碼:
1.先將頂點位置通過shader轉換為世界坐標鉴象,注意世界坐標是(-∞,+∞),而顏色是[0,1]何鸡,所以需要做一個映射纺弊,先歸一化,再映射到[0,1]骡男,注意dis * 0.01淆游,作為A通道輸出,是用于獲取世界坐標的逆運算隔盛,乘以0.01是為了轉換[0,1]區(qū)間犹菱,我目前的項目中不會超過100單位,這是是個經(jīng)驗值吮炕,可以自行嘗試得到一個較好的值腊脱。
這個shader是在Camera的OnPreRender時使用RenderWithShader方法,臨時替代原shader 獲取一張RT時使用的龙亲,所以要注意陕凹,凡是需要渲染頂點到顏色的材質(zhì)使用過的RenderType悍抑,都要實現(xiàn)一遍,關于RenderType可以參考筆者之前的一篇文章UnityShader RenderType杜耙。因為筆者的場景中材質(zhì)shader使用到三種RenderType搜骡,分別是:
Tags { "RenderType"="Opaque" }
Tags { "RenderType"="TransparentCutout" }
Tags { "RenderType"="Transparent" }
所以需要3個SubShader,Tags分別標記為上述三個類別佑女。
SubShader {
Tags { "RenderType"="Opaque" }
LOD 300
Pass {
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma target 2.0
#include "UnityCG.cginc"
struct appdata_t {
float4 vertex : POSITION;
float4 uv: TEXCOORD0;
};
struct v2f {
float4 vertex : SV_POSITION;
float2 uv: TEXCOORD0;
float4 worldPos : TEXCOORD1;
};
float4 _MainTex_TexelSize;
v2f vert (appdata_t v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
//需要處理UV翻轉問題
#if UNITY_UV_STARTS_AT_TOP
if(_MainTex_TexelSize.y < 0)
o.uv = float2(v.uv.x, 1-v.uv.y);
else
o.uv = v.uv;
#else
o.uv = v.uv;
#endif
o.worldPos = mul(unity_ObjectToWorld, v.vertex);
return o;
}
float4 frag (v2f i) : SV_Target
{
//世界坐標映射到顏色
float dis = length(i.worldPos.xyz);
float3 worldPos2 = i.worldPos.xyz/dis;
worldPos2 = worldPos2 * 0.5 + 0.5;
return float4(worldPos2,dis * 0.01);
}
ENDCG
}
SubShader {
Tags { "RenderType"="TransparentCutout" }
...
}
SubShader {
Tags { "RenderType"="Transparent" }
...
}
}
保存該shader记靡。接下來需要在Camera的OnPreRender中使用該shader替換得到一張RT。
public Camera depthCam;
private RenderTexture depthTexture;
private Texture2D texture2D;
private void OnPreRender()
{
if (depthCam == null) return;
if (depthTexture)
{
RenderTexture.ReleaseTemporary(depthTexture);
depthTexture = null;
}
depthCam.CopyFrom(Camera.main);
depthTexture = RenderTexture.GetTemporary(Camera.main.pixelWidth, Camera.main.pixelHeight, 32, RenderTextureFormat.ARGB32);
depthCam.backgroundColor = new Color(0, 0, 0, 0);
depthCam.clearFlags = CameraClearFlags.SolidColor;
depthCam.targetTexture = depthTexture;
depthCam.RenderWithShader(shader, "RenderType");//替換shader团驱,獲取rt
int width = depthTexture.width;
int height = depthTexture.height;
texture2D = new Texture2D(width, height, TextureFormat.ARGB32, false);//屏幕中心的顏色
RenderTexture temp = RenderTexture.active;
RenderTexture.active = depthTexture;
texture2D.ReadPixels(new Rect(0, 0, width, height), 0, 0);
texture2D.Apply();
RenderTexture.active = temp;
Color color = texture2D.GetPixel(width / 2, height / 2);//這里采樣為中心點
//逆運算得到世界坐標
Vector3 w = new Vector3(color.r, color.g, color.b);
float l = color.a * 100f;
w.x = (w.x - 0.5f) * 2 * l;
w.y = (w.y - 0.5f) * 2 * l;
w.z = (w.z - 0.5f) * 2 * l;
Debug.Log(w);
}
最后輸出的就是屏幕中心頂點的世界坐標摸吠,當然還可以改成鼠標點擊的位置。
小結
使用該方法獲取到的世界坐標的位置并不是非常準確店茶,大部分時候都是正確的蜕便,但是有時候會有一點偏差劫恒,筆者猜測是精度導致的問題贩幻,目前還沒有確定是哪里導致的。另外對于使用了法線貼圖两嘴、視差貼圖或者其他在shader中導致視覺位置改變的材質(zhì)丛楚,一樣會產(chǎn)生偏移,這個也是要注意的憔辫。因為筆者項目的原因趣些,有一點偏差,最后再通過手動微調(diào)也是可以接受的贰您。如果哪位大佬還有更好的方式獲取屏幕頂點坏平,也請留言告知,不勝感激锦亦。
最后給出github的地址https://github.com/eangulee/Color2Pos舶替。
好了,準備下班了杠园。