本文参照官方文档,涉及camera可以参考Cocos 3.0 camera
一、2D 对象概述
区别于 3D 模型对象,我们将不涉及模型的图片渲染体统称为 2D 渲染对象。2D 渲染对象的处理在底层的数据提交上与 3D 模型存在差异,其遵循自己的规则做出了一些针对性的调整以实现更好的效率表现和使用体验。
1.RenderRoot2D
2D 渲染对象的收集采用树状结构,RenderRoot 节点(带有 RenderRoot2D 组件的节点)为 2D 对象数据收集的入口节点,所有的 2D渲染对象需在 RenderRoot 节点下才可以被渲染。
由于 Canvas 组件本身继承 RenderRoot2D 组件,所以 Canvas 组件也可以作为数据收集的入口。2D 渲染节点必须带有 UITransform 组件作为渲染顶点数据、点击或者对齐策略等功能生效的必要条件。
2D 渲染也可以支持对模型进行渲染,唯一的条件是带有模型组件(例如 MeshRenderer/SkinnedMeshRenderer)的节点必须添加 UI/UIMeshRenderer 组件才可以和 UI 在相同的管线上进行渲染。
引擎中所有不拥有的 model 的渲染对象都为 2D 渲染对象。与 3D 对象不同,2D 对象本身不拥有 model 信息,其顶点信息是由 UITransform 组件的 Rect 信息持有并由引擎创建的,且本身没有厚度。由于引擎的设计要求,2D渲染对象需要为 RenderRoot 节点(带有 RenderRoot2D 组件的节点)的子节点才能完成数据的收集操作。
所以 2D 渲染对象的渲染要求有两点:
- 需要有 UITransform 组件
- 需要为 RenderRoot 节点的子节点
2.2D 渲染对象可见性说明
由于 2D 渲染对象在 Camera 的可见性判断上和 3D 渲染节点并无区别,所以用户需要自己控制节点的 layer 属性并设置 Camera 的 Visibility 来配合进行分组渲染,如果场景中出现多个相机的情况,错误的 layer 设置导致节点重复渲染或不渲染。
这里请 3D 1.2 版本升级的用户注意,我们纠正了之前的 Canvas 只会渲染其子节点的行为,目前需要用户自己管理节点的 layer 和相机的 Visibility,之前使用了多 Canvas 渲染的用户可能会需要对项目做出调整以达到更合理的场景结构。
二、渲染排序说明
https://docs.cocos.com/creator/3.0/manual/zh/ui-system/components/engine/priority.html
2D 渲染节点可分为在 Canvas 下的节点和不在 Canvas 下的节点两种,在 Canvas 下的节点可参考下文的 UI 节点排序。 对于不在 Canvas 下的节点,用户可选择通过自定义材质来开启深度检测实现和 3D 物体的遮挡显示,开启后则会按照物体的 Z 轴坐标进行遮挡渲染(可参考范例 2d-rendering-in-3d(GitHub | Gitee)。没有开启深度检测的话,则数据提交依旧会按照节点树顺序提交,也就意味着节点树靠下的节点会后渲染。
OpenGL学习笔记十二(深度测试)
OpenGL ES 学习教程(十二) DEPTH_TEST(深度缓冲测试)
当我们渲染多个物体时,这多个物体之间存在互相遮挡的关系,被遮挡的物体的部分将不可见,也就是它离相机更远,为了告诉计算机被遮挡的物体不需要渲染,我们就需要对物体上的点做深度测试,检测它是否需要渲染。
为了实现上述的检测,就需要深度缓冲,简单而言就是存储物体上点深度值的数组,这个数组一开始值为0,当目前渲染的物体的深度值大于缓冲区中存储的深度值时,就将这个值写入缓冲区,同时通过深度测试,如果与此相反,深度值小于缓冲区中的深度值,就是未通过深度测试,且不写入缓冲区,淘汰掉这个点(不做渲染)。
透明物体的深度写入问题
【关于cocos creator的渲染的提议】
1.UI 节点排序
UI 节点特指在 Canvas 节点下的 UI 节点,这些节点并未开启深度测试,所以节点的混合是严格按照节点树进行排序的。UI 的渲染排序采用的是一个深度优先的排序方式,每一个 UITransform 组件身上都有一个 priority 属性,根据 priority 的值来调整节点顺序。排序从根节点下的子节点开始,根据子节点的优先级来确定整体的渲染结构,也就是根节点下的子节点的排序已经决定了最终的渲染顺序。每一个节点下的所有子节点的 priority 则用来确定在当前节点下的渲染顺序。直接修改了 priority 也会直接改变节点树顺序。
举个例子:
因此,上图中整体的渲染顺序则是:B -> b1 -> C -> A -> a1 -> a2,在屏幕上的呈现状态为:a2 -> a1 -> A -> C -> b1 -> B。
注意事项
排序是一个很简单的功能,但是最终的呈现却是根据不同平台提供的渲染能力来的。因此,在这里说明一下,如果遇到了 UI 渲染出错,花屏,闪屏等现象,首先要检查的就是场景里所有相机(Camera 和 Canvas)的 ClearFlag,确保 场景里必须有一个相机要执行 Solid_Color 清屏操作。
具体如何设置 ClearFlag,可参考以下几种情况:
- 如果场景中只有一个 UI Canvas 或者 3D Camera,那么 ClearFlag 属性设置为 Solid_Color。
- 如果场景中包含 UI 背景层、3D 场景层、 UI 操作层,则:
- 2D 背景层:ClearFlag 属性设置为 Solid_Color。
- 3D 场景层:ClearFlag 属性设置为 Depth_Only。
- 2D UI 层:若有模型,ClearFlag 属性设置为 Depth_Only 以避免出现模型闪屏或者穿透的情况。若没有模型,ClearFlag 属性可设置为 Dont_Clear 或 Depth_Only。
2.论坛中的案例
原贴Cocos Creator3.0 在3D游戏场景下添加2D精灵(利用RenderRoot2D),2D精灵的会默认渲染在3D元素上面,类似于UI的渲染层级。
注:使用双相机时,注意调节3D物体的SCALE,太小了可能看不到
DepthTest可以参考
cocosCreator3.2.0 Skeleton的深度写入问题
三、UIMeshRenderer
参考
https://docs.cocos.com/creator/3.3/manual/zh/ui-system/components/editor/ui-model.html
Creator3D :太厉害了!3D模型原来可以这样显示在2DUI上
Creator 3.0怎么在2D场景里面跑3D人物?
UIMeshRenderer 是一个将 3D 模型从 3D 渲染管线转换到 2D 渲染管线的带有转换功能的渲染组件。该组件支持 3D 模型和粒子在 UI 上的显示,没有这个组件,即使模型和粒子节点在 UI 里也不会被渲染。
该组件的添加方式是在 层级管理器 中选中带有或继承自 MeshRenderer 组件的节点,然后点击 属性检查器 下方的 添加组件 按钮,选择 UI-> UIMeshRenderer 即可。而粒子则是添加到粒子节点上。通常结构如下所示:
注意,如果结点是多层结构,则每一层带有或继承自 MeshRenderer 组件的节点,都要添加UIMeshRenderer。如果运行后看不到,需要检查canvas中Camera的visibility设置。
四、3D模型显示在2D示例
1.UIMeshRenderer 方式
https://gitee.com/yeshao2069/CocosCreatorDemos/tree/v3.0.0/2DDemo/UIMeshRendererDemo
绑定了MeshRenderer组件的3d节点必须放在Canvas下,也就是2DUI层
3d节点的大小,3d节点添加了MeshRenderer组件,并且放置到Canvas下,那么他的大小将不会按照3d节点在摄像机下的大小显示,而是按照3d节点相对Canvas的大小来显示,通常模型的大小需要缩放到在UI层下的实际预览大小
材质,当大小和层级调整好以后你会发现模型是显示出来了,但是是黑的,,大家的默认effect 应该是builtin-standard吧,只需要将其改为builtin-unlit就可以了
但是,如果是两个material就会有问题,第2个material会失效
这个时候,如果是在UI上显示3D模型,不使用UIMeshRenderer只改scale,在摄像机可见性和3D模型层级匹配的情况下,其实本来就是能看到的。唯一的问题就是UI节点上的图片会挡住3D模型,此时将这些图片重新一个自定义的mtl即可,注意勾上USE TEXTURE和DepthTest:
如果后面有两张背景图片时,发现有一张看不到了,要检查一下是不是把Depth Write也勾上了。
2.RenderTexture方式
https://gitee.com/yeshao2069/CocosCreatorDemos/tree/v3.0.0/2DDemo/RenderTextureDemo
- 原理就是 把 3D 相机照射的内容绘制到 UI 的精灵帧上
- 那么主要就是3d摄像机,需要创建一个摄像机,然后将你所需要显示在ui上的3d节点移动到摄像机的视觉范围内
- 创建Sprite用来摄像机渲染出来的显示
- 在实现过程中可能会出现,本来只想显示指定的模型,但是会将摄像机的缓冲颜色也显示出来,这里菜鸟是将摄像机的ClearColor的透明度直接调为0,
@ccclass('ReaderTexture')
export class ReaderTexture extends Component {
@type(Sprite)
modelSprite!: Sprite;
@type(Camera)
camera!: Camera ;
@type(Node)
player!: Node;
private isRotate: boolean = false;
start() {
this.isRotate = false;
this.player.active = false;
this.refreshRenderTexture();
}
btnShowPlayerEvent(): void {
this.player.active = true;
}
btnHidePlayerEvent(): void {
this.isRotate = false;
this.player.active = false;
}
btnRotatePlayerEvent(): void {
this.isRotate = true;
}
update(deltaTime: number) {
if (this.isRotate) {
let eulerAngles: Vec3 = this.player.eulerAngles;
eulerAngles.y++;
this.player.eulerAngles = eulerAngles;
}
}
refreshRenderTexture(): void {
this.isRotate = false;
this.player.active = false;
const _colorAttachment = new GFXColorAttachment();
const _depthStencilAttachment = new GFXDepthStencilAttachment();
let renderTex = new RenderTexture();
renderTex.reset({
width: 350,
height: 610,
passInfo: {
colorAttachments: [_colorAttachment],
depthStencilAttachment: _depthStencilAttachment,
subPasses : []
}
});
let spriteframe: SpriteFrame = this.modelSprite.spriteFrame!;
let sp: SpriteFrame = new SpriteFrame();
sp.reset({
originalSize: spriteframe.originalSize,
rect: spriteframe.rect,
offset: spriteframe.offset,
isRotate: spriteframe.rotated,
borderTop: spriteframe.insetTop,
borderLeft: spriteframe.insetLeft,
borderBottom: spriteframe.insetBottom,
borderRight: spriteframe.insetRight,
});
this.camera.targetTexture = renderTex;
sp.texture = renderTex;
this.modelSprite.spriteFrame = sp;
}
}