EmbreeによるCPUレンダリング(その２) シェーディング


# 概要
その１でレイとオブジェクトの交差判定を紹介しました。次にシェーディングについて解説していきます。交差判定で得られた面のUV座標から法線を求めてシェーディングしていきます。

# 実装
[その１ レイの交差判定](http://qiita.com/denirou/items/1933dbb1dceb59e3aa3c) で、紹介したコードを改変しながら説明していきます。

シーンのコンテキストは色んなところで参照するので、とりあえずグローバル変数で登録しておきます。

```cpp
static RTCScene g_scene;
...
//初期化関数などで、シーンを作成し、グローバル変数のシーンコンテキストに渡す。
g_scene = rtcDeviceNewScene(hDev, RTC_SCENE_DYNAMIC, RTC_INTERSECT1);
```

交差判定後、頂点アトリビュートの補間を行うために　[”ユーザーが確保したメモリーにアクセスしてジオメトリメッシュの登録”](http://qiita.com/denirou/items/1933dbb1dceb59e3aa3c#%E3%83%A6%E3%83%BC%E3%82%B6%E3%83%BC%E3%81%8C%E7%A2%BA%E4%BF%9D%E3%81%97%E3%81%9F%E3%83%A1%E3%83%A2%E3%83%AA%E3%83%BC%E3%81%AB%E3%82%A2%E3%82%AF%E3%82%BB%E3%82%B9%E3%81%97%E3%81%A6%E3%82%B8%E3%82%AA%E3%83%A1%E3%83%88%E3%83%AA%E3%83%A1%E3%83%83%E3%82%B7%E3%83%A5%E3%81%AE%E7%99%BB%E9%8C%B2) のジオメトリの登録を使います。
交差判定後に、当たった箇所の挙動の登録は `rtcSetIntersectionFilterFunction`で行います。
`rtcSetIntersectionFilterFunction`の第三引数に交差判定後に呼び出されるコールバック関数を指定します。

```cpp
	rtcSetBuffer(hScene, geomID, RTC_VERTEX_BUFFER, VBs, 0, sizeof(vertex_t));
	rtcSetBuffer(hScene, geomID, RTC_INDEX_BUFFER, IBs, 0, sizeof(XMINT3));
	rtcSetIntersectionFilterFunction(hScene, geomID, &IntersectFilter);
```

交差判定後にフィルタ関数で得られた結果を返すためのパラメータをRTCRay構造体を拡張します。今回は色だけ返すように設定します。

```cpp
struct ray_t
{
public:
    DirectX::XMVECTOR org; //レイのスタート位置
    DirectX::XMVECTOR dir; //レイの方向
    float tnear;       //レイのスタート
    float tfar;        //レイの終端（ヒットした箇所との距離）
    float time;        //モーションブラー用
    int   mask;        //マスク
public:
    DirectX::XMVECTOR Ng; //ヒット面の方向
    float u; //ヒット面の座標U
    float v; //ヒット面の座標ｖ
    int   geomID; //ヒットジオメトリID（当たっていなければ-1）
    int   primID; //ヒットジオメトリの面ID（当たっていなければ-1）
    int   instID; //ヒットしたインスタンスのID（当たっていなければ-1）
public:
    DirectX::XMVECTOR color; //交差判定後に描かれる色
};
```

とりあえず面の交差判定したUV値を返すコードを記述してその動きを確かめてみます。

```cpp
void IntersectFilter(void *pData, RTCRay& ray)
{
	using namespace DirectX;
	ray.color = XMVectorSet(r.u, r.v, 0, 1);
}
```

PixelRender関数を下記のように書き換えます。

```cpp
DirectX::XMVECTOR PixelRender(RTCScene hScene,  DirectX::XMVECTOR eye, DirectX::XMVECTOR dir)
{
    ray_t ray;
    ZeroMemory(&ray, sizeof(ray));
    ray.org = pos;
    ray.dir = dir;
    ray.geomID = RTC_INVALID_GEOMETRY_ID;
    ray.instID = RTC_INVALID_GEOMETRY_ID;
    ray.primID = RTC_INVALID_GEOMETRY_ID;
    ray.tnear = 0.01f;
    ray.tfar = 10000.0f;
    ray.mask = 0xFFFFFFFF;
    ray.time = 0.0f;
    rtcIntersect(hScene, *(RTCRay*)&ray);
    //交差判定後の色を返す、当たっていなければ白色を返す。
    return (ray.geomID != -1) ? ray.color : DirectX::XMVectorSet(1,1,1,1);
}
```

交差判定後の色を表示させると、面のUVが描き込まれているのが確認できます。

![face_uv.png](https://qiita-image-store.s3.amazonaws.com/0/93694/7f2b276f-f470-09c1-9a15-e64a5a04a940.png)

下記のコードでray_t::Ng(面法線)を描画させると、こんな感じで描画されます。

```cpp
void IntersectFilter(void *pData, RTCRay& ray)
{
	using namespace DirectX;
    
    ray_t& r = *(ray_t*)&ray;
	//法線を可視化できるように color = n * 0.5 + 0.5;
	XMVECTOR nv = XMVectorScale(r.Ng, 0.5f);
	nv = XMVectorAdd(nv, XMVectorSet(0.5f, 0.5f, 0.5f, 0.0f));
	nv = XMVectorSetW(nv, 1.0);
	r.color = nv;
}
```

![face_Ng.png](https://qiita-image-store.s3.amazonaws.com/0/93694/c0da8148-ec88-b384-6da6-ceb934c93273.png)

## 頂点アトリビュートを補完する

```cpp
//視線方向に法線を回転させすグローバル偏す
static XMVECTOR g_normalRotate;
...
...
    //ViewMatrix から視線方向への法線の回転させる。
    XMMATRIX normalMatrix = XMMatrixInverse(nullptr, viewMatrix);
	g_normalRotate = XMQuaternionRotationMatrix(normalMatrix);
```

頂点アトリビュート（位置、法線、テクスチャ座標など）を補完するためには、`rtcInterpolate`を使います。
`rtcSetBuffer`でセットした頂点アトリビュートから交差判定した位置の補間情報を得ることができます。
とりあえず頂点法線の補間情報を描いてみます。

```cpp
void IntersectFilter(void *pData, RTCRay& ray)
{
	using namespace DirectX;

	ray_t& r = *(ray_t*)&ray;

	vertex_t v;
	size_t numFloats = sizeof(v) / sizeof(float);
	rtcInterpolate(g_scene, ray.geomID, ray.primID, ray.u, ray.v,
		RTC_VERTEX_BUFFER, (float*)&v, nullptr, nullptr, numFloats);

    //スクリーン方向の法線ベクトル
	XMVECTOR nv = XMLoadFloat3(&v.normal);
	nv = XMVector3Rotate(nv, g_normalRotate);
	nv = XMVector3Normalize(nv);

	//法線を可視化できるように color = n * 0.5 + 0.5;
	nv = XMVectorScale(nv, 0.5f);
	nv = XMVectorAdd(nv, XMVectorSet(0.5f, 0.5f, 0.5f, 0.0f));
	nv = XMVectorSetW(nv, 1.0);
	r.color = nv;
}
```
![interp_nrm.png](https://qiita-image-store.s3.amazonaws.com/0/93694/7ce4a25a-ac6c-28bb-475f-624f4b080ed1.png)

実際に`rtcInterpolate`得た頂点法線の補間情報を使って、マテリアルにディフューズ値0.8を与えた、カメラ方向からのライティングをしてみます。

```cpp
void IntersectFilter(void *pData, RTCRay& ray)
{
	using namespace DirectX;

	vertex_t v;
	size_t numFloats = sizeof(v) / sizeof(float);
	rtcInterpolate(g_scene, ray.geomID, ray.primID, ray.u, ray.v,
		RTC_VERTEX_BUFFER, (float*)&v, nullptr, nullptr, numFloats);
	
	ray_t& r = *(ray_t*)&ray;

	XMVECTOR diffuse = XMVectorSet(0.8, 0.8, 0.8, 1.0);
	diffuse = XMVectorPow(diffuse, XMVectorSet(2.2, 2.2, 2.2, 1.0));

	XMVECTOR nv = XMLoadFloat3(&v.normal);
	nv = XMVector3Rotate(nv, g_normalRotate);
	nv = XMVector3Normalize(nv);
	XMVECTOR dot = XMVector3Dot(XMVectorSet(0, 0, -1, 1), nv);
	dot = XMVectorClamp(dot, XMVectorSet(0, 0, 0, 1), XMVectorSet(1, 1, 1, 1));
	dot = XMVectorMultiply(diffuse, dot);
	dot = XMVectorPow(dot, XMVectorSet(1. / 2.2, 1. / 2.2, 1. / 2.2, 1.0));
	r.color = dot;
}
```
![diffuse.png](https://qiita-image-store.s3.amazonaws.com/0/93694/ce108cc3-1ccd-969d-0a3e-4e11f1fbfdf4.png)

以上、シェーディングはこんな感じです。

`rtcSetIntersectionFilterFunction`と`rtcInterpolate` を使えばGPUプログラムのピクセルシェーダ的な動作を組むことができます。