RadeonRaysを使ってみた

どうも,チョコです.

最近レイトレーシングをちょっとやってるんですけど,流石に自分でメモリ構造体を作るのが不効率すぎて辛い.昨日Siggraph Asia行った時に見たRenderMan22が速すぎて衝撃を受けました.

そこで,AMDが開発したRadeonRays(以下RR)というオープンソースなライブラリを見つけました.しかもopencl1.2なのでいろんな環境で動きますね.

(ここでレイトレプロのみなさんへの忠告です.この記事を書いた僕はくそレイトレ―シング初心者です.画像を見て吐き気がしたら見るのをやめましょう.)

さて,RRのサンプルを動かしてみよう.ここではVisualStudioを使ってCornellBoxサンプルをコンパイルして見ましょう.

DmqIdRdUwAA2Wmt.jpg large

おう.すごい.

しかし,これだとレイトレっぽくないので,とりあえず反射を入れましょう.

反射とは要するに,レイをdとしたら,表面の法線nに対して

d’=d2(dn)n

を計算すればいいですね.また,この時,最終の色は元の色と乗算します.

DmqWF7MV4AEb7vS.jpg large

レイトレっぽくなりましたね.

次に,反射をDiffuseにしましょう.ここではLambertを使います.Lambertは簡単にいうと,入射角と法線の内積で明るさが決まりますね.まずは単純に,表面から半球の法線をランダムに決めて内積を取って乗算します.ただし,ここでは光源を円に変え,円に当たらなかったレイは黒にします.

cwe

ノイズ高いですね(適当).

Diffuseの次は当然Specularですね.ここではBeckmannを使います.説明が面倒なのでググって.ただし,普通にランダムに取ると収束まで結構時間がかかりますので,ここではImportance Samplingを行います.

BeckmannのImportance Samplingはここを見てください.

LambertのImportance Samplingはここを見てください.

Specularだけだとこうなります(Beckmann coefficient = 0.2).

s

ピカピカですね.

DiffuseとSpecularの組み合わせはShlick’s Fresnelを使います.とりあえず単純に,このFresnelの値を確率のカットオフとして,ランダム値がこの値より小さかったらSpecular反射を解散して,そうでなかったらDiffuseということにします.こういうことです.

float3 ro = ray[k].o.xyz;
float3 rd = normalize(ray[k].d.xyz);
float frr = (1 - mat.ior) / (1 + mat.ior);
frr *= frr;
float fres = frr + (1 - frr)*pow(1 - dot(-rd, norm), 5);
if (Randf(&rnd) < (1 - ((1 - fres) * (1 - mat.specular)))) {
//sample specular
Beckmann(&norm, 1 - mat.gloss, rng);
norm = rd - 2 * dot(rd, norm.xyz) / (norm.x*norm.x + norm.y*norm.y + norm.z*norm.z) * norm;
diff_col = (float3)(1, 1, 1);
}
else {
//sample diffuse
float3 t1, t2;
GetTangents(norm, &t1, &t2);
float3 rh = Lambert(&rnd, rng);
norm = t1*rh.x + t2*rh.y + norm*rh.z;
}
ocol[k].xyz *= diff_col;
ray[k].d.xyz = norm;
pos.xyz += norm * 0.00001f;
ray[k].o.xyz = pos.xyz;

すると結果はこうなります(Specular = 0.4).

wv

ノイズ高いですね(これしかコメント言えん).

箱は見飽きたので,サルを入れてみましょう.サルは金属にしましょう.

Screenshot (381)

ふむ....

サルは可愛くないので,シャロちゃんを入れましょう().CornellBoxも消して,Skyboxを入れましょう.

レイが表面にぶつからなかった時,方向を使ってSkyboxから色をこのように取ります.ただし,レイがまだ反射していないものなら黒にします.

static float3 GetSkyColor(float3 dir, __global float* bg) {
const int bgw = 2048;
const int bgh = bgw/2;
float2 rf = -(float2)(dir.x, dir.z);
float cx = acos(clamp(rf.x, -0.9999f, 0.9999f))/(PI*2);
cx = mix(1-cx, cx, ceil(rf.y));
float sy = asin(clamp(dir.y, -0.9999f, 0.9999f))/PI;
int x = (int)(cx * bgw);
int y = (int)((sy + 0.5f) * bgh);
int off = clamp(x + y * bgw, 0, bgw * bgh);
return (float3)(bg[off*3], bg[off*3 + 1], bg[off*3 + 2]);
}
view raw sky.cl hosted with ❤ by GitHub

するとこのようになります(適当).

Screenshot (385)

かわいい...


まだまだダメですが満足したのでやめます.最後にコメントです.

RRのGeneratePerspectiveRaysは視野を気にしないので,表示の縦横比を帰ると変になります.そのため,GLMの行列を使って書き換えます.

Mat4x4 _MVP;
int screenW, screenH;
CLWBuffer<RR::ray> GeneratePrimaryRays() {
struct Cam
{
Mat4x4 ip = glm::transpose(glm::inverse(_MVP));
Vec2 zcap = Vec2(1, 1000);
} cam = Cam();
CLWBuffer<RR::ray> ray_buf = CLWBuffer<RR::ray>::Create(context, CL_MEM_READ_WRITE, screenW * screenH);
CLWBuffer<Cam> cam_buf = CLWBuffer<Cam>::Create(context, CL_MEM_READ_ONLY, 1, &cam);
CLWKernel kernel = program.GetKernel("GenerateCameraRays");
kernel.SetArg(0, ray_buf);
kernel.SetArg(1, cam_buf);
kernel.SetArg(2, screenW);
kernel.SetArg(3, screenH);
size_t gs[] = { static_cast<size_t>((screenW + 7) / 8 * 8), static_cast<size_t>((screenH + 7) / 8 * 8) };
size_t ls[] = { 8, 8 };
context.Launch2D(0, gs, ls, kernel);
context.Flush(0);
return ray_buf;
}

typedef struct _Ray {
/// xyz - origin, w - max range
float4 o;
/// xyz - direction, w - time
float4 d;
/// x - ray mask, y - activity flag
int2 extra;
/// Padding
float2 padding;
} Ray;
typedef struct _Mat {
float4 a;
float4 b;
float4 c;
float4 d;
} Mat;
static float4 MatVec(const Mat m, const float4 v) {
return (float4)(
dot(m.a, v),
dot(m.b, v),
dot(m.c, v),
dot(m.d, v)
);
}
__kernel void GenerateCameraRays(__global Ray* rays,
__global const Camera* cam,
int width,
int height) {
int2 globalid;
globalid.x = get_global_id(0);
globalid.y = get_global_id(1);
if (globalid.x < width && globalid.y < height) {
float x = -1.f + 2.f * (float)globalid.x / (float)width;
float y = -1.f + 2.f * (float)globalid.y / (float)height;
int k = globalid.y * width + globalid.x;
float4 cn = MatVec(cam->ip, (float4)(x, y, -1, 1));
cn.xyz /= cn.w;
rays[k].o.xyz = cn.xyz;
float4 cf = MatVec(cam->ip, (float4)(x, y, 1, 1));
cf.xyz /= cf.w;
rays[k].d.xyz = normalize(cf.xyz - cn.xyz);
rays[k].o.w = cam->zcap.y;
rays[k].extra.x = 0xFFFFFFFF;
rays[k].extra.y = 0xFFFFFFFF;
}
}

Importance Samplingが入っているLambertとBeckmannのコードです.ただし,乱数生成器はXorshiftを使います.

static uint xorshift32(uint rnd) {
rnd ^= rnd << 13;
rnd ^= rnd >> 17;
rnd ^= rnd << 5;
return rnd;
}
static float Randf(uint* r) {
*r = xorshift32(*r);
return 0.0001f*(*r % 10000);
}
static void GetTangents(float3 n, float3* t1, float3* t2) {
*t1 = normalize(cross(n, (fabs(n.x) > 0.9999f) ? (float3)(0, 0, 1) : (float3)(1, 0, 0)));
*t2 = normalize(cross(n, *t1));
}
static float3 Lambert(uint* r) {
float r1 = sqrt(Randf(r));
float r2 = Randf(r) * 2 * PI;
return (float3)(r1 * cos(r2), r1 * sin(r2), sqrt(max(1 - r1, 0.f)));
}
static void Beckmann(float3* nrm, float rough, uint* rnd) {
if (rough == 0) return;
float a = rough * rough;
float t1 = atan(sqrt(-a*log(1 - Randf(rnd))));
float t2 = Randf(rnd) * 2 * PI;
float3 n1, n2;
GetTangents(*nrm, &n1, &n2);
*nrm = *nrm * cos(t1) + (n1 * cos(t2) + n2 * sin(t2)) * sin(t1);
}

では.

Posted on: 2018年12月8日, by :