» チュートリアル エフェクト - 近傍ピクセル
This site relies heavily on Javascript. You should enable it if you want the full experience. Learn more.

チュートリアル エフェクト - 近傍ピクセル

English | Italian | Mandarin

The original english version of this page is newer and may contain information this translation does not have! Click here to view the english version.

TOC: エフェクトとシェーダー
Back: テクスチャ座標
Next: マルチテクスチャ


これは画像処理フィルタのシリーズで、現在のピクセルの色を決めるにあたって画像内の1つのピクセルの視点からその近傍ピクセル(左、上、右、下、そして斜め方向の近傍も)を考慮するというアイデアに基づくものです。

ピクセルシェーダでその様なフィルタを適用するさいに解くべき問題の鍵は、まず第一に、現在のピクセルのテクスチャ座標を使ってどのようにピクセルの近傍にアクセスするかでしょう。

テクスチャの元サイズ(WIDTH/HEIGHT)が与えられ、そのテクスチャ空間での2ピクセル間の距離が計算できます。この数学は簡単です: もしテクスチャ座標の0がピクセルの0でテクスチャ座標の1がピクセルのWIDTHと指定すれば、あるピクセル座標からその水平の近傍への距離は1/WIDTHを表すことができます。よって、ピクセル単位のピクセルサイズはテクスチャ空間単位のピクセルサイズにおいて言うまでもなく1x1で次のように考えられます:

 float2 pixelSize = 1 / float2(WIDTH/HEIGHT)

pixelSize.xyを使えば、ピクセル座標のto/fromの値で加算/減算することによって近傍ピクセルにアクセスできます。パッチの中でのこの計算を行いピクセルシェーダーの範疇を超えてこの結果を扱うにはこの様にするのがベストです

float2 PixelSize; //where .x will be the width and .y will be the height of pixels

エッジ検出

簡単なアプリケーションの例は全てのピクセルの左隣、右隣を用いたエッジ検出フィルターで、これは隣のピクセルの差異を計算し、その絶対値が与えられた閾値よりも大きくなるかどうかを判定します。ピクセルは白色、または黒色になって差し支えなければ。

float2 PixelSize;
float Threshold = 0.2;
float4 PS(vs2ps In): COLOR
{
    //the texture coordinate offset with vertical coordinate set to 0
    float2 off = float2(PixelSize.x, 0);
 
    //sample the left and the right neighbouring pixels
    float4 left = tex2D(Samp, In.TexCd - off);
    float4 right = tex2D(Samp, In.TexCd + off);
 
    if (abs(ConvertToGray(left).x - ConvertToGray(right).x) > Threshold)
      return 1;
    else
      return float4(0, 0, 0, 1);
}

上のコードでは2つの指摘があります:

  • 変数offはただの一時変数で、39行目と40行目で、左隣のピクセルに対応する為に現在のテクスチャ座標から引いたり、右隣のピクセルに対応する為に現在のテクスチャ座標に足したりする為に使います。
  • 42行目の.xのswizzlesの使い方: ConvertToGray関数がfloat4を返すので、さらなる計算がなければこの段階では2つの近傍ピクセルの明るさを比較するためにfloat4第一成分だけにアクセスし、明度の値だけが必要です。

エッジ検出を向上させるために、両方向でそれを行わなければなりません。よって左隣と右隣のピクセルに加えて上隣と下隣のピクセルもサンプリングする必要があります。

//the texture coordinate offset with horizonal coordinate set to 0
off = float2(0, PixelSize.y);
//sample the upper and the lower neighbouring pixels
float4 upper = tex2D(Samp, In.TexCd - off);
float4 lower = tex2D(Samp, In.TexCd + off);

そして、私たちはその返却する色を決定する時には左右両方のピクセルを一緒に考慮しなければなりません:

if (abs(ConvertToGray(left) - ConvertToGray(right)).x > Threshold
||
abs(ConvertToGray(upper) - ConvertToGray(lower)).x > Threshold)
    ...

||はhlslではbooleanのORの意味で、左と右のピクセル間の差異、または上と下のピクセル間の差異のどちらかが閾値を超えた場合、ピクセルを白色で塗るという意味です。

ブラー

ブラーもまたこの方法で実現できます。より良いブラーは、多くの近傍ピクセルを考慮します。ピクセル毎のテクスチャサンプリング数は16に制限され、良いブラーを達成する最良の方法は2通りでそれを行うことです。第一は水平方向に行います。その画像の結果にもう一度垂直方向にブラーをかけます。今のところは水平のブラーに集中します。

このアイデアは、近傍ピクセルの範囲をサンプルし、現在のピクセルからの距離に対応した影響で重み付けした色を加算すると言うものです。現在のピクセルは最大で重み付けされ、最左-最右のピクセルはより小さい重み付けがされます。結果は全ての重み付けの合計で除算され0..1の範囲で正規化されます。

float2 PixelSize;
float4 PSHorizontalBlur(vs2ps In): COLOR
{
    float4 sum = 0;
    int weightSum = 0;
    //the weights of the neighbouring pixels
    int weights[15] = {1, 2, 3, 4, 5, 6, 7, 8, 7, 6, 5, 4, 3, 2, 1};
    //we are taking 15 samples
    for (int i = 0; i < 15; i++)
    {
        //7 to the left, self and 7 to the right
        float2 cord = float2(In.TexCd.x + PixelSize.x * (i-7), In.TexCd.y);
        //the samples are weighed according to their relation to the current pixel
        sum += tex2D(Samp, cord) * weights[i];
        //while going through the loop we are summing up the weights
        weightSum += weights[i];
    }
    sum /= weightSum;
    return float4(sum.rgb, 1);
}

このピクセルシェーダの垂直方向版を実現するために、PS()関数を複製し、PSVerticalBlur()という名前にして、この様に垂直方向のブラーを計算するようにコードを変更します:

float4 PSVerticalBlur(vs2ps In): COLOR
{
    ...
        //7 upwards, self and 7 downwards
        float2 cord = float2(In.TexCd.x, In.TexCd.y + PixelSize.y * (i-7));
    ...
}

エフェクトの中に2つのピクセルシェーダー関数ができたので、2つのtechniqueが必要です:

technique THorizontalBlur
{
    pass P0
    {
        PixelShader  = compile ps_2_0 PSHorizontalBlur();
    }
}
 
technique TVerticalBlur
{
    pass P0
    {
        PixelShader  = compile ps_2_0 PSVerticalBlur();
    }
}

2つのtechniqueの好きなほうを選んで使うことができます。しかしながらまず言及しておくと、良い品質のブラーを得るには、最初の(水平方向の)ブラーに2つ目の(垂直方向の)ブラーを適用することです。さらに詳しくは、次の後のマルチパスエフェクトのチャプターで説明します。

これらはピクセル近傍を元にした画像処理の基本です。しかし実のところ、似たようなエフェクトは一般的に畳み込み/カーネルフィルターに任せられています。学んだ事で、今までのところはあなたは自分の目標を達成できるべきです。詳細と例はここここを見てください。


Next: マルチテクスチャ
Back: テクスチャ座標
TOC: エフェクトとシェーダー

anonymous user login

Shoutbox

~5h ago

joreg: vvvvTv S02E01 is out: Buttons & Sliders with Dear ImGui: https://www.youtube.com/live/PuuTilbqd9w

~7d ago

joreg: vvvvTv S02E00 is out: Sensors & Servos with Arduino: https://visualprogramming.net/blog/2024/vvvvtv-is-back-with-season-2/

~7d ago

fleg: hey there! What's the best tool for remote work? Teamviewer feels terrible. Thanks!

~21d ago

joreg: Last call: 6-session vvvv beginner course starting November 4: https://thenodeinstitute.org/courses/ws24-5-vvvv-beginners-part-i/

~1mth ago

joreg: Missed the last meetup? You can rewatch it here: https://www.youtube.com/live/MdvTa58uxB0?si=Fwi-9hHoCmo794Ag

~1mth ago

theurbankind: When is the next big event, like node festival ?

~1mth ago

~1mth ago

joreg: Join us for the next vvvv meetup on Oktober 17th: https://visualprogramming.net/blog/2024/25.-vvvv-worldwide-meetup/

~1mth ago

joreg: 6 session beginner course part 2 "Deep Dive" starts January 13th: https://thenodeinstitute.org/courses/ws24-5-vvvv-beginners-part-ii/