news 2026/4/17 10:27:22

OpenGL ES ->图片纹理不变形显示:两层宽高比校正详解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
OpenGL ES ->图片纹理不变形显示:两层宽高比校正详解

OpenGL ES图片纹理不变形显示:两层宽高比校正详解

OpenGL ES中把一张图片正确显示到屏幕上,需要解决两个完全不同的宽高比问题。本文用一个完整的数值示例,从顶点定义到最终像素,讲清楚每一步为什么必须这样做。


一、核心矛盾

OpenGL的顶点是[-1, 1]的正方形,但图片和屏幕都不是正方形,如果不做任何处理,正方形的纹理贴到正方形的顶点上再投影到长方形的屏幕,图片会被双重拉伸变形。

顶点(正方形) 图片(竖长方形) 屏幕(更长的竖长方形) ┌──────────┐ ┌───────┐ ┌───────┐ │ │ │ │ │ │ │[-1,1]│ │1000× │ │1080× │ │ │ │1380│ │2400│ │ │ │ │ │ │ └──────────┘ │ │ │ │ └───────┘ │ │ │ │ └───────┘

二、两层宽高比,各解决什么问题

第一层:imageAspect 第二层:viewPortRatio 值 imageWidth/imageHeight screenHeight/screenWidth(竖屏)示例1000/1380=0.722400/1080=2.22

模型矩阵(Model):图片自身的形状,让图片在数学空间中不变形
投影矩阵(Projection): 屏幕对世界的拉伸,让数学空间在屏幕上不变形


三、如果什么都不做会怎样?

原始图片1000×1380屏幕上看到的 ┌───────┐ ┌──────────┐ │ │ │ │ │ │ 两层变形叠加 │ 图片被 │ │ │ ─────────────▶ │ 横向拉宽 │ │ │ │ 纵向拉高 │ │ │ │ │ └───────┘ │ │ 宽高比0.72│ │ └──────────┘ 面目全非 ❌

四、第一层:imageAspect— 模型矩阵校正

问题:

顶点是[-1, 1]正方形,图片是0.72:1的竖长方形。纹理直接贴上去,图片会被横向拉宽:

纹理(0,0)(1,1)顶点(-1,-1)(1,1)┌───────┐ ┌──────────┐ │ │ 贴到正方形上 │ 图片被 │ │0.72│ ────────────▶ │ 横向 │ │:1│ │ 拉伸! │ └───────┘ └──────────┘

解决

在模型矩阵中,把正方形的X方向缩放imageAspect倍,使其变成和图片一样比例的竖长方形,此时模型空间中的形状已经和图片比例一致,纹理贴上去不再变形。如果图片是宽图(imageAspect > 1),则缩放Y方向:Scale(1, 1/imageAspect, 1)

// imageAspect = 0.72 < 1,X 方向缩小Matrix.scaleM(mModelMatrix,0,imageAspect,1f,1f)// Scale(0.72, 1, 1)Scale(0.72,1,1)前后对比: ┌──────────┐ ┌──────┐ │ │ X ×0.72│ │ │[-1,1]│ ─────────▶ │[-0.72│ │ 正方形 │ │,+1]│ │ │ │ │ └──────────┘ └──────┘ X:[-1,+1]X:[-0.72,+0.72]Y:[-1,+1]Y:[-1,+1]比例1:1比例0.72:1=图片比例 ✅

五、第二层:viewPortRatio— 投影矩阵校正

问题

模型空间中图片比例已经对了,但从模型空间到屏幕,还要经过NDC → Viewport映射。屏幕是1080×2400的竖长方形,NDC[-1, 1]被映射到屏幕时两个方向的像素长度不同,如果不处理,模型空间中的正方形在屏幕上会被纵向拉长 2.22 倍。

NDC[-1,1]屏幕 ┌──────────┐ ┌──────────┐ │ │ │ │ │1单位 │ Viewport │1单位X │ │=1单位 │ ──────────▶ │=540px │ │ │ │1单位Y │ └──────────┘ │=1200px │ ← 是X的2.22倍! 数学上等长 │ │ └──────────┘ 物理上不等长 ❌

解决

在投影矩阵中,让frustumY方向可视范围扩大viewPortRatio倍,这一步的本质:先压缩,再被拉伸,两次抵消,Y先被压缩1/2.22倍 → 再被Viewport拉伸2.22倍 → 抵消 ✅
原理:frustum中写入的范围越大 → 映射到固定的NDC [-1, 1]时压缩越多 → 正好抵消Viewport阶段对该方向的物理拉伸。

// 竖屏Matrix.frustumM(mProjectionMatrix,0,-1f*distance,// left: X 范围 [-d, +d]1f*distance,// right-viewPortRatio*distance,// bottom: Y 范围 [-2.22d, +2.22d] ← 比 X 大viewPortRatio*distance,// topnear,far)near 平面可视窗口 NDC 压缩 Viewport 拉伸 ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ │ │ │ │ │ │ Y范围大 │ │ 屏幕Y长 │ │ │ Y │ 映射到[-1,1]│ Y被压缩 │ 又把Y拉长 │ Y恢复 │ │ 范围大 │ ──────────▶ │2.22倍 │ ─────────▶│ 正常 │ │2.22x │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └──────────┘ └──────────┘ └──────────┘ 投影前 NDC中 屏幕上

六、完整变换链路图

以图片1000×1380、屏幕1080×2400为例,scale=1translate=(0,0)

┌─────────────────────────────────────────────────────────────────────────────────┐ │ 完 整 变 换 链 路 │ ├──────────┬───────────────┬────────────────┬────────────────┬───────────────────┤ │ 原始顶点 │ imageAspect │ Projection │ Y 翻转 │ 屏幕像素 │ │ │ 模型矩阵校正 │ 投影矩阵校正 │ 纹理方向 │ 最终结果 │ ├──────────┼───────────────┼────────────────┼────────────────┼───────────────────┤ │(-1,+1)(-0.72,+1)(-0.72,+0.45)(-0.72,-0.45)│ screen:│ │(-1,-1)(-0.72,-1)(-0.72,-0.45)(-0.72,+0.45)(151,660)│ │(+1,+1)(+0.72,+1)(+0.72,+0.45)(+0.72,-0.45)(929,1740)│ │(+1,-1)(+0.72,-1)(+0.72,-0.45)(+0.72,+0.45)│ 图片778×1080│ │ │ │ │ │ 比例0.72:1✅ │ │ 正方形 │ 竖长方形 │ Y被压缩1/2.22│ Y翻转 │ 屏幕上不变形 │ │1:10.72:1│ 比例变为1.6:1│ 图片正向 │ │ │ │ │(预补偿屏幕拉伸)│ │ │ └──────────┴───────────────┴────────────────┴────────────────┴───────────────────┘ 对应代码位置: ┌──────────────────────────┐ ┌────────────────────────┐ ┌───────────┐ │ Matrix.scaleM(│ │ Matrix.frustumM(│ │ Matrix.│ │ model,imageAspect,1,1)│ │ proj,...,│ │scaleM(│ │ │ │-vr*d,vr*d,...)│ │ mvp,1,-1,1│ │ 解决:图片不是正方形 │ │ 解决:屏幕不是正方形 │ │)│ └──────────────────────────┘ └────────────────────────┘ └───────────┘ 对应代码位置: ┌──────────────────────────┐ ┌────────────────────────┐ ┌───────────┐ │ Matrix.scaleM(│ │ Matrix.frustumM(│ │ Matrix.│ │ model,imageAspect,1,1)│ │ proj,...,│ │scaleM(│ │ │ │-vr*d,vr*d,...)│ │ mvp,1,-1,1│ │ 解决:图片不是正方形 │ │ 解决:屏幕不是正方形 │ │)│ └──────────────────────────┘ └────────────────────────┘ └───────────┘

七、为什么一个在Model,一个在Projection

Model 矩阵回答:「这个物体本身长什么样?」 → 图片是0.72:1的竖长方形 → 每张图片不同,所以跟着图片走 Projection 矩阵回答:「世界通过什么形状的窗口被观察?」 → 屏幕是1:2.22的竖长方形 → 对所有物体都一样,所以跟着屏幕走
图片比例 ──▶ Model ──▶ 模型空间(数学上不变形) │ ▼ 屏幕比例 ──▶ Projection ──▶ NDC(预补偿屏幕拉伸) │ ▼ Viewport │ ▼ 屏幕(物理上不变形)✅

八、图片纹理上叠加自定义View

自定义View不参与OpenGL渲染,但需要知道图片纹理在屏幕上占多少像素才能定位裁剪框。这个方法就是反算上面整条链路的最终结果:

如果不取短边的最小值/2,这个时候x,y虽然落到了[-1,1]区间,但是并不等长,也不是完整的归一化,x轴的1个单位不等于y轴的1个单位 x/(viewW/2),x 的1=viewW/2像素 y/(viewH/2),y 的1=viewH/2像素, Projection 让[-1,1]范围=min(screenW,screenH)像素 NDC 中长度=2↓ 屏幕像素长度=min(screenW,screenH)1个 NDC 单位=min(W,H)/2像素 Model 让某个方向缩放了 imageAspect 合在一起: 竖图(aspect ≤1):=min(W,H)× imageAspect ← X 被缩小 高=min(W,H)← Y 铺满 宽图(aspect>1):=min(W,H)← X 铺满 高=min(W,H)/imageAspect ← Y 被缩小 以1000×1380图片、1080×2400屏幕为例: customViewWidth=1080×0.72=778px customViewHeight=1080=1080px
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/16 17:06:16

【开题答辩全过程】以 离散制造企业生产管理系统为例,包含答辩的问题和答案

个人简介一名14年经验的资深毕设内行人&#xff0c;语言擅长Java、php、微信小程序、Python、Golang、安卓Android等开发项目包括大数据、深度学习、网站、小程序、安卓、算法。平常会做一些项目定制化开发、代码讲解、答辩教学、文档编写、也懂一些降重方面的技巧。感谢大家的…

作者头像 李华
网站建设 2026/4/15 21:56:32

【安全测试】1_安全测试体系 _安全测试介绍

文章目录一、安全测试介绍1.1 安全测试与传统测试的区别1.2 安全测试与渗透测试的区别二、安全测试常用方法三、安全测试维度四、安全测试点一、安全测试介绍 安全测试就是发现软件安全漏洞的过程&#xff0c;旨在保护软件系统的数据与功能。 安全测试以破坏系统的安全策略为…

作者头像 李华
网站建设 2026/4/5 15:49:23

贸发局主办全球最大一站式珠宝商贸平台

荟萃环球珍品 新设硬足金展馆展现黄金崭新技术由香港贸易发展局&#xff08;香港贸发局&#xff09;主办的全球最大一站式珠宝商贸平台&#xff0c;将于3月初以“两展两地”的成功模式揭幕。第12届香港国际钻石、宝石及珍珠展于3月2至6日在亚洲国际博览馆举行&#xff0c;展出…

作者头像 李华
网站建设 2026/4/12 11:40:35

去年的国自然本子修改之后可以今年再提交吗?

国自然评审意见公布后&#xff0c;不少科研同行都会陷入困境&#xff1a;拿到修改意见无从下手&#xff0c;去年未中的本子改来改去仍抓不住重点&#xff0c;专家点评言简意赅却藏着“潜台词”&#xff0c;解读不到位就会南辕北辙。作为过来人&#xff0c;我深知国自然修改比初…

作者头像 李华