ゲームボーイ風イメージエフェクト
2019-11-07
成果物
Unity でシェーダを書いてゲームボーイの液晶感のあるイメージエフェクトを作った:
動画
モバイルでの動作テスト
- 2017 年末くらいに出た中堅 Android (HUAWEI Mate 10 lite) で動作確認
- Android はスペックに対して解像度が高すぎるものが多いので、204 dpi くらいの解像度で出力
- 快適に動いたので実用には耐えそう
ソースコード
- 動作環境は Unity 2019.2.0b5
カメラにイメージエフェクト(ポストエフェクト)用のスクリプトをアタッチ:
// ImageEffect.cs
using UnityEngine;
[RequireComponent(typeof(Camera)), ExecuteInEditMode, ImageEffectAllowedInSceneView]
public class ImageEffect : MonoBehaviour {
public Material material;
void OnRenderImage(RenderTexture src, RenderTexture dest) {
Graphics.Blit(src, dest, material);
}
}
- Unity では
Graphics.Blit()
によって、Material の _MainTex にカメラのレンダリング結果が渡る
上記スクリプトにセットした Material に、以下のシェーダを当てる:
Shader "MyPostEffect/GBLike" {
Properties {
_MainTex ("Texture", 2D) = "white" {}
_TileScale ("TileScale", Range(0.001, 0.1)) = 0.01
_ColorThreshold ("ColorThreshold", Range(0, 2)) = 1
}
SubShader {
Cull Off ZWrite Off ZTest Always
Pass {
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
// Properties
uniform sampler2D _MainTex;
uniform float4 _MainTex_TexelSize;
uniform float _TileScale;
uniform float _ColorThreshold;
// Input
struct appdata {
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
// Vertex to Fragment
struct v2f {
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
};
//------------------------------------------------------------------------
// Vertex shader
//------------------------------------------------------------------------
v2f vert(appdata v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = v.uv;
return o;
}
//------------------------------------------------------------------------
// Fragment shader
//------------------------------------------------------------------------
fixed4 frag(v2f i) : SV_Target {
// モザイク
float widthRatio = _MainTex_TexelSize.x / _MainTex_TexelSize.y;
float2 scale = float2(_TileScale * widthRatio, _TileScale);
float2 uv = (floor(i.uv / scale) + 0.5) * scale;
fixed4 color = tex2D(_MainTex, uv);
// グリッド
float numPixelH = 1 / (_TileScale * widthRatio);
float numPixelV = 1 / _TileScale;
float2 grid_uv = float2((i.uv.x * numPixelH) + 0.5, (i.uv.y * numPixelV) + 0.5);
float2 range = abs(frac(grid_uv) - 0.5);
float2 gradient = fwidth(grid_uv);
float2 pixelRange = range / gradient;
float lineThickness = 0.05;
float lineWeight = saturate(min(pixelRange.x, pixelRange.y) - lineThickness);
fixed4 gridColor = fixed4(0.85 * 1.1, 1.0, 0.2 * 1.1, 1.0);
// 4 階調
// (※ パフォーマンス上 if による分岐は避けるべきだが、とりあえず)
half y = dot(color.rgb, half3(0.30, 0.59, 0.11)) * _ColorThreshold;
fixed c = 0.05;
if (y > 0.25) { c = 0.35; }
if (y > 0.50) { c = 0.60; }
if (y > 0.75) { c = 0.90; }
color.rgb = float3(c * 0.85, c, c * 0.2);
return lerp(gridColor, color, lineWeight);
}
ENDCG
}
}
}
※ 趣味コードなので多少雑なのはご愛嬌
解説
- モザイク処理と階調化はポストエフェクトではよくあるやつ
- これだけだとそれっぽくならない。そこで自分の中のゲームボーイ体験の記憶を掘り起こしたところ、 当時の液晶は 「目を近づけるとドット 1 粒 1 粒の区切りが見えた」 というのを思い出した
- そこでピクセルの境目にグリッドを引くようにしたら「らしく」なった
- グリッドの部分は適当にコードを書いたが、もっと軽くやれる書き方がありそう。研究中