ポストエフェクトをかけよう!!シェーダコード編
皆さんこんにちは。
前回までで、一度レンダリングした結果をテクスチャとして利用できるようになったので
今回はこのテクスチャをピクセルシェーダで加工してポストエフェクトを実装します。
テクスチャを画面全体に貼り付けるには、平行投影でプロジェクション行列を、ワールド変換行列は画面サイズに合わせたスケール行列にして描画でOKです。
これで一回描画した結果を貼り付けただけの状態。
ここからいろいろエフェクトを盛って、古いカメラ風の画面を目指していきます。
ホワイトノイズ
HLSLコードはこんな感じ。
float WhiteNoise(float2 coord) {
return frac(sin(dot(coord, float2(8.7819, 3.255))) * 437.645);
}
float4 PSMain(Pixel_UV pixel) : SV_TARGET
{
float2 samplePoint = pixel.uv;
float4 Tex = mainTexture.Sample(mainSampler, samplePoint);
float noise = WhiteNoise(pixel.uv * Time)-0.5 ;
Tex.rgb += float3(noise, noise, noise);
return Tex;
}
比較的低コストなフラクトサイン法によるノイズ生成で、地形生成などに使われるパーリンノイズよりも精度は低いですが見た目に影響させるノイズなら十分です。
走査線
HLSLコードはこんな感じ。
float4 PSMain(Pixel_UV pixel) : SV_TARGET
{
float2 samplePoint = pixel.uv;
float4 Tex = mainTexture.Sample(mainSampler, samplePoint);
float sinv = sin(pixel.uv.y * 2 + Time * -0.1);
float steped = step(0.99, sinv * sinv);
Tex.rgb -= (1 - steped) * abs(sin(pixel.uv.y * 50.0 +Time * 1.0)) * 0.05;
Tex.rgb -= (1 - steped) * abs(sin(pixel.uv.y * 100.0 - Time * 2.0)) * 0.08;
Tex.rgb += steped * 0.1;
return Tex;
}
こちらもサイン波を利用して画面に横縞を走らせています。
反対方向に進む線を一本入れ、被ってる部分だけ効果を外すなどもするといい感じです。
RGBずらし
HLSLコードはこんな感じ。
float4 PSMain(Pixel_UV pixel) : SV_TARGET
{
float2 samplePoint = pixel.uv;
float4 Tex = mainTexture.Sample(mainSampler, samplePoint);
samplePoint.x += 0.01 ;
Tex.r = mainTexture.Sample(mainSampler, samplePoint).r;
return Tex;
}
出力するカラーのRGBのサンプルする座標をずらす(今回はRのみ)ことで劣化感が出せます。今回はTikTok感。
今までのを組み合わせるとこんな感じ。
古いカメラの雰囲気が出てきてますね。
まだまだいきます。
樽状湾曲
HLSLコードはこんな感じ。
float4 PSMain(Pixel_UV pixel) : SV_TARGET
{
float2 samplePoint = pixel.uv;
samplePoint -= float2(0.5, 0.5);
float distPower = pow(length(samplePoint), Distortion);
samplePoint *= float2(distPower,distPower);
samplePoint += float2(0.5 , 0.5);
float4 Tex = mainTexture.Sample(mainSampler, samplePoint);
return Tex;
}
昔のモニタの湾曲を表現します。
シェーダ内定数のDistotionで歪み率を制御しています。
ビネット
HLSLコードはこんな感じ。
float4 PSMain(Pixel_UV pixel) : SV_TARGET
{
float2 samplePoint = pixel.uv;
float4 Tex = mainTexture.Sample(mainSampler, samplePoint);
float vignette = length(float2(0.5,0.5) - pixel.uv);
vignette = clamp(vignette - 0.2, 0, 1);
Tex.rgb -= vignette;
return Tex;
}
画面の端に行くほど暗くします。
シンプルだけど手っ取り早く雰囲気が出せます。
グリッチ
HLSLコードはこんな感じ。
float4 PSMain(Pixel_UV pixel) : SV_TARGET
{
float vertNoise = WhiteNoise(float2(floor((pixel.uv.x) / VertGlitchPase) * VertGlitchPase, Time *0.1));
float horzNoise = WhiteNoise(float2(floor((pixel.uv.y) / HorzGlitchPase) * HorzGlitchPase, Time*0.2));
float vertGlitchStrength = vertNoise / GlitchStepValue;
float horzGlitchStrength = horzNoise / GlitchStepValue;
vertGlitchStrength = vertGlitchStrength * 2.0 - 1.0;
horzGlitchStrength = horzGlitchStrength * 2.0 - 1.0;
float V = step(vertNoise, GlitchStepValue * 2) * vertGlitchStrength ;
float H = step(horzNoise, GlitchStepValue) * horzGlitchStrength ;
float2 samplePoint = pixel.uv;
float sinv = sin(samplePoint.y * 2 - Time * -0.1);
float steped = 1 - step(0.99, sinv * sinv);
float timeFrac = steped * step(0.8, frac(Time));
samplePoint.x += timeFrac * (V + H);
float4 Tex = mainTexture.Sample(mainSampler, samplePoint);
return Tex;
}
接触不良によるノイズ的なエフェクト。
だいぶ画面がうるさくなるので一定間隔で発動するようなコードの方がいいです。
全部合わせると
だいぶそれらしくなってますね。
HLSLコード
float4 PSMain(Pixel_UV pixel) : SV_TARGET
{
//グリッチ用の各パラメータの計算
float vertNoise = WhiteNoise(float2(floor((pixel.uv.x) / VertGlitchPase) * VertGlitchPase, Time *0.1));
float horzNoise = WhiteNoise(float2(floor((pixel.uv.y) / HorzGlitchPase) * HorzGlitchPase, Time*0.2));
float vertGlitchStrength = vertNoise / GlitchStepValue;
float horzGlitchStrength = horzNoise / GlitchStepValue;
vertGlitchStrength = vertGlitchStrength * 2.0 - 1.0;
horzGlitchStrength = horzGlitchStrength * 2.0 - 1.0;
float V = step(vertNoise, GlitchStepValue * 2) * vertGlitchStrength ;
float H = step(horzNoise, GlitchStepValue) * horzGlitchStrength ;
//樽状湾曲をかけた上でサンプリング
float2 samplePoint = pixel.uv;
samplePoint -= float2(0.5, 0.5);
float distPower = pow(length(samplePoint), Distortion);
samplePoint *= float2(distPower,distPower);
samplePoint += float2(0.5 , 0.5);
float4 Tex = mainTexture.Sample(mainSampler, samplePoint);
//エフェクトがかからない部分を作るための計算。上に走っていく横線とする
float sinv = sin(samplePoint.y * 2 - Time * -0.1);
float steped =1- step(0.99, sinv * sinv);
//走査線ノイズ
Tex.rgb -= steped * abs(sin(samplePoint.y * 50.0 - Time * 1.0)) * 0.05;
Tex.rgb -= steped * abs(sin(samplePoint.y * 100.0 + Time * 2.0)) * 0.08;
//一定周期でグリッチを有効に。ズレを抑えてGとBをずらす
float timeFrac = steped * step(0.8, frac(Time)) * 0.01;
samplePoint.x += timeFrac*(V+H);
Tex.g += mainTexture.Sample(mainSampler, samplePoint).g;
samplePoint.x += 2 * timeFrac*(V+H);
Tex.b += mainTexture.Sample(mainSampler, samplePoint).b;
//全体にビネットをかける
float vignette = length(float2(0.5,0.5) - pixel.uv);
vignette = clamp(vignette - 0.2, 0, 1);
Tex.rgb -= vignette;
//エフェクトがかかってない部分を白っぽく
Tex.rgb +=(1-steped )* 0.1;
//グリッチ用のノイズを再利用
float noise = vertNoise * 0.1;
Tex.rgb += steped * float3(noise, noise, noise);
//出力
return Tex;
}
というわけで、ここまででポストエフェクトをかけよう!という目的を達成できたので閉めたいと思います。
現在のc++側のコードだと一つのレンダーターゲットと一つの深度バッファを完全に紐づけてしまっているのであんまりフレキシブルにエフェクトをかけられない状況に陥ってしまっています。
実際、ここで書いたのはほとんどUVの加工ですものね。
この設計を直して、扱えるエフェクトが増えたらまたポストエフェクト系の記事を書こうと思います。
今回参考にしたのは
これらのサイトと
https://optipedia.info/opt/optics/distortion/
こちらのGLSLの記事です。
シェーダの資料っていうとHLSLよりもGLSLの方が多くなりがちなのは、WebGLの存在が大きいからでしょうか。
ShaderToyとかあるし、シェーダ言語の中で最も敷居低いですものね。
次回はFABRIKか自作エンジンの見た目を整える記事になります。
それでは。
この記事が気に入ったらサポートをしてみませんか?