RadeonRaysを使ってみた
どうも,チョコです.
最近レイトレーシングをちょっとやってるんですけど,流石に自分でメモリ構造体を作るのが不効率すぎて辛い.昨日Siggraph Asia行った時に見たRenderMan22が速すぎて衝撃を受けました.
そこで,AMDが開発したRadeonRays(以下RR)というオープンソースなライブラリを見つけました.しかもopencl1.2なのでいろんな環境で動きますね.
(ここでレイトレプロのみなさんへの忠告です.この記事を書いた僕はくそレイトレ―シング初心者です.画像を見て吐き気がしたら見るのをやめましょう.)
さて,RRのサンプルを動かしてみよう.ここではVisualStudioを使ってCornellBoxサンプルをコンパイルして見ましょう.
おう.すごい.
しかし,これだとレイトレっぽくないので,とりあえず反射を入れましょう.
反射とは要するに,レイをdとしたら,表面の法線nに対して
d’=d−2(d⋅n)n
を計算すればいいですね.また,この時,最終の色は元の色と乗算します.
レイトレっぽくなりましたね.
次に,反射をDiffuseにしましょう.ここではLambertを使います.Lambertは簡単にいうと,入射角と法線の内積で明るさが決まりますね.まずは単純に,表面から半球の法線をランダムに決めて内積を取って乗算します.ただし,ここでは光源を円に変え,円に当たらなかったレイは黒にします.
ノイズ高いですね(適当).
Diffuseの次は当然Specularですね.ここではBeckmannを使います.説明が面倒なのでググって.ただし,普通にランダムに取ると収束まで結構時間がかかりますので,ここではImportance Samplingを行います.
BeckmannのImportance Samplingはここを見てください.
LambertのImportance Samplingはここを見てください.
Specularだけだとこうなります(Beckmann coefficient = 0.2).
ピカピカですね.
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).
ノイズ高いですね(これしかコメント言えん).
箱は見飽きたので,サルを入れてみましょう.サルは金属にしましょう.
ふむ....
サルは可愛くないので,シャロちゃんを入れましょう().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]); | |
} |
するとこのようになります(適当).
かわいい...
まだまだダメですが満足したのでやめます.最後にコメントです.
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 : chokopan