@@ -105,11 +105,134 @@ glFrontFace(GL_CCW);
105105
106106---
107107
108+ ## 6. 透明与混合(Blending):从“能画”到“看起来对”
109+
110+ 透明不是“把 alpha 写成 0.5”,它是一整套规则:
111+
112+ - 颜色怎么混合(blend)
113+ - 深度怎么处理(depth write / depth test)
114+ - 绘制顺序怎么安排(排序)
115+
116+ ### 6.1 开启混合:最常见的 alpha 混合
117+
118+ 初始化(一次即可):
119+
120+ ```cpp
121+ glEnable(GL_BLEND);
122+ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
123+ ```
124+
125+ 如果你的片元输出 ` vec4(rgb, a) ` ,那么最终颜色约等于:
126+
127+ - out = src * a + dst * (1-a)
128+
129+ ### 6.2 深度与透明:最容易踩坑的组合
130+
131+ 不透明物体的习惯是:
132+
133+ - depth test 开
134+ - depth write 开(默认)
135+ - 顺序随意(通常前到后更省像素)
136+
137+ 透明物体通常更稳的规则是:
138+
139+ - depth test 开(仍然需要遮挡)
140+ - depth write 关(避免“透明物体把后面的透明物体挡死”)
141+ - 按相机距离从远到近画(保证混合正确)
142+
143+ 绘制透明物体前(每帧,开始画透明队列之前):
144+
145+ ``` cpp
146+ glDepthMask (GL_FALSE);
147+ ```
148+
149+ 画完透明队列后恢复:
150+
151+ ```cpp
152+ glDepthMask(GL_TRUE);
153+ ```
154+
155+ ### 6.3 预乘 Alpha(Premultiplied Alpha):UI/贴图常见语义
156+
157+ 有些贴图数据是“预乘 alpha”的:RGB 已经乘过 A。
158+
159+ 这时对应的混合因子通常是:
160+
161+ ``` cpp
162+ glBlendFunc (GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
163+ ```
164+
165+ 你可以用一个工程口径记住区别:
166+
167+ - 非预乘:src.rgb 还没乘 a,用 `SRC_ALPHA`
168+ - 预乘:src.rgb 已经乘 a,用 `ONE`
169+
170+ ---
171+
172+ ## 7. 模板缓冲(Stencil):用一个 8-bit mask 做“分区域渲染”
173+
174+ 模板缓冲的直觉是:每个像素除了 color/depth,还有一个小小的整数标签(常见 8-bit)。
175+
176+ 你可以用它做很多“效果技巧”:
177+
178+ - 描边/高亮(先写模板,再只在模板外画一圈)
179+ - 镜子/洞口/门户(只在模板区域画某个 pass)
180+ - HUD/遮罩(限制绘制范围)
181+
182+ ### 7.1 开启模板测试与基本用法
183+
184+ 初始化(一次即可):
185+
186+ ```cpp
187+ glEnable(GL_STENCIL_TEST);
188+ ```
189+
190+ 最小配置(概念上):
191+
192+ - ` glStencilFunc ` :通过条件(参考值 vs 当前 stencil 值)
193+ - ` glStencilOp ` :通过/失败时怎么改 stencil 值
194+
195+ 示例:先把某个物体所在像素的 stencil 写成 1:
196+
197+ ``` cpp
198+ glStencilMask (0xFF);
199+ glStencilFunc(GL_ALWAYS, 1, 0xFF);
200+ glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
201+ ```
202+
203+ 然后再画第二遍,只允许在 stencil != 1 的地方画(用于描边外扩):
204+
205+ ```cpp
206+ glStencilMask(0x00);
207+ glStencilFunc(GL_NOTEQUAL, 1, 0xFF);
208+ ```
209+
210+ 画完恢复写入:
211+
212+ ``` cpp
213+ glStencilMask (0xFF);
214+ glStencilFunc(GL_ALWAYS, 0, 0xFF);
215+ ```
216+
217+ ### 7.2 记住“清理”与 FBO 的关系
218+
219+ 如果你启用了 stencil buffer,每帧清理要把 stencil 也清掉:
220+
221+ ```cpp
222+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
223+ ```
224+
225+ 离屏渲染时,如果你的 FBO 没有 stencil 附件,你再怎么 ` glEnable(GL_STENCIL_TEST) ` 也不会按你预期工作。
226+
227+ ---
228+
108229## 常见坑
109230
110231- ** 只清了颜色没清深度** :画面会越来越怪
111232- ** near/far 乱填** :深度问题会在你以为“还没学到那一步”时就出现
112233- ** 剔除开启后全消失** :先检查绕序,再检查是否有负缩放
234+ - ** 透明物体“越画越黑/越画越怪”** :检查混合因子、深度写入、以及是否按远到近排序
235+ - ** Stencil 没效果** :检查是否有 stencil buffer,是否清了 stencil,是否禁用了 stencil 写入(mask)
113236
114237---
115238
@@ -125,13 +248,24 @@ glFrontFace(GL_CCW);
125248- 题目:画两个几乎重叠的平面,调整 near/far 观察闪烁变化
126249- 目标:把“深度精度”从概念变成现象
127250
251+ ### 练习 3:做一个透明 Billboard(树叶/玻璃)并排序
252+
253+ - 题目:画多张带 alpha 的四边形,按相机距离从远到近绘制
254+ - 目标:把“透明 = 混合 + 深度策略 + 顺序”跑通
255+
256+ ### 练习 4:做一个描边效果(Stencil)
257+
258+ - 题目:第一遍写 stencil=1,第二遍略微放大模型,只在 stencil!=1 的区域画纯色边
259+ - 目标:掌握 stencil 的最常用工程套路
260+
128261---
129262
130263## 小结
131264
132265- 3D 必须开启深度测试,并且每帧清 depth
133266- 裁剪/视锥问题优先从 Model/View/Projection 去排查
134267- 面剔除是简单有效的性能手段,但要理解绕序与负缩放
268+ - 透明渲染要同时管住:混合、深度写入、绘制顺序
269+ - Stencil 是“分区域渲染”的强力工具,描边/遮罩类效果都离不开它
135270
136271下一篇:见 [ 09-模型与网格:OBJ/GLTF(简化版)加载、法线、切线] ( 09-模型与网格-OBJ-GLTF加载-法线-切线.md )
137-
0 commit comments