X Motion: Sumi
墨、紙、氣息。這個主題將水墨畫的材料邏輯轉化為時間:用許多灰色稀釋的碳墨、溫暖的纖維紙、暈染的濕邊、乾澀的跳筆,以及作為主要材料的空白。它擴充了 x-motion;請先執行該方法(簡報、論文、分鏡、評論循環),然後使用此檔案進行藝術指導與技法配方。
視覺論點
讓每一個筆勢在時間中顯得必然,透過墨像墨一樣表現(擴散、暈染、乾枯)在一大片寧靜的紙上,使觀者在決定性時刻前後感受到靜止。
- 每次節拍一個筆勢。 一滴、一劃、一道山脊、一隻鳥。絕不同時出現兩個焦點事件。
- 空白承載重量。 畫面的 60% 到 80% 保持未觸及的紙張、霧或水。空白是經過構圖的,不是遺留的。
- 材質真實重於表層描繪。 外觀來自於模擬墨水如何吸收光線、紙張如何吸收墨水,而不是來自於輪廓線或平面填色。
- 尊重與原創。 無書法、無偽造字體、無真實或虛構文字、無服裝、寺廟或燈籠道具、無紅日加鶴圖案。印章是抽象的幾何標記。
調色盤
在線性光中工作;以下是 sRGB 十六進位錨點。
| 角色 | 色碼 | 備註 |
|---|---|---|
| 紙張(亮部) | #f1e9d8 | 溫暖和紙;絕不使用純白 |
| 紙張(陰影) | #e0d3ba | 暗角、光線折疊、斑駁 |
| 紙張(深色) | #cbbb9c | 僅在畫面邊緣或霧影中使用 |
| 墨,全濃度 | #14120f | 溫暖碳黑;絕不使用 #000 |
| 墨,中淡 | #5d5b57 | 相對於暖色紙張略偏冷 |
| 墨,極淡 | #a9a69f | 遠山、擴散的雲 |
| 朱紅 | #c8372d | 唯一的色彩。最多一個標記,最多約佔像素的 1.5% |
| 薄霧中的朱紅 | #d7735f | 朝紙色方向混合的朱紅,由大氣所致 |
墨不是你所塗上的顏色;它是一種吸收。使用比爾-朗伯定律合成:rgb = paper * exp(-density * absorb),其中 absorb 在藍色通道略大(例如 vec3(1.0, 1.04, 1.1)),使薄洗色略帶冷感,濃墨則呈現溫暖黑色。
光線與材質
- 紙張 從一側柔和照明:低頻斑駁(正負 3%)、可見的楮樹纖維(細長半透明線條,寬 1 到 2 像素)、以及細微的紋理,乾墨會附著其上。預先烘焙一次;它是靜態的。
- 濕墨 具有較深的邊緣,顏料遷移至邊界、羽毛狀擴散到纖維中、以及乾燥時逐漸消失的淡淡光澤。
- 乾墨 分解成平行的筆毛紋路,並跳過紙張的凹陷處(對紋理設門檻值)。
- 水中的墨 是背光體積:濃密的內核、從側面看呈現為精細黑暗線條的層狀水紋、以及圍繞一切的柔和薄紗。
- 大氣 是紙張色的。距離與薄霧將數值朝紙色方向提升,絕不朝藍色或白色。
構圖
- 將主體置於不對稱的三分線上;留出一個大面積的平靜區域(頂部或一側)作為平衡。
- 保持清晰的明度層次:一個接近黑色、幾個中間灰色、其餘皆為紙色。
- 畫面邊緣保持安靜。向
#e0d3ba的柔和暗角能將視線保持在畫面內。 - 朱紅點綴位於遠離主體、小而低調且晚出現的位置。它回應筆勢,不與之競爭。
- 檢查縮圖:單一筆勢的輪廓在 320 像素寬度下必須可識讀。
動作詞彙與時機
| 動詞 | 特徵 | 典型曲線 |
|---|---|---|
| 呼吸(停頓) | 沒有任何移動,只有光或霧 | 筆勢前 0.8 到 1.5 秒的靜止 |
| 落下 / 撞擊 | 果斷、快速、無超調 | 緩入,0.3 到 0.6 秒 |
| 繪畫 | 毛筆加速然後釋放 | 慢壓、快中段、輕提 |
| 綻放 / 擴散 | 快速然後指數減速 | 1 - exp(-t / tau),tau 1 到 2 秒 |
| 暈染 / 沉澱 | 事件後的微小成長 | 自接觸以來的時間的 sqrt,有上限 |
| 飄移 | 相機或霧,持續且緩慢 | 線性或非常長的緩動,每秒少於畫面的 2% |
| 溶解 | 對比下降,邊緣擴大 | 結尾的 2 到 3 秒,絕不切到黑 |
- 節奏是呼吸:靜止、一次決定性事件、一次長久的沉澱。不要用新的事件填滿沉澱期。
- 相機只為了跟隨筆勢而移動(隨墨下沉,滑過薄霧)。推近鏡頭保持在 8% 以下。
- 種子與噪聲場緩慢演化:場偏移每秒低於 0.05 單位,否則會看起來像沸騰。
聲音調色盤與混音
聲音與影像一樣簡約。寂靜是一種提示。
- 材質: 呼吸(帶通噪聲,帶有緩慢的濾波器掃頻)、水(水滴用上升正弦啁啾、悶響的氣泡)、紙與筆(帶通噪聲,帶有稀疏的顆粒點擊聲,隨著筆乾而增大)、風(兩個去相關的噪聲層,帶有種子化的陣風曲線)。
- 樂器: 氣息濃厚的直笛(正弦波加上少許 2 次與 3 次諧波,音高從下方滑入,氣息噪聲鎖定在音符頻帶上)、撥弦(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; // curl field
q += amp[i] * growth * c;
}
float dens = primitives(q, t); // 高斯核心 + 寬廣薄紗
dens += sheet(q) * veil; // 脊狀噪聲:側面薄片
- 保持扭曲為低頻(基底尺度為 1 到 3 個週期每畫面高度,一個弱的第三八度)。高頻扭曲會使墨水變成蠕動的蟲。
- 捲起來自局部旋轉(將
q繞每個渦流核心旋轉一個角度,該角度隨高斯函數衰減),而不是來自更多噪聲。 - 花瓣、瓣與薄紗需要柔和的邊緣(高斯,寬度為半徑的 0.2 到 0.3)。形狀內部的細暗輪廓線看起來像地形圖。
- 判斷輪廓,而非細節:一個從正面看到的放射狀五瓣形狀看起來像水母或海星。在它讀起來像花之前,將其傾斜(投影 3D 瓣,使相機向下約 30 度觀看)。
- 一條保持細長、銳利且不透明的軌跡看起來像繩子或莖。將其視為尾跡:使其變寬、柔和、彎曲並在大約一秒內淡出,頂部優先,使形態獨立出來。
- 使用暖色係數(藍色多於紅色,例如
vec3(3.2, 3.6, 4.2))吸收墨水核心,使用中性係數處理稀釋的邊緣。這樣核心看起來會是墨黑色,而非平灰色的。
4. 筆觸作為極座標場。 對於一個圓相,使用 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接近零時中斷。這是精確的,並且由於下層在一兩個圖層後停止,成本減半。 - 使用解析的 1D 梯度噪聲(哈希加五級平滑)計算山脊線,而非紋理查找:雙線性紋素沿著長脊線會顯示為扭結。
- 大氣需要一條陡峭的曲線(
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設定所有 uniform;無累積、Date、performance.now或Math.random。- 所有隨機性來自於種子化的 PRNG(mulberry32)或在載入時烘焙的紋理哈希。
- 紙張、噪聲和 IR 在載入時同步建構並且永遠不變。
- 向後尋找會產生相同的像素;在渲染後期幀後測試
t = 0。 - 以軟體 WebGL 在 1920x1080 解析度下測量幀成本;保持在 1.5 秒以下。為小細節使用邊界框,並將扭曲循環保持在四個步驟以內。
- 顆粒是靜態的,低於 2%;無全畫面逐幀噪聲。
- 線條和細絲至少有 1.5 像素的柔和衰減。
- 音頻使用種子化的噪聲緩衝區,每個混合點最多有兩個輸入;兩次呼叫必須回傳位元組完全相同的緩衝區。
- 預覽模式使用
requestAnimationFrame循環;渲染模式自身不繪製任何東西。
避免
- 純白紙張、純黑墨水、超出單一朱紅標記的飽和色彩。
- 類似文字的筆觸、文字、偽書法、簽名、現成的文化道具。
- 多個筆勢互相競爭;雜亂的背景;以相同速度持續移動的墨水。
- 光暈、鏡頭眩光、色差、散景;墨水是無光澤的。
- 使用均勻模糊假裝擴散;真正的擴散同時具有清晰的內核和柔和的薄紗。
- 風鈴、每個剪接處的鈴聲、持續的襯底音、呼嘯聲、現成的五聲音階樂句。
- 以切到黑畫面結束;讓影像沉澱或溶解到紙張或灰色中。