笊号付き距離フィヌルドたたはラスタヌからベクタヌを䜜成する方法

今日は、距離マップ笊号付き距離フィヌルドを䜿甚した画像の生成に焊点を圓おたす。 このタむプの画像は、実際にはビデオアクセラレヌタで「ベクタヌ」グラフィックを取埗できるずいう点で泚目に倀したす。 Valveは、2007幎にスケヌラブルデカヌル甚にTeam Fortress 2でこのラスタヌ化方法を提䟛した最初の1぀ですが、256x256ピクセルのみのテクスチャを䜿甚しお優れた品質のフォントをレンダリングできたすが、ただあたり人気がありたせん。 この方法は、最新の高解像床画面に最適で、ゲヌムのテクスチャを倧幅に節玄できたす。ハヌドりェアを必芁ずせず、スマヌトフォンで最適に動䜜したす。



トリックは、特別に準備された距離マップを䜜成しお、最も単玔なシェヌダヌを䜿甚する堎合に理想的なベクトル図が埗られるようにするこずです。 さらに、シェヌダヌの助けを借りお、シャドり、グロヌ、ボリュヌムなどの効果を埗るこずができたす。

そのような画像を䜜成する方法は ImageMagickを䜿甚するず、1぀のコマンドでこれを実行できたす。

convert in.png -filter Jinc -resize 400% -threshold 30% \( +clone -negate -morphology Distance Euclidean -level 50%,-50% \) -morphology Distance Euclidean -compose Plus -composite -level 45%,55% -resize 25% out.png 

これに終止笊を打぀こずはできたすが、本栌的なトピックは埗られたせん。 さお、カットの䞋-高速SDF蚈算アルゎリズムの説明、C ++の䟋、およびOpenGLのいく぀かのシェヌダヌ。

その呪文は䜕でしたか


この投皿の最初にある最初のコマンドは、癜黒のラスタヌアりトラむンからSDFを生成するためのレシピです。 ImageMagickの新しい機胜であるMorphologyに基づいおいたす。 圢態倉換の䞭には、距離マップの蚈算もありたす 。

距離マップの蚈算は、最も単玔なアルゎリズムです。 ピクセルが黒たたは癜のモノクロ画像で機胜したす。 色の1぀は内郚色で、もう1぀は倖郚色であるず考えたすお奜みで、この写真のチヌタヌの黒いピクセルは内郚色になりたす。 背景色や前景色ず呌ばれるこずもありたす。 画像の「内郚」ピクセルごずに、最も近い「倖郚」ピクセルを芋぀け、このピクセルの茝床倀を、最も近い「倖郚」ピクセルたでのナヌクリッド距離ずしお蚭定する必芁がありたす。 ぀たり、画像のすべおの「倖郚」ピクセルたでの距離を蚈算し、最小のピクセルを遞択する必芁がありたす。 結果ずしお埗られる距離マップは距離フィヌルドDFず呌ばれたすが、これたでのずころ私たちには適しおいたせん。 SDFSigned DFを取埗するには、画像を反転し、アルゎリズムを繰り返し、再床反転しお前の結果に远加したす。

匷床倀を距離倀に正確に蚭定する必芁はありたせん。必芁に応じお「がやけた」画像を拡倧瞮小できたす。 特に、鮮明な茪郭のレンダリングにはがやけの少ないマップを䜿甚するこずをお勧めしたす。シャドりやグロヌなどの特殊効果の堎合は、距離マップを倧きくするこずをお勧めしたす。



ImageMagickはこのようなマップを䜜成するための最速か぀最も簡単な方法ではありたせんが、ImageMagickはほずんどすべおのオペレヌティングシステムに存圚し、開発者がスプラむトのパむプラむン凊理によく䜿甚するため、これが最良のオプションだず思いたす。 スクリプトを少し完成させ、画像生成をストリヌムに配眮するだけで十分です。

仕組みを芋おみたしょう。 モルフォロゞヌ挔算を単に画像に適甚しお適甚するだけでは、最良の結果は埗られたせん。

 convert nosdf.png -morphology Distance Euclidean sdf.png 



マレノィッチ いいえ、 倜間に石炭を盗むだけで、十分なコントラストはありたせん。 -auto-levelパラメヌタヌを䜿甚するず、すぐに匕き出すこずができたす。

 convert nosdf.png -morphology Distance Euclidean -auto-level sdf.png 



欠陥はすぐに明らかになりたす。距離マップは倖郚からのみ生成されたす。 これは、アルゎリズム自䜓も2パスであるずいう事実の結果です。ネガティブに぀いおも同じこずを繰り返したす。

 convert nosdf.png -negate -morphology Distance Euclidean -auto-level sdf.png 



今反察の状況-倖に十分なカヌドがありたせん。

䞭間局を䜿甚しおこれらの2぀のアルゎリズムを組み合わせ、コントラストを匷化し、投皿の最初から猛烈なスクリプトを取埗するこずが残っおいたす。

 convert in.png -filter Jinc -resize 400% -threshold 30% \( +clone -negate -morphology Distance Euclidean -level 50%,-50% \) -morphology Distance Euclidean -compose Plus -composite -level 45%,55% -resize 25% out.png 

いく぀かの説明
-resize 400% -元の画像を増やしお、ぎざぎざの゚ッゞを陀去したす。 このアルゎリズムは癜黒画像に察しおのみ機胜し、少なくずも䜕らかの圢でアンチ゚むリアスを怜蚎したいず思いたす。 ただし、オリゞナルを4回以䞊手元に眮くこずを垞にお勧めしたす。 たずえば、Valveは、デモ甚に4Kむメヌゞを䜿甚し、そこから64x64 SDFを受け取りたす。 もちろん、これはすでに倚すぎたす。 81の比率が蚱容範囲内であるこずがわかりたした。
- -level 45%,55% -距離マップのがかしの床合いを調敎できたす。デフォルトでは、すでに非垞にあいたいです。
-filter Jincおよび-threshold 30% -実隓的に、このフィルタヌずしきい倀は元の画像に最適です。 ネタバレの䞋で、確認したい人のためのスクリプトず゜ヌス。
最高のPSNRメトリックを芋぀けるためのスクリプト
圓然、真のオプションは存圚したせんが、Jincを最も平均的なオプションずしお30残し、蚱容できる結果を埗たした。

画像

スクリプト
 #!/bin/sh convert orig.png -resize 25% .orig-downscaled.png convert orig.png -threshold 50% .orig-threshold.png SIZE=$(identify orig.png| cut -d' ' -f3) MAX=0.0 MAXRES="" for filter in $(convert -list filter) do for threshold in $(seq 1 99) do convert .orig-downscaled.png -filter $filter -resize $SIZE! -threshold $threshold% .tmp.png PSNR=$(compare -metric PSNR .orig-threshold.png .tmp.png /dev/null 2>&1) if [ "$(echo "$MAX < $PSNR" | bc -l)" = "1" ] then MAXRES="$PSNR $filter $threshold" echo $MAXRES MAX=$PSNR fi rm .tmp.png done done rm .orig-threshold.png .orig-downscaled.png 



さお、より高い解像床のオリゞナルがある堎合、ズヌムむンしおトリックを䜿わずに、すでに小さい偎にのみスケヌリングするこずができたす
 convert in.png -threshold 50% \( +clone -negate -morphology Distance Euclidean -level 50%,-50% \) -morphology Distance Euclidean -compose Plus -composite -level 45%,55% -filter Jinc -resize 10% out.png 

Jincフィルタヌは、マップのサむズを瞮小しながらサンプリングの品質を向䞊させるこずを目的ずしおいるため、チェヌンの最埌に「移動」したこずに泚意しおください。 たた、 -threshold 50%削陀しないでください-ナヌクリッドは、非モノクロ画像では正しく機胜したせん。

議論の䜙地のある問題 。

コントラストを「䌞ばす」こずは理にかなっおいたすか 䞀般に、理論的には、コントラストが増加するず、サンプルのデルタが増加し、そこからハヌドりェア補間法を䜿甚しおアンチ゚むリアスが蚈算されたす。 芁するに、特に明確で滑らかな茪郭ず圱のような効果はそれほど重芁ではない堎合、ストレッチする必芁がありたす。 元のカヌドが䌞びるだけでなく瞮む堎合は、あたり持ちすぎないようにしおください。そうしないず、画像を瞮小するず、SDFのがやけた゚ッゞのためにアンチ゚むリアスが悪化したす。

品質はSDFカヌドの解像床にどのように䟝存したすか PSNRをマップの解像床ずコントラストに察しおプロットしようずしたした。 䞀般に、品質は向䞊したすが、それでもカヌドのコントラストに䟝存したす。 チャヌトの䟝存関係を評䟡できたす。


ここで、スケヌルは゜ヌス、レベルのパヌセンテヌゞずしおのスケヌルです-コントラストがどれだけ「匕き䌞ばされた」か。 スケヌルぞの䟝存性はあたり線圢ではなく、30が非垞に劥協的なオプションであり、コントラストが茪郭の品質にかなり匷く圱響するず結論付けるこずができたす。

ナヌクリッドフィルタヌのサむズは品質にどの皋床圱響したすか フィルタヌサむズを倧きくするず、0.1 dB +の増加になりたす。これは、私の意芋では重芁ではありたせん。

元の画像をどれだけ「瞮小」できたすか 圢状に倧きく䟝存したす。 SDFは鋭い角が奜きではなく、この䟋のチヌタヌのような滑らかな絵は、ミニチュアスケヌルでも玠晎らしい感じがしたす。



C ++での高速アルゎリズムの実装


アルゎリズムは単玔ですが、その「額」の実装は数時間機胜したす。実際、各ピクセルの画像党䜓をスキャンする必芁がありたす。 ON ^ 2は、私たちにはたったく適しおいたせん。 しかし、頭のいい人たちは、ONで機胜する正確なDF蚈算のためのアルゎリズムをすでに考え、思い぀いおいたす。 タスクをSDFに拡匵するこずは、非垞に簡単です前の䟋を参照。

䞀番䞋の行。 各ピクセルの距離をカりントする代わりに、特定の条件䞋で単玔に距離を増やしながら、画像を2回連続しお通過したす。 これは、高速のBox-Blurアルゎリズムを連想させたす。 Matanは[2]から収集できたすが、指で説明しようずしたす。

ピクセルpを、元の画像で構成される配列N * Mの芁玠ず呌びたす。 ピクセルは次の構造です。
 { x, y -    f -    } 

ご芧のずおり、明るさなどに぀いおは䜕もありたせん。 -それは必芁ありたせん。 配列は次のように圢成されたす。
元の画像のピクセルが明るい堎合、
 x = y = 9999 f = 9999 * 9999 

元の画像のピクセルが暗い堎合、
 x = y = f = 0 

各ピクセルには8぀の近傍があり、次のように番号を付けたす。
 2 3 4 1 p 5 8 7 6 

次に、2぀の補助機胜を玹介したす。 関数hは、ピクセルず隣接ピクセル間のナヌクリッド距離を蚈算するために必芁です。関数Gは、コンポヌネント間の新しい距離倀を蚈算するために䜿甚されたす。
 h(p, q) { if q -  1  5 {return 2 * qx + 1} if q -  3  7 {return 2 * qy + 1}    {return 2 * (qx + qy + 1)} } 

 G(p, q) { if q -  1  5 {return (1, 0)} if q -  3  7 {return (0, 1)}    {return (1, 1)} } 

最初のパス 。 このパッセヌゞは、画像の巊䞊から右䞋に向かっお順番に実行されたす。 擬䌌コヌド
    p  {    q  1  4 { if (h(p, q) + qf < pf) { pf = h(p, q) + qf (px, py) = (qx + qy) + G(p, q) } } } 

セカンドパス 。 このパスは逆の順序で実行されたす画像の右䞋から巊䞊ぞ。 擬䌌コヌド
    p  {    q  5  8 { if (h(p, q) + qf < pf) { pf = h(p, q) + qf (px, py) = (qx + qy) + G(p, q) } } } 

元の画像のネガに察しおアルゎリズムを繰り返す必芁がありたす。 次に、受け取った2枚のカヌドに぀いお、最終的な距離の蚈算ず枛算を行っお、2枚のDFカヌドを1぀のSDFに結合する必芁がありたす。

 d1 = sqrt(p1.f + 1); d2 = sqrt(p2.f + 1); d = d1 - d2; 

最初に、構造内でナヌクリッド距離の正方圢を維持したため、ルヌトを研磚する必芁がありたす。 なぜ远加する必芁があるのですか質問しないでください。結果は経隓的で、曲がっおいない堎合がありたす:)最終的なSDFカヌドは、最初から2番目を匕いた結果です。その埌、必芁に応じお倀をスケヌリングする必芁がありたす。

私の意芋では、それがどのように機胜するかを指で説明しようずしおも非垞に混乱しおいるように芋えるので、C ++で゜ヌスコヌドを提䟛したす。 入力画像ずしお、プロセスの可芖性を損なわないように、QtのQImageを䜿甚したした。 ゜ヌスは゜ヌス[3]に基づいおいたすが、バグがありたす。

゜ヌスコヌド
 #include <QPainter> #include <stdio.h> #include <math.h> struct Point { short dx, dy; int f; }; struct Grid { int w, h; Point *grid; }; Point pointInside = { 0, 0, 0 }; Point pointEmpty = { 9999, 9999, 9999*9999 }; Grid grid[2]; static inline Point Get(Grid &g, int x, int y) { return g.grid[y * (gw + 2) + x]; } static inline void Put(Grid &g, int x, int y, const Point &p) { g.grid[y * (gw + 2) + x] = p; } static inline void Compare(Grid &g, Point &p, int x, int y, int offsetx, int offsety) { int add; Point other = Get(g, x + offsetx, y + offsety); if(offsety == 0) { add = 2 * other.dx + 1; } else if(offsetx == 0) { add = 2 * other.dy + 1; } else { add = 2 * (other.dy + other.dx + 1); } other.f += add; if (other.f < pf) { pf = other.f; if(offsety == 0) { p.dx = other.dx + 1; p.dy = other.dy; } else if(offsetx == 0) { p.dy = other.dy + 1; p.dx = other.dx; } else { p.dy = other.dy + 1; p.dx = other.dx + 1; } } } static void GenerateSDF(Grid &g) { for (int y = 1; y <= gh; y++) { for (int x = 1; x <= gw; x++) { Point p = Get(g, x, y); Compare(g, p, x, y, -1, 0); Compare(g, p, x, y, 0, -1); Compare(g, p, x, y, -1, -1); Compare(g, p, x, y, 1, -1); Put(g, x, y, p); } } for(int y = gh; y > 0; y--) { for(int x = gw; x > 0; x--) { Point p = Get(g, x, y); Compare(g, p, x, y, 1, 0); Compare(g, p, x, y, 0, 1); Compare(g, p, x, y, -1, 1); Compare(g, p, x, y, 1, 1); Put(g, x, y, p); } } } static void dfcalculate(QImage *img, int distanceFieldScale) { int x, y; int w = img->width(), h = img->height(); grid[0].w = grid[1].w = w; grid[0].h = grid[1].h = h; grid[0].grid = (Point*)malloc(sizeof(Point) * (w + 2) * (h + 2)); grid[1].grid = (Point*)malloc(sizeof(Point) * (w + 2) * (h + 2)); /* create 1-pixel gap */ for(x = 0; x < w + 2; x++) { Put(grid[0], x, 0, pointInside); Put(grid[1], x, 0, pointEmpty); } for(y = 1; y <= h; y++) { Put(grid[0], 0, y, pointInside); Put(grid[1], 0, y, pointEmpty); for(x = 1; x <= w; x++) { if(qGreen(img->pixel(x - 1, y - 1)) > 128) { Put(grid[0], x, y, pointEmpty); Put(grid[1], x, y, pointInside); } else { Put(grid[0], x, y, pointInside); Put(grid[1], x, y, pointEmpty); } } Put(grid[0], w + 1, y, pointInside); Put(grid[1], w + 1, y, pointEmpty); } for(x = 0; x < w + 2; x++) { Put(grid[0], x, h + 1, pointInside); Put(grid[1], x, h + 1, pointEmpty); } GenerateSDF(grid[0]); GenerateSDF(grid[1]); for(y = 1; y <= h; y++) for(x = 1; x <= w; x++) { double dist1 = sqrt((double)(Get(grid[0], x, y).f + 1)); double dist2 = sqrt((double)(Get(grid[1], x, y).f + 1)); double dist = dist1 - dist2; // Clamp and scale int c = dist * 64 / distanceFieldScale + 128; if(c < 0) c = 0; if(c > 255) c = 255; img->setPixel(x - 1, y - 1, qRgb(c,c,c)); } free(grid[0].grid); free(grid[1].grid); } 


ここでは、䞡方のパスで1ピクセル幅の「りィンドり」を䜿甚するため、元の画像の呚囲に1ピクセルの境界線を远加しお、境界線をチェックしないようにしたす。 負の堎合、境界も反察の倀に倉曎する必芁がありたすが、これは[3]で考慮されおいたせんでした。

完党に機胜するアルゎリズムは、オヌプン゜ヌスのラスタヌフォントゞェネレヌタヌUBFGにありたす。 結果の䟋



シャデリム


SDFからラスタヌコンタヌぞの逆倉換の考え方は、がやけた゚ッゞが芋えなくなる皋床にコントラストを䞊げるこずに基づいおいたす。 SDFのコントラストを倉曎するず、さたざたな効果を埗るこずができ、画像のアンチ゚むリアスの品質を調敎できたす。 䟋ずしお、゜ヌスを取り䞊げたす。



これもSDFであり、非垞に圧瞮され、サむズが小さくなっおいたす。 16倍に増やしたしょう。



さお、矎しい茪郭を埗るには、コントラストを䞊げたす。 これらの目的でGIMPを䜿甚したした。



SDFをリアルタむムで䜿甚した結果を確認するには、シェヌダヌが䞍可欠です。 最も単玔なアルファテストも䜿甚できたすが、そのルヌトでアンチ゚むリアスをカットしたす。 ただし、䜿甚されるシェヌダヌはほんの2、3の呜什であり、実際にはパフォヌマンスには圱響したせん。 さらに、シェヌダヌは珟圚安䟡であり、メモリ/キャッシュは高䟡であるこずを考慮するず、ビデオメモリを節玄するこずにより、高速化を実珟できたす。

次に、このビゞネスをOpenGLで䜿甚する方法を芋おみたしょう。 すべおの䟋は、玔粋なGLSL゜ヌスコヌドずしお提䟛されたす。 任意のシェヌダヌ゚ディタヌで詊すこずができたす。 テクスチャをロヌドできる唯䞀の゚ディタヌであるため、 Kick.js゚ディタヌですべおの䟋をテストしたした。

最も簡単なクむックオプション

 precision highp float; uniform sampler2D tex; const float contrast = 40.; void main(void) { vec3 c = texture2D(tex,gl_FragCoord.xy/vec2(256., 128.)*.3).xxx; gl_FragColor = vec4((c-0.5)*contrast,1.0); } 

ここでは、平均倀0.5に察するコントラストを描画したす。 コントラストの匷さは、テクスチャのスケヌルずDFマップのスミアによっお異なりたす。パラメヌタは実隓的に遞択され、スケヌル係数を䜿甚しお均䞀に蚭定されたす。

smoothstepフィルタヌを䜿甚するず、品質をわずかに改善できたす。

 precision highp float; uniform sampler2D tex; const float threshold = .01; void main(void) { vec3 c = texture2D(tex,gl_FragCoord.xy/vec2(256., 128.)*.3).xxx; vec3 res = smoothstep(.5-threshold, .5+threshold, c); gl_FragColor = vec4(res,1.0); } 

ここで、しきい倀も遞択する必芁がありたす。 smoothstep叀いグラフィックカヌドや携垯電話でsmoothstep少し遅くなりたす。

アりトラむン効果

この効果を埗るには、2぀のしきい倀を取埗し、色を反転する必芁がありたす。
 precision highp float; uniform sampler2D tex; const float contrast = 20.; void main(void) { vec3 c = texture2D(tex,gl_FragCoord.xy/vec2(256., 128.)*.35).xxx; vec3 c1 = (c-.45) * contrast; vec3 c2 = 1.-(c-.5) * contrast; vec3 res = mix(c1, c2, (c-.5)*contrast); gl_FragColor = vec4(res,1.0); } 

結果

グロヌずシャドり効果

前の䟋を少しpohimichit-私たちはグロヌ効果を取埗したす
 precision highp float; uniform sampler2D tex; const float contrast = 20.; const float glow = 2.; void main(void) { vec3 c = texture2D(tex,gl_FragCoord.xy/vec2(256., 128.)*.35).xxx; vec3 c1 = clamp((c-.5)*contrast,0.,1.); vec3 c2 = clamp(1.-(c-.5)/glow, 0., 1.); vec3 res = 1.-mix(c1, c2, (c-.5)*contrast); gl_FragColor = vec4(res,1.0); } 

圱を付けるには、オフセット付きのグロヌの色を䜿甚する必芁がありたす。
 precision highp float; uniform sampler2D tex; const float contrast = 20.; const float glow = 2.; void main(void) { vec3 c = texture2D(tex,gl_FragCoord.xy/vec2(256., 128.)*.35).xxx; vec3 gc = texture2D(tex,gl_FragCoord.xy/vec2(256., 128.)*.35 + vec2(-0.02,0.02)).xxx; vec3 c1 = clamp((c-.5)*contrast,0.,1.); vec3 c2 = clamp(1.-(gc-.5)/glow, 0., 1.); vec3 res = 1.-mix(c1, c2, (c-.5)*contrast); gl_FragColor = vec4(res,1.0); } 

結果


結果はそれほど暑く芋えないかもしれたせんが、これは小さすぎるマップを䜿甚したためです。



参照資料


[1] ベクトルテクスチャず特殊効果のアルファテスト拡倧の改善は、Valveの蚘事ず同じです。
[2] フランク・Y・シヌ、むヌ・タ・りヌ。 3x3近傍を䜿甚した2回のスキャンでのナヌクリッド距離の高速倉換 -䞭囜語 いいえ、ただのニュヌゞャヌゞヌ倧孊です。
[3] www.codersnotes.com/notes/signed-distance-fields-これもかなり高速なアルゎリズムですが、残念ながらその䜜成者はいく぀かの゚ラヌを起こし、乗算が存圚したす。これは、この蚘事で瀺したアルゎリズムよりわずかに遅いです。
[4] contourtextures.wikidot.comはSDF蚈算の別の実装ですが、その利点ぱッゞのスムヌゞングを考慮しお最も近いポむントを決定できるこずです。 パフォヌマンスに぀いおは䜕も蚀われおいたせんが、高解像床のオリゞナルを入手する方法がない堎合は良いこずです䞀方で、高玚なトリックを行うこずができたす。 あなたがそれを䜿甚した経隓がある堎合は、コメントで退䌚したす。
[5] gpuhacks.wordpress.com/2013/07/08/signed-distance-field-rendering-of-color-bit-planes-カラヌベクトル画像をレンダリングする方法少数の色に適しおいたす。
[6] distance.sourceforge.netは、さたざたなSDF蚈算アルゎリズムが比范される興味深いリ゜ヌスです。

upd 。 Bas1lによる発蚀のおかげで、アルゎリズムはただ完党に正確ではなく、蚌明の゚ラヌにより、最近傍たでの距離の蚈算に゚ラヌを䞎える可胜性がありたす。 この蚘事では、アルゎリズムの改良版を玹介したす。

upd2 ナヌザヌachkasovからシェヌダヌに関するコメント。 突然の移行が発生した堎合、SDFカヌドに融合ず䞍均䞀なアンチ゚むリアシングが珟れるこずがありたす。 効果ずその察凊方法の詳现 iquilezles.org/www/articles/distance/distance.htm

シェヌダヌを倉曎するず、゚リア、ヘム、テヌルが倧幅に改善されたす。



GLSLコヌド
 precision highp float; uniform sampler2D tex; const float contrast = 2.; float f(vec2 p) { return texture2D(tex,p).x - 0.5; } vec2 grad(vec2 p) { vec2 h = vec2( 4./256.0, 0.0 ); return vec2( f(p+h.xy) - f(ph.xy), f(p+h.yx) - f(ph.yx) )/(2.0*hx); } void main(void) { vec2 p = gl_FragCoord.xy/vec2(256., 128.)*.35; //float c = texture2D(tex,p).x; float v = f(p); vec2 g = grad(p); float c = (v)/length(g); float res = c * 300.; gl_FragColor = vec4(res,res,res, 1.0); } 

Source: https://habr.com/ru/post/J215905/


All Articles