)
图形学【免费下载链接】webgl-fundamentalsWebGL lessons that start with the basics项目地址https://gitcode.com/gh_mirrors/we/webgl-fundamentals点击查看免费下载本篇文章基于 webgl-fundamentals 仓库的 QA 文档 How to draw correctly textured trapezoid polygons 展开当你在 WebGL 中用两个三角形拼一个梯形并贴纹理时纹理经常会出现两半各自斜切、像平行四边形而不是透视梯形的典型错误。本文会讲清这一现象的根因varying 插值与透视校正并给出两种可落地的修复方案——手动提供 3D 纹理坐标或在顶点着色器中用abs(position.x)生成投影坐标配合texture2DProj采样。读完你不仅能修好梯形路面还能理解 WebGL 透视校正贴图的底层原理并直接复跑仓库里提供的完整示例。问题场景伪 3D 赛车游戏中的梯形路面老式伪 3Dfake-3D赛车游戏中路面通常由一串梯形构成远端的梯形窄、近端的梯形宽视觉上形成向地平线收拢的纵深效果。开发者的做法是给每个梯形贴上一段路面纹理。但很快会发现一个经典 bug纹理并不是整体呈现近大远小的透视压缩而是像平行四边形一样上下两半各朝一个方向斜切——每个梯形被拆成的两个三角形纹理各自按自己的方向发生水平错切接缝处纹理对不上。这个问题非常普遍。它的直接原因是构成梯形的两个三角形形状不对称不等腰、上下底不等长而着色器对纹理坐标做的是 2D 线性插值。两个三角形各自做仿射affine插值结果自然拼不出一个整体的透视投影。根因varying 的插值为什么不透视要理解修复方案先要明白 WebGL 里 varying 到底是怎么插值的。webgl-fundamentals 的教程 WebGL 3D Perspective Correct Texture Mapping 讲得很清楚顶点着色器把值写进 varying 后光栅化阶段会为三角形内每个像素插值该 varying。WebGL 使用的并不是简单的线性插值公式而是带W权重的透视校正公式result (1 - t) * a / aW t * b / bW ----------------------------- (1 - t) / aW t / bW其中aW、bW是写入gl_Position.w的值。只有当aW bW 1时这个公式才退化为普通的线性插值result (1 - t) * a t * b。这正是关键只有 varying 在携带非 1 的W时插值才是透视正确的。仓库里有两个对照示例直观演示了这一点透视校正立方体正常输出gl_Position u_matrix * a_positionW携带透视信息纹理正确非透视校正立方体在顶点着色器里手动执行gl_Position / gl_Position.w把W变成 1立方体形状不变但纹理被明显扭曲——因为 WebGL 失去了做透视校正插值所需的W。回到梯形场景每个三角形的 UV 插值没有透视权重参与像素在屏幕空间里对 UV 做均匀线性插值于是纹理的横向线条不会朝消失点收拢而是各拉各的看起来就是平行四边形的一半。解决方案用顶点的 X 坐标充当投影权重修复思路来自对texture2DProj的使用这是一个投影纹理采样函数采样时会把坐标的xy分量除以最后一个分量传vec3时除以z传vec4时除以q。如果把梯形顶点的 X 坐标取绝对值当作这个投影分量就能在逐像素插值过程中重新引入按权重除法的效果等价于手工重建透视校正。文档给出的做法是把原来的 2D UV 扩充成 3D 纹理坐标每个 UV 分量都乘以该梯形顶点 X 坐标的绝对值并把该绝对值作为第三个分量。设梯形四个顶点的 X 坐标为X0、X1、X2、X3则纹理坐标从0, 0, 0, 1, 1, 0, 1, 1,变成0 * X0, 0 * X0, X0, 0 * X1, 1 * X1, X1, 1 * X2, 0 * X2, X2, 1 * X3, 1 * X3, X3,即通用的逐顶点规则对 UV 为(u, v)、顶点 X 坐标为x的顶点投影纹理坐标为(u * |x|, v * |x|, |x|)。由于texture2DProj在顶点处会除以|x|端点的采样坐标仍然回到(u, v)纹理本身没有变化但两个顶点之间z即|x|被线性插值再逐像素执行除法插值路径就从仿射变成了透视两个三角形的纹理方向被统一梯形呈现整体透视收缩。注意这里必须取绝对值梯形左侧顶点的x是负数而投影除法要求权重为正否则会出现坐标翻转。两种落地方式按文档的说明投影纹理坐标既可以预先算好放进 buffer也可以在顶点着色器里现算。方式一把 3D 纹理坐标预先写入 buffer在 CPU 端按上面的规则计算好每个顶点的(u * |x|, v * |x|, |x|)写入纹理坐标 buffer顶点着色器里把a_texcoord直接透传给varying vec3 v_texcoord片元着色器用texture2DProj(u_texture, v_texcoord)采样即可。这种方式的优点是着色器最简单缺点是数据要随梯形顶点同步维护。方式二顶点着色器内现算推荐文档给出的着色器方案更为通用顶点数据只需提供普通的 2D UVattribute vec4 position; attribute vec2 texcoord; uniform mat4 u_matrix; varying vec3 v_texcoord; void main() { gl_Position u_matrix * position; v_texcoord vec3(texcoord.xy, 1) * abs(position.x); }片元着色器不变仍用投影采样precision mediump float; varying vec3 v_texcoord; uniform sampler2D u_texture; void main() { gl_FragColor texture2DProj(u_texture, v_texcoord); }vec3(texcoord.xy, 1) * abs(position.x)恰好把u、v各乘以|x|并把|x|放入第三分量——与手动计算的结果完全一致且任何梯形只要x随顶点正确变化都能直接用。仓库配套的可运行示例拆解本 QA 在仓库中带有一个可直接运行的示例页面webgl-qna-how-to-draw-correctly-textured-trapezoid-polygons-example-1.html。它用 twgl 辅助库完整实现了上述方式二并用一块旋转的棋盘纹理让透视效果一目了然。逐段拆解如下。顶点着色器与文档答案一致attribute vec4 position; attribute vec2 texcoord; uniform mat4 u_matrix; varying vec3 v_texcoord; void main() { gl_Position u_matrix * position; v_texcoord vec3(texcoord.xy, 1) * abs(position.x); }片元着色器precision highp float; varying vec3 v_texcoord; uniform sampler2D tex; void main() { gl_FragColor texture2DProj(tex, v_texcoord); }顶点数据只含x、y两个分量梯形下底宽-1到1、上底窄-0.5到0.5恰好对应伪 3D 赛车路面的一节const bufferInfo twgl.createBufferInfoFromArrays(gl, { position: { numComponents: 2, data: [ -1, -1, 1, -1, -.5, 1, 1, -1, .5, 1, -.5, 1, ], }, texcoord: [ 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, ], });纹理是一张 4×4 的棋盘格LUMINANCE 格式、NEAREST过滤横向重复的明暗格子在透视插值下会呈现近处稀疏、远处密集的收拢感任何一半斜切、接缝错位的瑕疵都极易观察const tex twgl.createTexture(gl, { src: [ 0xC0, 0x80, 0xC0, 0x80, 0x80, 0xC0, 0x80, 0xC0, 0xC0, 0x80, 0xC0, 0x80, 0x80, 0xC0, 0x80, 0xC0, ], format: gl.LUMINANCE, minMag: gl.NEAREST, });渲染循环使用m4.rotationZ(time)让梯形随时间旋转。旋转会让顶点的x不断变化透视与非透视的差异因此被放大——这是验证修复是否生效的最好方式function render(time) { time * 0.001; gl.useProgram(programInfo.program); twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo); twgl.setUniforms(programInfo, { u_matrix: m4.rotationZ(time), }); twgl.drawBufferInfo(gl, bufferInfo); requestAnimationFrame(render); } requestAnimationFrame(render);本地运行方式由于页面从 CDN 引入 twgl并参考了仓库 3rdparty/twgl-full.module.js 所对应的 twgl 库需要在本地起一个静态 HTTP 服务再打开页面例如在仓库根目录执行python3 -m http.server后访问webgl/webgl-qna-how-to-draw-correctly-textured-trapezoid-polygons-example-1.html。原提问者代码中的问题排查清单结合文档中提问者贴出的原始代码除了缺少投影坐标这一核心问题外还存在几个容易踩的坑逐条列出供自查纹理坐标声明与数据不匹配着色器把a_texcoord声明为vec4、v_texcoord声明为vec4但实际 buffer 里只有 2 个分量每顶点vertexAttribPointer(texcoordLocation, 3, gl.FLOAT, false, 0, 0)又按 3 分量读取导致后几个顶点读越界、拿到垃圾数据。正确做法是数据用 2 分量 UV着色器用vec2 a_texcoord把vec3投影坐标留在 varying 阶段生成。没有乘以投影权重提问者的着色器直接v_texcoord a_texcoord透传texture2DProj拿到的第三分量不是顶点的|x|除法起不到透视校正作用。必须按vec3(texcoord.xy, 1) * abs(position.x)或在 buffer 中预乘生成坐标。两个三角形 UV 需要全梯形统一规划梯形的四角 UV 必须按(u0/1, v0/1)的完整范围铺开两个三角形共享边界上的 UV这样texture2DProj插值后接缝处的纹理才是连续的。总结与延伸一句话概括本文方案给每个梯形顶点准备一个投影权重用texture2DProj采样把 WebGL 丢失的透视校正插值补回来。该技巧适用于一切屏幕上的四边形其实是某个 3D 平面的透视投影的场景——伪 3D 赛车路面、平面投射贴图、透视网格等。想进一步吃透原理推荐在仓库中继续阅读WebGL 3D Perspective Correct Texture MappingW与 varying 插值公式的完整推导WebGL 3D Perspective透视矩阵如何产生非 1 的WWebGL 3D Textures纹理坐标与采样的基础WebGL How It Worksattribute / varying / 光栅化管线透视与非透视立方体对照示例 与 非透视校正版本同一模型下透视校正开与关的直观对比本 QA 的目录入口Questions and Answers。另外texture2DProj是 WebGL 1 / GLSL ES 1.00 的语法在 WebGL 2 / GLSL ES 3.00 中对应的是textureProj投影采样函数思路完全一致移植时替换函数名即可。赞分享图形学【免费下载链接】webgl-fundamentalsWebGL lessons that start with the basics项目地址https://gitcode.com/gh_mirrors/we/webgl-fundamentals点击查看免费下载相关推荐WebGL 纹理贴图实战指南从着色器采样到纹理图集webgl-fundamentals 系列WebGL 纹理贴图实战指南从着色器采样到纹理图集webgl fundamentals 系列 本指南以 webgl fundamentals https:图形学WebGL 3D 透视投影实战从「除以 Z」到透视矩阵webgl-fundamentals 教程精读WebGL 3D 透视投影实战从「除以 Z」到透视矩阵webgl fundamentals 教程精读 本教程是 webgl fundamentals 项目图形学WebGL 纹理完全指南纹理坐标、mipmap、NPOT 限制与纹理图集webgl-fundamentals 实战WebGL 纹理完全指南纹理坐标、mipmap、NPOT 限制与纹理图集webgl fundamentals 实战 本文是 webgl fundament图形学创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考