曾经, 我的第一台电脑找不到显卡驱动, 常年只有 16 色.
1. 调色板是干什么的
旧社会的时候以前机能不好的时候, 同屏颜色数受限, 经常只有 16 色或更少. 每个像素显示哪种颜色也不是用 RGB 来记录的, 而是记录一个颜色索引值, 到绘制这个像素时用这个索引去寻找真正的颜色 (经常仍然是一个索引值, 笑). 这个颜色索引表格, 就是像素游戏时代的调色板.
基本上, 历史上应用调色板的思路我能想到的有四种:
为像素角色更换皮肤
这个是最容易想到的. 比如角色身上的衣服用同一种颜色绘制, 改变这个索引的颜色就可以改变衣服的颜色.
除了换衣服之外, 策略游戏的团队色也可以用调色板来做.
突破颜色数限制
《三国志 IV》是我非常喜欢的一款游戏. 他的很多细节非常值得推敲. 比如, 这是一款同屏 16 色的游戏, 而武将的头像就用掉了 8 色.(顺带一提, 这 8 种颜色因为不可能变动, 所以也被用在 UI 上.) 留给环境绘制的颜色不多了. 然而《三国志 IV》还是可以表现从南到北, 一年四季的景色变化, 它是怎么做的呢?
三国志 IV 的地图, 左侧为从西凉到南蛮的颜色变化, 右侧为西凉不同季节的颜色变化
答案就是改变调色板. 当我们从北到南卷动地图时, 可以发现地图的颜色不是从北到南渐变的, 而是随着视角的移动, 整体改变颜色. 在北方时整体饱和度低, 到南方整体饱和度高.
假设地图上没有任何细节, 那么只需要消耗一个颜色索引就能实现从北到南的颜色变化. 只要平滑地改变索引颜色, 就可以使最终的效果非常平滑. 当然游戏中实际添加了大量的草, 山, 水等细节, 这些细节的颜色也随着视角移动而改变. 这样就看起来更自然了.
那么, 同屏还是不超过 16 种索引颜色, 但是在游戏全程能够呈现的颜色远远不止这么点.
老游戏经常用这种方法获得不同风格的场景颜色, 或者在转场时做 FadeOut.
调色板动画
把『改变索引颜色』这件事做到极致, 变成『时刻不停地改变颜色』, 就得到了调色板动画. 下面这张图可以很明显地感受到瀑布是由几个固定索引绘制的.
这似乎是 SFC 的耀西岛
通过改变索引颜色实现动画的最大意义在于 -- 节省资源. 只需要一张图就能做动画了. 随着存储越来越不值钱, 调色板动画逐渐被帧动画或者 sprite 动画替代了.
受攻击或者 buff 特效
例如受到攻击发亮的效果, boss 血量低下时闪红的效果, 无敌时亮闪闪的效果等等, 请回忆一下童年.
2. 调色板在今天
今天, 调色板仍然存在. 大家在绘制像素画时仍然有意识地控制自己使用的颜色.(毕竟不限制颜色的像素画就不怎么『像素』了.) 不过随着显示设备和存储设备的进步, 即便是在像素游戏里, 在游戏技术层面大家往往也不太关注调色板了.
今天, 完美继承了调色板思想的应用是...... 美图滤镜.(望天
颜色滤镜可以理解为将每一种颜色映射为另一种颜色. 效率最好的实现就是将这种映射提前记录下来, 存成一张查找表, 从而得到了 Color Lookup Table.
某个 LUT
很明显, 颜色本身的 RGB 就成了 256 * 256 * 256 的三维索引, 这个 LUT 的图就是调色板.
游戏中也有用 LUT 的实例, 比如这篇文章提到了在 Grave Keeper 中使用 LUT 实现不同时间的光照.
2D 游戏的光照和颜色是一个大话题, 以后可能会说吧...... 本文的重点不在这里, 我们先从最原始的调色板开始讨论.
3. 实现简单的调色板
How to Use a Shader to Dynamically Swap a Sprite's Colors 这篇文章给了一个非常简明的思路: 用图像的一个通道作为索引 (所以是 256 色的调色板), 到一张一维的纹理上查找颜色.
Shader 改动前:
- fixed4 frag(v2f IN) : SV_Target
- {
- fixed4 c = SampleSpriteTexture (IN.texcoord) * IN.color;
- c.rgb *= c.a;
- return c;
- }
改动后:
- fixed4 frag(v2f IN) : SV_Target
- {
- fixed4 c = SampleSpriteTexture (IN.texcoord);
- fixed4 swapCol = tex2D(_SwapTex, float2(c.r, 0));
- fixed4 final = lerp(c, swapCol, swapCol.a) * IN.color;
- final.a = c.a;
- final.rgb *= c.a;
- return final;
- }
注意这个实现其实是基于颜色替换, 所以调色板的透明度可以决定颜色替换的比例, 不过这不是本文重点. 我们就当做刚健朴实的调色板来用.
原图, 调整颜色的结果, 使用的调色板如下:
下方为调色板图
原图只有四种颜色, 所以调色板里只有 4 条线, 就对应着那 4 个颜色. 另外为什么调色板背景是灰色的? 只是为了 debug.
在 Unity 中的注意事项:
原图和调色板图的 Filter Mode 都选择 Point
原图和调色板图的 Compression 都选择 None
否则像素颜色和位置都是不准确的, 不会得到想要的结果.
这个实现是用 r 通道实现的 256 色调色板, 如果用 r,g 通道一起做索引, 就得到了 16 位色调色板. 远远超过一般像素游戏的需要了.
当然在实际应用中我们不会用左侧那种原图. 实际的做法我们放在最后讨论. 先在基础静态调色板的基础上看看能做什么.
4. 动态调色板
How to Use a Shader to Dynamically Swap a Sprite's Colors 这篇文章里已经给了动态改变调色板的实现了. 我懒得画图了, 鸽了鸽了.
5. 调色板动画
调色板动画的思路就是随时间改变调色板. 在刚才的静态例子中我们只用到了一维的纹理, 那么把第二个维度用于时间映射就好了, 是不是很简明.
说起调色板动画最简单的就是做个水流或者瀑布了. 首先我做了一个大概这样的瀑布效果图:
瀑布效果图, 标上了用到的颜色索引. 在这个图里我想用 16 色的调色板做一个瀑布 (实际用到了 15 色).
接下来生成索引图, 也就是用 r 通道记录颜色索引:
瀑布的索引图
什么都看不出来就对了. 因为这张图只用到了 16 种颜色, 所以 r 通道最大只有 15,g,b 都是 0.
接下来是调色板图. 按照设想, 颜色应该是循环映射的, 这样水才能循环流动起来. 最后做出来的调色板图类似这样:
调色版图. 放大了. 注意这个图里每一行是一帧内的颜色索引.
把二者结合就做出了这样的动画:
动画效果
调色板动画可以节省大量的存储空间, 并且可以通过更换调色板改变效果. 比如这张瀑布的图可以很容易地通过改颜色改成流下的粘液或者熔岩.
调色板纹理的第二个维度可以干很多事, 例如描述同一对象的不同皮肤, 或者同一对象在一天内不同时间的颜色等等.
5. 如何愉快绘制
一个现实的问题是, 怎么导出索引颜色的原图, 以及怎么导出调色板.
我的做法是这样的:
我用的工具是 Aseprite, 创建一个使用索引颜色的图:
首先新建文件
首先确定一个场景用多少颜色, 以瀑布为例是 16 种颜色, 我们就随便建立一个 16 色的调色板然后开始画:
添加其他细节
这一步能够区分出所画的颜色, 便于绘制就可以, 颜色不一定十分正确.
然后我们手动建立一个调色板文件. 类似下面这样:
- GIMP Palette
- #
- 0 0 0 Untitled
- 1 0 0 Untitled
- 2 0 0 Untitled
- 3 0 0 Untitled
- 4 0 0 Untitled
- 5 0 0 Untitled
- ...
- 254 0 0 Untitled
- 255 0 0 Untitled
在刚才那种图里载入这个调色板, 就会将颜色索引映射到 (0, 0, 0),(1, 0, 0),(2, 0, 0)...... 这样的颜色上. 这时候再导出就得到了我们想要的索引图.
对! 还是这张!
调色板图的制作方法是把这个过程反过来:
先建立一张 256x1 的图
每个像素依次填入 index=0,index=1...... 的颜色
载入绘制效果图所用的调色板文件 (比如刚才的瀑布所用的 16 色调色板), 这样每个索引所在的位置都被对应的颜色着色了
导出
这样导出的是用于一帧的调色板. 如果要做多个调色板就反复进行这个步骤, 然后把图拼起来.
实际上具体问题具体分析. 在做瀑布的调色板动画时我是手动编辑的, 因为大体上每帧之间就是颜色循环. 用图像编辑软件处理的时候还能统一改改饱和度亮度之类的, 更方便一些.
来源: http://www.jianshu.com/p/15796e8d982e