Unity3D正交-透视混合相机的实现

An implementation of mixed ortho-persp camera in Unity3D

(本文需要一定的Unity3D及其ShaderLab的知识)

1. 动机

在2D游戏开发中,经常会出现需要处理前景遮挡物体。我之前的做法是新建一个相机,然后depth设置大于主相机。把遮挡物体的sprite 都放在这个相机里。一直觉得这样对相机进行分层渲染场景没什么问题,直到有一天,看到了这个插件——SpriteSharp

Sprite Sharp 最大的好处是在于能够把带有透明通道的sprite,进行透明-不透明像素的分离。原来单独的一个sprite,经过SpriteSharp处理之后,变成了两个关联的sprite。一个是完全不透明的区域,一个是半透明的区域。这样做的好处是,对于不透明的sprite,我们可以开启Zwrite和Ztest,进行遮挡剔除(参见Unity3D Culling and Depth Testing 和 Early Ztest)。这样一来凡是在不透明sprite之后出现的像素都不会去渲染,极大的节省了移动设备上的渲染带宽占用。当前景遮挡物变多的时候,这点尤其明显。因此,在有了SpriteSharp之后,如何让场景里的物体进行充分的ZTest是我接下来要思考的重点。

Sprite Sharp 可以对原先的半透明sprite根据alpha值分离成2个sprite

2. 分层相机带来的问题

一般在Unity3D当中,我们根据项目需要,都会对渲染内容进行分层设置。然后相机分层进行渲染,一般分层的相机设置如下:

层叠的相机

一般需要叠加渲染的相机都会设置ClearFlag = Depth Only,以及对应的Depth值。Depth大的相机会在Depth小的相机之上渲染。这样看似没什么问题,相信很多人也都是这么做的。但是却带来了一个些问题。底层相机渲染的内容,很有可能会被上层相机直接覆盖掉。这样一来,造成了底层相机的渲染的浪费。而如果做Ztest的话,又需要上层相机的不透明内容,先于底层相机渲染,这又与unity3d相机基于depth渲染顺序的方式相矛盾。因此,在FISH中,我去掉了原先分层的相机设置,所有内容,都放在一个正交相机里进行渲染了。这样前景物体能够在RenderQueue = Gemometry里,先于RenderQueue = Transparent 的物体进行渲染,并写入深度缓存,以便之后的透明物体进行ZTest。

3. 单一相机带来的问题

首先场景管理上,原先通过分层设置Layer以及相机LayerMask的方法,这里显然就不行了。虽然我还是保留了UI相机方便管理,但是大部分的场景内容,尤其是有前后遮挡关系的内容,我都尽量的合并在一起了,通过设置物体的Z值结合Sorting Order来控制前后关系。

其次,因为原先前景遮挡的内容,是单独的透视相机渲染的,而现在都放在一个正交相机里的话,前景物体没有了透视相机所形成的视差效果,因此如何对于前景物体在正交相机里实现透视相机的投影变换是接下来要思考的问题。

简单回顾一下,一个物体要正确渲染在屏幕上,需要经历Model - View - Projection 的投影变换。而不论是正交相机还是透视相机,Model - View 这两步变换是一样的。也就是说,对于前景需要视差的物体和一般的物体,他们的顶点Shader里,UNITY_MATRIX_MV 对他们是没差别的。差别只是在于正交相机的Projection Matrix 和 透视相机的Projection Matrix不同。那么如何根据一个正交相机,生成一个透视相机的Projection Matrix呢?

首相,我的正交相机高度是20,nearClipPlane = 0,farClipPlane = 100, position.z = -50f。这样一来,正交相机在xy平面的前后,就各有50的空间。而为了取得最好的透视效果,透视相机的位置需要和正交相机重合,并且透视相机的上下边缘,在通过xy平面的时候,需要和正交相机重合;也就是说,对于处于z=0的sprite,正交相机和透视相机的成像是一致的。此时透视相机的FOV可以通过正交相机计算得到:

fov=2f*Mathf.Rad2Deg*Mathf.Atan2(orthoCam.orthographicSize,Mathf.Abs(orthoCam.transform.position.z));

对于实际透视相机的Projection Matrix的计算,Unity3D里有一个方便的API:Matrix4x4.Perspective,可以通过fov,相机的长宽比,和剪切平面的距离来获得Projection Matrix。有时候,直接计算所得到的Projection Matrix 并不是Unity在Shader里最终会用到的,还需要用GL.GetGPUProjectionMatrix 获得最终使用在Shader里的Projection Matrix。之后我们把获得的变换矩阵设置成一个全局的Shader变量。该部分代码如下:(对于不同设定的正交相机,该代码具有通用性)

Matrix4x4 projmtx = Matrix4x4.Perspective(fov,orthoCam.aspect,orthoCam.nearClipPlane,orthoCam.farClipPlane);

proj=GL.GetGPUProjectionMatrix(projmtx,false);

Shader.SetGlobalMatrix("_PerspCamProj",proj);

正交相机与透视相机的取景框

4. 在正交相机里应用透视投影变换

在前面的部分里,我们已经得到了正交相机对应的透视相机的投影变换矩阵,并保存在了一个_PerspCamProj 的Shader全局变量里。接下来看看,如何对于需要透视变化的sprite应用特定的投影变换。

一般我们在Shader里处理顶点变换的时候,都会这么写:

o.pos = mul (UNITY_MATRIX_MVP, v.vertex);

对于我们的正交相机,这么做会把顶点变换到正交投影空间,而前面提到过,对于不同的相机,UNITY_MATRIX_MV都是相同的,区别只是在P的不同。因此,对于需要透视变换的物体,我们需要应用刚刚计算好的_PerspCamProj变换矩阵。这里先看代码:

#ifdef _PARALLAX_ON

float4 p =mul(UNITY_MATRIX_MVP, IN.vertex);//计算正交投影

OUT.vertex=mul(mul(_PerspCamProj,UNITY_MATRIX_MV), IN.vertex);//计算透视投影

OUT.vertex.z = p.z * OUT.vertex.w;//修正齐次坐标w

#endif

这里特别需要注意的是,为了正确的和正交相机的内容进行深度对比,我们需要让透视相机产生的内容在正交空间里,和正交相机产生的内容深度一致。正交投影变换过后,p.w 也就是齐次坐标的w值是1,而投影变换过后,w值不是1,GPU最后需要除以这个w来确定顶点在透视投影空间的最终的位置。因此我们需要人工的修正一下顶点的z值。

5. 总结

至此,我们了解到了,在正交相机里使用投影透视的一种方法。也大概了解到了用单一相机渲染场景并通过深度测试来节省渲染带宽占用的方法。视差模拟也可以直接在脚本上,通过设置位置来模拟。这里提供一种通过Shader来模拟的思路。希望和大家交流学习。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,311评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,339评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,671评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,252评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,253评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,031评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,340评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,973评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,466评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,937评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,039评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,701评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,254评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,259评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,485评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,497评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,786评论 2 345

推荐阅读更多精彩内容

  • 更新:【面试题含答案】http://bbs.9ria.com/thread-288394-1-1.html 高频问...
    好怕怕阅读 4,719评论 3 52
  • 前言:这篇文章是之前自己博客里的,这次重新用Markdown排下版,给需要的人, 想要成为一个优秀的U3D程序员...
    道阻且长_行则将至阅读 1,742评论 0 20
  • 转载注明出处:点击打开链接 Shader(着色器)是一段能够针对3D对象进行操作、并被GPU所执行的程序。Shad...
    游戏开发小Y阅读 3,325评论 0 4
  • 一:什么是协同程序? 在主线程运行的同时开启另一段逻辑处理,来协助当前程序的执行,协程很像多线程,但是不是多线程,...
    胤醚貔貅阅读 2,069评论 0 13
  • 一:什么是协同程序? 答:在主线程运行时同时开启另一段逻辑处理,来协助当前程序的执行。换句话说,开启协程就是开启一...
    好怕怕阅读 3,870评论 2 23