こんにちわ。Pocolです。
今日は前に読んだ,Separable Subsurface Scatteringの補足資料を読んでみようと思います。
資料は
[Jimenez 2015] Jorge Jimenez, Karoly Zsolnai, Adrian Jarabo, Christain Freude, Thomas Auzinger, Xian-Chun Wu, Javier von der Pahlen, Michael Wimmer, Diego Gutierrez, “Separable Subsurface Scattering”, Compute Graphics Forum 2015.
のプロジェクトページhttp://www.iryoku.com/separable-sss/にあります。
いつものように誤字・誤訳があるかと思いますので,ご指摘いただける場合は正しい翻訳例と共に指摘していただけると幸いです。
カテゴリー: Other
どうでもいい話。
もうすこし待たれよ!
こんにちわ、Pocolです。
執筆している本ですが…,順調に進んでいます。
企画から5年。そして無名ということで企画が通るまでの,半年近くはほぼ何もできず。
担当さんの地道な根回しがあって,ようやく執筆までたどり着いた本です。
折角企画も通り,発売に向けて動き出したので…
発売後にマサカリ投げられるのは,嫌なので説明部分に関してはあらかじめ豪華メンバーに査読していただきました。
説明については丁寧にレビューしていただきました。
本当にこの場を借りて,感謝を述べさせていただきたいと思います。
「本業がありタイトル開発などお忙しい中,レビューアーの皆様本当にありがとうございました!」
実をいうと,レビューで心が折れました。
やっぱり,色々と見てもらうと自分がいかに愚か者であるかということをヒシヒシと感じますね。
説明の仕方もそうですが,言葉の選び方や言い回しなんかもそうです。
独りよがりはやっぱりいかんなと改めて思いました。
また、レビューしていただいて本当によかったなと心の底から感じています(本当最初の状態は酷かった)。
レビュー無しで,国会図書館なんかに寄贈されて,ダメダメな説明とかが自分が死んだ後も一生記録されてしまうと思うと,ゾッとしますね。
レビューが無かったら,そうした恥をさらしながら生きていかなければならないのですが,レビューをしてもらうことで,少しでも回避することができて本当によかったなと思います。
やはり,レビューをしてもらって感じたのですが,きちんとした議論があってこそ,良いものが生まれてくるんだろうなと… そう感じました。
自分が見ていて「これは良いな!」と思う書籍は共著だったり,きちんと監修が付いていたりしますしね。
自分は筆不精でして,期待されて待っている方には本当に申し訳ないのですが,
実はとある競合書籍が発売されていなければ,今頃は既に発売済みだったんですね。
何もせずに,当初通りそのまま発売というのもありだったのですが,単なる2番煎じになるもの,流石にどうかなと思いまして…。
(まぁ、だったら先越される前に早く書けよっていうのはごもっともです。)
やはり,先手を打ち出されてしまうと,後手側は何かしらの対策を練らねければなりません。
何も対策を練らないというのは愚の骨頂ではないかと思いましたし,何かしら付加価値を付けて提供したいと思いました。
また,何もせずにそのまま発売にもっていってしまうのは,色々な方の力を借りている分,自分として申し訳なかったですし,待っている方にも申し訳が立たないです。
担当さんは,「こちらのほうが先にやっているのに…後からを先越されて…」と,がっくし来ていたみたいです。
自分もさらに,その本の内容を見てガックシきました。ちなみに,その本の内容は,細かい所は違えど目指す方向性としては,自分が一番最初に出版社に企画を出してボツになった内容ほぼそのものでした。こっちの出版社だったら,すんなり受け入れられていたかもしれないな…って。
まぁ、いずれこういう先に越されることは目に見えていたので,筆不精な自分が悪い以外のなにものでもないんですけどもね。
ただ,今書いている内容はボツになったものとは方向性が違います。
…というか,出版社側のダメ出しだったり,他社に先越されるのとか想定して,じゃぁちょっと変えましょうと,企画段階で変更したんですよね。
だから,そのまま予定に沿って発売に向けて動いても問題はなかったのですが,何か悔しいなと思って…。
何かしら手は打たねばならないと思いました。
そこで,1章分を新たに追加執筆し,さらに開発に役立つと思われる付録もちょっとしょぼい内容ですが一応付けました。
具体的な内容については,もう少し言える状況になったらお伝えしたいと思います。
(内容を書いて競合他社にパクられて,差別化をするためにまた発売を延期するのは流石に勘弁したいので…)
皆さん,完成まで着々と進んでいます。もうしばらくお待ちください!!
明けましておめでとうございます。
新年あけましておめでとうございます!
本年度もどうぞProjectASURAをよろしくお願い致します。
今年は執筆していた本も発売されるので,本発売まで更新停止していたホームページもぼちぼち更新頻度を高められたらなぁと思っています。
色々とノウハウも徐々についてきたので,どこかで文章化出来たらいいなと思っています。
あとは昨年から息抜きとしてゲームっぽいものも作り始めました。こちらについてもある程度形になってきたらホームページの方で記事化出来たらと思ってます。
動画にしてみた。 pic.twitter.com/SzYmJVhAis
— Pocol (@ProjectAsura) November 23, 2020
簡易な敵を作って,攻撃判定も付けてみた。 pic.twitter.com/pQjtqnnFuW
— Pocol (@ProjectAsura) November 23, 2020
雑だけどHPゲージ付けた。 pic.twitter.com/k0kFXMV62k
— Pocol (@ProjectAsura) November 24, 2020
押せるギミックを実装してみた。 pic.twitter.com/1GzCzM3Eh0
— Pocol (@ProjectAsura) November 26, 2020
キャラが微妙にずれているけど,一応マップ遷移の仕組み作った。(マップ作るのがめんどいので,同じレイアウトのままですが…) pic.twitter.com/tfCDgW1vpx
— Pocol (@ProjectAsura) November 29, 2020
Dual Senseのサポート始めてみました。
おはようございます。Pocolです。
こちらで改造版RdpGamepadですが,DualSenseを試験的にサポートしてみることにしました。
Dual Sense Alpha Version
基本的には,開発しているlibDS4を差し替えただけなので,RdpGamepad側のロジック変更はありません。
人柱になってくれるかたがいらっしゃいましたら,不具合報告などをいただけると有難いです。
また,ViGEmBus側が,Dual Senseに対応していないので,転送先PCではDS4かXBoxコントローラーとしてしか振舞いませんので注意してください。
レンズデータについて
nikqさんが紹介していたサイト。
http://www.lens-designs.com/PhotoPrime
位置座標を復元したい。
こんるる~、Pocolです。
また,前回は深度について書きましたが,今回はスクリーンスペース系のテクニックなどで有用な位置座標の復元について忘れないようにメモしておこうと思います。
D3DXの行列は行優先(row-major)形式なので,列優先形式として表すと
\begin{eqnarray}
M_{RH} = \begin{bmatrix} s_x & 0 & 0 & 0 \\
0 & s_y & 0 & 0 \\
0 & 0 & \frac{z_f}{z_n – z_f} & \frac{z_n z_f}{z_n – z_f} \\
0 & 0 & -1 & 0 \end{bmatrix}
\end{eqnarray}
と書けます。
ビュー空間の位置座標を\((v_x, v_y, v_z, 1)^{\mathrm T}\)として,透視投影を行い,透視投影後の位置座標を\((p_x, p_y, p_z, p_w)^{\mathrm T}\)とすると,次のように書くことができます。
\begin{eqnarray}
\begin{bmatrix} p_x \\ p_y \\ p_z \\ p_w \end{bmatrix} &=& \begin{bmatrix} s_x & 0 & 0 & 0 \\
0 & s_y & 0 & 0 \\
0 & 0 & \frac{z_f}{z_n – z_f} & \frac{z_n z_f}{z_n – z_f} \\
0 & 0 & -1 & 0 \end{bmatrix} \begin{bmatrix} v_x \\ v_y \\ v_z \\ 1 \end{bmatrix} \\
&=& \begin{bmatrix} s_x v_x \\ s_y v_y \\ v_z \left( \frac{z_f}{z_n – z_f} \right) + \frac{z_n z_f}{z_n – z_f} \\ -v_z \end{bmatrix}
\end{eqnarray}
ここで,\(p_x\),\(p_y\),\(p_z\)を\(p_w\)で除算し,正規化デバイス座標系での位置座標\(P_x\), \(P_y\), \(P_z\)を計算すると
\begin{eqnarray}
P_x &=& \frac{p_x}{p_w} &=& -\frac{s_x v_x}{v_z} \tag{1} \\
P_y &=& \frac{p_y}{p_w} &=& -\frac{s_y v_y}{v_z} \tag{2} \\
P_z &=& \frac{p_z}{p_w} &=& -\frac{z_f}{z_n – z_f} \left( 1 \, + \, \frac{z_n}{v_z} \right) \tag{3}
\end{eqnarray}
となります。
今,\(v_x\),\(v_y\)を復元する方法を考えたいので\(P_x\)と\(P_y\)についての式を\(v_x\),\(v_y\)について解きます。
\begin{eqnarray}
v_x &=& -\frac{v_z}{s_x} P_x \\
v_y &=& -\frac{v_z}{s_y} P_y
\end{eqnarray}
DirectXの正規化デバイス座標系は\(x\)成分と\(y\)成分が\([-1, 1]\)で,\(z\)成分が\([0, 1]\)であるのでテクスチャ座標\(s\),\(t\)とすると,\(P_x\)と\(P_y\)との関係は次のように書くことができます。
\begin{eqnarray}
s &=& 0.5 P_x + 0.5 \tag{4} \\
t &=& -0.5 P_y + 0.5 \tag{5}
\end{eqnarray}
DirectXのテクスチャ座標を求めるために上下反転させていることに注意してください。
この式を\(P_x\),\(P_y\)について書くと,
\begin{eqnarray}
P_x &=& 2s – 1 \\
P_y &=& -2t + 1
\end{eqnarray}
となります。よって,\(v_x\)と\(v_y\)をこの式を使って書き直すと次のようになります。
\begin{eqnarray}
v_x &=& -v_z \frac{1}{s_x} (2s – 1) \\
v_y &=& -v_z \frac{1}{s_y} (-2t + 1)
\end{eqnarray}
ここで,前回の記事で導出した深度バッファから\(v_z\)を求める式は
\begin{eqnarray}
v_z = -\frac{z_f z_n}{d(z_n – z_f) + z_f}
\end{eqnarray}
であったので,深度バッファの値\(d\)と,ニアクリップ平面までの距離\(z_n\),ファークリップ平面までの距離\(z_f\),\(s_y = \cot(\frac{fovY}{2})\),\(s_x = s_y / aspect\),そしてテクスチャ座標\(s\)と\(t\)があればビュー空間の位置座標が復元することができることになります。
定数バッファとして渡せるようにパラメータをfloat4にパックします。成分の内訳は
\begin{eqnarray}
{\rm param} = \left(\frac{1}{s_x}, \frac{1}{s_y}, z_n, z_f \right)
\end{eqnarray}
とします。
\(s_x\)と\(s_y\)はもともと射影行列の要素なので,射影行列があれば簡単に計算することができます。以上を踏まえてコードに落とし込むと
param.x = 1.0f / projection._11; // matrix[0][0]成分. param.y = 1.0f / projection._22; // matrix[1][1]成分. param.z = nearClip; // ニアクリップ平面までの距離. param.w = farClip; // ファークリップ平面までの距離.
シェーダ側ではparamを使って次の復元処理を実装すればよいです。
// 深度バッファの値からビュー空間深度値を求めます. float ToViewDepth(float hardwareDepth, float nearClip, float farClip) { return -nearClip * farClip / (hardwareDepth * (nearClip - farClip) + farClip); } // テクスチャ座標と深度値からビュー空間位置座標を求めます。 float3 ToViewPos(float2 st, float hardwareDepth, float4 param) { float z = ToViewDepth(hardwareDepth, param.z, param.w); float2 p = st * float2(2.0f, -2.0f) + float2(-1.0f, 1.0f); return float3(-z * param.x * p.x, -z * param.y * p.y, z); }
そんなわけでビュー空間深度さえ求めれば,単純な計算だけでビュー空間が復元できます。
ビュー空間から射影空間に戻す場合は,式(1), 式(2),式(3)を使って求められるので…
// ビュー空間から射影空間に変換します. float3 ToProjPos(float3 viewPos, float4 param) { return float3( -viewPos.x / (viewPos.z * param.x), -viewPos.y / (viewPos.z * param.y), -(param.w / (param.z - param.w)) * (1.0f + (param.z / viewPos.z) ); }
となります。
ビュー空間から,テクスチャ座標を求めたい場合は,さらに式(4),式(5)を使えばよいので
// ビュー空間からテクスチャ座標に変換します. float2 ToTexCoord(float3 viewPos, float4 param) { float2 p; p.x = -viewPos.x / (viewPos.z * param.x); p.y = -viewPos.y / (viewPos.z * param.y); return p * float2(0.5f, -0.5f) + float2(0.5f, 0.5f); }
で求められるはずです。
もし、書き間違いとか「そもそも計算あってないよ!」という箇所あれば遠慮なく指摘してください (…というのも若干自信無いからです)。
深度の精度を高くしたい。
いつも忘れるので,忘れないようにメモをしておこうと思います。
Reverse-Z
DirectXの右手座標系の透視投影行列は[Microsoft 2018 a]で次のように記載されています。
xScale 0 0 0 0 yScale 0 0 0 0 zf/(zn-zf) -1 0 0 zn*zf/(zn-zf) 0 where: yScale = cot(fovY/2) xScale = yScale / aspect ratio
D3DXの行列は行優先(row-major)形式なので,列優先形式として表すと…
\begin{eqnarray}
M_{perspectiveRH} = \begin{bmatrix} s_x & 0 & 0 & 0 \\
0 & s_y & 0 & 0 \\
0 & 0 & \frac{z_f}{z_n – z_f} & \frac{z_n z_f}{z_n – z_f} \\
0 & 0 & -1 & 0 \end{bmatrix}
\end{eqnarray}
となります。ただし,\(s_y = \cot(\frac{fovY}{2})\),\(s_x = \frac{s_y}{aspect}\),\(z_n\)はニアクリップ平面までの距離,\(z_f\)はファークリップ平面までの距離,\(fovY\)は垂直画角で,\(aspect\)は画面のアスペクト比とします。
今,\(z\)の範囲を\([0, 1] \rightarrow [1, 0]\)に変換したいので,\(z\)に対して-1倍して1を足せば範囲が変換できることになります。この変換を行うための行列を左からかけてReverse-Z形式にします。
\begin{eqnarray}
M_{reverse} M_{perspectiveRH} &=& \begin{bmatrix}
1 & 0 & 0 & 0 \\
0 & 1 & 0 & 0 \\
0 & 0 & -1 & 1 \\
0 & 0 & 0 & 1 \end{bmatrix}
\begin{bmatrix}
s_x & 0 & 0 & 0 \\
0 & s_y & 0 & 0 \\
0 & 0 & \frac{z_f}{z_n – z_f} & \frac{z_n z_f}{z_n – z_f} \\
0 & 0 & -1 & 0 \end{bmatrix} \\
&=& \begin{bmatrix}
s_x & 0 & 0 & 0 \\
0 & s_y & 0 & 0 \\
0 & 0 & \frac{z_n}{z_f – z_n} & \frac{z_f z_n}{z_f – z_n} \\
0 & 0 & -1 & 0 \end{bmatrix}
\end{eqnarray}
[Reed 2015]に記載されていますが,ファーを無限遠にするとちょっとだけ結果が良くなるケースがあるので,\(z_f \rightarrow \infty\)の極限をとります。\( z_n /(z_f – z_n)\)は分母が\(\infty\)になるのでゼロに近づくことと,\(z_f z_n / (z_f – z_n)\)は\(\infty/\infty\)の不定形になるので,分子・分母を\(z_f\)で割って極限をとると,次のように整理できます。
\begin{eqnarray}
M_{reverseInf} = \lim_{z_f \rightarrow \infty} \left( M_{reverse} M_{pervespectiveRH} \right) = \begin{bmatrix}
s_x & 0 & 0 & 0 \\
0 & s_y & 0 & 0 \\
0 & 0 & 0 & z_n \\
0 & 0 & -1 & 0 \end{bmatrix}
\end{eqnarray}
この行列を使ってベクトルを変換します。
\begin{eqnarray}
p = \begin{bmatrix}
s_x & 0 & 0 & 0 \\
0 & s_y & 0 & 0 \\
0 & 0 & 0 & z_n \\
0 & 0 & -1 & 0 \end{bmatrix} \begin{bmatrix} v_x \\ v_y \\ v_z \\ 1 \end{bmatrix}
\end{eqnarray}
興味があるのは,深度だけなので射影変換後のz値(\(p_z\))とw値(\(p_w\))を求めます。
\begin{eqnarray}
p_z &=& z_n \\
p_w &=& -v_z
\end{eqnarray}
z値をw値で割ったのが深度バッファに出力されるデプス\(d\)になるので,
\begin{eqnarray}
d &=& \frac{p_z}{p_w} \\
&=& \, – \frac{z_n}{v_z}
\end{eqnarray}
よって,深度バッファの値\(d\)からビュー空間の深度\(v_z\)を逆算出する場合は,
\begin{eqnarray}
v_z = -\frac{z_n}{d}
\end{eqnarray}
で求めればよいことになります。
コードに落とし込むと…
// farClip = ∞とするリバースZ深度バッファの値からビュー空間深度値を求めます. float ToViewDepthFromReverseZInfinity(float hardwareDepth, float nearClip) { return -nearClip / hardwareDepth; }
となります。
ちなみに,\(z_f\)の\(\infty\)を取らない場合のビュー空間深度は
\begin{eqnarray}
v_z = -\frac{z_f z_n}{d(z_f – z_n) + z_n}
\end{eqnarray}
で求められます。よって,リバースZを用いた場合の線形深度値は
\begin{eqnarray}
d_{linear} = -\frac{z_n}{d(z_f – z_n) + z_n}
\end{eqnarray}
となります。
コードに落とし込むと
// リバースZ深度バッファの値からビュー空間深度値を求めます。 float ToViewDepthFromReverseZ(float hardwareDepth, float nearClip, float farClip) { return -farClip * nearClip / (hardwareDepth * (nearClip - farClip) + nearClip); } // リバースZ深度バッファの値から[0, 1]の線形深度値を求めます。 float ToLinearDepthFromReverseZ(float hardwareDepth, float nearClip, float farClip) { return -nearClip / (hardwareDepth * (nearClip - farClip) + nearClip); }
となります。
線形深度
ついでに標準の射影行列(D3DXMatrixPerspectiveFovRH)を使った場合の線形深度の算出方法についてもメモしておきます。
まず,先ほどと同様に\(p_z\)と\(p_w\)を求めます。
\begin{eqnarray}
p_z &=& v_z (\frac{z_f}{z_n – z_f}) + \frac{z_f z_n}{z_n – z_f} \\
p_w &=& – v_z
\end{eqnarray}
除算して深度を求めます。
\begin{eqnarray}
d &=& \frac{p_z}{p_w} \\
&=& – \frac{v_z z_f}{v_z(z_n – z_f)} – \frac{z_f z_n}{v_z(z_n – z_f)} \\
\end{eqnarray}
上記の式を\(v_z\)について解き,ビュー空間深度を求めます。
\begin{eqnarray}
d(v_z(z_n – z_f)) &=& -v_z z_f – z_f z_n \\
d v_z z_n – d v_z z_f + v_z z_f &=& – z_f z_n \\
v_z(d(z_n – z_f) + z_f) &=& -z_f z_n \\
v_z &=& -\frac{z_f z_n}{d(z_n – z_f) + z_f}
\end{eqnarray}
線形深度は,ビュー空間深度をファークリップまでの距離で割り\([0, 1]\)の範囲内におさめたものになるので,
\begin{eqnarray}
d_{linear} &=& \frac{v_z}{z_f} \\
&=& -\frac{z_n}{d(z_n – z_f) + z_f}
\end{eqnarray}
となります。
コードに落とし込むと…
// 深度バッファの値からビュー空間深度値を求めます. float ToViewDepth(float hardwareDepth, float nearClip, float farClip) { return -nearClip * farClip / (hardwareDepth * (nearClip - farClip) + farClip); } // 深度バッファの値から[0, 1]の線形深度値を求めます. float ToLinearDepth(float hardwareDepth, float nearClip, float farClip) { return -nearClip / (hardwareDepth * (nearClip - farClip) + farClip); }
となります。
正射影
正射影についてもリバースZを一応求めてみます。
D3DXMatrixOrthoOffCenterRH行列は[Microsoft 2018 b]より
2/(r-l) 0 0 0 0 2/(t-b) 0 0 0 0 1/(zn-zf) 0 (l+r)/(l-r) (t+b)/(b-t) zn/(zn-zf) 1
と記載されています。この行列を列優先形式で書くと次のようになります。
\begin{eqnarray}
M_{orthoRH} = \begin{bmatrix}
s_x & 0 & 0 & t_x \\
0 & s_y & 0 & t_y \\
0 & 0 & s_z & t_z \\
0 & 0 & 0 & 1
\end{bmatrix}
\end{eqnarray}
ただし,\(s_x = \frac{2}{r – l}\),\(s_y = \frac{2}{t – b}\),\(s_z = \frac{1}{z_n – z_f}\),\(t_x = \frac{l + r}{l – r}\),\(t_y = \frac{t+b}{b-t}\),\(t_z = \frac{z_n}{z_n – _zf}\)とします。
この行列に対してリバースZ形式にするために左から行列を掛けます。
\begin{eqnarray}
M_{reverse}M_{orthoRH} &=& \begin{bmatrix}
1 & 0 & 0 & 0 \\
0 & 1 & 0 & 0 \\
0 & 0 & -1 & 1 \\
0 & 0 & 0 & 1
\end{bmatrix}
\begin{bmatrix}
s_x & 0 & 0 & t_x \\
0 & s_y & 0 & t_y \\
0 & 0 & s_z & t_z \\
0 & 0 & 0 & 1
\end{bmatrix} \\
&=& \begin{bmatrix}
s_x & 0 & 0 & t_x \\
0 & s_y & 0 & t_y \\
0 & 0 & -s_z & -t_z + 1 \\
0 & 0 & 0 & 1
\end{bmatrix} \\
&=& \begin{bmatrix}
\frac{2}{r – 1} & 0 & 0 & \frac{l+r}{l-r} \\
0 & \frac{2}{t – b} & 0 & \frac{t+b}{b-t} \\
0 & 0 & \frac{1}{z_f – z_n} & \frac{z_f}{z_f – z_n} \\
0 & 0 & 0 & 1
\end{bmatrix}
\end{eqnarray}
結果として,正射影の場合のリバースZ形式は単純にニアクリップ平面とファークリップ平面を入れ替えしたものになります。
参考文献
・[Microsoft 2018 a] Microsoft, “D3DXMatrixPerspectiveFovRH function”, https://docs.microsoft.com/en-us/windows/win32/direct3d9/d3dxmatrixperspectivefovrh
・[Microsoft 2018 b] Microsoft, “D3DXMatrixOrthoOffCenterRH function”, https://docs.microsoft.com/en-us/windows/win32/direct3d9/d3dxmatrixorthooffcenterrh
・[Reed 2015] Nathan Reed, “Depth Precision Visualized”, https://developer.nvidia.com/content/depth-precision-visualized
おしらせ
こんるる~。Pocolです。
皆様にご連絡があります。
執筆している書籍ですが,企画当初よりもページ数が120ページほど増えた関係で,初版の発行部数が減りました。
もう一度、言います。
「初版の発行部数が減りました」
どうしても手に入れたい!という方は,お早目にご購入の決断をしていただいた方が良いかもしれません。
さらに、もうひとつご連絡。
新型コロナウィルスの影響を受けまして,発売までもう少し時間をいただく運びになりました。
お待ちしていただいている皆様には申し訳ございませんが,何卒ご了承下さいますようお願い申し上げます。
正式な発売日が決定次第,ご連絡致します。
その頃にはコロナウィルスが治まっていることを願っています。