iT邦幫忙

2022 iThome 鐵人賽

DAY 7
1

一般在電腦中儲存顏色是使用3個byte的整數,比如16777215就代表了白色,不過這樣的表示方法很難看得出顏色的資訊,所以寫程式時通常會使用16進位的格式,比如剛剛說的白色以16進位的格式就會寫成0xFFFFFF,其中開頭的0x是一個專門給電腦看的標記,表示接在後面的是個16進位的數字。

在0x後面接的是三組兩個字元的字串,分別表示紅色(R)、綠色(G)、藍色(B)的亮度,最暗是00,最亮是FF,代表的是16進位的最小值和最大值,換算成十進位的話,就會是0到255。於是上面舉的例子,0xFFFFFF就代表三原色RGB都是FF,也就是最亮的狀態,合起來就是白色,以此類推,0xFF0000就是紅色,0x00FF00就是綠色,而0xFFFF00就是黃色。
色票
若想讓顏色也套用靠近演算法去接近目標顏色的時候,我們可以讓顏色中的RGB分別套用靠近演算法去得到一個更靠近目標色的顏色。

問題所在

使用RGB去靠近目標色的問題在於,顏色的本質其實不是RGB那麼單純,我們在電腦中採用RGB作為三原色並不是一個「本來就是這樣啊」的決定。在色彩學中,理論上只要是三個飽和度100%、色相相差120度的顏色都可以當作三原色。

RGB的顏色空間是一個正方形,使用RGB來執行靠近演算法,其實就是讓顏色在這個RGB的空間裏進行空間位置的靠近。
RGB Cube

顏色除了能在RGB空間找到位置,其實還可以出現在其他更多的色彩空間,比如印刷時用的CMYK、電視常用的xvYCC、以及繪圖師常用的HSB等等。

現在我們就來看看繪圖軟體中最常見的HSB色彩空間。
HSB指的是:

  • Hue(色相):一個在色盤上的角度,值介於0度到360度。
  • Saturation(飽和度):顏色在鮮豔和灰階之間的百分比,值介於0到1。
  • Brightness(亮度):顏色發光的程度,值介於0到1。

關於顏色在HSB和RGB兩個空間中的轉換,可以參考小弟的另一篇文章:
科普一下顏色在不同空間轉換的意義和演算法

為什麼不用RGB就好,非要看這什麼HSB呢?那是因為在色彩學中,很多理論和技巧都圍繞在HSB的色彩空間,像是互補色、三分色、補色分割、相似色等等,而這些顏色的性質在RGB空間中比較難加以操作。
色彩學

所以大家可能猜到我想做什麼了是吧?
color transform
我們可以把顏色從RGB轉換到HSB空間,然後在HSB空間中對色相、飽和度、亮度分別進行靠近演算法,計算完成後,再把新的顏色轉回RGB空間就行了。
顏色靠近法比較
使用HSB空間的靠近演算法,可以避免顏色在RGB空間靠近時彩度降低再回升的問題,但也會造成色相變化較大的效果,所以在HSB空間靠近的時候,有時會特別降低Hue(色相)的靠近率。

示範程式碼

我們先假設程式中已經有了昨天和前天寫好的兩種靠近演算法。

/** 靠近演算法 */
function numberFollowTarget(current: number, target: number, rate: number): number;
/** 角度靠近演算法 */
function degreeFollowTarget(current: number, target: number, rate: number): number;

然後我們要實作HSB空間的靠近演算法。

程式中會用到CG提供的Color類別,如果想看原始碼的同學,可以點擊下面的連結。
Color.ts

// 先定義一個開始的顏色
let current = new Color(0xFFDD00);
// 然後定義目標顏色
let target = new Color(0x00FF55);

/** 在進行更新前要注意我們必須在更新的整個過程中,
 * 全部都使用HSB來進行計算
 * 才不會在整數/小數之間轉換時產生失真誤差
 */
let currentHSB = current.toHSB();
let targetHSB = target.toHSB();

/** 定義靠近率 */
let followRate = 0.1;

/** 宣告遊戲更新的函式 */
function updateGame(): void {
    // 對飽和度(S)和亮度(B)分別用靠近演算法計算新值
    currentHSB.brightness = numberFollowTarget(
                                currentHSB.brightness,
                                targetHSB.brightness,
                                followRate);
    currentHSB.saturation = numberFollowTarget(
                                currentHSB.saturation,
                                targetHSB.saturation,
                                followRate);
    // 對色相(H)必須要使用角度專用的靠近演算法
    currentHSB.hue = degreeFollowTarget(
                                currentHSB.hue,
                                targetHSB.hue,
                                followRate);
    // 最後把HSB轉換回RGB的空間
    current.fromHSB(
                currentHSB.hue,
                currentHSB.saturation,
                currentHSB.brightness);
                
    // 列印一下現在的顏色16進位碼
    console.log('顏色碼 = ' + current.colorHex);
}
// 將updateGame安排進每幀執行函式的列表裏
cg.addUpdateFunction(updateGame);

這樣就能在不失去彩度的情況下,進行顏色的靠近演算法了。

當然,很多情況直接使用RGB空間的靠近就很棒了,所以什麼時候該用什麼方法,就是程式設計師(或美術總監)要去考慮的問題了。

CG示範專案


上一篇
Trick 5: 旋轉角度的靠近演算法
下一篇
Trick 7: 追著主角跑的攝影機大哥
系列文
30個遊戲程設的錦囊妙計32
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言