NEUROMANTIC

自分でC/C++/UE4/Graphics/ゲームなどをやったことをメモするブログ

ShaderToyで「アナログ画面」っぽくレンダリングしてみた。

はじめに

以前にやったプロジェクトの(けど結局限界性がはっきりしてて捨てざるを得なかった)OPGS16ゲームフレームワークでは、下のように昔のテレビでゲームをするようにする後処理がありました。

f:id:neuliliilli:20181206221910g:plain
OPGS16の`OLD TV`画面処理。

当時のOPGS16というプロジェクトはまさに「レトロ」を志向してましたので、解像度も小さく、なんでもレトロっぽくしてた記憶がします。
しかしこれに踏まえてなんか「昔のテレビでやるような後処理があれば良いかもしれない」として色々と工夫してやったのがあの画像でした。

今はOPGS16じゃなくて別のDyというフレームワークを新たに開発していますので、使う気は全く(すみません)ありません。 しかし当時に実装した通称「OLD TV」効果は別のところに残したかったです。それでShaderToyというウェブレンダラを使用して移植をすることになりました。


実装

コード

const float uIntensity = 0.03f; // Frame distortion intensity. 0.02 ~ 0.05 recommended. 
const float uThreshold = 0.85f; // 0.75 ~ 0.90 would be recommended.
const float uMax       = 64.0f; // Distortion for edge of threshold.
const float uMargin    = 8.0f;  // Margin.

float GetOverThreadsholdIntensity(const float a, const float t) {
    float b = pow(t, 2.0f) * (1.0f - (1.0f / uMax));
    return uMax * pow(a - (t - (t / uMax)), 2.0f) + b;
}

bool IsOob(const vec2 inputTexCoord) {
    return inputTexCoord.x > 1.0f || inputTexCoord.y > 1.0f 
        || inputTexCoord.x < 0.0f || inputTexCoord.y < 0.0f;
}

vec2 ApplyMargin(const vec2 texel, const float margin) {
    vec2 m = vec2(margin * 4.0f) / iResolution.xy;
    return (texel - 0.5f) * (1.0f + m) + 0.5f;
}

vec3 GetColor(const vec3 inputOffset) {
    return 0.5f + 0.5f * cos(iTime + (inputOffset * 4.0f) + vec3(0, 2, 4));
}

float ScaleWithAxis(const float value, const float axis, const float scale) {
    return (value - axis) * scale + axis;
}

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{   // Normalized pixel coordinates (from 0 to 1)
    vec2 uv = fragCoord/iResolution.xy;
    float x = uv.x * 2.0f - 1.0f;
    float y = uv.y * 2.0f - 1.0f;
    
    // Distort uv coordinate, and if closer to frame bound, do more distortion.
    float x_intensity = uIntensity;
    float y_intensity = uIntensity;
    if (abs(x) >= uThreshold && abs(y) >= uThreshold) {
        y_intensity *= GetOverThreadsholdIntensity(abs(x), uThreshold);
        x_intensity *= GetOverThreadsholdIntensity(abs(y), uThreshold);     
    }
    else {
        y_intensity *= pow(x, 2.0f);
        x_intensity *= pow(y, 2.0f);
    }
    
    // Get texel and apply margin (px)
    float y_offset  = y_intensity * y;
    float x_offset  = x_intensity * x;
    vec2 finalTexel = ApplyMargin(uv + vec2(x_offset, y_offset), uMargin);

    // ShaderToy does not support border (to be out-of-bound black color),
    // so checking texel is out of bound.
    if (IsOob(finalTexel) == false)
    {
        fragColor = texture(iChannel0, finalTexel);
            //GetColor(finalTexel.xyx) * 
            //ScaleWithAxis(texture(iChannel0, finalTexel).r, 1.0f, 1.0f), 1.0f);
    }
}

結果

f:id:neuliliilli:20181206224258g:plain
レンダリング結果。フレームが昔のテレビっぽくなってきました。

ShaderToyでみたい方は下のリンクでご覧ください。

https://www.shadertoy.com/view/XtKfDK

OPGS16より改善できた所としてはよりアナログテレビのフレームっぽくするために間隔(Margin)を入れることが出来たところです。
OPGS16の開発した当時には画面と縁との間隔を入れることが出来なくてその部分は諦めて実装しましたが、ようやくShaderToyに移植する時に入れることが出来ました。


まとめ

ShaderToyを本格的に使ったのは初めてですが、何か所のバグを除いては良い出来に感じました。glslで後処理などをプロトタイプで実装したい時にはよく使っていきたいと思います。