08 多个图形与 RGB 叠加
前置知识:第 4 章的遮罩组合、第 5 章的软边,以及第 7 章的比例修正。
本章示例:三个圆的 RGB 叠加,完整源码。
学完本章能做什么
你将能分别绘制多个图形遮罩,把它们映射到 RGB 通道,并区分“颜色逐分量相加”“按顺序覆盖”和“只求图形并集”三种结果。
先看现象
打开示例,默认有三个相交的圆。单独的区域分别偏红、绿、蓝;两个圆相交时出现黄、品红或青;三个圆的交集接近白色。改变圆心间距,可以控制交集面积;改变羽化宽度,可以观察边缘的半透明式过渡。这里平面始终是不透明的:这些效果是在一个片元着色器内计算颜色,并非把三个透明 Three.js 网格依次叠放。
1. 先分别得到三个遮罩
用第 7 章的比例修正坐标:
vec2 point = ( vUv - 0.5 ) * vec2( uAspect, 1.0 );
示例平面宽高比为 1.5,因此 x 方向要乘 1.5。在这个坐标中,三个圆用相同半径 0.28,不会因为平面较宽而变成椭圆。把画圆代码写成一个函数:
float circleMask( vec2 point, vec2 center, float radius, float feather ) {
float d = length( point - center );
return 1.0 - smoothstep( radius - feather, radius + feather, d );
}
point - center 把指定圆心移到坐标原点,再用长度取得距离。三个圆心不同,得到 redMask、greenMask、blueMask。每个遮罩在内部接近 1,在外部接近 0,边缘则有 0.0~1.0 的中间值。改变圆心坐标只移动图形,不改变它的绘制方法。
2. 一个图形占一个 RGB 通道
最直接的叠加方式是:
vec3 color = vec3( redMask, greenMask, blueMask );
也可以理解成三个基色向量相加:
vec3 color = vec3( 1.0, 0.0, 0.0 ) * redMask +
vec3( 0.0, 1.0, 0.0 ) * greenMask +
vec3( 0.0, 0.0, 1.0 ) * blueMask;
当遮罩都是精确的 0 或 1,结果很好预测:
| 覆盖的图形 | (R, G, B) |
结果 |
|---|---|---|
| 红圆、绿圆 | (1, 1, 0) |
黄 |
| 红圆、蓝圆 | (1, 0, 1) |
品红 |
| 绿圆、蓝圆 | (0, 1, 1) |
青 |
| 三个圆 | (1, 1, 1) |
白 |
软边处遮罩可能是 0.4、0.7 之类的中间值,因此颜色也连续变化。因为纯红、纯绿、纯蓝各占不同通道,三者重叠时不会让某个单独通道超过 1;示例的“颜色增益”或使用含多个非零通道的图形颜色,则可能使通道总和超过 1。
3. 给每个图形指定任意颜色后相加
vec3 sum = redColor * redMask +
greenColor * greenMask +
blueColor * blueMask;
vec3 color = clamp( sum * uGain, 0.0, 1.0 );
这里的名字只是标识三个图形;redColor 也可以含有绿色或蓝色分量。若两个图形都对同一通道有贡献,交集处就可能超过 1.0。示例在输出前用 clamp 限制范围;增益过高时,重叠区域可能大片变成同一种饱和颜色,细节就丢失了。clamp 防止超出范围,但不会找回被截掉的差别。
本教程先在线性 RGB 中计算这些颜色,最后执行 #include <colorspace_fragment> 输出到 sRGB 显示空间。因此,直接把屏幕截图中的亮度当作 shader 内部的线性数值来相加,会得出错误直觉。
4. 按顺序覆盖是另一种运算
如果每个圆的遮罩代表“这一层覆盖下面颜色的程度”,可以逐层 mix:
vec3 color = vec3( 0.0 );
color = mix( color, vec3( 1.0, 0.0, 0.0 ), redMask );
color = mix( color, vec3( 0.0, 1.0, 0.0 ), greenMask );
color = mix( color, vec3( 0.0, 0.0, 1.0 ), blueMask );
若三个遮罩在某片元都是 1,第一步变红,第二步被绿覆盖,第三步被蓝覆盖,最终是蓝色,不是白色。改变这三行的顺序,交集颜色也会改变。边缘遮罩在 0 和 1 之间时,下面的颜色还能部分透出。
这段代码只是在一个片元内部模拟图层覆盖;它没有让 ShaderMaterial 变透明,也不涉及 Three.js 场景中多个物体的深度排序。需要将整个物体与背景做透明混合时,还要另外设置材质的透明度和混合方式。
5. 只求并集不会增加亮度
float unionMask = max( redMask, max( greenMask, blueMask ) );
vec3 color = vec3( unionMask );
此时只关心“任意圆有没有覆盖该点”,而不关心覆盖了几个圆。三个圆的交集不会比单个圆更亮。若需要所有图形都覆盖的交集,可把 max 换成嵌套 min。若需要“红圆扣掉蓝圆”,可从红遮罩中减去蓝遮罩,并用 max(..., 0.0) 防止负值。
常见问题
- **交集总是最后一个图形的颜色:**代码可能用了连续的
mix覆盖,而不是逐分量相加。 - **交集一片纯白:**多个颜色对相同通道相加后被
clamp截断,或增益设置过高。 - **圆在宽平面上变成椭圆:**忘记在求距离前按平面宽高比修正坐标。
- **图形边缘有怪异的硬线:**检查
smoothstep的两个边界是否从小到大,以及uFeather是否大于 0。 - **以为
vec4(color, 1.0)表示圆外透明:**这里 alpha 始终为 1;圆外只是颜色为黑色。
本章小结
多个图形先各自产生 0.0~1.0 遮罩,再决定如何组合:放进不同 RGB 通道会产生加色效果,逐层 mix 会按顺序覆盖,max 则只求并集。相同的三个圆,仅改变组合方法,画面含义就不同。