<HEAD>
<TITLE>FSAA with Blur Effects</TITLE>
</HEAD>

<BODY BACKGROUND="../Image/Back2.GIF"
	TEXT="#E0E0E0"
	BGCOLOR="#191919"
	LINK="#90D0FF"
	VLINK="#4080D0"
	ALINK="#0000FF">

<BASEFONT SIZE="3"> <!-- FACE="ＭＳ Ｐ明朝"> -->

<BR>
<CENTER>
<FONT SIZE="6" COLOR="A0FFA0"><B><I>FSAA with Blur Effects</I></B></FONT>
<P>
<P>
</CENTER>

<CENTER>
<IMG SRC="../Image/Bar2.gif" ALT "-------------------------------------------" width=100% height="8">
</CENTER>
<P>

<P>
<DL>
<DT>
<FONT SIZE="3" COLOR="#FFFF80"><B><I>2000/07/20（木）　</I></B></FONT>
<FONT SIZE="4"><B>− FSAA with Blur Effects − 動いたらモーションブラー止まったらアンティエイリアス</B></FONT>
<P>
<DD>
　もう随分前に考えてた方法だが、これまで実際に試して評価するのを忘れていたアイディア。
という程大したモノではなく、単に
<FONT COLOR="#FF80FF" SIZE="3">
ブラーエフェクトの各サンプル描画時に、
射影行列もアンティエイリアス用にジッタ処理（Jittering：画像を微妙にずらして描画）
</FONT>
してしまうだけである。
<BR>
　おそらく既にやろうとしている事が判ってしまった方も多いと思うが、
これによって
<FONT COLOR="#FF80FF" SIZE="3">
画面上での動きが止まっている部分には、
自動的にアンティエイリアスがかかる
</FONT>
訳だ。
アンティエイリアスのジッタ処理は元々
<FONT COLOR="#FF80FF" SIZE="3">１ピクセル未満のずらし</FONT>であるため、
<FONT COLOR="#FF80FF" SIZE="3">
動いている部分にはほとんど影響を与えない</FONT>。
従って、動いている部分については
<FONT COLOR="#FF80FF" SIZE="3">通常のモーションブラー</FONT>となる（下図参照）。

<P>
<CENTER>
<TABLE>
<TR ALIGN="CENTER">
<TD COLSPAN="2"><FONT SIZE="4" COLOR="#C0C0FF"><B>6 sample Motion-Blur (non-movement)</B></FONT></TD>
</TR>
<TR ALIGN="CENTER">
<TD><FONT SIZE="3" COLOR="#A0FFA0"><B>Standard method</B></FONT></TD>
<TD><FONT SIZE="3" COLOR="#A0FFA0"><B>With FSAA Jittering</B></FONT></TD>
</TR>
<TR ALIGN="CENTER">
<TD><IMG SRC="Image/withoutFSAA_00.png" BORDER="2" WIDTH="256" HEIGHT="256"></TD>
<TD><IMG SRC="Image/withFSAA_00.png" BORDER="2" WIDTH="256" HEIGHT="256"></TD>
</TR>
</TABLE>
</CENTER>
</P>

<P>
<CENTER>
<TABLE>
<TR ALIGN="CENTER">
<TD COLSPAN="2"><FONT SIZE="4" COLOR="#C0C0FF"><B>6 sample Motion-Blur (movement)</B></FONT></TD>
</TR>
<TR ALIGN="CENTER">
<TD><IMG SRC="Image/motion.jpg" BORDER="0" WIDTH="328" HEIGHT="343"></TD>
</TR>
</TABLE>
</CENTER>
</P>

　<FONT COLOR="#FF80FF" SIZE="3">
動いている部分は元々ジャギーが目立ちにくい</FONT>事と、
この処理による
<FONT COLOR="#FF80FF" SIZE="3">
速度的なオーバヘッドはほとんど皆無</FONT>（射影行列のセットのみ）
である事から、処理速度が速ければなかなか有効に使えそうである。

<P>
<BR>
　シーン・アンティエイリアスのジッタ処理は非常に簡単で、
<FONT COLOR="#FF80FF" SIZE="3">
視錐体を１ピクセル未満で変形（射影行列を変化）</FONT>
させるだけである。
ただ、
<FONT COLOR="#FF80FF" SIZE="3">
射影行列（Projection Matrix）</FONT>
は意外に理解されていない事が多いようなので、
ここで簡単に説明することにする。
<P>
　Direct3D や OpenGL の
<FONT COLOR="#FF80FF" SIZE="3">
射影変換は、ビューボリュームに含まれる空間を
正規化デバイス座標系（クリップ座標系）に射影する座標変換</FONT>である。
この変換により、
<FONT COLOR="#FF80FF" SIZE="3">
ビューボリューム内のｘ、ｙ、ｚ座標は -1 〜 1 の範囲
（ただし Direct3D ではｚ座標のみ 0 〜 1）に射影</FONT>される。
そしてこの範囲に納まらない（ビューボリュームの外の）頂点はクリップの対象となる。
<BR>
　Direct3D と OpenGL では扱う座標系が異なり（右手または左手座標系）、
クリップ座標のｚの範囲も異なり、さらに行列の表現（縦横）も異なるため、
射影行列も当然異なる。
とりあえず、
<FONT COLOR="#FF80FF" SIZE="3">
Direct3D と OpenGL の透視射影行列</FONT>
を以下に示す。
<P>
　ただし、行列中の
<BLOCKQUOTE>
<FONT SIZE="+1"><B>f </B></FONT>はファークリップ平面の位置（視点座標系でのｚ座標）<BR>
<FONT SIZE="+1"><B>n </B></FONT>はニアクリップ平面の位置（同じくｚ座標）<BR>
<FONT SIZE="+1"><B>r </B></FONT>はニアクリップ平面の右端（ｘ座標）<BR>
<FONT SIZE="+1"><B>l </B></FONT>はニアクリップ平面の左端（ｘ座標）<BR>
<FONT SIZE="+1"><B>t </B></FONT>はニアクリップ平面の上端（ｙ座標）<BR>
<FONT SIZE="+1"><B>b </B></FONT>はニアクリップ平面の下端（ｙ座標）<BR>

</BLOCKQUOTE>
とする。

<P>

<!--
<BLOCKQUOTE>
<TABLE>
<TR ALIGN="CENTER"> <TD><B> <U>　2 n　</U><BR>　r - l　</B></TD> <TD><B>　0　</B></TD> <TD><B>　0　</B></TD> <TD><B>　0　</B></TD> </TR>
<TR ALIGN="CENTER"> <TD><B>　0　</B></TD> <TD><B> <U>　2 n　</U><BR>　t - b　</B></TD> <TD><B>　0　</B></TD> <TD><B>　0　</B></TD> </TR>
<TR ALIGN="CENTER"> <TD><B> <U>　r + l　</U><BR>　r - l　</B></TD> <TD><B> <U>　t + b　</U><BR>　t - b　</B></TD> <TD><B> <U>　f　</U><BR>　f - n　</B></TD> <TD><B>　1　</B></TD> </TR>
<TR ALIGN="CENTER"> <TD><B>　0　</B></TD> <TD><B>　0　</B></TD> <TD><B> <U>　- f n　</U><BR>　f - n　</B></TD> <TD><B>　0　</B></TD> </TR>
</FONT>
</TABLE>
</BLOCKQUOTE>
-->

<TABLE>
<TR ALIGN="CENTER">
<TD><FONT SIZE="+1" COLOR="#C0C0FF"><B>Direct3D</B></FONT></TD>
<TD><FONT SIZE="+1" COLOR="#C0C0FF"><B>OpenGL</B></FONT></TD>
</TR>
<TR ALIGN="CENTER">
<TD><IMG SRC="Image/projectionmatrix_D3D.gif" WIDTH="254" HEIGHT="200" BORDER="0"></TD>
<TD><IMG SRC="Image/projectionmatrix_OGL.gif" WIDTH="307" HEIGHT="199" BORDER="0"></TD>
</TR>
</TABLE>

<!--
<BLOCKQUOTE>
<TABLE>
<TR ALIGN="CENTER"> <TD><B> <U>　2 n　</U><BR>　r - l　</B></TD> <TD><B> 0 </B></TD> <TD><B> <U>　r + l　</U><BR>　r - l　</B></TD> <TD><B>　0　</B></TD> </TR>
<TR ALIGN="CENTER"> <TD><B> 0 </B></TD> <TD><B> <U>　2 n　</U><BR>　t - b　</B></TD> <TD><B> <U>　t + b　</U><BR>　t - b　</B></TD> <TD><B>　0　</B></TD> </TR>
<TR ALIGN="CENTER"> <TD><B> 0 </B></TD> <TD><B>　0　</B></TD> <TD><B> <U>　- ( f + n )　</U><BR>　f - n　</B></TD> <TD><B> <U>　- 2 f n　</U><BR>　f - n　</B></TD> </TR>
<TR ALIGN="CENTER"> <TD><B> 0 </B></TD> <TD><B>　0　</B></TD> <TD><B>　- 1　</B></TD> <TD><B>　0　</B></TD> </TR>
</TABLE>
</BLOCKQUOTE>
-->

<P>
式で書くなら
<P>

<TABLE>
<TR ALIGN="CENTER">
<TD><FONT SIZE="+1" COLOR="#C0C0FF"><B>Direct3D</B></FONT></TD>
<TD><FONT SIZE="+1" COLOR="#C0C0FF"><B>OpenGL</B></FONT></TD>
</TR>
<TR ALIGN="CENTER">
<TD><IMG SRC="Image/projectionequ_D3D.gif" WIDTH="160" HEIGHT="200" BORDER="0"></TD>
<TD><IMG SRC="Image/projectionequ_OGL.gif" WIDTH="186" HEIGHT="200" BORDER="0"></TD>
</TR>
</TABLE>

である。
Direct3D と OpenGL では、
<FONT COLOR="#FF80FF" SIZE="3">z と w のみ</FONT>
異なっている。

<P>
　念の為に書いておくが、これによる変換後の座標（x, y, z, w）はあくまで
<FONT COLOR="#FF80FF" SIZE="3">
同次座標</FONT>であり、
<FONT COLOR="#FF80FF" SIZE="3">これをｗ除算した値（x/w, y/w, z/w）が実際の座標
</FONT>
となる。
透視投影の場合ｗには視点座標系のｚ座標（OpenGL では -z）が入っていることから、
透視変換も同時に行われていることが判るだろう。
各自、f,n, r,l, t,b に適当な値をセットし、
与えた頂点（x, y, z, w）がどのような値に変換されるか試してみると良いだろう。
また、
<FONT COLOR="#FF80FF" SIZE="3">
ビューボリューム内の座標を変換した場合、（x/w, y/w, z/w）はかならず
-1 〜 1 の範囲に含まれる
</FONT>
ことを確認しよう（Direct3D の z/w のみ 0 〜 1）。
<P>
　参考までに、W-Buffering に使用されるｗ値とは、
視点座標系での頂点を上の射影行列で変換した後のｗ値のことである
（内部的にはｗ値の取り得る最大値によってスケーリングされるため、
この値そのものではない）。
また、ｗ値は実際にはその逆数である rhw によって渡される。
<FONT COLOR="#FF80FF" SIZE="3">
rhw はつまりは透視変換されたｚ座標
</FONT>
であるため、
ラスタライズ時には
<FONT COLOR="#FF80FF" SIZE="3">
rhw 値を線形補間し、ピクセル毎にその逆数を計算することで、
補正された正確なｗ値（視点座標系でのｚ値といっても良い）を復元</FONT>できる。

<P>

　さて本題に戻り、射影行列に与えるパラメタの内、
<FONT COLOR="#FF80FF" SIZE="3">
投影面の矩形を定義する l,r, b,t </FONT>
を、１ピクセル未満の量でずらして複数回レンダリングし、
サンプリング不足によるジャギーを軽減するのが、
<FONT COLOR="#FF80FF" SIZE="3">
（フル）シーン・アンティエイリアス</FONT>
である。
<BR>
　ビューポートのサイズが判っていれば、
<FONT COLOR="#FF80FF" SIZE="3">
ビューポート上での１ピクセルの大きさ（範囲）が、
視点座標系での投影面（l 〜 r, b 〜 t によって定義される矩形）に対して
どの程度の大きさを持つかを逆算
</FONT>
できる。
例えばビューポートのサイズが（640x480）で、ニアクリップ平面（投影面）が
（l=-8, b=-6）〜（r=8, t=6）までの範囲（16x12 のサイズ）であるとすると、
投影面上での１ピクセルに相当するサイズは（16/640, 12/480）で求まる。
当たり前ですね。
<P>
　後は、各サンプル毎に、
<FONT COLOR="#FF80FF" SIZE="3">
１ピクセルの範囲内（上で求めた範囲）でパラメタ r,l, t,b にオフセットを
与えてずらし、
射影行列を変化（ビューボリュームを変形）</FONT>させてやれば良い訳だ。
最後に全ての画像を平均化すれば、シーン・アンティエイリアスの完成である。

<P>
　と言う訳でサンプルプログラムっていうか glclock で実装してみただけです。
<BLOCKQUOTE>
<FONT SIZE="+1"><B><I>
<A HREF="../archives/jittertest.zip">jittertest.zip(139KB) for win32</A>
</I></B></FONT>
</BLOCKQUOTE>
ちなみに実行ファイルしか入っていないので、
動かない場合はまず glclock 本体が動く環境にしてください（無責任）。
あと、現時点で公開している最新版の glclock-6.0 Beta 6.0 では実装されてませんので、
試したい場合は上の奴の実行ファイルが必須です。
<P>
<BLOCKQUOTE>
<FONT SIZE="+1" COLOR="#FFFFA0"><B>glclock.exe -NOANTI</B></FONT>
</BLOCKQUOTE>
で実行すると従来のジッタを与えない方法になり、
<BLOCKQUOTE>
<FONT SIZE="+1" COLOR="#FFFFA0"><B>glclock.exe</B></FONT>
</BLOCKQUOTE>
のように -NOANTI を付けなければ（デフォルトで）FSAA ジッタが有効になります。
<P>
　適当に実行して、m キーでモーションブラーをかけてみてください。
ちなみに、GeForce シリーズの nVIDIA reference driver 5.x だと高速になります。
</DL>

<!-- Signature -->
<FONT SIZE="3" FACE="ＭＳ Ｐ明朝">

<CENTER>
<IMG SRC="../Image/Bar2.gif" ALT "-------------------------------------------" width=90% height="16">
</CENTER>

<UL>
	<A HREF="index.html"><B><I>Masa's Column</I></B>に戻る</A>

	<TABLE>
	<TR>
	<TD>
	<A HREF="../index.html"><IMG SRC="../Image/MasaPlate.gif" BORDER="0" WIDTH="112" HEIGHT="48"></A>
	</TD>
	<TD><A HREF="../index.html">ホームページに戻る</A></TD>
	</TR>
	</TABLE>

	本ページの御意見・御感想は<BR>
<B>
<ADDRESS>
	<A HREF="mailto:masa@daionet.gr.jp">
		<I>E-Mail: masa@daionet.gr.jp</I></A>
</ADDRESS>
</B></UL>

</FONT>
<!-- Signature -->

</BODY>
