Pre-Integrated SkinのLUTを作ってみた。

こんにちわ。
Pocolです。

会社でキャラの肌が調整しずらいからどうにかしてくれ!
…と,言われてしまったので,とりあえずPre-Integrated Skinを調べてみようと思いました。

LUTテーブル作るところまでは実装してみました。
出来上がったLUTのテクスチャはこちら。

で,これを作るソースコードは以下のような感じ。


#define STBI_MSC_SECURE_CRT
#define STB_IMAGE_WRITE_IMPLEMENTATION

//-----------------------------------------------------------------------------
// Includes
//-----------------------------------------------------------------------------
#include <cstdio>
#include <stb_image_write.h>
#include <vector>
#include <asdxMath.h>

//-----------------------------------------------------------------------------
//      リニアからSRGBに変換.
//-----------------------------------------------------------------------------
inline float ToSRGB(float value)
{
    return (value <= 0.0031308) 
        ? 12.92f * value 
        : (1.0f + 0.055f) * pow(abs(value), 1.0f / 2.4f) - 0.055f;
}

//-----------------------------------------------------------------------------
//      ガウス分布.
//-----------------------------------------------------------------------------
inline float Gaussian(float v, float r)
{ return 1.0f / sqrtf(2.0f * asdx::F_PI * v) * exp(-(r * r) / (2.0f * v)); }

//-----------------------------------------------------------------------------
//      散乱計算
//-----------------------------------------------------------------------------
inline asdx::Vector3 Scatter(float r)
{
    // GPU Pro 360 Guide to Rendering, "5. Pre-Integrated Skin Shading", Appendix A.
    return 
          Gaussian(0.0064f * 1.414f, r) * asdx::Vector3(0.233f, 0.455f, 0.649f)
        + Gaussian(0.0484f * 1.414f, r) * asdx::Vector3(0.100f, 0.336f, 0.344f)
        + Gaussian(0.1870f * 1.414f, r) * asdx::Vector3(0.118f, 0.198f, 0.000f)
        + Gaussian(0.5670f * 1.414f, r) * asdx::Vector3(0.113f, 0.007f, 0.007f)
        + Gaussian(1.9900f * 1.414f, r) * asdx::Vector3(0.358f, 0.004f, 0.00001f)
        + Gaussian(7.4100f * 1.414f, r) * asdx::Vector3(0.078f, 0.00001f, 0.00001f);
}

//-------------------------------------------------------------------------------
//      Diffusionプロファイルを求める.
//-------------------------------------------------------------------------------
asdx::Vector3 IntegrateDiffuseScatterOnRing(float cosTheta, float skinRadius, int sampleCount)
{
    auto theta = acosf(cosTheta);
    asdx::Vector3 totalWeight(0.0f, 0.0f, 0.0f);
    asdx::Vector3 totalLight (0.0f, 0.0f, 0.0f);

    const auto inc = asdx::F_PI / float(sampleCount);
    auto a = -asdx::F_PIDIV2;

    while(a <= asdx::F_PIDIV2)
    {
        auto sampleAngle = theta + a;
        auto diffuse     = asdx::Saturate(cosf(sampleAngle));
        auto sampleDist  = abs(2.0f * skinRadius * sinf(a * 0.5f));
        auto weights     = Scatter(sampleDist);

        totalWeight += weights;
        totalLight  += weights * diffuse;
        a += inc;
    }

    return asdx::Vector3(
        totalLight.x / totalWeight.x,
        totalLight.y / totalWeight.y,
        totalLight.z / totalWeight.z);
}

//-----------------------------------------------------------------------------
//      ルックアップテーブル書き出し.
//-----------------------------------------------------------------------------
bool WriteLUT(const char* path, int w, int h, int s)
{
    std::vector<uint8_t> pixels;
    pixels.resize(w * h * 3);

    float stepR = 1.0f / float(h);
    float stepT = 2.0f / float(w);

    for(auto j=0; j<h; ++j)
    {
        for(auto i=0; i<w; ++i)
        {
            auto radius    = float(j + 0.5f) * stepR;
            auto curvature = 1.0f / radius;
            auto cosTheta  = -1.0f + float(i) * stepT;
            auto val       = IntegrateDiffuseScatterOnRing(cosTheta, curvature, s);

            // 書き出しを考慮して, 上下逆にして正しく出力されるようにする.
            auto idx = ((h - 1 - j) * w * 3) + (i * 3);

            pixels[idx + 0] = static_cast<uint8_t>(ToSRGB(val.x) * 255.0f + 0.5f);
            pixels[idx + 1] = static_cast<uint8_t>(ToSRGB(val.y) * 255.0f + 0.5f);
            pixels[idx + 2] = static_cast<uint8_t>(ToSRGB(val.z) * 255.0f + 0.5f);
        }
    }

    auto ret = stbi_write_tga(path, w, h, 3, pixels.data()) != 0;
    pixels.clear();

    return ret;
}

//-----------------------------------------------------------------------------
//      メインエントリーポイントです.
//-----------------------------------------------------------------------------
int main(int argc, char** argv)
{
    std::string path = "preintegrated_skin_lut.tga";
    int w = 256;
    int h = 256;
    int s = 4096;

    for(auto i=0; i<argc; ++i)
    {
        if (_stricmp(argv[i], "-w") == 0)
        {
            i++;
            auto iw = atoi(argv[i]);
            w = asdx::Clamp(iw, 1, 8192);
        }
        else if (_stricmp(argv[i], "-h") == 0)
        {
            i++;
            auto ih = atoi(argv[i]);
            h = asdx::Clamp(ih, 1, 8192);
        }
        else if (_stricmp(argv[i], "-s") == 0)
        {
            i++;
            auto is = atoi(argv[i]);
            s = asdx::Max(is, 1);
        }
    }

    return WriteLUT(path.c_str(), w, h, s) ? 0 : -1;
}

ちょっとググって調べた感じだと,https://j1jeong.wordpress.com/2018/06/20/pre-intergrated-skin-shading-in-colorspace/というBlog記事をみて,検証もせずにとりあえず鵜呑みで,sRGBで書き出してみました。
シェーダで使う場合は補正入れてリニアになるようにしてから使ってください。

NaNをなくす

良くライティング計算結果がNaNになってポストエフェクトでバグるというのが頻発していて困っていたのですが,
ようやく最近回避方法を知りました。
StackOverflowとかみていたら,

step(value, value) * value;

みたいにすると,NaNである場合には0になり,NaN以外の場合にはvalueで返すことが出来るらしい…。
と書いてあったのですが,よくよく考えてみるとstep(value, value)のところは確かにゼロとなるので問題ないなさそうに思えるのですが,NaNとの四則演算結果はNaNになるような気がします。実際に試してみたのですが,やっぱり駄目でした。

あとは,Googleのfilamentに

#define MEDIUMP_FLT_MAX    65504.0
#define saturateMediump(x) min(x, MEDIUMP_FLT_MAX)

みたいなのが書いてあったので,下記のような実装を試してみました。

static const float HALF_MAX = 65504.0;
clamp(value, 0.0f, HALF_MAX);

結果として,NaNが発生しなくなりバグが解消されるようになりました。
自分なりになぜNaNが発生しなくなるのかを考えてみたのですが,NaNの性質として !=以外の演算は常にfalse, !=演算は常にtrueという性質があるようです。よって,clamp()メソッドを使うと比較演算が実行されるので,この結果としてNaNをはじいて指定範囲内に収まると考えれば非常に納得がいきます。ここではclamp()を試しましたが,同様の理屈が通るのであればmax()などでも代用できるはずです。

さて,特に物理ベースレンダリングに移行する際に,GGXモデルなどを使うことが多いと思うのですが,定式化されているものが除算を含む形で定義されているので,実装する際に除算をしなくてはならないということが発生します。
GGXの計算する際に,分母がゼロになることにより,ゼロ除算が発生し,計算結果がNaNになる可能性があります。
また,pow()関数を使用している場合は,MSDNのドキュメント にも書いてあるように第1引数が0未満の場合はNaNになり,第1引数・第2引数がともにゼロの場合はハードウェア依存によりNaNが発生する可能性があります。

昔からよくある方法としてゼロ除算を発生させないために,例えば1e-7fなどのような小さな値を分母側に足しておき,ゼロにならないようにオフセットを掛けるという回避方法もあるのですが,この方法は除算には適用できるのだけれども,pow()関数には適用できません。

GGXを用いたライティング計算の場合は,ゼロ除算が発生する可能性があるため最初はこのオフセットを足し込むというやり方を取っていたのですが,これをやってしまうと計算結果が変わってしまい,ハイライトが鈍くなるなど見た目上に影響を及ぼすことが分かりました。
PBRやっているはずなのに,何故かなまった感じの画が出る場合には,下駄をはかせてハイライトを潰してしまっていないかどうか確認するのを強くお勧めします。
下駄をはかせている箇所を下駄を取って普通に除算し,最後に上記のようなNaNをはじく処理をいれることで,ハイライトがなまったり,ライティング以降のレンダリングパスでバグるという問題を解決することが出来ます。また,pow()関数を用いる場合にも適用でき,NaNをなくすることができるので,NaNで困っている方はぜひ試してみてください。

※もっとより良い方法ご存知であれば,是非教えてください!

DirectX Raytracing

GDC2018でDirectX Raytracingが発表されたようです。
実験段階のSDKが公開されています。
http://forums.directxtech.com/index.php?topic=5860.0

今日帰ったら,本の執筆の息抜きとしてちょっと触ってみようかなと思います。 

Direct3D12メモ

こんにちわ、Pocolです。
Direct3D12を触り始めたのですが,いくつかよく分からん用語があるのでメモとして残しておこうかなと思います。

Root Signature

リソースとシェーダーの対応付けを行うためのテーブルの定義で,レジスタとのバインド状態を指定するようです。
直接 Descriptor を格納すると 4個しか入らないので DescriptorTable と組み合わせて使うようです。
https://msdn.microsoft.com/en-us/library/windows/desktop/dn899208(v=vs.85).aspx

D3D12_ROOT_SIGNATURE_DESC
    ルートシグニチャのレイアウトを表します。
    https://msdn.microsoft.com/en-us/library/windows/desktop/dn986747(v=vs.85).aspx
D3D12_ROOT_PARAMTER
    ルートシグニチャのスロットを表します。
    https://msdn.microsoft.com/en-us/library/windows/desktop/dn879477(v=vs.85).aspx
D3D12_ROOT_PARAMTER_TYPE
    ルートシグニチャのスロットのタイプを表します。
    https://msdn.microsoft.com/en-us/library/windows/desktop/dn879478(v=vs.85).aspx
D3D12_ROOT_DESCRIPTOR_TABLE
    Descriptor Heapに次々と表示されるDescriptor範囲の集合としてDescriptor Tableのレイアウトを表します。
    https://msdn.microsoft.com/en-us/library/windows/desktop/dn859382(v=vs.85).aspx
D3D12_DESCRIPTOR_RANGE
  DescriptorTable内の与えられたDescriptorの範囲を定義する。
  https://msdn.microsoft.com/en-us/library/windows/desktop/dn859380(v=vs.85).aspx
D3D12_DESCRIPTOR_RANGE_TYPE
    範囲タイプを指定する。指定するデータ範囲がSRVを表すものなら,D3D12_DESCRIPTOR_RANGE_TYPE_SRVを指定する。
    https://msdn.microsoft.com/en-us/library/windows/desktop/dn859381(v=vs.85).aspx
D3D12_ROOT_CONSTANTS
    1つの定数バッファとして表示されるルートシグニチャ内の定数インラインを表します。
    https://msdn.microsoft.com/en-us/library/windows/desktop/dn879475(v=vs.85).aspx
D3D12_SHADER_VISIBILITY
    与えられたルートシグニチャのスロットのコンテンツにアクセス可能なシェーダを指定します。
  https://msdn.microsoft.com/en-us/library/windows/desktop/dn879482(v=vs.85).aspx
D3D12_STATIC_SAMPLER_DESC
    静的サンプラーを表します。
    https://msdn.microsoft.com/en-us/library/windows/desktop/dn986748(v=vs.85).aspx
D3D12_ROOT_SIGNATURE_FLAGS
    ルートシグニチャのレイアウトのオプションを指定します。
    https://msdn.microsoft.com/en-us/library/windows/desktop/dn879480(v=vs.85).aspx

ルートシグニチャの例
https://msdn.microsoft.com/en-us/library/windows/desktop/dn899123(v=vs.85).aspx

Descriptor

D3D12における単一リソースのバインディング(結び付け)の基本単位。
https://msdn.microsoft.com/en-us/library/windows/desktop/dn899109(v=vs.85).aspx
関係するものは以下のとおり。

* Index Buffer View
    D3D12_INDEX_BUFFER_VIEW 構造体を使用して作成。
    https://msdn.microsoft.com/en-us/library/windows/desktop/dn891445(v=vs.85).aspx
* Vertex Buffer View
    D3D12_VERTEX_BUFFER_VIEW 構造体を使用して作成。
    https://msdn.microsoft.com/en-us/library/windows/desktop/dn903819(v=vs.85).aspx
* Shader Resource View
    D3D12_SHADER_RESOURCE_VIEW_DESC 構造体を使用して作成。
    https://msdn.microsoft.com/en-us/library/windows/desktop/dn770406(v=vs.85).aspx
    作成には ID3D12Device::CreateShaderResourceView を呼び出します。
    https://msdn.microsoft.com/en-us/library/windows/desktop/dn788672(v=vs.85).aspx
* Constant Buffer View
    D3D12_CONSTANT_BUFFER_VIEW_DESC 構造体を使用して作成。
    https://msdn.microsoft.com/en-us/library/windows/desktop/dn770351(v=vs.85).aspx
    作成には ID3D12Device::CreateConstantBufferView を呼び出します。
    https://msdn.microsoft.com/en-us/library/windows/desktop/dn788659(v=vs.85).aspx
* Sampler
    D3D12_SAMPLER_DESC 構造体を使用して作成。
    https://msdn.microsoft.com/en-us/library/windows/desktop/dn770403(v=vs.85).aspx
    作成には ID3D12Device::CreateSampler を呼び出します。
    https://msdn.microsoft.com/en-us/library/windows/desktop/dn788671(v=vs.85).aspx
* Unordered Access View
    D3D12_UNORDERED_ACCESS_VIEW_DESC 構造体を使用して作成。
    https://msdn.microsoft.com/en-us/library/windows/desktop/dn770451(v=vs.85).aspx
    作成には ID3D12Device::CreateUnorderedAccessView を呼び出します。
    https://msdn.microsoft.com/en-us/library/windows/desktop/dn788674(v=vs.85).aspx
* Stream Output View
    D3D12_STREAM_OUTPUT_DESC 構造体を使用して作成。
    https://msdn.microsoft.com/en-us/library/windows/desktop/dn770410(v=vs.85).aspx
* Render Target View
    D3D12_RENDER_TARGET_VIEW_DESC 構造体を使用して作成。
    https://msdn.microsoft.com/en-us/library/windows/desktop/dn770389(v=vs.85).aspx
    作成には ID3D12Device::CreateRenderTargetView を呼び出します。
    https://msdn.microsoft.com/en-us/library/windows/desktop/dn788668(v=vs.85).aspx
* Depth Stencil View
    D3D12_DEPTH_STENCIL_VIEW_DESC 構造体を使用して作成。
    https://msdn.microsoft.com/en-us/library/windows/desktop/dn770357(v=vs.85).aspx
    作成には ID3D12Device::CreateDepthStencilView を呼び出します。
    https://msdn.microsoft.com/en-us/library/windows/desktop/dn788661(v=vs.85).aspx

Descriptor Heap

Descriptor Heap は Descriptor の連続するメモリ割り当てのコレクションです。
Descriptor Heap は Pipeline Stat Object(PSO), Shader Resource View (SRV), Unordered Access View (UAV), Constant Buffer View (CBV)などいくつかのオブジェクトタイプを含みます。

Resource Hazard

http://www.isus.jp/article/game-special/direct3d-12-overview-part-3-resource-binding/から引用。

何かが描画ターゲットやテクスチャーとバウンドされている場合、その処理中はランタイムとドライバーの両方が追跡しています。
ランタイムとドライバーがバインドを検出すると、古い設定をアンバインドし、新しい設定を適用します。
この方法でゲームは必要に応じてスイッチを作り、ソフトウェア・スタックはシーンの背後にあるスイッチを管理します。
さらに、ドライバーは描画ターゲットをテクスチャーとして使用するため、GPU パイプラインをフラッシュする必要があります。
そうしないと、GPU でリタイアする前にピクセルが読み取られると、コヒーレント状態を保てません。

上記の参照ページに詳細が載っているので、そちらを見たほうが早い。

Conservative Rasterization

Conservative Rasterization はピクセルレンダリングにある確実性を追加します。特に,衝突検出アルゴリズムに有用とのこと。
Conservative Rasterization は,描画されたプリミティブによって,少なくとも部分的に覆われるすべてのピクセルがラスタライズされ,ピクセルシェーダが起動します。
https://msdn.microsoft.com/en-us/library/windows/desktop/dn914594(v=vs.85).aspx

Rasterizer Ordered View

ラスタライズ処理によって同一画面座標上となったピクセルの陰影処理に起用されるピクセルシェーダの処理順を規定できる機能らしい。
GDCでは,OITの話をしていた。https://software.intel.com/sites/default/files/managed/4a/38/OIT-to-Volumetric-Shadow-Mapping.pdf

Direct3D12 開発環境構築 -プレビュー版-

こんにちわ、Pocolです。
いつの間にやら7月になってしまっていますね。歳をとると早いものですね。

さて,7月に入りVisual Studio 2015 の正式版リリースまで 9日を切りました。
もうすぐVisual Studio 2015 と Windows 10 が Microsoft から正式版としてリリースされます。
そして,先日 Window 10 Insider Preview と Window 10 Developer Preview Tool の最新版である Build 10166(現在は,Windows 10 RTM Build 10240が提供されています) が提供されました。
d3d12.h のヘッダファイルも前バージョンとの差分がなくなり,ようやく仕様として固まったのかな? という感じがします。

実はとある事情で,d3d12の記事を書いていなかったのは,仕様が変更される可能性があり対応は6月以降にしたほうが良いと。
とあるイベントで,とある会社の人から教えてもらったからなのであります。

そんなわけで,正式版リリースが間近に迫った今大規模な変更はおそらく可能性少ないので(…と思いたい)
Direct3D 12についてちまちま書き始めます。

まずは,リリースまで待てない人のために開発環境の構築方法について説明します。

必要なものは以下の通りです。

(1) Windows 10 Insider Preview
(2) Visual Studio Community 2015 RC
(3) Windows 10 Developer Tool Preview Tool
(4) Graphics Tools

まず,Windows 10 Insider Preview ですが,ぶっ壊れても良いマシンにインストールしましょう。まだ正式版ではないので,最悪データが全て吹っ飛んだりなどする可能性があります。
絶対に普段使うメインマシンにはインストールしないようにしましょう。
そんなわけで,別の物理マシンがあればよいのですが,ない場合は仮想マシンにインストールするという手もあります。
ちなみに自分の場合は仮想マシンにインストールしています。
Windows 10 Insider Preview は下記のマイクロソフトのサイトから*.isoでダウンロードできます。

http://windows.microsoft.com/ja-jp/windows/preview-iso

最新版であるWindows 10 RTM Build 10240 は下記からダウンロードできます。
http://microsoft-news.com/download-windows-10-rtm-build-10240-iso/

仮想マシンの場合は,インストールする際にこの*.isoファイルを選択しましょう。
物理マシンの場合は,*.isoをDVDに焼いてインストールするか,Windows USB/DVD Toolを使ってUSBからブートできるようにすると良いと思います。
このインストールあたりは,Windows 8とさほど変わらないので,世の中にあふれているWindows 8/8.1などのブートディスクの作成方法を参考にすると良いと思います。

続いてVisual Studio Community 2015 RCのインストールです。
下記のマイクロソフトのダウンロードページからRC版をダウンロードします。

https://www.visualstudio.com/ja-jp/downloads/visual-studio-2015-downloads-vs.aspx

ダウンロードしたら,ダウンロードした*.exeをクリックしてインストールを開始します。
このときPocolが注意したのは,「カスタム」でインストールすることです。

vs2015rc_01

次の画面では,Windows SDKにチェックを入れました。

vs2015rc_02

この設定に気をつけて,インストールボタンを押してインストールを開始します。
きちんとボタンを押す前にWindow SDKの古いバージョンがインストールされることを確認しましょう。

vs2015rc_03

しばらくすると,インストールが終わるので待ちましょう。
仮想マシンを使っている人は,インストールが全然進まない!とイラつく前にネットワークの設定がきちんとされているか確認しておくと良いと思います。
VM Wareにインストールしたときはすんなりいったのですが,Virtual Box にインストールしたときは,このネットワークの設定をしていなかったせいで,インストーラーがファイルをダウンロードできず全然インストールが進みませんでした。
ネットワーク設定をきちんとしたら,すんなりインストール終わりました。

続いて Windows 10 Developer Preview Tools の最新版を手動でインストールしましょう。
SDK自体は,下記のマイクロソフトのサイトからダウンロードできます。

https://dev.windows.com/en-us/downloads/windows-10-developer-preview

ダウンロードしたら,ダウンロードしたファイルをクリックしてインストールをしましょう。
この際,インストール前にSDKのバージョン番号が表示されますが10.0.10166であることを確認しましょう。
windowsdk_preview

最後に,Graphics Tools がインストールされていることを確認します。
インストールされていない場合はインストールしましょう。
確認方法は,スタートボタンからスタートメニューから「設定」→「システム」→「アプリと機能」→「オプション機能の管理」に行き,
「Graphics Tools – This adds DirectX Graphics Tools support」があることを確認します。
Graphics Tools が無い場合は「機能の追加」ボタンを押して追加を行いましょう。
gfxtool

これで開発環境が構築できました。
あとは,ガリガリとコーディングしていきましょう!

次の記事では,Direct3D 12ってなんぞ?というあたりを書きたいなぁと思っています。

DirectX12予習

一応,Later of this yearにDirectX12のプレビューがでるということなので,DirectX12についての予習をしておこうかなと思います。
…というか今年残りわずかなんだけど,きちんとプレビュー版をリリースしてくれるんかいな?

まずはYouTubeか,Channel9あたりでプレゼンを見ておきましょう。

ファイルに保存して,携帯等で見たい人はChannel9からダウンロードするのがお勧めです。

DirectX12 Preview

http://channel9.msdn.com/Blogs/DirectX-Developer-Blog/DirectX-Evolving-Microsoft-s-Graphics-Platform

 

Direct3D 12 API Preview

http://channel9.msdn.com/events/Build/2014/3-564

あとは,IntelのMicheal Coppockさんという方がBlogで”Direct3D 12 Overview”として説明をしてくださっています。
Overviewの方はPart1 ~ Part8まであるようです。
https://software.intel.com/en-us/blogs/author/1048217

ちなみに,iSUSさんが日本語訳してくているようで以下から閲覧できます。
Direct3D 12 概要 パート 1: ハードウェアに近く
http://www.isus.jp/article/game-special/direct3d-12-overview-part-2-pipeline-state-object/
Direct3D 12 概要 パート 3: リソースバインド
http://www.isus.jp/article/game-special/direct3d-12-overview-part-3-resource-binding/
Direct3D 12 概要 パート 4: ヒープとテーブル
http://www.isus.jp/article/game-special/direct3d-12-overview-part-4-heaps-and-tables/
Direct3D 12 概要 パート 5: バンドル
http://www.isus.jp/article/game-special/direct3d-12-overview-part-5-bundles/
Direct3D 12 概要 パート 6: コマンドリスト
http://www.isus.jp/article/game-special/direct3d-12-overview-part-6-command-lists/
Direct3D 12 概要 パート 7: ダイナミック・ヒープ
http://www.isus.jp/article/game-special/direct3d-12-overview-part-7-dynamic-heaps/
Direct3D 12 概要 パート 8: CPU の並列性
http://www.isus.jp/article/game-special/direct3d-12-overview-part-8-cpu-parallelism/