自由学习记录(152)

如果 PDF 是本地文件,还多一层限制。

即便你的扩展本来支持 file:///,Chrome 也要求用户在扩展详情页手动开启"允许访问文件网址(Allow access to file URLs)",否则扩展对本地 PDF 也不能工作。

浏览器把"网页内容"和"浏览器自身/扩展自身的受信 UI"刻意隔离开了。

你可以把它理解成三层:

第一层是普通网页,比如 https://...

内容脚本本来就是为这层设计的:它运行在网页上下文里,可以读 DOM、改 DOM、响应点击。Chrome 官方对 content scripts 的定义就是"运行在 web pages 的上下文中"。

第二层是本地文件,比如 file:///...

这层已经比普通网页更敏感,因为可能涉及你磁盘上的文档。Chrome 因此不默认放开,要求用户单独开启"Allow access to file URLs"。也就是说,即使扩展声明了权限,浏览器仍然要用户再做一次显式授权。

内置 PDF Viewer,本质上就是浏览器/扩展提供的专用界面,不是普通网页。若任意第三方扩展都能把脚本注入进去,就会出现非常危险的能力升级:它可能读取你在浏览器受信界面中的内容、劫持点击、篡改 UI、伪造权限提示,甚至干扰别的扩展页面。Chrome 用扩展页 CSP、扩展页与内容脚本分离、以及受限的注入模型,目的就是防止这种"扩展之间"或"网页到浏览器 UI"的越权。Chrome for Developers+2Chrome for Developers+2

浏览器自己的界面、权限页、PDF 查看器、别的扩展页,如果允许随便注入,攻击者可以把按钮、菜单、文案做得和系统 UI 一样,诱导用户点错。这类风险比改普通网页严重得多,因为用户天然更信任浏览器原生界面。这个动机和 CSP 的设计目标是一致的:尽量减少脚本注入面、降低 XSS/篡改风险。Chrome for Developers+1

对应到你这个 PDF Viewer 场景,本质上是两道门:

一是这个 PDF 可能来自 file:///,所以先受"文件访问"限制。

二是显示 PDF 的壳子又是 chrome-extension://... 的 PDF Viewer,所以还受"不能把普通 content script 注入受信扩展页"的限制。

这就是为什么你会看到"插件不能读取或更改此站点的数据"这一类提示。前者是数据来源敏感,后者是宿主页面本身敏感。

URL 拦截 / 打开方式 → 扩展 viewer → PDF.js 渲染 → text layer 点击取词 → 后台翻译服务

重点是换入游览器的js形式----算了,右键也是一样

WebRTC 不是"网站都默认在用"的通用传输层。它主要用于实时双向 场景:浏览器里的音视频通话、屏幕共享、实时语音、以及低延迟双向数据通道。技术上通常会看到 RTCPeerConnectiongetUserMedia()RTCDataChannel 这些接口;如果站点没有建立对等连接或采集媒体,检测工具通常就会显示"未使用 WebRTC"。MDN Web Docs+3MDN Web Docs+3MDN Web Docs+3

所以"哪些网站可以用 WebRTC"要分成两类看。第一类是明确需要实时互动 的网站,例如 Google Meet、Discord 网页版、Zoom Web、Slack Huddle/Call、一些在线客服语音/视频页面、浏览器云游戏/远控/白板协作、文件点对点传输工具等。这类页面通常真的会建立 WebRTC 会话。第二类是平时不用,只有某个功能触发时才用 的网站;你不开那个功能,检测页就会显示没有 WebRTC 活动。这个现象本身是正常的,因为 WebRTC 连接不是"网页一打开就必须存在"。MDN Web Docs+2MDN Web Docs+2

YouTube 普通看视频基本不属于 WebRTC 的典型使用场景。WebRTC 设计重点是浏览器到浏览器或浏览器到实时服务的低延迟双向通信;而 YouTube 常规点播是大规模分发的视频流业务,不需要浏览器和服务端之间建立那种实时对等连接。Google 官方公开的 YouTube Live 文档侧重的是直播事件和视频流管理,并没有把普通观看页面描述成 WebRTC 应用;帮助页讲的是"直播"这一产品能力,而不是网页观看端一定通过 WebRTC 传输。你在 YouTube 页面上看到"未使用 WebRTC",通常是符合预期的。Google Help+3MDN Web Docs+3Google for Developers+3

ChatGPT 也是同理。普通文本聊天 不需要 WebRTC,所以检测工具显示未使用,很正常。OpenAI 官方文档明确写的是:浏览器里的 Realtime/voice agent 场景推荐通过 WebRTC 连接实时模型;也就是说,WebRTC 主要对应的是实时语音/音频交互 ,不是普通文本问答页面本身。OpenAI 还单独提供了 "Realtime API with WebRTC" 文档,这进一步说明 WebRTC 是某些实时能力的传输选项,而不是所有 ChatGPT 页面都会启用的底层。OpenAI+3OpenAI Developers+3OpenAI Developers+3

你检测时之所以常看到"没用",还有一个常见原因:很多检测扩展看的是当前页面有没有活跃的 RTCPeerConnection / ICE candidate / 媒体采集 。如果页面只是加载了相关脚本、预留了通话功能、或者代码里有 WebRTC 能力但当前没拨号、没开麦、没共享屏幕,它也会显示未使用。这不代表网站"永远不用",只代表你当前这个标签页、当前这个时刻 没有在跑 WebRTC 会话。MDN Web Docs+2MDN Web Docs+2

判断一个网站到底有没有在用 WebRTC,最可靠的方法不是只看"这个域名",而是看你触发了什么功能 。例如:进入语音通话、视频会议、网页端实时客服、屏幕共享、浏览器内对讲,这时更可能出现 WebRTC;只是看文章、看普通视频、刷信息流、文本聊天,通常不会。MDN Web Docs+2MDN Web Docs+2

你可以自己做一个很直接的验证:打开 Chrome DevTools,在 Console 里执行 window.RTCPeerConnection 看浏览器是否支持;再在 Network 之外结合扩展观察,只有当你真正发起语音/视频/屏幕共享时,才更可能看到候选地址收集和连接建立。单纯停留在 YouTube 播放页或 ChatGPT 文本页,看到"未使用"基本就是正常结果。MDN Web Docs+1

不同 UI 元素的三角面数量情况

  1. 基础矩形 UI(Image/Button,无特殊效果)
    这类最常规的矩形 UI,确实是由 两个三角面组成(拼成一个矩形),这是最基础的情况。
    2. 带复杂形状的 UI

    • 圆角矩形:圆角部分需要用多个小三角面来逼近圆弧,圆角越平滑,三角面数量越多。
    • 不规则图形(如多边形、自定义 Sprite):根据图形的顶点数决定三角面数,比如一个正六边形需要 6 个三角面拼接。
    • 文本(Text/TMP):每个字符的轮廓由字体的矢量数据转化而来,会生成大量三角面(尤其是复杂字体或大字号),并非简单的两个三角面。
    1. 带特效 / 遮罩的 UI
    • 遮罩(Mask):若遮罩是不规则形状,会额外生成用于裁剪的三角面,或通过 Stencil Buffer 实现但不增加三角面,不过复杂遮罩的计算仍会间接关联几何数据。
    • 渐变 / 描边 / 阴影:部分特效会通过扩展顶点生成额外三角面(如描边需要在原图形外再生成一圈三角面)。

简单说,只有 纯矩形、无任何特殊形状和特效的基础 UI,才会保持两个三角面的最简结构;一旦形状或效果变复杂,三角面数量会显著增加。

4 个顶点的采样插值为何不会让 UI 图片模糊?

UI 图片的采样清晰度 不取决于顶点数量 ,而是由 纹理采样方式UV 坐标的映射逻辑决定,核心原因有两点:

  1. UV 坐标的线性插值特性
    GPU 在渲染时,会根据顶点的 UV 坐标,对像素级别的纹理位置做线性插值 。矩形的 4 个顶点对应纹理的(0,0)(1,0)(1,1)(0,1)四个 UV 点,GPU 会在这四个点之间,为每个像素计算出精确的纹理采样坐标,而非只在顶点处采样。哪怕只有 4 个顶点,也能覆盖整个纹理的像素信息。

UI 的纹理过滤与像素对齐

  • Unity 等引擎中 UI 默认使用bilinear/trilinear 过滤,会对纹理的相邻像素做插值采样,减少锯齿和模糊;
  • UI 渲染时还会开启像素完美对齐(Pixel Perfect),让 UI 的像素与屏幕像素一一对应,避免因缩放、偏移导致的采样失真,这是保证 UI 清晰的关键,和顶点数量无关。

所以顶点的数量更多的是控制采样的位置合适,而不是贴图是否清晰,贴图的采样永远是非常清晰的,顶点的数量是为了让贴图贴的更"准"

过程拆解成 '几何定义''纹理填充' 两个完全独立的步骤

纹理采样是在 "填肉",且是超采样

当 GPU 要画出这个矩形的每一个像素时,它会做两件事:

  1. 定位 :对于屏幕上的第 i 个像素,它通过顶点数据插值算出这个像素在局部坐标系的位置。
  2. 取样(关键):根据这个局部位置,去贴图(Texture)上找颜色。

虽然你的几何只有 4 个点,但 纹理采样是针对屏幕上的每一个像素进行的。

  • 假如你的 UI 图是 1000x1000 的,而你显示在屏幕上是 200x200 的像素。
  • 虽然几何 Mesh 只有两个三角形(4 个点),但 GPU 会在这 200x200 = 40,000 个像素点上,逐个去贴图里采样颜色。

所以,绝对不会出现 "只采 4 个点" 的情况。顶点数量决定了 "架子的精度",而采样的密度取决于 "屏幕像素密度"。

另外一个防模糊的黑科技:UV 拉伸与像素完美(Pixel Perfect)

UI 系统为了让你觉得 "图贴得准",做了一个隐式操作: UV 拉伸(Stretch)

  • 你的图是 512x512。
  • UI 系统在构建 Mesh 时,会把这张图的 UV 坐标 (0,0) 严格映射到顶点 0,(1,1) 严格映射到顶点 1
  • 这就意味着,贴图的每一个像素都会被拉伸到屏幕的对应像素 。如果开启了 Canvas ScalerPixel Perfect,屏幕像素就会 1:1 匹配贴图像素,采样出来的结果就是绝对清晰的。

纹理的清晰程度,取决于 贴图本身的分辨率采样时的拉伸比例(UV),而不在于你用了 4 个顶点还是 400 个顶点去框这个矩形。

  • 逐像素纹理采样:是 GPU 对 UI 矩形区域内的每个屏幕像素,根据 UV 坐标从纹理中取色的常规操作,是 2D UI 渲染的基础,无性能额外开销。
  • 超采样(SSAA) :是对渲染画面的过采样(比如用 2K 分辨率渲染再缩放到 1080P),目的是抗锯齿,会大幅增加 GPU 负载,UI 渲染中极少使用。

UI 纹理分辨率固定,放大时会因贴图像素数 < 屏幕显示像素数,导致 GPU 通过纹理过滤(如双线性)插值补色,出现模糊;缩小则可能因纹理过滤丢失细节,这是纹理本身的分辨率瓶颈,而非采样方式的问题。

Unity 的Canvas Scaler提供了Constant Pixel Size (固定像素)、Scale With Screen Size(随屏幕缩放)等模式,若选择固定像素模式,UI 在不同分辨率屏幕上缩放会失真;但选对适配模式(如按屏幕宽高比缩放),就能实现自由缩放且保持清晰。

实现 UI 自由缩放且不模糊

  • 使用矢量图 / 可缩放纹理(SDF):如 TextMeshPro 的 SDF 字体、Sprite Shape 的矢量图形,缩放时不会丢失细节。
  • 开启Mipmap并设置合适的纹理过滤模式(如 Trilinear),减少缩放后的纹理失真。
  • 采用多分辨率纹理集:为不同屏幕分辨率准备对应尺寸的 UI 纹理,适配时调用匹配的纹理。

在Unity中,设置纹理的Filter Mode为

**Bilinear(双线性滤波)**会改变GPU采样时的原理,相比Nearest(临近采样),它会在采样时对像素进行加权平均,使纹理在放大时更平滑,消除锯齿,但这属于常规的GPU纹理采样映射机制

  • 采样原理 :当纹理映射的像素点不处于纹理纹素(Texel)的中心时,Bilinear采样会选取目标点周围最近的4个纹素,根据其距离远近进行线性加权平均,得出最终颜色。
  • 适用场景:适用于大多数3D场景下的纹理,提供比Point(Nearest)更平滑的视觉效果,比Trilinear(三线性滤波)计算量小。

如何设置 :选中图片资源,在Inspector窗口中找到 Filter Mode,设置为Bilinear。

3D 物体 SetActive 和 UI SetActive,性能后果天差地别。
我用最干脆的话给你讲清楚:

1. 3D 物体:SetActive 几乎不触发重建、不打断合批

  • 3D 的 Mesh 是静态 / 独立的,不会合并成一个大 Mesh
  • SetActive 只是把物体从渲染列表移除 / 加回
  • 不会重新计算布局(3D 没有 Layout)
  • 不会重建 Mesh
  • 不会破坏合批(静态批处理已经预先打好,动态批是引擎临时处理,也不会因为你显隐一个物体就全炸)

所以:
3D 里频繁 SetActive,开销非常小,几乎没问题。

一句话总结区别

  • 3D:SetActive = 渲染开关,轻量。
  • UGUI:SetActive = 重建整个面板,重量级。

4. 延伸一个你可能马上会踩的坑

  • 3D 用 enabled = false(MeshRenderer.enabled)
    → 和 SetActive 接近,轻量,只是不渲染。
  • UI 用 graphic.enabled = false
    仍然会打断合批,比 SetActive 轻一点,但依然不推荐高频使用。

UI 想要真正轻量隐藏、不重建、不打断合批:
只有 alpha = 0 这一条路。

UI 物体:SetActive = 爆炸级开销

原因你已经知道:

  • UI 所有元素共用一个大 Mesh
  • 显隐一个元素 = 整个 Canvas 重新生成 Mesh
  • 会触发 LayoutRebuild + GraphicRebuild
  • 合批全部打断,重新 Batch

这是 UGUI 结构决定的,不是 Unity 故意坑。

合批的前提:所有东西必须是同一套渲染状态

GPU 一次 DrawCall,只能画 一套状态完全一致的东西:

  • 同一个 Shader
  • 同一个材质 / 纹理
  • 同一个混合模式(Blend)
  • 同一个模板测试 / 深度测试配置(Stencil / ZTest)

只要 任何一项不一样,GPU 就必须:

  • 结束当前 DrawCall
  • 切换渲染状态
  • 开新的 DrawCall

不能跨状态合批,这是硬件级限制,不是软件优化问题。

Mask 本质就是强行改了 Stencil 状态

普通 UI:

  • Stencil 关闭固定不变

带 Mask 的 UI:

  • 先写 Stencil(标记可显示区域)
  • 再用 Stencil 测试(只显示区域内像素)
  • 最后还要恢复 Stencil 状态

这意味着:

  • 普通 UI = 状态 A
  • Mask 内 UI = 状态 B
  • 恢复后又 = 状态 A

状态变了 → 必须切 DrawCall → 合批直接断开。

Alpha Cut(AlphaTest / Discard)

属于片元着色器内部逻辑

  • 看纹理的 alpha 值
  • 如果 alpha < 阈值,直接 discard 丢弃这个像素
  • 不写颜色、不写深度、不写模板
  • 只是不画这个像素

关键点:

  • 不改变任何 RenderState
  • 不影响合批
  • 同一个材质、同一个 shader,不管怎么 discard,都能正常合批

所以场景模型、树叶、草、瓦片用 AlphaCut, 完全不会打断合批

Stencil(Mask 用的模板缓冲)

属于 GPU 固定管线状态

  • 是 GPU 硬件层面的开关
  • 会修改 Stencil 操作、参考值、比较函数
  • 属于 RenderState

关键点:

  • 只要 Stencil 状态不同 → 不能合批
  • 这是硬件规则,不是 shader 逻辑
  • UGUI 的 Mask 本质就是一套固定的 Stencil 状态

UI 是 "先合批,再裁剪",3D 是 "先裁剪,再合批"

这是最根本的结构差异。

  • 3D 模型
    每个物体独立渲染 → 自己 shader 里 discard 像素 → 不影响别人
    → Alpha Cut 完全没问题。
  • UGUI
    所有 UI 先合并成 同一个大 Mesh、同一个 DrawCall
    → 再一起送入 GPU 渲染

如果你在这个大 Mesh 里 某一部分想用 Alpha Cut 做遮罩

  • 你怎么告诉 GPU:
    "只让这一小块区域内的像素保留,外面的 discard"

Shader 里做不到,因为:
整个 UI 是同一个 DrawCall、同一个材质、同一个 shader 实例。
你不能让 shader 对一部分像素裁剪、另一部分不裁剪。
Alpha Cut 做不到局部区域裁剪,只能全局裁剪。

Alpha Cut 不能嵌套,Mask 可以

UI 经常需要:

  • 滚动列表遮罩
  • 窗口内遮罩
  • 按钮内遮罩
  • 多层嵌套遮罩

Alpha Cut 是像素丢弃,没有层级、没有标记、没有状态。
你根本实现不了多层嵌套裁剪。
但 Stencil 可以:

  • 写 1
  • 只允许 1 的区域显示
  • 子遮罩写 2
  • 只允许 2 的区域显示
  • 最后恢复

这是 Alpha Cut 永远做不到的。

UI 用 Alpha Cut 会产生严重锯齿,无法修复

UI 是屏幕像素对齐的,边缘要求极干净。

Alpha Cut 是 0/1 二值丢弃,边缘必然锯齿。但 UI 遮罩需要平滑边缘、抗锯齿

Stencil 配合矩形边缘精确采样,才能做到干净裁剪。


4. 真正的致命问题:UI 裁剪需要 "外部形状",Alpha Cut 只能看自身纹理

UGUI 的 Mask 是:用一个矩形(父物体)定义裁剪区域,子物体全部被这个区域限制。

这是外部区域约束

Alpha Cut 是:看自己贴图的 alpha,丢弃像素。 这是自身纹理约束

你要实现 "滚动框" 这种:

  • 子物体随便动
  • 超出父矩形就隐藏

Alpha Cut 完全做不到。只有 Stencil 能实现 "区域裁剪"。

Canvas 内部维护了一个私有 Batch 列表。它会主动忽略场景中其他 3D 物体的合批机会,只把自己内部的 UI 元素(矩形 Mesh)合并在一起。这使得 Canvas 的渲染数据在 GPU 调度队列中是一个独立的连续块

  1. 为了渲染层级(Z-Order)的绝对可控 :UI 必须盖在 3D 场景之上。如果 Canvas 尝试和 3D 物体合批,渲染顺序就会被打乱,导致 UI 被场景遮挡或者出现严重的闪烁。
  2. 为了重建效率:UI 的变化通常非常频繁(比如每帧变动的进度条、字体)。Canvas 内部维护私有 Batch 列表,意味着当 UI 元素变化时,它只需要重新计算这个小范围内的顶点数据,而不会触发整个 3D 场景的渲染重绘(Rebuild)。

本质上,Canvas 就是在 GPU 繁忙的任务队列里强行划出了一块"自留地",专门用来高效处理那些基于矩形的、平面化的渲染任务。

渲染管线隔离:Render Target / Frame Buffer 分离

Screen Space - Overlay 模式下,Canvas 拥有 独立的渲染优先级

UI 是在 3D 场景渲染完成后,作为最后一个渲染阶段直接画在主 Frame Buffer(即 Back Buffer)之上的。

直接渲染到主缓冲(默认常用方式)

流程 :渲染 3D 场景 -> 后处理(Post-processing) -> 渲染 UI -> 显示到屏幕。

优点:省内存,减少了一次全屏贴图的内存占用和一次昂贵的读写开销(Blit)。

Canvas 体现 :Unity 的 Screen Space - Overlay 模式通常就是这种逻辑。它直接在当前 Render Target 上按顺序绘制 UI 的 Batch。
渲染到独立的 Render Texture / Frame Buffer
在某些特定需求下,引擎确实会开辟一块独立的 Buffer 来渲染 UI:

UI 需要特殊后处理 :如果你想给整个 UI 界面加一个模糊效果、描边,或者像《赛博朋克 2077》那种 UI 故障抖动效果,必须先将 UI 画在一张独立的 Render Texture 上,处理完后再和场景合并。

复杂的层级混合:当 3D 场景和 UI 需要极其复杂的遮挡关系(例如某些 UI 元素要在 3D 物体中间穿插)时,可能会使用独立的 Buffer。

异速更新(Optimization):为了节省性能,某些引擎可能会让 UI 以较低的频率(比如 30fps)渲染到一个离屏 Buffer 中,而 3D 场景保持 60fps。每一帧只需把上一帧画好的 UI 贴图直接盖上去即可。

Canvas 强制所有 UI 使用 Alpha Blending 且通常 ZWrite Off
在 3D 空间中,我们靠深度缓冲区(Z-Buffer)来决定谁在前谁在后。但 UI 走的是另一套逻辑:

  • 层级由顺序决定 :UI 的前后遮挡关系是由你在 Hierarchy 面板里的渲染顺序(Order in Layer / Sibling Index)决定的。

性能权衡:关闭 ZWrite 意味着 GPU 不需要去更新深度缓冲区,省下了一点带宽。

Canvas 必须开启 Alpha Blending (通常是 SrcAlpha OneMinusSrcAlpha )。
这带来的副作用:Overdraw(过度绘制)
因为 ZWrite Off + Alpha Blending ,GPU 无法进行 Early-Z 剔除(即:无法因为后面有东西挡着就不画前面的)。

这意味着:即使一个全屏的大 UI 背景盖住了后面的 3D 场景,GPU 还是会老老实实地先画 3D 场景,再把 UI 叠加上去。

  • 性能瓶颈 :UI 优化中最头疼的 Overdraw 就在这里。如果你叠了 10 层透明 UI,那同一个像素点就会被着色器计算 10 次。

虽然 Canvas 默认关掉它,但在某些特殊情况下你会看到它被开启:

  1. 3D UI:当 UI 穿插在 3D 场景中(World Space),为了和 3D 物体有正确的遮挡关系。
  2. 极端优化:在某些性能极差的移动设备上,如果 UI 包含大量完全不透明(Opaque)的矩形块,开发者会自定义 Shader 开启 ZWrite 并改用不透明渲染序列,以此减少 Overdraw。

Sub-Canvas(子画布)最直接的底层物理表现就是"切断":它强制终结当前的 Mesh 合并进程

  • 普通 Canvas:会尝试将所有子节点下的 UI 元素(Image, Text 等)扫描一遍,打乱顺序(在保持层级逻辑的前提下)合并成一个巨大的顶点缓冲区(Vertex Buffer)。
  • Sub-Canvas :当扫描器遇到 Canvas 组件(即使是子物体上的)时,它会立刻停止当前的合并,将 Sub-Canvas 及其所有子对象划归为一个全新的、独立的 Mesh 列表
  • 结果:父 Canvas 的 Batch 无法穿透进入子 Canvas,子 Canvas 的元素也无法与外部元素合批。
  1. 指令切断:独立的 Draw Call

因为 Mesh 被切断了,GPU 必须通过两个独立的绘制指令来处理它们:

  1. 绘制父 Canvas 的顶点数据。

  2. 切换状态(如果需要),绘制 Sub-Canvas 的顶点数据。
    这在 Profiler 中表现为:增加了一个 Draw Call

  3. 重绘切断:脏标记(Dirty Flag)的隔离

这是使用 Sub-Canvas 最核心的优化理由。

  • 隔离重绘(Rebuild):在 UI 系统中,如果一个元素变动(位移、缩放、改文字),整个 Canvas 都需要重新计算网格(Rebuild Mesh)。
  • 局部化 :如果你把一个频繁闪烁或移动的图标放在 Sub-Canvas 里,当它变动时,只有这个 Sub-Canvas 会触发 Rebuild。父 Canvas 及其它静态元素会保持静止,完全不受干扰。

无图 UI 遮罩点击优化 ,核心是 用非渲染的碰撞体 / 检测区域替代空 Image 做点击交互,彻底消除空 Image 带来的 Overdraw 开销。

为什么需要这个优化?

UGUI 中如果用 ** 空 Image(无贴图、alpha=0)** 做点击遮罩:

  1. 空 Image 仍会被 GPU 渲染,产生无效的 Overdraw(像素填充但无视觉效果),占用 GPU 算力;
  2. 即使 alpha=0,它也会参与 Canvas 的 Mesh 构建和批处理,增加 CPU 的 Rebuild 开销。

具体优化方案

  1. 使用GraphicRaycaster+Collider替代空 Image
  • 给 UI 父物体添加BoxCollider2D(匹配遮罩区域);
  • 保留GraphicRaycaster负责点击检测,移除空 Image 组件。
  • 原理:Collider 仅参与射线检测,不生成渲染 Mesh,无 Overdraw。
  1. UnityEvent+ 代码逻辑实现区域点击
  • 直接在脚本中通过屏幕坐标判断点击区域,完全抛弃 UI 图形组件;
  • 适合简单的矩形 / 规则形状点击遮罩,性能最优。

给 ScrollView 父节点添加单个碰撞体(如 BoxCollider2D),替代子元素的射线检测,减少遍历次数。

优先使用 RectMask2D 替代 Mask:RectMask2D 基于几何裁剪,不修改 Stencil 状态,不会打断合批,性能损耗远低于 Mask。

核心机制:数学剔除 vs. 模板测试

  • Mask (Stencil) :利用 GPU 的 Stencil Buffer(模板缓冲)。它先画一个"隐形"的形状存入缓冲区,后续元素绘制时要逐像素比对这个缓冲区。这涉及复杂的渲染状态切换。
  • RectMask2D :利用 数学运算 。它直接把遮罩矩形的四个边坐标(Left, Top, Right, Bottom)传给 Shader。Shader 在处理 UI 顶点的片元(Pixel)时,简单判断一下坐标是否在矩形外,如果在外面就直接 discard(丢弃)或设置 Alpha 为 0。
  • 确保 RectMask2D 的裁剪区域与 ScrollView 视口完全匹配,减少无效裁剪计算。

对"合批"的保护:不增加 Draw Call

这是它最大的优势:

  • Mask :每一个 Mask 组件都会强制开启一个 Stencil 状态。这通常会导致 Draw Call 翻倍(至少多出一个写入 Stencil 的 Call,且内部元素无法与外部合并)。
  • RectMask2D :由于它只是在 Shader 里多跑了几行简单的代数运算,并没有改变渲染状态(Render State)。这意味着:同一个 Canvas 下,使用相同材质的多个 RectMask2D 内部元素,理论上依然可以合并成一个 Batch。

RectMask2D 不打断合批,但如果你的 UI 列表非常长(比如 1000 个 Item),它在 CPU 端计算"哪些元素在矩形外需要隐藏"的操作(Culling)依然会产生开销。

  • CPU 端:RectMask2D 几乎没有额外开销,只是传递几个浮点数。
  • GPU 端 :虽然 Shader 里多了几行代码(插值计算坐标并对比),但现代 GPU 对这种简单的数学指令(CMP/SELECT)处理速度极快,远比读写 Stencil Buffer 的带宽消耗要低。
相关推荐
woai33643 小时前
JVM学习-基础篇-堆&方法区
jvm·学习
星幻元宇VR3 小时前
VR摩托车|科技赋能交通安全教育新模式
科技·学习·安全·vr·虚拟现实
AnalogElectronic3 小时前
uniapp学习9,同时兼容h5和微信小程序的百度地图组件
学习·微信小程序·uni-app
Dxy12393102163 小时前
ECharts入门学习:从零开始打造炫酷数据可视化
学习·信息可视化·echarts
晨枫阳3 小时前
从零搭建私有 npm 仓库:一次完整的实战学习笔记
笔记·学习·npm
罗罗攀3 小时前
PyTorch学习笔记|单层神经网络
人工智能·pytorch·笔记·神经网络·学习
ACGkaka_3 小时前
ES 学习(五):DSL常用操作整理
大数据·学习·elasticsearch
lizhihai_993 小时前
股市学习心得-新手生存法则
学习
Slow菜鸟11 小时前
AI学习篇(三) | AI效率工具指南(2026年)
人工智能·学习