Unity游戏引擎开发者联盟

 找回密码
 立即注册
查看: 58|回复: 9

虚幻4渲染编程(客户端篇)【第二卷:Split Screen Rendering of UE4】

[复制链接]
发表于 2021-4-14 14:29 | 显示全部楼层 |阅读模式
MY BLOG DIRECTORY:
INTRODUCTION:
作为从业人员其实最怕碰到两个工种的交接地带,这部分地带简直就是死亡地带。前几天我在玩马里奥赛车8的时候,注意到它是分屏渲染的。所以我就去查了下Unreal中分屏渲染怎么实现,发现贴心的Unreal早已给出了解决方案。
首先在项目设置里把分屏打开
然后再创建两个摄像机
然后在创建一个Player,游戏默认会创建一个Player,但是为了分屏需要有两个Player所以需要再创建一个。我这里为了快速演示直接在关卡蓝图里创建,并且把它的ControllerID设为1
然后再把两个controller的ViewTarget设置成我们放置在场景里的两个摄像机
然后点击Play你将会看到分屏!
一般情况下这个时候就该收工大吉了,但是仔细一想发现不对啊,并提出以下几个问题:
(1)SplitScreen究竟是如何实现的引擎是如何控制渲染器渲染的,切屏的机制到底是什么?
(2)Camera,Player,Pawn,PlayerController,CameraManager,ViewPort到底是怎么样的关系,渲染器是如何知道我的Camera的,并且这个Camera是如何和渲染器关联上的,凭什么渲染器知道要渲染我这台摄像机?
(3)虚幻的Player机制到底是什么,Viewport和Controller和UPlayer的关系是如何的?
带着这些疑问我们开始下面的旅程。要解决上述问题需要潜入根深的引擎代码中寻找答案,其实一直以来我的文章都是在探索Render这部分,但是Render其实是整个管线的中间部分,Render之前引擎干了什么,Render之后引擎干了什么其实都还是非常庞大的内容。
MAIN CONTENT:
【1】几个关键的类型
这部分内容不要太纠结细节,大概知道每个类是干嘛的即可。
(1)FViewport
虽然它名字叫Viewport但是它其实是RenderTarget !
可以看到它有把自己设置到BackBuffer的方法。所以其实Render函数渲染半天最终其实是在往它身上画东西。


(2)UGameViewportClient/FViewportClient
带有ViewportClient字样的类型
它是当前GameInstance的GameViewprot,它内部含一个FViewport,GamePlay层和渲染层的视口概念是如此对应的。
FViewport内部也有对当前Game Viewport的指针
FViewport:
FViewport和GameViewport在功能上做了些拆分
不过FViewPort是衔接场景渲染器绘制完以后的阶段和UI层绘制阶段的媒介。


(3)FSceneView
FSceneView解决了几个问题,我是谁,我在哪儿,我在往哪儿看等问题,它由UPlayer管理,UPlayer对应的摄像机数据会被传到这里面来,所以摄像机矩阵等也在它里面。
当然它也管理了View的Uniformbuffer,就是之前我博客里介绍的那令人印象深刻的UniformBuffer
FSceneView还负责ViewRect,这也是分屏渲染需要非常关注的地方!


(4)FSceneViewFamily
Unreal的命名也非常有趣,看这个类型的名字其实就能猜到它是干嘛的,对它就是场景里所有FSceneView的集合


(5)UPlayer/ULocalPlayer
这个Player需要和游戏中的Pawn或者是Character区别一下。Player代表玩家,比如单机不联网游戏就只有一个Player,如果是双人游戏,比如马里奥赛车8,超级马里奥就有两个Player,以此类推。而Pawn或者Character代表游戏中的角色,比如一个NPC是一个Pawn,一个怪物也是一个Pawn。UPlayer和FSceneView存在对应关系:
在CalcSceneView函数中,Player会创建它的FSceneView。所以如果我有两个Player就会有两个FSceneView
同时UPlayer还有一个PlayerController




【2】引擎里处理分屏渲染的方法
很显然,最开始的三个问题应该是Render(CPU端)这件事情之前的,因为引擎需要把组件信息,Camera信息先收集起来。所以只需要找CreateRender之前的逻辑就可以了。
从WinMain到渲染开始之前可以看到步骤还是很清晰很少的。
在Tick函数里我们找到了分屏渲染需要的东西
在UpdateActiveSplitscreenType();中你可以找到引擎从配置文件里拿设置信息的逻辑,这里的信息就是从之前项目设置里拿到的。
最后GameViewPortClient的成员变量会被赋值
现在引擎从项目配置中拿到了分屏设置,接下来就是具体来执行渲染了。
这里会调用FViewport的Draw函数
然后FViewport会调用Client的Draw,并且把自己传进去。FViewport内涵了一个最终要提交的Rendertarget,并且FViewport和UI层是绑定好了的,也就是说如果渲染好了FViewport的这个Render target那么UI层就能拿到最终的渲染结果
这个Viewport会去初始化ViewFamily,最终这个ViewFamily会用来创建Renderer
如果要分屏那么必须要两个View,从之前的分析可以知道,View是根据UPlayer创建的,具体的创建逻辑在CalcSceneView里,View会被加到View Family里
View Family会被在创建Renderer的时候被加入到Renderer里
这样FViweport的那张Render target就成功塞给了Renderer
然后RenderViewFamily_RenderThread在渲染线程会调用Renderer的Draw


//开始渲染线程---------
在Renderer的渲染函数中,所有渲染函数全部都有遍历View的逻辑
每个View里都存了渲染投影变换需要的大量数据
其中很重要的一个数据便是ViewRect
这个数据会限制我们绘制的区域。现在我有两个View,只要限制View再RT上的渲染区域那么最后渲染出来的RT自然就有两个View的画面了
下面是我第一个view的Rect
下面是第二个View的Rect
我的游戏窗口时这样的:
//结束渲染线程--------
那上面的ViewRect数据又是哪里来的呢。可以追溯到LayoutPlayers
这里会根据SplitType去设置Player的Size变量。


暂时不继续在渲染线程里跟进了,我们只需要知道渲染线程会把我们需要的图像渲染出来就可以了,下面还是回到主线程最一开始的地方。在FEngineLoop::Tick()函数里的如下图所示的位置就是我们各种渲染和逻辑线程的入口。
而在它的下面就是我们的UI层
UI层会根据之前绑定的FViewport完成最后的渲染。


【3】如何处理分屏渲染的冲突问题。
UE4.24不对引擎进行修改为先提条件,假设现在有以下情景:现在有两个场景,一个白天一个黑夜,现在如何把它们拼到一起实现分屏渲染呢。出现冲突的核心原因是Renderer只有一个UScene,而实现分屏的机制仅仅是架设多个FViewport罢了,但是UScene还是只有一个,所以如果要求两个完全不同的渲染设置进行分屏渲染肯定是会出现冲突的。解决之道就是对两个渲染场景的数据进行剥离。剥离的方法就是把要冲突的数据烘焙下来或者进行分层。
先建三个场景,一个白天场景a,一个黑夜场景b
首先处理静态物体,把全部灯光全部设置成静态灯光
场景a
场景b
然后处理反射部分,每个场景使用不同的Reflection Probe就好了
场景a
场景b
接下来处理静态灯光对动态物体的光照
这里需要使用 VolumeLightSamples 的方法储存静态光照的信息,如果使用 VolumetricLightMap 会出现两个子管卡冲突的情况(4.24 版本)。这里可以适当增大VolumeLightSamples 的密度。
场景a
场景b
下面开始处理动态物体的影子问题,把两个关卡加载到主关卡里,然后给两盏平行光,放到不同的灯光channel里然后分别调整相应的参数
即可得到一个白天一个黑夜的合并场景,然后在架两个摄像机做分屏就可以了。
其中需要注意的是需要关闭静态物体接受Dynamicshadow的选项
在动态物体影子部分如果觉得设置灯光channel比较麻烦,可以尝试把平行光改成面光源或者广角聚光灯。
SUMMARY AND OUTLOOK:
目前实现分屏的方法有两种,一种是使用Split Screen:这种方式会创建两个View,渲染的时候只是变换下View即可。第二种方式是架两个SceneCapture然后渲染两个RT。这两个方法虽然都能实现分屏但是明显第一种方法的消耗要小得多,因为第一种方法不会创建两个SceneRenderer,第二种方法会创建两个SceneRenderer,所有Buffer都是第一种方法的两倍,所以还是推荐使用第一种方法!
Enjoy it。
NEXT:
todo...
By YivanLee 2020/2/24

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
发表于 2021-4-14 14:38 | 显示全部楼层
服气
发表于 2021-4-14 14:38 | 显示全部楼层
两张rendertexture,然后贴上去
发表于 2021-4-14 14:40 | 显示全部楼层
不是正规做法
发表于 2021-4-14 14:43 | 显示全部楼层
曾经用第二种方法加3d投影机搞过3D尝试
[害羞]
发表于 2021-4-14 14:52 | 显示全部楼层
学习了
发表于 2021-4-14 14:56 | 显示全部楼层
双总 V587
发表于 2021-4-14 14:59 | 显示全部楼层
双~ 我现在知乎推荐阅读全是你。
发表于 2021-4-14 15:01 | 显示全部楼层
[调皮]学习
发表于 2021-4-14 15:04 | 显示全部楼层
太赞了,受益匪浅!

有个疑问,文中说:“实现分屏的机制仅仅是架设多个FViewport罢了......”

我看此文理解:UGameViewportClinet和FViewport应该是一一对应的吧?
那么应该只有一个FViewport。

这句话是不是想说:“实现分屏的机制仅仅是架设多个FSceneView罢了......”?
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity游戏引擎开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2021-5-8 21:02 , Processed in 0.186247 second(s), 22 queries .

Powered by Discuz! X3.4 Licensed

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表