X 运动: 墨韵
墨、纸、呼吸。该主题将水墨画(ink-wash painting)的材质逻辑转化为时间维度:稀释的碳墨呈现丰富的灰度层次,温暖的纤维纸张、洇湿的边缘、干燥的笔触以及留白作为主要材质。它扩展了 x-motion;请先运行该方法(构思、剧本、故事板、评审循环),然后使用本文件进行艺术指导和具体方案制定。
视觉主题
通过墨水如同墨水般(扩散、洇染、干涸)在宁静的纸面上表现,让每个动作在刹那间显得必然,从而让观众感受到决定性瞬间前后的静寂。
- 每个节拍一个动作。 一滴、一笔、一道、一只鸟。绝不同时发生两个焦点事件。
- 留白承载重量。 画幅的 60% 到 80% 保持为未触碰的纸、雾气或水。空白空间是精心构图的,而非剩下的。
- 材质的真实优于插图风格。 视觉效果源于模拟墨水如何吸收光、纸张如何吸收墨水,而非轮廓线或纯色填充。
- 尊重与原创。 不使用书法、伪文字、真实或虚构字符、无服装、庙宇或灯笼道具,不使用红日+仙鹤纹样。印章是抽象的几何标记。
调色板
在线性光下工作;以下为 sRGB 十六进制锚点。
| 角色 | 十六进制 | 备注 |
|---|---|---|
| 宣纸(受光面) | #f1e9d8 | 暖调的和纸;绝不用纯白 |
| 宣纸(阴影面) | #e0d3ba | 渐晕、光线的褶皱、斑驳 |
| 宣纸(深色面) | #cbbb9c | 仅在画幅边缘或雾影中使用 |
| 墨,浓墨 | #14120f | 暖调碳黑;绝不用 #000 |
| 墨,中洗 | #5d5b57 | 在暖调纸上略显冷感 |
| 墨,淡洗 | #a9a69f | 远山、扩散的云 |
| 朱红 | #c8372d | 唯一的颜色。最多一个标记,最多约占像素的 1.5% |
| 雾中朱红 | #d7735f | 朱红受大气影响趋向纸色 |
墨不是用来涂的颜色;它是一种吸收。使用比尔-朗伯定律(Beer-Lambert)复合:rgb = paper * exp(-density * absorb),其中 absorb 在蓝色通道稍大(例如 vec3(1.0, 1.04, 1.1)),使淡洗偏冷而浓墨呈暖调黑色。
光影与材质
- 纸张 从一侧被柔和地照亮:低频斑驳(±3%)、可见的楮纤维(长而细的半透明丝状,宽 1 到 2 像素)、以及干燥墨水附着时产生的细微纹理。预渲染一次;它是静态的。
- 湿墨 具有较暗的边缘(颜料迁移到边缘所致)、羽化渗入纤维的效果、以及干燥过程中逐渐消失的微弱光泽。
- 干墨 分裂成平行的笔毫纹理,并跳过纸张的低洼处(对纹理进行阈值处理)。
- 水中的墨 是逆光下的体积:浓密的核心、侧视呈细暗线的层流片状、以及周围的柔和雾纱。
- 大气 是纸色的。距离和雾气将亮度值趋向纸色,而非趋向蓝色或白色。
构图
- 将主体放置在不对称的三分点上;留出一个大的平静区域(顶部或一侧)作为平衡。
- 保持清晰的价值层次:一个接近黑色的区域,几个中间灰度,其余皆为纸色。
- 画幅边缘保持平静。朝
#e0d3ba的柔和渐晕能将视线吸引在画面内。 - 朱红点缀远离主要物体,个体小、位置低、出现晚。它回应动作,而非与之竞争。
- 检查缩略图:单个动作的轮廓在 320 像素宽时必须清晰可辨。
动作词汇与时间
| 动词 | 特征 | 典型曲线 |
|---|---|---|
| 呼吸(保持) | 只有光或雾气在动 | 动作前 0.8 至 1.5 秒的静止 |
| 落下 / 撞击 | 果断、快速、无过冲 | ease-in,0.3 至 0.6 秒 |
| 画 | 画笔加速然后释放 | 缓慢按压、快速中段、轻抬笔 |
| 绽放 / 扩散 | 快速然后指数慢速 | 1 - exp(-t / tau),tau 1 至 2 秒 |
| 洇 / 沉淀 | 事件后微小的增长 | 接触时间以来的 sqrt,有上限 |
| 漂移 | 相机或雾气,恒定且缓慢 | 线性或极长的缓动,每秒不超过画幅的 2% |
| 溶解 | 对比度下降,边缘扩大 | 结尾的 2 至 3 秒,从不切到黑色 |
- 节奏如同呼吸:静止、一个决定性事件、漫长的沉淀。不要在沉淀期引入新的事件。
- 相机只跟着动作移动(随墨水下沉,滑行穿过雾气)。推镜头保持在 8% 以下。
- 种子和噪声场缓慢演变:场偏移量每秒小于 0.05 单位,否则会看起来像在沸腾。
声音调色板与混音
声音和画面一样稀疏。静默本身就是一种信号。
- 材质: 呼吸(带慢速滤波器扫频的带通噪声)、水(水滴用的上升正弦啁啾声、闷响的气泡声)、纸与笔(带通噪声,伴随稀疏的颗粒咔哒声,笔触变干时增强)、风(两个去相关的噪声床,带有预设的阵风曲线)。
- 乐器: 气息感强的竖笛(正弦波加上少量二次和三次谐波,音高从下方滑动进入,呼吸噪声与音符锁定)、拨弦(Karplus-Strong 算法渲染到缓冲区,拨弦后可选的音高滑动)、低锣(非谐波泛音带有拍频对,低泛音持续最久)。
- 和声: 少量开放音程(四度、五度、九度)的音符。避免常见的“东方”五声音阶乐句;让单个音符说话。
- 空间: 一个长的人工混响(3 至 5 秒,尾部变暗,20 至 40 毫秒预延迟)。乐器湿声,纹理干声。
- 同步: 滴落声与冲击同步;呼吸领先动作 0.3 至 0.8 秒;锣声与印章同步;旋律音符在视觉事件后 0.1 至 0.3 秒。
- 目标: 集成功率约为 -18 LUFS,真峰值不超过 -1.5 dBFS,最后一帧尾部低于 -60 dBFS,无爆裂声(每次增益变化至少 5 毫秒的渐变)。
技术方案
1. 预渲染噪声和纸张一次。 上传可平铺的 256x256 RGBA16F 纹理:RG 保存旋度场(周期性梯度噪声的旋转梯度),BA 保存两个独立的噪声。用预设的 JS 代码(颗粒加上 Canvas 2D 纤维笔触)将带有 mipmap 的纸张预渲染为 RGBA8 纹理;每帧采样该纹理,而不是每像素计算 fbm。
2. 墨复合(线性光)。
vec3 lin(vec3 c){ return pow(c, vec3(2.2)); }
vec3 inkOver(vec3 paperLin, float density){
return paperLin * exp(-density * vec3(3.4, 3.55, 3.75));
}
3. 通过逆平流模拟水中墨。 不进行模拟。将墨描述为少数解析基元(滴、茎、涡环、花瓣瓣),其位置是时间闭形式的函数,然后在扭曲的坐标上求值。扭曲是沿旋度场的几步,振幅随时间(从冲击时刻起)增长,从而将基元拉伸成卷曲的卷须。
vec2 q = p;
for (int i = 0; i < 4; i++) {
vec2 c = texture(uNoise, q * freq[i] + drift * t).rg; // 旋度场
q += amp[i] * growth * c;
}
float dens = primitives(q, t); // 高斯核 + 宽雾纱
dens += sheet(q) * veil; // 脊状噪声:侧视片状
- 保持扭曲低频(基尺度为每帧高度 1 到 3 个周期,弱的三分之一倍频程)。高频扭曲会使墨变成扭动的虫子。
- 卷曲效应来自局部旋涡(围绕每个涡核旋转
q,角度按高斯衰减),而非更多噪声。 - 花瓣、瓣和雾纱需要柔和的边缘(高斯,宽度为半径的 0.2 至 0.3)。形状内部的细暗等高线看起来像地形图。
- 判断轮廓,而非细节:一个径向五瓣形状从正面看像水母或海星。倾斜它(为向下约 30 度的相机投影 3D 瓣),否则会像花。
- 保持细、锐、不透明的轨迹看起来像线或茎。将其视为尾迹:加宽、柔化、卷曲并在约一秒内淡出(先顶部),使形态独立。
- 用暖色系数(蓝色多于红色,例如
vec3(3.2, 3.6, 4.2))吸收墨芯,用中性系数吸收稀释的边缘。这样芯呈现为墨黑色,而非平淡的灰色。
4. 笔触作为极坐标场。 对于円相(enso),使用 r(theta) = R + wobble(theta)。对每个像素计算角度,转换为笔触进度 u 和跨越笔触的有符号距离 d / width(u)。在 u < brushHead(t) 处显现。湿润度、宽度和干度是 u 的函数;洇染度是 t - timeTheBrushPassed(u) 的函数。
float lanes = texture(uNoise, vec2(across * 5.0, u * 0.35)).b; // 笔毫纹理
float dryInk = step(dryness(u), lanes * 0.6 + tooth * 0.5); // 跳过纸张低洼处
float rim = smoothstep(0.75, 1.0, abs(across)) * wet(u); // 边缘暗化
5. 分层景深用于山水画。 每个山脊是深度 z 处的一维高度轮廓。对每个像素投影到世界 x,查找高度,与透视下该高度处的屏幕 y 比较。墨色在山脊线最强,向下向雾中衰减。从远到近复合,按 1 - exp(-depth * k) 向纸色混合。雾片位于山脊之间的山谷中,随时间漂移。当相机即将穿过山脊时将其淡出。
- 从近到远复合,使用透射率(
acc += T * a * layer; T *= 1.0 - a;),并在T接近零时终止。这是精确的,并且成本减半,因为画幅下半部分在一两层后停止。 - 使用解析一维梯度噪声(哈希加五阶缓动)计算山脊线,而非纹理查找:双线性纹素会在长脊上显示为扭结。
- 大气需要陡峭的曲线(
exp(-0.45 * depth)),以便最远距离读作淡淡的影子而非另一种灰色。
6. 小剪影(鸟、印章)作为边界框测试内的有符号距离场(SDF)胶囊和方框,具有 1.5 像素的抗锯齿边缘。用相位函数驱动翅膀动作,而非物理模拟。
7. 音频构建块。
// 带有可选滑音的 Karplus-Strong 拨弦,确定性地渲染到缓冲区
for (let n = 0; n < len; n++) {
const d = sr / freqAt(n) - 0.5, r = w - d;
const a = read(r), b = read(r - 1);
y = exc[n] + g * 0.5 * (a + b); write(y); out[n] = y;
}
混响脉冲:预设立体声噪声乘以 exp(-t / decay),通过一个截止频率随时间下降的单极点低通滤波器。主效果链:温和压缩器(比例 2 至 3),接着一个快速高比率压缩器作为限制器,然后是可测量的增益补偿,最后是线性主淡出,在结束前约 0.15 秒达到零,以确保混响尾部消失。
- 位精确混音。 Chromium 以不确定的顺序对节点的输入求和。浮点加法仅对两个操作数产生相同位;压缩器会将 1e-7 舍入放大为可听电平的差异。通过链接增益节点,使每个混音点最多具有两个输入:
function summer(ctx, target) { // 返回 add(node); 每个混音点 <= 2 个输入
let open = target, count = 0;
return node => { if (count === 1) { const j = ctx.createGain(); j.connect(open); open = j; count = 0; } node.connect(open); count++; };
}
DynamicsCompressorNode会自行应用自动增益补偿,因此降低阈值会使混音更响。改用输出增益来设置响度。- 检查各声道的平衡:在不带旋律的情况下渲染一次。如果背景音与完整混音之差在 2 dB 以内,则旋律被掩盖了。
确定性渲染检查表
renderFrame(t)仅根据t设置所有统一变量;不使用积累、Date、performance.now或Math.random。- 所有随机性来自预设的 PRNG(mulberry32)或加载时预计算的纹理哈希。
- 纸张、噪声和 IR 在加载时同步构建,且永不改变。
- 向后查找产生相同的像素;在播放后期后测试
t = 0。 - 在 1920x1080 分辨率下使用软件 WebGL 测量帧成本;保持在 1.5 秒以下。对细微细节使用边界框,并将扭曲循环限制为四步。
- 颗粒是静态的,低于 2%;无逐帧全屏噪声。
- 线条和细丝至少有 1.5 像素的柔和衰减。
- 音频使用预设的噪声缓冲区,且每个混音点最多有两个输入;两次调用必须返回字节相同的缓冲区。
- 预览模式使用
requestAnimationFrame循环;渲染模式自身不绘制任何内容。
避免
- 纯白纸、纯黑墨、超出单独朱红标记之外的高饱和度颜色。
- 类似书法的笔触、字符、伪书法、签名、常见的文化道具。
- 多个动作同时出现;杂乱的背景;以恒定速度不断运动的墨水。
- 发光、镜头光晕、色差、散景;墨水是哑光的。
- 用均匀模糊冒充扩散;真正的扩散同时具有清晰的核和柔和的纱。
- 风铃、每次剪辑时的铃铛声、持续的垫音、呼啸声、常见的五声音阶乐句。
- 以切到黑色结尾;让图像沉淀或溶解到纸色或灰色中。