卡通材质

用 PixelNormalWS + DDX/DDY 提取曲率 / 边缘遮罩

基本原理

在材质中,用 PixelNormalWS(世界空间像素法线) 配合 DDX / DDY 偏导数,计算屏幕空间下法线的变化率:

这样可以快速得到一个简易的 曲率遮罩 或 边缘提取遮罩,常用于边缘光、磨损、脏迹、高光强化等效果。


参数控制:Thickness(粗细)与 Contrast(对比度)

为进一步可控,加入两个输入参数:

最后把调整后的边缘值 saturate 一下,cheaply clamp 到 0–1 范围,确保输出规范。

卡通着色:用高光、中间调、阴影覆盖光照

思路

计算光照强度后,用指定的颜色覆盖原本着色,分别控制 阴影(Shadows)、中间调(Midtones) 和 高光(Highlights)。

简化漫反射

可以用简化版 Lambertian 反射模型 来近似计算漫反射光照:

参数设置

添加三个颜色输入:

同时添加 Threshold(阈值) 参数,用来划分三个色调的过渡范围。

加入 Softness(柔和度)

增加一个 Softness(柔和度) 控制,用来调节色调过渡的软硬程度:

这需要稍微改造一下原有代码。


与边缘 / 曲率遮罩结合

最后把卡通着色和之前做的 Edge/Curvature Mask 结合起来。这里用最简单的方式:

  1. 把边缘遮罩 取反(invert)。
  2. 与卡通着色结果 相乘。
  3. 用 saturate 把结果限制在 0–1 范围内。

这样边缘区域就不会被卡通着色完全覆盖,产生常见的描边或边缘提亮效果。


半调图案(Half-Tone Pattern)

效果描述

半调图案是一种常见的漫画/印刷风格效果:暗部(亮度低)显示大圆点,亮部(亮度高)显示小圆点,整体看起来像网点纸。

核心代码

// 1. 计算亮度
float luminance = dot(color, float3(0.299, 0.587, 0.114));

// 2. 用 UV 生成棋盘/圆点网格
float dist = length(frac(uv * tiling) - 0.5);

// 3. 暗部越大,亮部越小
float radius = (1.0 - luminance) * maxSize;

// 4. 圆点内部为深色,外部保持原色
return lerp(1, 0.5, step(dist, radius));

亮度公式说明

代码中使用的亮度公式是:

L = 0.299R + 0.587G + 0.114B

这是 BT.601(Rec.601) 亮度计算公式,用于把 RGB 图像转换成符合人眼视觉感知的灰度亮度。该标准来自早期模拟电视(标清/NTSC/PAL),用来从 RGB 中分离出 YUV 色彩空间里的 Y(亮度)信号。

常用亮度公式对比

标准 公式 适用场景
BT.601(当前代码使用) Y = 0.299R + 0.587G + 0.114B 老视频、复古 Shader、半色调/漫画网点、传统调色
BT.709 Y = 0.2126R + 0.7152G + 0.0722B HD/4K 视频、sRGB 显示器、现代影视渲染

代码说明

行 作用
luminance 用标准 RGB 权重计算亮度。
frac(uv * tiling) - 0.5 把 UV 重复平铺,并把坐标原点移到每个单元中心。
length(...) 得到当前像素到单元中心的距离。
radius 由亮度决定圆点半径:越暗半径越大。
step(dist, radius) 距离小于半径时为 1,表示在圆点内部。
lerp(1, 0.5, ...) 圆点内部输出 0.5,外部输出 1,形成黑白网点。