【Unity】确保3D物体显示在最前的几种方法

前言:

最近项目里有一个需求,要求某个单位(3D)一直显示在所有物体的最上层,哪怕他在其他物体后面。

经过一番折腾解决了,这里提供几种思路。这里先起一个Demo,如下图:

可以看到,黄色方块是在白色方块后面的。在Game视图中可以看到,黄色方块是被白色方块挡住了一部分。

现在我们用一些操作把黄色方块提到最前。

 

一、使用2个相机

这种可以说是最简单的了:

把主相机复制一份,然后剔除掉黄色方块的层。再在复制出来的相机里设置为Depth Only并且只照黄色方块的层:

可以看到这个黄方块立马就到最前来了,而且效果和预计一样的。

但是这里有以下几个问题:

1、这种情况黄色方块必须占用一个独立的层,不过这是个小问题;

2、多个相机性能会下降,也是小问题;

3、LWRP对多相机支持不好,很可能会出现各种渲染异常!(大问题!)

我就遇到LWRP的对多相机支持不好的情况,导致我最后不得不放弃此解决方案。我用的是 Unity 2018.4.23f1 ,LWRP是4.10.0 ,在部分设备上出现了显示异常。后面听说LWRP有升级后支持多相机了,并且看到网上有文章说可以解决LWRP不支持多相机的问题,不过这里不展开了。

 

二、修改ZTest/ZWrite

这个同样也在各种文章里面有讲解了,直接搜素ZTest就有很多讲解,这里就不赘述了。

那么我直接关掉ZTest,看看效果:

可以看到,不论是Scene视图还是Game视图,方块都挪到最前来了。看似非常完美,但并非如此:

如果我们增加一个绿色Cube,使用和黄色Cube一样的Shader,并且和黄色Cube重合一部分,再观察,问题就出来了:

我发现这两个方块交叠的部分没有正确显示,要么黄色方块最前,要么绿色方块最前(可以通过设置Render Queue 来改变顺序),而不能像C那样各自剔除一部分

原因也很简单:因为关闭了ZTest之后,方块的深度都无了,所以一旦出现这种交叠情况,GPU就无法剔除了。有时候这样没有关系,但有时候会导致模型闪烁(毕竟深度叠在一起了),就很严重,这就是我最后放弃这个方案的原因。

 

三、修改ClipPos

这个比较复杂,在Shader里面找到 UnityObjectToClipPos 这个函数调用,在其下面加一行代码:

		float _ZTestAddValue;

		vertexOutput vert(appdata_full input)
		{
			vertexOutput output;
			output.pos = UnityObjectToClipPos(input.vertex);

			//计算深度时额外增加一个值:
			output.pos.z+=_ZTestAddValue;	

			output.posInObjectCoords = input.texcoord;
			return output;
		}

_ZTestAddValue我是在属性面板定义的,他的意义稍后解释。这里先给随意给他一个值(比如0.5):

可以看到两个方块都显示在最上层,而且互相的剔除关系也正确了,这就是我们要达到的效果。

这里解释一下修改的原理:其实我修改的就是 SV_POSITION ,也就是物体的屏幕坐标。

 

关于 SV_POSITION 的简要解释和故障排除:

SV_POSITION的介绍网上有很多也不解释了,可以简单理解为屏幕坐标,我们修改的Z轴就是在屏幕坐标的深度值。

在不同底层 OpenGL或者 DirectX 其取值范围有所不同,在OpenGL里面是 【-1,1】,在DirectX 里面是 【0,1】。不过我们并不用在意这些,只需要知道他是一个相对值,表示距离相机的远近。因此我们修改其Z值就是修改深度值,在处理遮挡时把Z值增加使其更靠近屏幕,就达到了我们的“显示在最前”的效果了。

当然,肯定有人发现问题了。Z轴明明是越小离相机越近,为啥增加Z值反而使得物体更靠前了呢?

答案就是 :UNITY_REVERSED_Z  

Unity 为了计算深度时获得更高的精度,因此会有翻转 Z 轴的情况,所以会 Z 值变大反而距离屏幕越近。但是这个 UNITY_REVERSED_Z  并不是所有平台都一致的,详细看官方文档:

https://docs.unity3d.com/Manual/SL-PlatformDifferences.html

简单来说,在 DirectX 11, DirectX 12, PS4, Xbox One, Metal 平台下会翻转 Z 轴,其他平台则不会。虽然我并不很关心为什么,但是这会导致我再其他平台(如安卓)就不能 加,而是应该减一个值。所以之前的代码还要修改:

		float _ZTestAddValue;

		vertexOutput vert(appdata_full input)
		{
			vertexOutput output;

			float4 clipPos = UnityObjectToClipPos(input.vertex);
			//计算深度时额外增加一个值:			
        	#if UNITY_REVERSED_Z
                clipPos.z+=_ZTestAddValue;
        	    clipPos.z = min(clipPos.z, clipPos.w * UNITY_NEAR_CLIP_VALUE);//限定在范围内
        	#else
                clipPos.z-=_ZTestAddVal;
        	    clipPos.z = max(clipPos.z, clipPos.w * UNITY_NEAR_CLIP_VALUE);//限定在范围内
        	#endif
			output.pos = clipPos;

			output.posInObjectCoords = input.texcoord;
			return output;
		}

这样就能根据不同平台,正确地设置 Z 轴的值了。

 

_ZTestAddValue 的取值问题:

最后,再来谈谈关于Z轴附加值(_ZTestAddValue)的取值问题。

现在已经知道 这个 SV_POSITION 的Z轴是相对值,和相机距离物体的远近有直接关系。

当我们附加值 (_ZTestAddValue) 不改变的时候,改变相机和物体的距离,就会使得遮挡关系改变。可以理解为,如果相机够远 ,+0.1 就可以把黄色方块拉到前面来了,但是相机如果太近,就不足够了。

这里用一张网上的图:

可以看到在0附近(靠近相机)的地方取值范围更大,而越远变化越小。

因此 _ZTestAddValue 的值需要根据物体距离相机的远近来设定一个合适的值(距离相机近则大,远则小)。如果需要甚可以用代码动态修改。

 

完整代码

最后贴上完整的Shader代码:

Shader "Custom/TestShader"
{
    Properties
    {
        _Color ("Color", Color) = (1,1,1,1)
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
		_ZTestAddValue("ZTest Add Value",float)=0
        [Enum(UnityEngine.Rendering.CompareFunction)] _ZTest ("ZTest", Float) = 4
    }

    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 200
		ZTest[_ZTest]

        Pass
		{

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

		struct vertexOutput 
		{
			float4 pos : SV_POSITION;
			float4 posInObjectCoords : TEXCOORD0;
		};

		float _ZTestAddValue;

		vertexOutput vert(appdata_full input)
		{
			vertexOutput output;

			float4 clipPos = UnityObjectToClipPos(input.vertex);
			//计算深度时额外增加一个值:			
        	#if UNITY_REVERSED_Z
                clipPos.z+=_ZTestAddValue;
        	    clipPos.z = min(clipPos.z, clipPos.w * UNITY_NEAR_CLIP_VALUE);//限定在范围内
        	#else
                clipPos.z-=_ZTestAddVal;
        	    clipPos.z = max(clipPos.z, clipPos.w * UNITY_NEAR_CLIP_VALUE);//限定在范围内
        	#endif
			output.pos = clipPos;

			output.posInObjectCoords = input.texcoord;
			return output;
		}
		
        sampler2D _MainTex;
        fixed4 _Color;

		float4 frag(vertexOutput input) : COLOR
		{
			float4 col = tex2D(_MainTex, input.posInObjectCoords)*_Color;
			return col;
		}

			ENDCG
		} 
    }
    FallBack "Diffuse"
}

 

  • 15
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
Unity中,有几种方法可以防止物体穿模。首先,你可以调整碰撞体的形状,使其更贴合物体的实际形状。这可以通过在Unity编辑器中选中穿模的物体,找到其碰撞体组件(如Box Collider、Sphere Collider等),然后根据物体的形状和需求,调整碰撞体的大小、位置和旋转,以确保碰撞体完全包围物体,避免穿模现象。\[1\] 其次,你可以增加物体的包围盒大小。物体的包围盒是物理引擎用于进行碰撞检测的基本形状。如果物体以高速运动且穿模问题比较严重,你可以尝试增加物体的包围盒大小。这可以通过修改物体的碰撞体组件的大小或者增加一个额外的碰撞体来实现。\[2\] 这些方法可以结合使用,根据具体情况选择合适的解决方案。另外,如果你的物体不需要进行物理模拟,也可以考虑将物体的碰撞体组件替换为触发器组件,然后使用自定义的算法来处理物体的碰撞逻辑,以避免穿模问题的发生。\[3\] #### 引用[.reference_title] - *1* *2* *3* [unity3d 物体高速运动下穿模的解决方案](https://blog.csdn.net/lalate/article/details/131311052)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值