# 第三方H5引擎混合渲染
微信小游戏3D支持与第三方H5引擎混合渲染的能力,但无法对第三方引擎进行加速。
开发者可以2D使用其他引擎渲染,3D使用小游戏框架渲染来完成开发,可以对现有小游戏针对性的性能提升,或是可以渐进式优化,保留原本部分的开发模式。
在微信小游戏中,第三方引擎只能绘制在一个离屏canvas上,要渲染在小游戏中,只能借助小游戏的engine.Texture2D,并通过engine.UISprite组件将离屏canvas的内容绘制在小游戏UI界面中。可以将第三方引擎渲染的结果看做小游戏的一个普通的UI节点,因此可以与小游戏的其他3D和2D节点混合。
Texture2D提供了两个接口可以实现这样的功能,但都有一定的限制,另外,编辑工具提供了另一种只在工具中渲染的接口。三种接口的对比说明如下
# 三种渲染方式的对比
HudCanvas | ReadPixels | InitWithCanvas | |
---|---|---|---|
接口(可参考下面的脚本) | engine.Editor.API.CanvasBgManager .setCustomHudCanvas(canvas) | texture2d.initDynamicTexture() texture2d.updateSubTexture() | texture2d.initWithCanvas() |
原理 | 将第三方引擎创建出来的canvas通过webgl texImage2D的方式同步渲染在编辑器的hudCanvas上。 | 每帧对第三方引擎创建出来的canvas进行readPixels,并用readPixels出来的arraybuffer更新texture2d的渲染内容。 | 通过传入第三方引擎创建出来的canvas,小游戏框架底层会同步该canvas的渲染内容到texture2d上。 |
适用范围 | 工具播放态 | 工具播放态、模拟器、真机 | 真机 |
性能 | 高 | 低 | 高 |
是否支持与小游戏2d渲染混用 | 不支持 | 支持 | 支持 |
# 注意事项:
- 由于第三方引擎的渲染依赖于脚本的执行,渲染结果无法在工具的编辑态,以及Scene窗口中展示出来,只能在播放态的Game窗口中渲染。
- 在真机上渲染,只建议使用initWithCanvas的方式。
- 在模拟器中展示只能使用ReadPixels的方式。
- 在工具播放态,若有与小游戏2d渲染混用的需求,请使用ReadPixels的方式;若没有,即2d完全由第三方引擎渲染,则使用HudCanvas的方式。
# 示例
打开小游戏版开发者工具,然后点击下面的新手引导链接即可直接下载引导并开始学习。
下面的示例代码,是第三方引擎在小游戏中启动的脚本组件,应挂载在一个空2d场景的节点上,此脚本会创建一个2D子节点和UISprite组件以及Texture2D承接第三方引擎的渲染结果,并配置在不同情况使用的不同接口。
import engine from "engine";
declare var require: any
@engine.decorators.serialize("ThirdEngineStartUp")
export default class ThirdEngineStartUp extends engine.Script {
private _thirdEngineNode?: engine.Entity; // 用于承接第三方引擎渲染内容的节点
private _uiSprite?: engine.UISprite // 用于承接第三方引擎渲染内容的渲染组件
private _tex?: engine.Texture2D; // 用于承接第三方引擎渲染内容的texture2d
private _pixels?: Uint8Array; // 从第三方引擎中读出的渲染像素值。
private _systemInfo = wx.getSystemInfoSync() // 获取系统信息
private _useReadPixelsMethodInEditor = false // 在工具上绘制是否使用readPixels的方式,readPixels的方式表现真实,但是在工具上绘制效率低。
private _inEditor = (engine as any).Editor && (engine as any).Editor.API && (engine as any).Editor.API.CanvasBgManager // 是否在小游戏工具中播放
private _inSimulator = this._systemInfo.platform === "devtools" // 是否在模拟器中播放
private _hasSetHudCanvas = false // 是否已经设置过hudCanvas
public onAwake() {
// 把第三方引擎项目构建出来的微信小游戏game.js入口脚本require进来。
// require('/game.js')
// 创建一个子节点用于承接第三方引擎的渲染结果
this._createNodeWithUISprite()
}
public onUpdate(dt) {
if (/** 第三方引擎初始化完成 */) {
this._render()
}
}
public onDestroy() {
this.clear()
}
// 创建一个有UISprite组件的2d节点,UISprite组件上的spriteframe(所对应的texture2d)用于代理显示第三方引擎渲染的结果
private _createNodeWithUISprite() {
const SHAREDWIDTH = engine.game.rootUICanvas.entity.transform2D.size.x;
const SHAREDHEIGHT = engine.game.rootUICanvas.entity.transform2D.size.y;
if (!this._thirdEngineNode) {
this._thirdEngineNode = engine.game.createEntity2D("thirdEngineNode");
this._thirdEngineNode.transform2D.size.x = SHAREDWIDTH;
this._thirdEngineNode.transform2D.size.y = SHAREDHEIGHT;
this._thirdEngineNode.transform2D.position.x = 0;
this._thirdEngineNode.transform2D.position.y = 0;
this.entity.transform2D.addChild(this._thirdEngineNode.transform2D);
this._uiSprite = this._thirdEngineNode.addComponent(engine.UISprite);
this._uiSprite.colorBlendType = engine.BlendType.Alpha;
// 由于框架里面的坐标系与HTMLCanvas的坐标系垂直方向相反,所以需要对绘制的RenderTexture做一个垂直方向的翻转
this._thirdEngineNode.transform2D.scaleY = -1;
}
}
/**
* 有三种方式,可以将第三方引擎绘制结果,渲染出来:
* 1. renderByReadPixels的方式,原理:每帧对第三方引擎创建出来的canvas进行readPixels,并用readPixels出来的arraybuffer更新texture2d的渲染内容。
* 此方法是小游戏工具、真机、模拟器都通用的渲染方式。因为要每帧readPixels,所以性能很低。
* 2. renderInRuntimeByInitWithCanvas,原理:通过传入第三方引擎创建出来的canvas,小游戏框架底层会同步该canvas的渲染内容到texture2d上。
* 此方法只能在真机上正常渲染,小游戏工具和模拟器中都无法正常显示。性能好。
* 3. renderInEditorByHudCanvas的方式,原理:将第三方引擎创建出来的canvas通过webgl texImage2D的方式同步渲染在编辑器的hudCanvas上。
* 此方法只能值小游戏工具内使用,无法在模拟器和真机上使用。性能好,主要用于替代前两种方式在小游戏工具中的高性能渲染。
* 缺陷:只能渲染在最上层,如果想在第三方引擎层级之上再绘制,层级显示会不符合预期。这种情况在小游戏工具中应该renderByReadPixels的方式
*
* 三种渲染方式中只有renderByReadPixels需要每帧都进行调用,其他两种都只需要调用一次即可。
*/
private _render() {
if (this._inEditor) { // 小游戏工具中有两种方式进行渲染,可以按需自行配置_useReadPixelsMethodInEditor属性
if (this._useReadPixelsMethodInEditor) {
this._renderByReadPixels()
} else {
this._renderInEditorByHudCanvas()
}
} else if (this._inSimulator) { // 工具模拟器中只能使用readPixels的方式进行渲染
this._renderByReadPixels()
} else { // 真机上只建议使用initWithCanvas的方式
this._renderInRuntimeByInitWithCanvas()
}
}
// readPixels方式将第三方渲染的结果以ArrayBuffer的形式传给texture
private _renderByReadPixels() {
const glCtx = /** 获取离屏canvas的webgl绘制的上下文 */ ;
const needBufferLen = glCtx.drawingBufferWidth * glCtx.drawingBufferHeight * 4
if (!this._tex || (this._pixels && this._pixels.length !== needBufferLen)) { // 若第一次进来,或buffer大小不一致,需要创建一段buffer,以及一个texture2d
const tex = new engine.Texture2D();
this._pixels = new Uint8Array(needBufferLen);
// 用第三方引擎canvas的宽高来初始化texture2d
tex.initDynamicTexture(glCtx.drawingBufferWidth, glCtx.drawingBufferHeight);
const sf = engine.SpriteFrame.createFromTexture(tex);
this._uiSprite.spriteFrame = sf;
this._tex = tex
}
// 读取canvas上的像素值
glCtx.readPixels(0, 0, glCtx.drawingBufferWidth, glCtx.drawingBufferHeight, glCtx.RGBA, glCtx.UNSIGNED_BYTE, this._pixels);
// 将像素值赋值给texture2d,进行真正的渲染
this._tex.updateSubTexture(this._pixels, 0, 0, glCtx.drawingBufferWidth, glCtx.drawingBufferHeight)
}
// 通过小游戏工具的hudCanvas方式进行渲染
private _renderInEditorByHudCanvas() {
if (!this._hasSetHudCanvas) {
// 把第三方引擎的canvas作为渲染源
(engine as any).Editor.API.CanvasBgManager.setCustomHudCanvas(/** 第三方引擎的离屏canvas */)
this._hasSetHudCanvas = true
}
}
// 在真机上运行时需要使用使用initWithCanvas的方式创建texture2d
private _renderInRuntimeByInitWithCanvas() {
if (!this._uiSprite.spriteFrame) {
const tex = new engine.Texture2D();
tex.initWithCanvas(/** 第三方引擎的离屏canvas */);
const sf = engine.SpriteFrame.createFromTexture(tex);
this._uiSprite.spriteFrame = sf;
}
}
public clear() {
if (this._inEditor) {
(engine as any).Editor.API.CanvasBgManager.setCustomHudCanvas(null)
this._hasSetHudCanvas = false
}
if (this._thirdEngineNode) {
this.entity.transform2D.removeChild(this._thirdEngineNode.transform2D);
}
}
}