原理:场景先渲进"数据缓冲",再用全屏后处理把每个像素"画"成圆珠笔/铅笔笔触。

github:https://github.com/zsj23333/doodleBreak
在线体验:https://zsj23333.github.io/doodleBreak/
一、两步管线
pass 1 数据缓冲 pass 2 全屏后处理
场景 ──► RGBA半浮点(r=明暗 g=墨色ID ba=法线xy) ──► 每像素画成: 轮廓+阴影线+纸纹
+ Float深度纹理
- 数据缓冲:自定义
ShaderMaterial,只存"作画所需信息",无任何风格化。 - 后处理:全屏 quad(独立场景 + 正交相机),逐像素决定画成纸色、墨线还是轮廓。
- 好处:风格化与场景内容解耦,任意模型/相机运动都有一致手绘感。
二、数据缓冲 pass
- 片元输出
vec4(shade, inkId, n.x, n.y),shade = dot(n, L)*0.5+0.5半球光;fill模式输出shade=-1(后处理整片涂纯色)。 - 深度纹理必须
FloatType;RT 用HalfFloatType+NearestFilter、无 mipmap。 - 同色物体共享材质实例,减少 draw call。
三、后处理五层笔触
1. 手抖采样:值噪声偏移采样 UV,整幅笔触同步微抖。
2. 轮廓 = 逆深度 Laplacian(关键):
任意平面
ax+by+cz=d的1/z在屏幕空间是仿射函数 → 二阶差分恒 0。平面内部无响应,只有穿过真实深度不连续(轮廓)时非零 。除以1/d归一化,任意距离轮廓强度一致。
glsl
float lap = abs(1.0/linDepth(zl) + 1.0/linDepth(zr) - 2.0*iw)
+ abs(1.0/linDepth(zu) + 1.0/linDepth(zd) - 2.0*iw);
float edge = smoothstep(0.07, 0.30, lap / (iw + 1e-7)); // 尺度不变
法线突变也补一条轮廓(贴在一起的物体间);轮廓颜色取最近深度物体的墨色。
3. 阴影线 = 表面锚定 + LOD + 三方向排线:
- 从深度重建世界坐标和法线 → 条纹"钉"在表面上,随物体转动不漂移。
- 法线最朝外的轴决定投影面,避免条纹拉伸。
- 间距按
sp = 0.5 * exp2(floor(log2(0.016*d/0.5)))做 2 的幂分级,远处自动变粗防摩尔纹。 - 三方向(45°/-45°/15°)叠加,密度随 shade 递减:
smoothstep(0.64→0.5)、(0.42→0.32)、(0.24→0.14),最暗处涂黑。
glsl
float stripes(vec2 p, vec2 dir, float spacing, float width) {
float t = dot(p, vec2(-dir.y, dir.x));
float f = abs(fract(t / spacing) - 0.5) * spacing;
float soft = width * 0.6;
return 1.0 - smoothstep(width*0.5 - soft, width*0.5 + soft, f);
}
4. 纸面 :双层值噪声(低频大块+高频细颗粒)做纸纹;横线 = 屏幕 y 取模,间距 画布高/13.5,淡蓝 (0.58,0.70,0.92)。红边距可选(不需要就删)。
5. 合成 :paper → mix(墨色, hatch*0.72) → mix(轮廓墨色*0.92, edge*ew),ew = 0.75+0.35*vnoise 让轮廓线毛糙一点。
渲染器:render(scene) 到 RT → render(postScene, ortho) 到屏幕。后处理 quad 必须单独场景,否则主场景被重复渲染。
四、调参速查表
| 参数 | 作用 | 调大效果 |
|---|---|---|
wob*2.0*sc*px |
手抖幅度 | 更"手抖" |
o=1.15*sc |
轮廓采样半径 | 线更粗 |
smoothstep(0.07,0.30,...) |
轮廓灵敏度 | 更易勾边 |
0.016(LOD 基准) |
阴影线间距 | 线更稀 |
w=sp*0.17 |
笔宽 | 排线更粗 |
0.64/0.42/0.24 |
三方向明暗阈值 | 更早出现更多方向线 |
hatch*0.72 |
墨色浓度 | 更黑 |
grain / rule*0.5 |
纸纹强度 / 横线透明度 | 纸更糙 / 线更明显 |
uPaper / uLineSpacing |
纸色 / 横线间距 | 改纸色 / 改密度 |
五、常见坑
- r147 默认 Linear 输出,示例色是手调亮的;换深色系需自行
pow(col, 1/2.2)或开sRGBEncoding。 - r152+ 升级注意
texture2D → texture与outputEncoding改名。 - 深度纹理必须
FloatType,否则逆深度轮廓出噪点。 - RT 用
NearestFilter无 mipmap;深度与颜色采样必须用同一 wob 偏移,否则轮廓错位。 - 空场景也要渲一帧,否则黑屏。