<六>CocosCreator3D && 2D混合
在3D场景中2DUI和3D的混合需要用到一系列渲染纹理的技术。
纹理
在开发中,纹理贴图资源是一种用于程序采样的资源,如模型上的贴图、精灵上的 UI。当程序渲染 UI 或者模型时,会使用纹理坐标获取纹理颜色,然后填充在模型网格上,再加上光照等等一系列处理便渲染出了整个场景。
关于纹理:https://learnopengl-cn.github.io/01%20Getting%20started/06%20Textures/#_1
渲染纹理
渲染纹理是一张在 GPU 上的纹理。通常我们会把它设置到相机的 目标纹理 上,使相机照射的内容通过离屏的 frambuffer 绘制到该纹理上。一般可用于制作汽车后视镜,动态阴影等功能。
创建渲染纹理资源
在使用渲染纹理前需要先创建渲染纹理资源,在资源管理器 中点击左上方的 + 按钮,然后选择 渲染纹理。
在属性检查器中可设置渲染纹理的相关属性。
一般来说,会把纹理贴图设置到相机的渲染输出目标贴图上。
渲染纹理在2D/UI中使用
备注:无论渲染纹理在哪种情况下使用,都需要至少两个或以上相机。渲染纹理作为某相机的输出目标纹理时,不能再做为该相机的要渲染的纹理,不然会宝N多错误。最少创建两个相机,一个相机作为输入,把渲染的内容绘制到目标渲染纹理上,另一个相机渲染的对象(如精灵作为输出)纹理设置为渲染纹理。
RenderTexture 可以像普通贴图一样使用。
以 Sprite 为例:
在场景中创建一个精灵,从 资源管理器 拖拽到精灵的 SpriteFrame 属性。
在场景中创建几个3D物体,运行看一下效果:
作为材质贴图使用
将 RenderTexture 设置为材质贴图包括三个步骤:
1.在 effect 中处理 uv。判断 SAMPLE_FROM_RT,并调用 CC_HANDLE_RT_SAMPLE_FLIP 函数:
#if USE_TEXTURE
v_uv = a_texCoord * tilingOffset.xy + tilingOffset.zw;
#if SAMPLE_FROM_RT
CC_HANDLE_RT_SAMPLE_FLIP(v_uv);
#endif
#endif
完整的effect:
// Copyright (c) 2017-2020 Xiamen Yaji Software Co., Ltd.
CCEffect %{
techniques:
- name: opaque
passes:
- vert: camera-texture-vs:vert
frag: camera-texture-fs:frag
properties: &props
mainTexture: { value: grey }
tilingOffset: { value: [1, 1, 0, 0] }
mainColor: { value: [1, 1, 1, 1], editor: { type: color } }
colorScale: { value: [1, 1, 1], target: colorScaleAndCutoff.xyz }
alphaThreshold: { value: 0.5, target: colorScaleAndCutoff.w, editor: { parent: USE_ALPHA_TEST } }
color: { target: mainColor, editor: { visible: false } } # backward compability
migrations: &migs
properties:
mainColor: { formerlySerializedAs: color }
- name: transparent
passes:
- vert: camera-texture-vs:vert
frag: camera-texture-fs:frag
depthStencilState: &d1
depthTest: true
depthWrite: false
blendState:
targets:
- blend: true
blendSrc: src_alpha
blendDst: one_minus_src_alpha
blendDstAlpha: one_minus_src_alpha
properties: *props
migrations: *migs
- name: add
passes:
- vert: camera-texture-vs:vert
frag: camera-texture-fs:frag
rasterizerState: &r1 { cullMode: none }
depthStencilState: *d1
blendState:
targets:
- blend: true
blendSrc: src_alpha
blendDst: one
blendSrcAlpha: src_alpha
blendDstAlpha: one
properties: *props
migrations: *migs
- name: alpha-blend
passes:
- vert: camera-texture-vs:vert
frag: camera-texture-fs:frag
rasterizerState: *r1
depthStencilState: *d1
blendState:
targets:
- blend: true
blendSrc: src_alpha
blendDst: one_minus_src_alpha
blendSrcAlpha: src_alpha
blendDstAlpha: one_minus_src_alpha
properties: *props
migrations: *migs
}%
CCProgram camera-texture-vs %{
precision highp float;
#include <legacy/input>
#include <builtin/uniforms/cc-global>
#include <legacy/decode-base>
#include <legacy/local-batch>
#include <legacy/input>
#if USE_VERTEX_COLOR
in lowp vec4 a_color;
out lowp vec4 v_color;
#endif
#if USE_TEXTURE
out vec2 v_uv;
uniform TexCoords {
vec4 tilingOffset;
};
#endif
vec4 vert () {
vec4 position;
CCVertInput(position);
mat4 matWorld;
CCGetWorldMatrix(matWorld);
#if USE_TEXTURE
v_uv = a_texCoord * tilingOffset.xy + tilingOffset.zw;
#if SAMPLE_FROM_RT
CC_HANDLE_RT_SAMPLE_FLIP(v_uv);
#endif
#endif
#if USE_VERTEX_COLOR
v_color = a_color;
#endif
return cc_matProj * (cc_matView * matWorld) * position;
}
}%
CCProgram camera-texture-fs %{
precision highp float;
#include <legacy/output>
#if USE_ALPHA_TEST
#pragma define-meta ALPHA_TEST_CHANNEL options([a, r, g, b])
#endif
#if USE_TEXTURE
in vec2 v_uv;
uniform sampler2D mainTexture;
#endif
uniform Constant {
vec4 mainColor;
vec4 colorScaleAndCutoff;
};
#if USE_VERTEX_COLOR
in lowp vec4 v_color;
#endif
vec4 frag () {
vec4 o = mainColor;
o.rgb *= colorScaleAndCutoff.xyz;
#if USE_VERTEX_COLOR
o *= v_color;
#endif
#if USE_TEXTURE
o *= texture(mainTexture, v_uv);
#endif
#if USE_ALPHA_TEST
if (o.ALPHA_TEST_CHANNEL < colorScaleAndCutoff.w) discard;
#endif
return CCFragOutput(o);
}
}%
运行:
把UI绘制到模型上
1.创建一个四方形,用于绘制rt
新建一个材质就叫ui-to-m吧,shader用内置的builtin-camera-texture,该effect已经处理好了设置rt的逻辑。
2.创建要待渲染的ui,把canvas就重命名为rendertexture好了,把该ui的相机重命名为UICamera_rendertexture。(重命名只是为了顾名思义)
3.创建一个把ui绘制到model的脚本,就叫RenderUIToModel,挂载到rendertexture上
编辑脚本:
创建一个目标模型的引用属性:
@property(MeshRenderer)
public model: MeshRenderer = null!;
创建一个rt的私有属性
实现就放在start中好了:
import { _decorator, Canvas, Component, MeshRenderer, Node, RenderTexture, view } from 'cc';
const { ccclass, property } = _decorator;
@ccclass('RenderUIToModel')
export class RenderUIToModel extends Component {
@property(MeshRenderer)
public model: MeshRenderer = null!;
public renderTexture: RenderTexture | null = null;
start() {
//获取当前Canvas
const canvas = this.getComponent(Canvas);
//实例化一个渲染纹理
const tex = new RenderTexture();
tex.name = 'render-ui-to-model';
//设置渲染纹理的宽高
const size = view.getVisibleSize();
tex.reset({
width: size.width,
height: size.height,
});
this.renderTexture = tex;
//指定相机的输出贴图
canvas.cameraComponent.targetTexture = tex;
//指定目标模型的贴图
this.model.material.setProperty('mainTexture', tex);
}
protected onDestroy(): void {
if (this.renderTexture) {
this.renderTexture.destroy();
this.renderTexture = null;
}
}
update(deltaTime: number) {
}
}
运行预览:
通过readPixels方法实现3D内容绘制到2D中
readPixels方法是一个用于从渲染目标(例如屏幕)读取像素数据的方法。这个方法可以用来捕获当前渲染窗口的内容,或者特定的渲染目标(如RenderTexture)的像素数据。这里可以把3D 相机映射的内容通过readPixels方法读取到ArrayBuffer。
在Cocos Creator中,ArrayBuffer 是一个二进制的大块数据的容器。它是一个固定长度的通用类型数组,可以用来存储不同类型的数据(如整数、浮点数等)。ArrayBuffer 主要用于处理二进制数据,特别是在与浏览器的WebGL API交互时非常有用,因为WebGL API需要直接访问二进制数据来进行渲染操作。
readPixels 方法读取屏幕或渲染目标的像素数据时,返回的是一个 ArrayBufferView,如 Uint8Array 或 Float32Array。
关于readPixels 有三个参数:
rect: Rect 类型的对象,定义了要读取的矩形区域。rect 包含四个属性:x, y, width, height。
targetBuffer: 可选参数,用于存储读取的像素数据的数组缓冲区。如果不提供,将会自动创建一个新的数组缓冲区。
format: 可选参数,指定读取的像素格式,默认为 PixelFormat.RGBA8888。
返回:
一个 ArrayBufferView 类型的对象,通常是一个 Uint8Array 或其他类型的视图,包含了指定区域的像素数据。
TODO