Unity原生渲染方案

Unity原生渲染方案

 

作者:3dimensions

three_dimensions@live.cn

本文为原创内容,转载请注明出处。

 

  这是2014年做的一个方案,当时做这个的动机是想在原生代码中使用Unity的材质系统绘制,同时由原生代码提供绘制数据,省掉Unity内部的分配内存及数据转换,以及动态模型数据“非托管内存→ 托管内存→ 非托管内存”的传输过程。适用于有大量动态模型数据生成的情况,测试结果在PC和移动平台上均有数倍的性能提升。注意,如果不使用Unity的材质系统,而是自己做渲染的话,并不需要按这个方案做。

 

一、目标


  在Unity中,动态生成三维模型需要把数据填入Mesh对象中,当中Unity内部需要分配内存及做数据转换,效率不佳。而且如果要编写Unity原生插件去生成三维模型,模型数据需要经“非托管内存→ 托管内存→ 非托管内存”的传输过程,浪费很多内存频宽及时间,特别是每帧都需要更新的串流数据。所以,我们希望能绕过这数据传输过程,直接进行原生渲染。本文总结的原生渲染方案,目标是令原生插件继续使用Unity的材质系统,然后在插件内生成顶点数据并提交Draw-call。

 

二、方案

 

  大致的想法是希望让Unity设置好OpenGL绘制状态后,在原生插件中获取这些状态并 加以利用。此方案暂不考虑多线程渲染及多步骤材质(Multi-pass material),并针对Open GL (Windows)及OpenGL ES(iOS和Android)进行测试。

 

2.1 进入原生渲染的时机──选项A:GL.IssuePluginEvent


  首先我们考虑使用Unity的GL.IssuePluginEvent,但这个方法还存在问题,在PC上测试 的结果显示,通过GL.IssuePluginEvent进入原生渲染后,一些绘制资源(如纹理)已经被解 绑,Unity中设置好的绘制状态被破坏了。所以此方案不可行。


2.2 进入原生渲染的时机──选项B:直接调用原生API


  另一个方法是在Unity脚本中先设置好材质,然后直接调用原生插件。首先,在Unity中调 用Material.SetPass()设置材质,但这个命令并不一定会被Unity立即执行,此时若直接进入 原生渲染,可能会发现绘制状态不正确。我们找到一个可行的方法是,在调用SetPass()后紧 随执行DrawMeshNow()去绘制一个小网格,此时整个OpenGL的绘制状态已经被正确设置,然 后才调用原生插件,在原生渲染器中查询到的绘制状态,便是相应材质对应的正确绘制状态。

 

2.3 如何在Native plugin中利用Unity的绘制状态


  如何利用Unity设置好的绘制状态,对于PC和Android有一些区别:

  1. 在PC上,Unity进入原生渲染之后,查询到的当前着色器名字为0,但这并不意味着绘制 状态被破坏,仍然可以绘制出正确的结果。我们怀疑DrawMeshNow()选择了设置材质到固 定管线,在PC原生渲染中只能利用这一固定管线。OpenGL固定管线的顶点属性是有语 义的,在原生渲染中调用gl*Pointer接口提供顶点数据就可以提交draw-call。
  2. 在Android上,OpenGL ES 2.0以上版本只能使用可编程管线,且移除了顶点属性的语义, 所有顶点属性变成了Generic。为了给当前绘制状态提供顶点数据,需要在原生渲染器中 查询当前着色器名称,并解析着色器的接口信息。Unity对顶点属性有比较固定的命名格 式,可以根据这些属性字符串从当前着色器中查询到Location信息。在原生渲染器中调 用glVertexAttribPointer提供顶点数据到相应Location,然后再提交Draw-call。

 

2.4 参与Unity的视锥裁剪


  为了使原生渲染器正确的参与视锥裁剪,我们需要在原生渲染器的GameObject设置和原生 几何体相同的包围盒。 具体方法为如下。首先,在挂载原生渲染器的GameObject上关联Renderer及MeshFilter。 然后,在MonoBehaviour.Update()时,从原生渲染器中读取原生几何体的包围盒,再把一个 拥有相同包围盒的Mesh设置到这个GameObject的MeshRenderer,然后Mesh重新计算包围盒引 发MeshRenderer重新计算包围盒。最后,在OnWillRenderObject()时,记录GameObject是否 通过了视锥裁剪。而在OnRenderObject()时,根据是否通过了裁剪来决定是否调用原生渲染 器.


2.5 多个摄像机及多个原生调用


  我们测试了多个摄像机的情况,以及在Material.SetPass()后紧随多次原生调用的情况, 原生渲染器均能正确绘制。


2.6 绘制次序


  原生渲染器的绘制次序通过Unity的层(Layer)控制,原生渲染器对应不同于场景其他物 体的Layer,原生渲染器的层对应独立的摄像机。摄像机之间依据其Depth属性决定绘制顺序。

 

三、原生渲染器的性能

 

  测试了一个4W粒子的原生粒子系统,材质为Unity内置的“Particles/Alpha Blended”。测试结果:

 

四、其他事项

 

  1. OpenGL状态是全局的,原生渲染器中除自持资源外的其他状态改变,必须在返回Unity前还原回进入原生渲染器时的状态,否则可能崩溃。
  2, 关于粒子数据上传到GPU:如果上传新数据时Buffer仍然被绘制占用,可能引发隐式同步。通过每帧循环使用不同的Buffer可以消减这方面的开销。
  3. 在PC上运行Unity时要给予命令行参数: -force-opengl。
  4. 多步骤材质(Multi-pass material)的情况比较复杂,暂未考虑参与原生渲染。
  5. 方案未将遮挡剔除考虑在内。
  6. 开启多线程渲染后,方案可能会有问题。
  7. 内置材质“Particles/Multipy”在PC上效果不正确,原因未明。
  8. Unity调用DrawMeshNow()绘制的网格模型需要被隐藏起来。

转载于:https://www.cnblogs.com/3dimensions/p/5428963.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值