概要
その1でレイとオブジェクトの交差判定を紹介しました。次にシェーディングについて解説していきます。交差判定で得られた面のUV座標から法線を求めてシェーディングしていきます。
実装
その1 レイの交差判定 で、紹介したコードを改変しながら説明していきます。
シーンのコンテキストは色んなところで参照するので、とりあえずグローバル変数で登録しておきます。
static RTCScene g_scene;
...
//初期化関数などで、シーンを作成し、グローバル変数のシーンコンテキストに渡す。
g_scene = rtcDeviceNewScene(hDev, RTC_SCENE_DYNAMIC, RTC_INTERSECT1);
交差判定後、頂点アトリビュートの補間を行うために ”ユーザーが確保したメモリーにアクセスしてジオメトリメッシュの登録” のジオメトリの登録を使います。
交差判定後に、当たった箇所の挙動の登録は rtcSetIntersectionFilterFunction
で行います。
rtcSetIntersectionFilterFunction
の第三引数に交差判定後に呼び出されるコールバック関数を指定します。
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構造体を拡張します。今回は色だけ返すように設定します。
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; //ヒット面の座標v
int geomID; //ヒットジオメトリID(当たっていなければ-1)
int primID; //ヒットジオメトリの面ID(当たっていなければ-1)
int instID; //ヒットしたインスタンスのID(当たっていなければ-1)
public:
DirectX::XMVECTOR color; //交差判定後に描かれる色
};
とりあえず面の交差判定したUV値を返すコードを記述してその動きを確かめてみます。
void IntersectFilter(void *pData, RTCRay& ray)
{
using namespace DirectX;
ray.color = XMVectorSet(r.u, r.v, 0, 1);
}
PixelRender関数を下記のように書き換えます。
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が描き込まれているのが確認できます。
下記のコードでray_t::Ng(面法線)を描画させると、こんな感じで描画されます。
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;
}
頂点アトリビュートを補完する
//視線方向に法線を回転させすグローバル偏す
static XMVECTOR g_normalRotate;
...
...
//ViewMatrix から視線方向への法線の回転させる。
XMMATRIX normalMatrix = XMMatrixInverse(nullptr, viewMatrix);
g_normalRotate = XMQuaternionRotationMatrix(normalMatrix);
頂点アトリビュート(位置、法線、テクスチャ座標など)を補完するためには、rtcInterpolate
を使います。
rtcSetBuffer
でセットした頂点アトリビュートから交差判定した位置の補間情報を得ることができます。
とりあえず頂点法線の補間情報を描いてみます。
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;
}
実際にrtcInterpolate
得た頂点法線の補間情報を使って、マテリアルにディフューズ値0.8を与えた、カメラ方向からのライティングをしてみます。
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;
}
以上、シェーディングはこんな感じです。
rtcSetIntersectionFilterFunction
とrtcInterpolate
を使えばGPUプログラムのピクセルシェーダ的な動作を組むことができます。