読者です 読者をやめる 読者になる 読者になる

椿の日記

たぶんプログラムの話をします

カーソルをアクティブウィンドウのクライアント領域に制限する

カーソルをアクティブウィンドウのクライアント領域に制限するには、Win32 APIの ClipCursor 関数を使うのですが、なかなか期待する動作をしてくれません。

多くのサイトでは、ウィンドウのアクティブ/非アクティブ状態をWM_ACTIVATEで検出し、クリッピングの開始と終了を行えばよい、と解説されています。しかしこの方法だけだと、ウィンドウを非アクティブな状態から「タイトルバーをクリックして」アクティブ状態にした場合、カーソルがデスクトップ全体を移動できる状態になってしまいます。

きちんと調べたわけではないのですが、どうもWM_ENTERSIZEMOVE~WM_EXITSIZEMOVEの間で勝手にClipCursorの設定が無効化されているようです。

ということで、WM_EXITSIZEMOVEの中でもClipCursorします。これにより、ウィンドウの移動を終えた後に、カーソルをクライアント領域にクリッピングできるようになります。

Haskell@Windowsのコンソールで日本語が文字化けする話の対策

コンソールのコードページがCP932なので、最終的にCP932に変換しなければならないのですが、どうせWindowsでしか起こらない問題なので、WindowsのAPIを使っちゃいます。

import Data.Word
import Foreign.Ptr
import Foreign.C.Types
import Foreign.C.String
import Foreign.Marshal.Alloc
import System.IO.Unsafe

foreign import stdcall "WideCharToMultiByte" c_WideCharToMultiByte ::
    Int ->              -- CodePage
    Word32 ->           -- dwFlags
    Ptr CWchar ->       -- lpWideCharStr
    Int ->              -- cchWideChar
    Ptr CChar ->        -- lpMultiByteStr
    Int ->              -- cchMultiByte
    Ptr CChar ->        -- lpDefaultChar
    Ptr Bool ->         -- lpUsedDefaultChar
    IO Int

toCP932 :: String -> String
toCP932 cs = unsafePerformIO $ do
    withCWStringLen cs (\(src,srcLen) -> do
        dstLen <- c_WideCharToMultiByte 0 0 src srcLen nullPtr 0 nullPtr nullPtr
        allocaBytes dstLen (\dst -> do
            c_WideCharToMultiByte 0 0 src srcLen dst dstLen nullPtr nullPtr
            peekCStringLen (dst,dstLen)))

main = putStrLn $ toCP932 "ほげ"

Frequency Domain Normal Map Filtering実装

f:id:tbk:20130112193013p:plain

少し忙しくて放置してたのですがFrequency Domain Normal Map Filteringを実装しました。

画像はそれぞれ次をあらわしていて、左上をリファレンス画像だと思って見ます。

通常のバンプマップの32倍スーパーサンプリング 通常のバンプマップ
NDFをmovMFで近似 NDFをSHで近似

バンプマップはこんな感じの単純なものを利用しています。
f:id:tbk:20130112193931p:plain

モデルはStanford Bunnyに球面UV展開を適用して出力したやつです。球面UV展開なのでウサギの背のところで山と谷が密集していますが、ここでスーパーサンプリングしていない通常のバンプマップは法線が潰れて真っ白になってしまっているのが一目瞭然ですね。

別の角度から。ウサギの尻。
f:id:tbk:20130112192952p:plain
f:id:tbk:20130112193002p:plain
f:id:tbk:20130112193009p:plain

拡大していると他との違いが全然分からないのですが、縮小すると違いが明確に出ます。

pixel shaderでshared memoryって使えるの?

ここまで最適化を進めてきたのですが、これ以上はpixel shaderでは高速化は無理な気がしてきました…

ループに[unroll]を付けたり階乗計算を漸化式的に書き直したりした結果、アセンブリコードの大半は積和計算になりました。それでも遅いということは、レジスタ数が多すぎるか、定数バッファへのアクセスのレイテンシが大きいのか、そのあたりが問題なのではないかと見ています。

よく知らないのですが、DX11のpixel shader内のcbufferはconstant memory領域に割り振られるのでしょうか? そうだとすると、BRDFや入射光の球面調和係数をshader memory領域に割り振ることで劇的に速くなるかもしれません。

ただ、pixel shaderでshared memoryを使う方法が分かりません…。というか、そもそも使えないのかもしれません。もし使えないとすると、pixel shaderは必要最小限の情報をUAVとかで出力して、それをcompute shaderで処理するのでしょうか。そうなるとdeferred rendering的な形のほうが良いことになるのかなあ。

シェーダの最適化ってどうするのがいいのかな

あんまりにもSH係数の計算が重いのでシェーダコードの最適化をしています。ただ、ある程度のところより先は、どう最適化するのがよいのかよく分からんことに。というのは、生成されたアセンブリコードをCUDAコアで実行するにあたって結局どういう風にコンバートされるかよく分からないからです。ピクセルシェーダって1ピクセルがCUDAコアの1つに割り振られるのかな。それともコンパイラが頑張って1ピクセルのためにCUDAコアを複数使って並列計算してくれるのか。うーん。
とりあえずは基本的なこと(無駄なループ除去とか整数/浮動小数変換を減らすとか)を中心にやるくらいですね。

球面調和関数の練習

f:id:tbk:20121229032125j:plain

やったこと

やっと時間取れたので実装してみました。上段はリファレンスとなる通常の照明計算、下段はそれを球面調和関数の係数空間で計算しました。左から順にそれぞれ次のような照明計算を行いました。

  • Lambertian BRDF * DirectionalLight
  • Phong BRDF * DirectionalLight
  • Lambertian BRDF * ConstantLight
  • Lambertian BRDF * (DirectionalLight + ConstantLight) + Phong BRDF * DirectionalLight

Lambertian BRDFやPhong BRDFの球面調和関数展開は「Lambertian Reflectance and Linear Subspaces(Basri and Jacobs)」にあるので、こちらを参考にします。平行光源はデルタ分布の光源と考えれば球面調和関数展開の係数が求まります。定数光源のほうはY_{00}だけ使って展開すればいいです。

疑問など

シェーダ内でY_{lm}(\omega)を必要とするのですが、どうやって与えるのが普通なんだろう。今回はシェーダ内で漸化式を解きながら計算したけど、どうも処理が重かった…。

あと、Phong BRDFでshininessが32とかになると、球面調和関数を次数15くらいまで伸ばさないとまともに近似できないらしい。次数15になると256項も必要になります。うーむ。

Frequency Domain Normal Map Filtering続き^4

※式が違ったので修正(12/24)

セクション4.3の次の式

\displaystyle \rho(\omega;\gamma) = \int_{S^2} f(\omega \cdot n) \gamma(n) dn

について、次のようにそれぞれ球面調和関数展開できるとき、

\displaystyle \gamma(n) = \sum_{l=0}^{\inf} \sum_{m=-l}^l \gamma_{lm} Y_{lm}(n)
\displaystyle f(\omega \cdot n) = \sum_{l=0}^{\inf} f_l Y_{l0}(\omega \cdot n)
\displaystyle \rho(\omega) = \sum_{l=0}^{\inf} \sum_{m=-l}^l \rho_{lm} Y_{lm}(\omega)

その係数が次のようになる、

\displaystyle \rho_{lm} = \sqrt{\frac{4\pi}{2l+1}} f_l \gamma_{lm}

というこの部分について流し読みしていたのですが、よくよく考えてみると、よくある球面調和関数の内積と違って\omega \cdot nを含んでいるため、全然分からないことに気づいてしまいました。

というわけで何故これが成り立つのか調べていたのですが、これはLambertian Reflectance and Linear Subspaces (Basri and Jacob, IEEE TRANSACTIONS ON PATTERN ANALYSIS AND MACHINE INTELLIGENCE, VOL. 25, NO. 2, FEBRUARY 2003)に背景が説明されていました。この論文によれば、kが放射的に対称でzonal harmonicsで展開できるとき、次が成り立つそうです(Funk-Hecke Theorem)。

\displaystyle \int_{S^2} k(v \cdot u) Y_{lm}(u) du = \sqrt{\frac{4\pi}{2l+1}} k_l  Y_{lm}(v)

つまり、言い換えると「何も解決してねえ!」という感じなのですが、法線分布関数\gammaだけ先に展開してFunk-Hecke Theoremを適用すれば上記式が求まることは分かります。なので次はこの定理を掘り下げれば良さそうなのですが、どうもこの定理を深追いするのはかなり大変であるようです。しがないプログラマとしてはツールとして使うのが良さそうですね…。