From 3372337fdf84b9b8f0ab104d6f0955b1d6255e9f Mon Sep 17 00:00:00 2001 From: unanmed <1319491857@qq.com> Date: Wed, 26 Nov 2025 15:12:30 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=9C=B0=E5=9B=BE=E6=B8=B2=E6=9F=93?= =?UTF-8?q?=E5=90=8E=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../client-modules/src/render/map/element.ts | 7 +- .../client-modules/src/render/map/renderer.ts | 142 +++++++++++++++++- .../client-modules/src/render/map/types.ts | 22 ++- 3 files changed, 159 insertions(+), 12 deletions(-) diff --git a/packages-user/client-modules/src/render/map/element.ts b/packages-user/client-modules/src/render/map/element.ts index e929aae..2ce0093 100644 --- a/packages-user/client-modules/src/render/map/element.ts +++ b/packages-user/client-modules/src/render/map/element.ts @@ -15,10 +15,9 @@ export class MapRender extends RenderItem { ) { super('static', false, false); - this.renderer.setLayerState(layerState); - this.renderer.setCanvasSize(this.width, this.height); - this.renderer.setCellSize(CELL_WIDTH, CELL_HEIGHT); - this.renderer.setRenderSize(MAP_WIDTH, MAP_HEIGHT); + renderer.setLayerState(layerState); + renderer.setCellSize(CELL_WIDTH, CELL_HEIGHT); + renderer.setRenderSize(MAP_WIDTH, MAP_HEIGHT); this.delegateTicker(time => { this.renderer.tick(time); diff --git a/packages-user/client-modules/src/render/map/renderer.ts b/packages-user/client-modules/src/render/map/renderer.ts index f37e8f4..49872f8 100644 --- a/packages-user/client-modules/src/render/map/renderer.ts +++ b/packages-user/client-modules/src/render/map/renderer.ts @@ -188,6 +188,15 @@ export class MapRenderer /** 图块动画器 */ private readonly tileAnimater: ITextureAnimater; + /** `gl.viewport` 横坐标 */ + private viewportX: number = 0; + /** `gl.viewport` 纵坐标 */ + private viewportY: number = 0; + /** `gl.viewport` 宽度 */ + private viewportWidth: number = 0; + /** `gl.viewport` 高度 */ + private viewportHeight: number = 0; + //#endregion //#region 初始化 @@ -227,6 +236,7 @@ export class MapRenderer this.viewport = new MapViewport(this); this.tileAnimater = new TextureColumnAnimater(); this.initVertexPointer(this.gl, data); + this.setViewport(0, 0, this.canvas.width, this.canvas.height); } /** @@ -348,11 +358,43 @@ export class MapRenderer setCanvasSize(width: number, height: number): void { this.canvas.width = width; this.canvas.height = height; + // 更新 FBO 的纹理尺寸信息 + const gl = this.gl; + const { pingTexture2D, pongTexture2D } = this.contextData; + gl.bindTexture(gl.TEXTURE_2D, pingTexture2D); + gl.texImage2D( + gl.TEXTURE_2D, + 0, + gl.RGBA, + width, + height, + 0, + gl.RGBA, + gl.UNSIGNED_BYTE, + null + ); + gl.bindTexture(gl.TEXTURE_2D, pongTexture2D); + gl.texImage2D( + gl.TEXTURE_2D, + 0, + gl.RGBA, + width, + height, + 0, + gl.RGBA, + gl.UNSIGNED_BYTE, + null + ); + gl.bindTexture(gl.TEXTURE_2D, null); this.updateRequired = true; } setViewport(x: number, y: number, width: number, height: number): void { - this.gl.viewport(x, y, width, height); + this.viewportX = x; + this.viewportY = y; + this.viewportWidth = width; + this.viewportHeight = height; + this.updateRequired = true; } clear(color: boolean, depth: boolean): void { @@ -674,6 +716,57 @@ export class MapRenderer const tileVAO = gl.createVertexArray(); const backVAO = gl.createVertexArray(); + // Post effect + const pingFramebuffer = gl.createFramebuffer(); + const pongFramebuffer = gl.createFramebuffer(); + const pingTexture2D = gl.createTexture(); + const pongTexture2D = gl.createTexture(); + + // 初始化 Post effect FBO 和 Texture + gl.bindTexture(gl.TEXTURE_2D, pongTexture2D); + gl.texImage2D( + gl.TEXTURE_2D, + 0, + gl.RGBA, + this.canvas.width, + this.canvas.height, + 0, + gl.RGBA, + gl.UNSIGNED_BYTE, + null + ); + gl.bindFramebuffer(gl.FRAMEBUFFER, pingFramebuffer); + gl.framebufferTexture2D( + gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0, + gl.TEXTURE_2D, + pongTexture2D, + 0 + ); + gl.bindTexture(gl.TEXTURE_2D, pingTexture2D); + gl.texImage2D( + gl.TEXTURE_2D, + 0, + gl.RGBA, + this.canvas.width, + this.canvas.height, + 0, + gl.RGBA, + gl.UNSIGNED_BYTE, + null + ); + gl.bindFramebuffer(gl.FRAMEBUFFER, pongFramebuffer); + gl.framebufferTexture2D( + gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0, + gl.TEXTURE_2D, + pingTexture2D, + 0 + ); + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + gl.bindTexture(gl.TEXTURE_2D, null); + + // 初始化图块纹理 gl.bindTexture(gl.TEXTURE_2D_ARRAY, tileTexture); gl.texStorage3D(gl.TEXTURE_2D_ARRAY, 1, gl.RGBA8, 4096, 4096, 1); gl.bindTexture(gl.TEXTURE_2D_ARRAY, null); @@ -717,6 +810,12 @@ export class MapRenderer backVAO, tileTexture, backgroundTexture, + + pingFramebuffer, + pongFramebuffer, + pingTexture2D, + pongTexture2D, + tileTextureWidth: 4096, tileTextureHeight: 4096, tileTextureDepth: 1, @@ -1273,7 +1372,11 @@ export class MapRenderer insTilePosAttribLocation: tilePos, insTexCoordAttribLocation: texCoord, insTileDataAttribLocation: tileData, - insTexDataAttribLocation: texData + insTexDataAttribLocation: texData, + pingFramebuffer, + pongFramebuffer, + pingTexture2D, + pongTexture2D } = data; // 图层检查 @@ -1311,6 +1414,19 @@ export class MapRenderer gl.bindBuffer(gl.ARRAY_BUFFER, null); } + gl.viewport( + this.viewportX, + this.viewportY, + this.viewportWidth, + this.viewportHeight + ); + + if (this.postEffects.length > 1) { + gl.bindFramebuffer(gl.FRAMEBUFFER, pingFramebuffer); + } else { + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + } + // 背景 gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); gl.useProgram(backProgram); @@ -1365,6 +1481,28 @@ export class MapRenderer gl.bindVertexArray(null); gl.bindTexture(gl.TEXTURE_2D_ARRAY, null); + // Post effects + let inputTextrue = pongTexture2D; + let outputFBO: WebGLFramebuffer | null = pingFramebuffer; + + this.postEffects.forEach((v, i, a) => { + v.render(gl, inputTextrue, outputFBO, data); + if (inputTextrue === pongTexture2D) { + inputTextrue = pingTexture2D; + } else { + inputTextrue = pongTexture2D; + } + if (i === a.length - 2) { + outputFBO = null; + } else { + if (outputFBO === pingFramebuffer) { + outputFBO = pongFramebuffer; + } else { + outputFBO = pingFramebuffer; + } + } + }); + // 清空更新状态标识 this.updateRequired = false; this.needUpdateFrameCounter = false; diff --git a/packages-user/client-modules/src/render/map/types.ts b/packages-user/client-modules/src/render/map/types.ts index 399c786..5e69984 100644 --- a/packages-user/client-modules/src/render/map/types.ts +++ b/packages-user/client-modules/src/render/map/types.ts @@ -131,6 +131,15 @@ export interface IContextData { /** 背景程序的 VAO */ readonly backVAO: WebGLVertexArrayObject; + /** 第一个 framebuffer */ + readonly pingFramebuffer: WebGLFramebuffer; + /** 第二个 framebuffer */ + readonly pongFramebuffer: WebGLFramebuffer; + /** 第一个 texture2D */ + readonly pingTexture2D: WebGLTexture; + /** 第二个 texture2D */ + readonly pongTexture2D: WebGLTexture; + /** 当前画布的图块纹理宽度 */ tileTextureWidth: number; /** 当前画布的图块纹理高度 */ @@ -280,17 +289,18 @@ export interface IMapRendererPostEffect { init(gl: WebGL2RenderingContext, data: IContextData): void; /** - * 渲染效果对象,将内容渲染到输出 FBO 上 + * 渲染效果对象,将内容渲染到输出 FBO 上,不建议使用 `gl.viewport` 切换渲染区域,因为在调用此方法时已经处理完毕了。 + * 需要自行绑定输出 FBO 和输入纹理、缓冲区清空等内容。 * @param gl WebGL2 画布上下文 + * @param input 输入的 Texture2D + * @param output 输出 FBO,内容要画到这个 FBO 上,如果是 `null` 的话说明本次绘制会直接推送到画布 * @param data 地图渲染的上下文数据 - * @param input 输入 FBO - * @param output 输出 FBO,内容要画到这个 FBO 上 */ render( gl: WebGL2RenderingContext, - data: IContextData, - input: WebGLFramebuffer, - output: WebGLFramebuffer + input: WebGLTexture, + output: WebGLFramebuffer | null, + data: IContextData ): void; }