WebGL实现独立于相机的Minimap:如何固定纹理四边形至视口右上角?
WebGL 小地图固定到视口右上角的解决方案
我用WebGL构建了带键盘控制相机的3D世界,想添加小地图——通过四边形渲染俯视视角相机的纹理实现。目前已能将纹理渲染到四边形,但无法把四边形固定在视口右上角;尝试用第二个Canvas方案失败,要么场景图渲染错误,要么内容无法显示。
核心解决方案:单Canvas叠加绘制
无需第二个Canvas,直接在主Canvas上用正交投影绘制固定位置的小地图四边形,步骤如下:
1. 优化俯视纹理渲染
你的renderToTexture函数已能生成俯视纹理,需修改相机参数,让俯视视角跟随主相机(假设主相机位置存在cameraPos变量):
function renderToTexture() { gl.bindFramebuffer(gl.FRAMEBUFFER, renderTargetFramebuffer); gl.viewport(0, 0, framebufferWidth, framebufferHeight); gl.clearColor(0.9, 0.9, 0.9, 1.0); gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); const context = createSGContext(gl); // 俯视相机:高度设为20,看向主相机位置,保持Y轴向上 const topDownPos = [cameraPos[0], 20, cameraPos[2]]; context.projectionMatrix = mat4.perspective(mat4.create(), glm.deg2rad(90), framebufferWidth / framebufferHeight, 0.1, 100); context.viewMatrix = mat4.lookAt(mat4.create(), topDownPos, cameraPos, [0,1,0]); root.render(context); gl.bindFramebuffer(gl.FRAMEBUFFER, null); }
2. 绘制固定位置的小地图四边形
新增渲染小地图的函数,用正交投影直接映射屏幕坐标,确保四边形固定在右上角:
function renderMinimap() { // 切换到主画布,设置完整视口 gl.bindFramebuffer(gl.FRAMEBUFFER, null); gl.viewport(0, 0, gl.canvas.width, gl.canvas.height); // 禁用深度测试,避免小地图被3D场景遮挡 gl.disable(gl.DEPTH_TEST); // 正交投影:直接对应屏幕像素坐标 const orthoMatrix = mat4.ortho(mat4.create(), 0, gl.canvas.width, 0, gl.canvas.height, -1, 1); const context = createSGContext(gl); context.projectionMatrix = orthoMatrix; context.viewMatrix = mat4.identity(mat4.create()); context.modelMatrix = mat4.identity(mat4.create()); // 绑定俯视纹理并启用纹理采样 gl.activeTexture(gl.TEXTURE0); gl.bindTexture(gl.TEXTURE_2D, renderTargetColorTexture); const shaderProgram = yourShaderProgram; // 替换为你的着色器程序对象 gl.uniform1i(gl.getUniformLocation(shaderProgram, 'u_tex'), 0); gl.uniform1i(gl.getUniformLocation(shaderProgram, 'u_enableObjectTexture'), true); // 定义右上角四边形顶点(占屏幕1/4大小,可按需调整) const minimapSize = Math.min(gl.canvas.width, gl.canvas.height) / 4; const x = gl.canvas.width - minimapSize; const y = gl.canvas.height - minimapSize; // 顶点格式:x, y, z, 纹理s, 纹理t(WebGL纹理原点在左下角,需翻转y轴纹理坐标) const vertices = [ x, y, 0, 0, 1, x + minimapSize, y, 0, 1, 1, x, y + minimapSize, 0, 0, 0, x + minimapSize, y + minimapSize, 0, 1, 0, ]; // 更新顶点缓冲区并绘制(假设你已有minimapVertexBuffer顶点缓冲区) gl.bindBuffer(gl.ARRAY_BUFFER, minimapVertexBuffer); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.DYNAMIC_DRAW); gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); // 恢复深度测试 gl.enable(gl.DEPTH_TEST); }
3. 调整主渲染循环顺序
按以下顺序执行渲染,确保小地图叠加在3D场景之上:
function renderLoop() { // 1. 渲染俯视场景到纹理 renderToTexture(); // 2. 渲染主3D场景 renderMainScene(); // 3. 绘制小地图 renderMinimap(); requestAnimationFrame(renderLoop); }
关键注意事项
- 深度测试控制:绘制小地图时必须禁用深度测试,否则会被3D场景中的物体遮挡。
- 纹理坐标翻转:WebGL纹理原点在左下角,因此小地图的纹理Y坐标需要反向对应屏幕Y坐标。
- 自适应大小:用
Math.min(gl.canvas.width, gl.canvas.height)计算小地图尺寸,确保在不同分辨率下比例一致。
参考代码:帧缓冲纹理初始化
function initRenderToTexture() { gl.activeTexture(gl.TEXTURE0); renderTargetFramebuffer = gl.createFramebuffer(); gl.bindFramebuffer(gl.FRAMEBUFFER, renderTargetFramebuffer); // 创建颜色纹理 renderTargetColorTexture = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, renderTargetColorTexture); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, framebufferWidth, framebufferHeight, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); // 创建深度纹理 renderTargetDepthTexture = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, renderTargetDepthTexture); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); gl.texImage2D(gl.TEXTURE_2D, 0, gl.DEPTH_COMPONENT32F, framebufferWidth, framebufferHeight, 0, gl.DEPTH_COMPONENT, gl.FLOAT, null); // 绑定到帧缓冲 gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, renderTargetColorTexture, 0); gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.TEXTURE_2D, renderTargetDepthTexture, 0); if(gl.checkFramebufferStatus(gl.FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE) { alert('Framebuffer incomplete!'); } gl.bindTexture(gl.TEXTURE_2D, null); gl.bindFramebuffer(gl.FRAMEBUFFER, null); }
参考着色器代码(纹理部分)
//texture related variables uniform bool u_enableObjectTexture; varying vec2 v_texCoord; uniform sampler2D u_tex; void main (void) { vec4 textureColor = vec4(0,0,0,1); if(u_enableObjectTexture) { textureColor = texture2D(u_tex,v_texCoord); gl_FragColor = texture2D(u_tex,v_texCoord); return; } // 保留原Phong着色逻辑 // ... }
内容的提问来源于stack exchange,提问作者Justin Hangoebl
相关产品推荐
相关产品推荐

