01 从片元到最终像素
前置知识:知道
Scene、Camera、Mesh和Material的基本用途即可。
本章示例:像素路径 · 完整源码。
预期画面:橙色矩形压在深蓝底板和浅色条带上;改变 Alpha 时,两处重叠区呈现不同颜色。
学完本章能做什么
你将能解释一个透明片元如何与画面里已有的颜色合成,区分 片元的 Alpha 值 与 是否启用透明混合,并识别后续深度、排序、纹理问题各发生在渲染流程的哪个位置。
先看现象
打开示例。橙色前景覆盖两种背景:左侧深蓝、右侧浅色。保持 transparent 开启,把 Alpha 从 1 降到 0.2:橙色越来越淡,底色逐渐露出,而且两处重叠区的颜色不同。现在取消“启用 transparent”,继续拖动 Alpha:背景不会按刚才的方式露出。
这说明“着色器算出了 Alpha”和“GPU 用 Alpha 混合”是两件事。前者提供一个数,后者是材质与渲染状态决定的操作。
1. 先把画面理解成一张颜色表
屏幕可以看作许多像素位置。对其中一个位置,颜色缓冲区(color buffer)暂存到目前为止画出的颜色。渲染器先清空或绘制背景,再处理场景中的物体。物体的顶点经过变换和光栅化,形成覆盖某些屏幕位置的片元(fragment)。片元着色器计算该位置的颜色和 Alpha,之后 GPU 决定它能否通过测试、是否与已有颜色混合、是否写入缓冲区。
本系列会沿着下面的关系逐步展开:
顶点与图元 → 光栅化 → 片元颜色/Alpha
↓
深度等测试(第 04—08 章)
↓
混合:新片元 + 颜色缓冲中已有颜色
↓
最终显示的像素
这里的“已有颜色”叫目标颜色(destination,记作 D);这次要画的片元叫源颜色(source,记作 S)。它不一定就是某个“背景物体”的原始颜色:若此前已经画过别的半透明片元,D 就是它们累积后的结果。
深度缓冲区是另一张表,用于判断前后遮挡。本章暂不改变它;第 04—06 章会分别解释深度测试、深度写入和颜色写入。颜色混合不会自动解决前后遮挡,也不会自动给透明片元正确排序。
2. 普通透明混合的公式
默认非预乘 Alpha 的普通混合(THREE.NormalBlending),其 RGB 计算可写成:
C结果 = α源 × C源 + (1 − α源) × C目标
RGB 三个通道分别计算。假设源是纯红 (1, 0, 0),目标是纯蓝 (0, 0, 1),源 Alpha 为 0.25,那么在同一个用于混合的数值空间里,结果是 (0.25, 0, 0.75)。Alpha 越接近 0,越接近目标;越接近 1,越接近源。
这只是解释混合关系的数值例子,不是页面截图中 RGB 字节值的承诺。Three.js 会处理纹理色彩空间和输出转换;我们还会在第 03 章区分“颜色解码”和“Alpha 混合”。真实效果也可能受渲染目标、色调映射和后处理影响。
示例右侧浅色背景与左侧深蓝背景虽然使用同一个橙色源片元,结果依然不同,因为公式中的 C目标 不同。理解这一点,后面才能解释为什么加法火花在黑背景上清晰,在亮背景上容易发白。
3. opacity 与 transparent 分工
示例使用不受光照影响的 MeshBasicMaterial,使颜色关系更容易观察:
const frontMaterial = new THREE.MeshBasicMaterial( {
color: 0xff8957,
opacity: 0.55,
transparent: true
} );
opacity 提供整体透明度;transparent: true 使该材质进入透明渲染路径,并在默认 NormalBlending 下按 Alpha 与已有画面合成。仅写 opacity: 0.55,却保持 transparent: false,不能指望底板以正常半透明方式露出。r174 的 WebGLRenderer 对默认普通混合且 transparent: false 的材质会关闭固定功能混合。
当纹理也有 Alpha 时,材质整体 opacity 会与纹理 Alpha 共同影响最终片元 Alpha。第 03 章会用带软边的圆形贴图验证这一点。
4. 为什么同一画面还涉及绘制顺序
混合使用的是当前颜色缓冲值,因此先画 A 再画 B,与先画 B 再画 A,通常不完全相同。试想两层各为 50% 的红、蓝薄雾:第一层先与背景混合,第二层再与第一层的结果混合;交换顺序,最靠后的层获得更大权重。
Three.js 把透明对象作为独立一类处理,并尝试排序,但“对象级排序”不能保证所有交叉面或一个 Points 中大量点都逐粒子正确。第 07 章会专门复现这个问题。本章只需记住:混合公式处理已经确定的绘制顺序,公式本身不会替你决定顺序。
5. 用示例逐项验证
- 保持
transparent开启,调整 Alpha:观察前景覆盖深蓝与浅色条带时的差异。 - 设 Alpha 为
0.2,关闭transparent:背景不再按普通混合公式露出。重新开启后恢复。 - 切换背景底板的深浅:源颜色不变,结果却变化。这说明目标颜色参与了计算。
示例的橙色平面在蓝色底板前方。这里故意避免粒子纹理、光照和复杂交叉几何,让变量只有源颜色、目标颜色、Alpha 与透明开关。
常见问题
Alpha 为 0 是否意味着不会绘制? 不一定。Alpha 只是片元数据;若未启用透明混合或丢弃机制,它仍可能经过渲染流程。是否影响深度缓冲还取决于 depthWrite,后面会单独讲。
transparent: true 是否保证所有半透明物体正确? 不能。它使材质进入透明处理路径,但排序、相交几何与深度写入仍可能造成错误。
为什么颜色公式算出的值和截图像素不完全一致? 数值例子假设值处在同一混合空间。纹理色彩空间、输出转换、屏幕显示与抗锯齿会影响你直接读到的像素值。
下一章把普通混合推广为多种源/目标因子,理解烟、火、阴影为何通常采用不同模式。完成第 01 章练习。