Unity UGUI渲染3D对象
Unity UGUI渲染3D对象
在3D游戏里,我们经常可以看到将角色、道具物品等3D对象渲染到UI上的情况,例如MMORPG中角色的展示,FPS游戏中的枪匠系统等。但UI一般都是2D的,接下来就给大家介绍一下在UGUI中是如何实现将3D物体渲染到界面上的。
一、简介
目前主流的两种实现方法:
- 方法一:将3D对象渲染到RenderTexture(简称RT)上,再将RT设置在RawImage上。
- 方法二:将3D内容直接挂在界面节点下,通过UI Camera直接渲染。
需要注意的是,在我们使用方法一的时候通常会遇到在渲染半透明的物体或者使用Additivce(透明混合模式)方式混合的物体时,会出现渲染颜色混合错误的问题。在我们使用方法二的时候,因为3D对象不是UI组件,所以我们需要特殊处理以保证UI与3D对象的前后遮挡关系,并且因为3D对象并不受UI裁剪组件Mask和RectMask2D的影响,所以也需要特殊的手段来实现3D对象被UI裁剪组件的的裁剪,而且,因为3D对象是直接挂在UI下通过UI Camera渲染的,而UI Camera一般都是正交相机,所以当我们对展示的3D对象使用透视相机时候也需要做特殊处理。接下来就详细给大家来说一下这两种渲染方法的实现和问题的解决方法。
二、使用Render Texture的方法
使用RT的方法是最常用也是实现起来最简单的方法,具体步骤如下:
- 新建一个专门拍摄3D对象的Camera和一张尺寸与UI上显示3D对象的RawImage尺寸相同的RT,将RT设置在Camera.targetTexture上,同时也设置在RawImage.texture上。
- 将需要拍摄的3D对象移动到远处(通常放到离场景1000左右,太多的话可能会让skinmesh计算骨骼动画的时候出现因浮点精度导致的抖动问题),最好将3D对象设置为一个单独的layer,确认好是否正交,远近裁剪平面是否合适。将Culling Mask设置为这个特殊的layer,将相机的Clear Flag设置为Solid Color(纯色)模式,BackGround(背景颜色)设置为(0.5,0.5,0.5,0.0)。
如果UI Canvas的RenderMode(渲染模式)为ScreenSpace-Camera(相机)模式,拍摄Camera的Depth需要比UI Canvas的Depth小,以确保UI Canvas的渲染在前面。
经过以上步骤我们就可以成功将3D对象渲染在UI上了,但当我们渲染的是半透明的物体时,这里就会遇到我们之前提到过的渲染颜色混合错误。如果我们渲染的是不透明的3D物体就不会遇到这个问题。
一般情况下,我们渲染物体,假定的混合方式是:Blend A B,对应的混合计算公式是:FinalColor = A * SrcColor + B * DestColor。
但是,当我们将3D内容渲染到RT时,物体会先和RT的背景混合一次,然后RT再和屏幕背景混合一次,这就会出现渲染错误的问题。要解决这个问题有两种方法如下:
方法一是直接将3D对象对应在UI上的背景窗体纹理直接Copy到RT上,拍摄相机的Clear Flage设为Depth only,修改混合方式为Blend A B,Zero One,这样虽然可以让最终结果与预期的一样,但是3D对象不能超出UI背景的范围,而且会多一次Texture Copy操作。
1
Blend SrcAlpha OneMinusSrcAlpha, Zero One
方法二则是通过调整两次渲染的Alpha Blend公式来还原一次Alpha Blend的效果,具体步骤如下:
- 修改渲染3D对象的Alpha Blend方式,单独指定Alpha通道的混合方式为:Blend A B,Zero B
1
Blend SrcAlpha OneMinusSrcAlpha, Zero OneMinusSrcAlpha
- 用官方的UI-Default为基础修改一个特殊的Shader用于渲染RT所在的RawImage,Alpha Blend修改为:Blend One SrcAlpha
1
Blend One SrcAlpha
- 修改拍摄相机的Background为(0,0,0,1)
现在让我们来推导一下上述过程:
我们设物体的颜色是SrcColor,屏幕背景的颜色是ScreenColor,那我们期望的混合计算为
FinalColor = A * SrcColor + B * ScreenColor
我们假定原本RT的颜色为OrgRtColor,原本RT的Alpha通道为OrgRtAlpha,则按照上述方法得到的第一步渲染RT的过程如下:
NewRtColor = A * SrcColor + B * OrgRtColor
NewRtAlpha = 0 *SrcAlpha + B * OrgRtAlpha
又因为我们将RT初始颜色设为(0,0,0,1)(拍摄相机Background),所以得到:
NewRtColor = A * SrcColor
NewRtAlpha = B
紧接着我们渲染RT所在的RawImage到屏幕上:
FinalColor = 1 * NewRtColor + NewRtAlpha * ScreenColor
代入前面的结果:
FinalColor = A *ScrColor + B * ScreenColor
这便是我们所期望的混合计算结果!- 修改渲染3D对象的Alpha Blend方式,单独指定Alpha通道的混合方式为:Blend A B,Zero B
三、将3D对象直接挂在UI节点下的方法
将3D对象直接挂在UI节点下,通过UI Camera进行渲染的实现步骤如下:
- 将UI根节点Canvas(幕布)的RenderMode设置为ScreenSpace-Camera,并指定渲染UI的相机,将UI Camera的Clear Flags设置为Depth only(仅深度模式),Culling Mask设置为UI(UI和3D对象的layer都要设置为UI),Depth要比渲染场景的相机大。
- 将3D对象挂在合适的UI节点下,layer设为UI,调整缩放,保证3D对象与UI有正确的遮挡关系,调整3D对象上Renderer的SortOrder,并且保证3D对象在世界中的位置与前后UI有正确的Z轴遮挡顺序。这里需要注意,一般的UI节点的SortOrder默认是根据其在父节点下的顺序隐式确定的,但是我们也可以通过在其上面添加一个Canvas,通过OverrideSorting来重新指定。这种方式是将3D对象认为是UI的一部分,直接使用UI Camera进行渲染,所以不会出现Alpha Blend的问题,只有保证其前后正确的遮挡关系即可。
这里还有一个需要注意的地方,那就是这个3D对象在美术人员制作的时候,其本身可能会有些内部的SortOrder(其子节点,粒子系统等其他渲染节点,例如粒子节点的Renderer组件中的Order In Layer),所以,我们在调整其SortOrder的时候,需要保证重设后不会影响其内部的SortOrder,我们可以通过代码进行这一步的修改:
1 | void SetSortOrder(GameObject _objRoot, int _setOrder) |
如果考虑使用对象池,我们还需要在其放回对象池时再次还原其节点SortOrder,那上述代码就需要做备份初始状态的操作。
如果我们想用透视相机渲染3D对象,则需要多个相机,通过相机的Depth来控制其先后渲染的顺序,背景层、3D对象层、前景层都需要用不同的相机对其进行渲染。
四、UI上的3D对象裁剪
在UGUI中,一般是通过Mask和RectMask2D两个组件来实现对区域的裁剪。
- Mask的实现是模板缓冲(Stencil Buffer),在渲染Mask裁剪区域时,先将需要显示的区域做模板缓冲标记,在后续的绘制中,通过模板测试来控制只绘制标记区域内的像素。
- RectMask2D的实现是通过 Alpha Cut ,在官方shader——UI-Default.shader中我们可以看到关于RectMask2D的相关实现:
简述了UGUI内置裁剪的原理后,我们来看看UI上的3D如何实现裁剪。
- 当我们使用RT进行渲染实现的时候,其本身就是通过RawImage组件实现的,所以它是支持UGUI的内置裁剪,直接按UI的方法即可。
- 当我们使用将3D对象挂载到UI节点上的方案时,这时就需要我们做点特殊处理来实现裁剪效果了。考虑到RectMask2D的实现需要使用clip指令,在像素阶段需要新增一些代码且还要加 #prgma multi_compile 生成变体,所以我们选用Mask模板缓冲来实现3D对象裁剪,具体步骤如下:
- 在UI-Default.shader的基础上修改shader代码,在Properties段中将渲染物体的模板值#StencilID修改为用于将参考值赋给当前像素的#StencilRef。
- 在渲染阶段加入以下声明
- 这个shader只是用来标记需要显示区域的缓冲模板,并不输出颜色。将这个shader指定到一个材质上,将其设置到用于裁剪的RawImage或Image上,指定一个模板标记值(Stencil Ref)n。打开渲染3D对象时的模板测试,操作符为Equal,值为指定的n,这样3D对象就会被正确的裁剪(如果3D对象后面还有UI界面,则在其上可以添加RectMask2D组件用于裁剪背景层)。
- 在UI-Default.shader的基础上修改shader代码,在Properties段中将渲染物体的模板值#StencilID修改为用于将参考值赋给当前像素的#StencilRef。
五、在UI上为3D对象添加后期效果
以最常见的Bloom效果举例,使用RT来表现3D对象的时候比较简单,直接将Bloom控制脚本添加到拍摄的相机上即可,但如果是直接将3D物体挂在UI下的做法,我们就不能这样做了,因为UI也会受到影响。我们有以下两个方案:
- 第一种比较简单的做法就是用多个摄像机来实现,用一个摄像机单独渲染3D内容,把Bloom脚本添加在这个相机上,之后我们设置好这些相机的Depth渲染顺序即可,但是这种方法存在对UI的设计带来了一定的局限性,而且过多的相机实现起来也不方便。
- 第二种方法则类似多重渲染(MRT Multi Render Target),我们做一些标记来指定哪些地方实现Bloom效果。或者,一般情况下我们都是将颜色最终输出到ARGB32的RT上,这里Alpha通道是没用的,我们就可以用Alpha通道来做标记。我们在渲染UI的时候,将Alpha通道写成0,渲染3D对象的时候则采用默认方式,那么最终输出到RT上Alpha大于0的区域是最后渲染了3D对象的,Alpha值为0的区域则是渲染UI的。
- 以官方下载的UI-Default.shader为例对其进行修改,将Alpha混合方式改成:Blend SrcAlpha OneMinusSrcAlpha, Zero Zero
- 对Bloom进行处理,第一个Pass(Prefilter pass)种会对输入RT做一些与处理,应用类似如下区域标记代码:
1
2half4 texInput = tex2D(_MainTex, uv);
half3 fmtC = texInput.rgb * sign(texInput.a);
这里需要注意一点,我们需要UI Camera输出的RT必须有Alpha通道,因为LDR的渲染目标通常是ARGB32,所以这在LDR时不会有什么问题,但在HDR时,渲染目标有两种格式(FP16和R11G11B10),前者有Alpha通道,但会需要一些内存,后者没有Alpha通道,就不适合采用上诉方法,不过一般低端机上才会这样,也就不需要开启这些后处理效果,自然也就没有什么问题。