(Graphics2Dで深度バッファを実装する方法に関する記事)。
問題
かつて、3Dの建物で都市の地図を描く必要がありました。 この場合、標準のSwing Graphics2Dのみを使用できました。 2D平面へのポリゴンの投影では、すべてが明確で些細なものですが、建物が複雑で重複する可能性があるため、完全な3Dではこれで十分ではありません。 その結果、建物の面の描画順序が重要になります(前面は背面の後に描画する必要があります)。 すべての顔を正しい順序で並べ替えることができ、すべてが完璧になります。 ただし、面が交差する場合、ソートは問題を解決しません。 写真の例:
私の知る限り、この問題には少なくとも2つの解決策があります。1つは深度バッファー(zバッファー)の使用、もう1つは交差を避けるためにポリゴンを小さなパーツに分割することです。
解決策
Java 2Dには深度バッファーの標準的な実装(私の知る限り)はなく、java.awt.Compositeをよく見るまで非常に長い間この問題の解決策が見つかりませんでした。 判明したように、デプスバッファの実装にはほぼ完璧であり、その方法は次のとおりです( アンダーコートのあるプロジェクトページ )。
ご存知のように、深度バッファは画面ピクセルのZ座標の配列にすぎません。 描画手順を開始する前に、何らかの値にリセットされます(たとえば、最も遠いZ座標の値が設定されます)。 次に、新しいポイントが画面に表示されるたびに、表示されたポイントのZ座標値が、バッファーに既に格納されている深度のZ座標値よりも小さいかどうかが確認されます。 その場合、ポイントが画面に表示され、バッファ内のZ座標値が更新されます。そうでない場合、ポイントは単純に破棄されます。 したがって、正しい画像が画面に表示されます。
主なアイデアは、描画プロセス中に画面に表示される各ポイントがグラフィックコンテキストのコンポジットセットによって処理されるため、Compositeを使用して深度バッファを実装できることです。 したがって、コンポジットでは、座標の配列(そのサイズはグラフィックコンテキストのサイズと一致します)を作成し、コンポジットの各出力ポイントを確認できます。 ご覧のとおり、簡単に聞こえます。
実装
上記を実装するには、まず、画面上の任意の点(x、y)のz座標の値を決定する方法を学ぶ必要があります。 これを行うには、対応するZValueResolverインターフェイスを開始します。
/**
* Converts given x, y coordinate to z coordinate
* @author caiiiycuk
*
*/
public interface ZValueResolver {
/**
* @param x given x coordinate
* @param y given y coordinate
* @return z coordinate of x, y
*/
double resolve( double x, double y);
...
}
Graphics2Dを介して使用可能な描画メソッド(drawPolygonなど)を呼び出す前に、このポリゴンの任意のポイント(x、y)のz座標を取得できる対応するZValueResolverについて説明します。 (ほとんどの場合、ZValueResolverは平面の方程式で3点で記述されるため、ほとんどの場合、実装は簡単であると言えます。対応する標準実装もあります )。
さらに、すべてが簡単です。java.awt.Compositeインターフェースを実装し、深さバッファー値を保存します。
/**
* ZComposite emulates ZBuffer
* @author caiiiycuk
*/
public class ZComposite implements Composite {
...
protected double [] buffer;
protected int width;
protected int height;
...
/**
* Set z-value in buffer for given point
* @param x coordinate
* @param y coordinate
* @param value z-value
*/
public void setZOf( int x, int y, double value ) {
if (x >= width || x < 0 ||
y >= height || y < 0) {
throw new IllegalArgumentException( "Point [" + x + ", " + y + "] is outside of the Z Buffer array" );
}
buffer[y*width + x] = value ;
}
public double getZOf( int realX, int realY) {
return buffer[realY*width + realX];
}
...
}
したがって、深度バッファがあり、任意のポイント(x、y)のz座標を決定できます。CompositeContextのみを実装するために残ります。
/**
* Composite emulates Z buffer
* @author caiiiycuk
*/
public class ZCompositeContext implements CompositeContext {
protected final static byte R_BAND = 0;
protected final static byte G_BAND = 1;
protected final static byte B_BAND = 2;
protected ZComposite zComposite;
ZCompositeContext(ZComposite zComposite) {
this .zComposite = zComposite;
}
/**
* {@inheritDoc}
*/
public void compose(Raster src, Raster dstIn, WritableRaster dstOut) {
ZValueResolver zValueResolver = zComposite.getValueResolver();
if (zValueResolver == null ) {
throw new IllegalArgumentException( "You must set a ZValueResolver before draw any polygon with this composite" );
}
int maxX = dstOut.getMinX() + dstOut.getWidth();
int maxY = dstOut.getMinY() + dstOut.getHeight();
for ( int y = dstOut.getMinY(); y < maxY; y++) {
for ( int x = dstOut.getMinX(); x < maxX; x++) {
int dstInX = -dstIn.getSampleModelTranslateX() + x;
int dstInY = -dstIn.getSampleModelTranslateY() + y;
double dstZ = zComposite.getZOf(dstInX, dstInY);
double srcZ = zValueResolver.resolve(dstInX, dstInY);
if (srcZ < dstZ) {
zComposite.setZOf(dstInX, dstInY, srcZ);
dstOut.setSample(x, y, R_BAND, src.getSample(x, y, R_BAND)); //R
dstOut.setSample(x, y, G_BAND, src.getSample(x, y, G_BAND)); //G
dstOut.setSample(x, y, B_BAND, src.getSample(x, y, B_BAND)); //B
} else if (srcZ == dstZ) {
dstOut.setSample(x, y, R_BAND, src.getSample(x, y, R_BAND)); //R
dstOut.setSample(x, y, G_BAND, src.getSample(x, y, G_BAND)); //G
dstOut.setSample(x, y, B_BAND, src.getSample(x, y, B_BAND)); //B
} else {
dstOut.setSample(x, y, R_BAND, dstIn.getSample(x, y, R_BAND)); //R
dstOut.setSample(x, y, G_BAND, dstIn.getSample(x, y, G_BAND)); //G
dstOut.setSample(x, y, B_BAND, dstIn.getSample(x, y, B_BAND)); //B
}
}
}
}
/**
* {@inheritDoc}
*/
public void dispose() {
}
}
したがって、z座標(srcZ <= dstZ)に適している場合にのみ、着信ピクセルを記録します。
アンチエイリアス
驚くべきことに、この実装は、アンチエイリアスを使用しようとするまで機能しません。 アンチエイリアシングを有効にすると、予測不可能な効果やアーティファクトが発生します。 これは、平滑化アルゴリズム自体の実装によるものです。 問題の本質は、描画時のアルゴリズムが、描画されたポリゴンの「境界」を超えるため、z座標が誤って決定されることです。 この点で、ZValueResolver実装のポリゴンを超えて現在の出力のチェックを追加する必要がありました。これはパフォーマンスに悪影響を及ぼしましたが、スムージングモードで深度バッファーを正しく実装できました。
結果は次のとおりです(目標を達成しました)。
また、深度バッファーの実装で作業する簡単な例 (よくわかりません)。
ご清聴ありがとうございました。