LoginSignup
7
4

More than 5 years have passed since last update.

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

Last updated at Posted at 2015-11-22

概要

その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が描き込まれているのが確認できます。

face_uv.png

下記のコードで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;
}

face_Ng.png

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

//視線方向に法線を回転させすグローバル偏す
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;
}

interp_nrm.png

実際に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;
}

diffuse.png

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

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

7
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
4