ガラス球の面積がFPS影響大
レイ数
FPSでるとき、
・CPUが冷えてる
・JIT最適化?
環境
Processing 4.4.10
MacBookPro (M1, P=8Core, E=2Core)
コード
Size : 512 x 512
Depth: 6
ミラー球、ガラス球
Hard Shadowあり
ガラスのエッジにリムライト追加
擬似的な色収差あり
raytrace.pde
int W = 512;
int H = 512;
int MAX_DEPTH = 6;
int THREADS = 10;//Runtime.getRuntime().availableProcessors();
Vec camPos = new Vec(0, 1.5, 4); // カメラ位置
Vec lightPos = new Vec(2, 4, 4); // ポイントライト
void setup() {
size(512, 512);
pixelDensity(1);
println("CPU Threads:", THREADS);
}
void draw() {
// ライトを円運動させる(お好みで固定でもOK)
lightPos.x = 4 * cos(frameCount * 0.05);
lightPos.z = 4 * sin(frameCount * 0.05);
camPos.x = 0.5 * cos(frameCount * 0.02);
camPos.y = 1.5+ 0.1 * cos(frameCount * 0.03);
renderFrame();
updatePixels();
surface.setTitle("Classic Ray Tracer | FPS : " + nf(frameRate, 1, 2));
}
// ======================
// レンダリング
// ======================
void renderFrame() {
loadPixels();
Thread[] workers = new Thread[THREADS];
for (int t = 0; t < THREADS; t++) {
final int id = t;
workers[t] = new Thread(() -> {
for (int y = id; y < H; y += THREADS) {
int row = y * W;
for (int x = 0; x < W; x++) {
// 正規化スクリーン座標 (0..1)
float u = (x + 0.5f) / W;
float v = (y + 0.5f) / H;
// シンプルなピンホールカメラ
// もとのパストレーサと同じ向きに揃える
Vec dir = norm(new Vec(u - 0.5, 0.5 - v, -1));
Ray ray = new Ray(camPos, dir);
Vec col = trace(ray, 0);
// ガンマ補正 (1/2)
float r = sqrt(max(0, col.x));
float g = sqrt(max(0, col.y));
float b = sqrt(max(0, col.z));
int ir = (int)(255 * constrain(r, 0, 1));
int ig = (int)(255 * constrain(g, 0, 1));
int ib = (int)(255 * constrain(b, 0, 1));
pixels[row + x] = color(ir, ig, ib);
}
}
});
workers[t].start();
}
for (Thread t : workers) {
try { t.join(); } catch(Exception e){}
}
}
// ======================
// レイトレ本体
// ======================
Vec trace(Ray r, int depth) {
if (depth > MAX_DEPTH) return new Vec(0, 0, 0);
Hit h = intersect(r);
if (h == null) return sky(r.d);
// マテリアル別の処理
if (h.mat == 0) {
// 床(拡散)
return shadeDiffuse(h, r);
} else if (h.mat == 1) {
// 金属球:反射
return shadeMetal(h, r, depth);
} else { // h.mat == 2
// ガラス球:反射+屈折
return shadeGlass(h, r, depth);
}
}
// 拡散床のシェーディング(直接照明+シャドウ+アンビエント)
Vec shadeDiffuse(Hit h, Ray r) {
Vec base = h.col; // チェッカーパターン色
// アンビエント
float ambient = 0.1;
// ライト方向
Vec toL = sub(lightPos, h.p);
float distL = length(toL);
Vec L = mul(toL, 1.0 / distL);
// シャドウ判定
boolean shadow = inShadow(h.p, L, distL);
float diff = 0;
if (!shadow) {
diff = max(0, dot(h.n, L));
}
float c = ambient + diff;
return mul(base, c);
}
// 金属球:ほぼ純粋な反射+少しだけローカルなハイライト
Vec shadeMetal(Hit h, Ray r, int depth) {
// 反射成分
Vec reflDir = reflect(r.d, h.n);
Ray reflRay = new Ray(offsetPoint(h.p, h.n, reflDir), reflDir);
Vec reflCol = trace(reflRay, depth + 1);
// ローカルな Phong シェーディング(擬似的な金属光沢)
Vec toL = norm(sub(lightPos, h.p)); // ライト方向
Vec V = norm(sub(camPos, h.p)); // 視線方向
Vec H = norm(add(toL, V)); // ハーフベクトル
float diff = max(0, dot(h.n, toL)); // 拡散成分
float spec = pow(max(0, dot(h.n, H)), 64.0); // 鋭いハイライト
Vec baseMetal = new Vec(0.9, 0.9, 0.9); // 金属の地の色
Vec local = add(
mul(baseMetal, diff), // 拡散
new Vec(spec, spec, spec) // スペキュラ
);
// 反射 80% + ローカルシェーディング 20%
return add(mul(reflCol, 0.8), mul(local, 0.2));
}
// ガラス球:フレネルで反射と屈折をブレンド
// ガラス球:フレネル + 分光風色収差
Vec shadeGlass(Hit h, Ray r, int depth) {
// Fresnel は中間の IOR で計算
float iorMid = 1.5;
float fr = fresnel(r.d, h.n, iorMid);
// -------- 反射は従来どおり 1 本 --------
Vec reflDir = reflect(r.d, h.n);
Ray reflRay = new Ray(offsetPoint(h.p, h.n, reflDir), reflDir);
Vec reflCol = trace(reflRay, depth + 1);
// -------- 分光風:RGB で屈折率を少し変える --------
float iorR = 1.52;
float iorG = 1.50;
float iorB = 1.48;
// 赤用
Vec dRefrR = refract(r.d, h.n, iorR);
Ray refrRayR = new Ray(offsetPoint(h.p, h.n, dRefrR), dRefrR);
Vec colR = trace(refrRayR, depth + 1);
// 緑用
Vec dRefrG = refract(r.d, h.n, iorG);
Ray refrRayG = new Ray(offsetPoint(h.p, h.n, dRefrG), dRefrG);
Vec colG = trace(refrRayG, depth + 1);
// 青用
Vec dRefrB = refract(r.d, h.n, iorB);
Ray refrRayB = new Ray(offsetPoint(h.p, h.n, dRefrB), dRefrB);
Vec colB = trace(refrRayB, depth + 1);
// R,G,B それぞれ別の屈折画像から拾う
Vec refrCol = new Vec(colR.x, colG.y, colB.z);
// ほんのり水色ガラスのティント
Vec tint = new Vec(0.9, 0.95, 1.0);
refrCol = mul(refrCol, tint);
// フレネルブレンド(反射 + 屈折)
Vec col = add(mul(reflCol, fr), mul(refrCol, 1.0 - fr));
// -------- Rim ライトで輪郭を強調 --------
float rim = 1.0 - abs(dot(h.n, norm(r.d)));
rim = pow(rim, 2.5);
col = add(col, mul(new Vec(1, 1, 1), rim * 0.15));
// 軽く抑えたいなら 0.98〜1.0 で調整
// col = mul(col, 0.98);
return col;
}
// ローカルスペキュラ(Phong風、簡易)
Vec localSpecular(Hit h) {
Vec toL = sub(lightPos, h.p);
float distL = length(toL);
Vec L = mul(toL, 1.0 / distL);
if (inShadow(h.p, L, distL)) {
return new Vec(0, 0, 0);
}
// 視線方向
Vec V = norm(sub(camPos, h.p));
Vec H = norm(add(L, V));
float nh = max(0, dot(h.n, H));
float spec = pow(nh, 64); // ハイライトの鋭さ
return new Vec(spec, spec, spec);
}
// ======================
// シャドウ判定
// ======================
boolean inShadow(Vec p, Vec L, float distL) {
// 少しだけオフセットして自己交差を回避
Vec origin = offsetPoint(p, L, L);
Ray shadowRay = new Ray(origin, L);
Hit h = intersect(shadowRay);
if (h == null) return false;
// ヒット点がライトより手前なら遮蔽あり
if (h.t > 0 && h.t < distL - 1e-2) {
return true;
}
return false;
}
// ======================
// オブジェクト交差
// ======================
Hit intersect(Ray r) {
Hit best = null;
float tmin = 1e9;
// 金属球(左)
float t1 = intersectSphere(new Vec(-1.0, 1, 0), 0.75, r);
if (t1 > 0 && t1 < tmin) {
Vec p = add(r.o, mul(r.d, t1));
Vec n = norm(sub(p, new Vec(-1.2, 1, 0)));
best = new Hit(p, n, new Vec(1, 1, 1), 1, t1);
tmin = t1;
}
// ガラス球(右)
float t2 = intersectSphere(new Vec(1.0, 1.2, 0), 0.5, r);
if (t2 > 0 && t2 < tmin) {
Vec p = add(r.o, mul(r.d, t2));
Vec n = norm(sub(p, new Vec(1.2, 1, 0))); // 元コードと同様の中心
best = new Hit(p, n, new Vec(1, 1, 1), 2, t2);
tmin = t2;
}
// 床(y = 0 の無限平面)
float t3 = intersectPlane(0, r);
if (t3 > 0 && t3 < tmin) {
Vec p = add(r.o, mul(r.d, t3));
int cx = floor(p.x);
int cz = floor(p.z);
boolean c = ((cx + cz) & 1) == 0;
Vec col = c ? new Vec(1, 1, 1) : new Vec(0.1, 0.1, 0.1);
best = new Hit(p, new Vec(0, 1, 0), col, 0, t3);
tmin = t3;
}
return best;
}
// 球
float intersectSphere(Vec c, float rad, Ray r) {
Vec oc = sub(r.o, c);
float b = dot(oc, r.d);
float c2 = dot(oc, oc) - rad * rad;
float h = b * b - c2;
if (h < 0) return -1;
return -b - sqrt(h);
}
// 平面 y = y0
float intersectPlane(float y0, Ray r) {
if (abs(r.d.y) < 1e-4) return -1;
float t = (y0 - r.o.y) / r.d.y;
return (t > 0) ? t : -1;
}
// ======================
// 光学計算
// ======================
Vec reflect(Vec d, Vec n) {
return sub(d, mul(n, 2 * dot(d, n)));
}
Vec refract(Vec d, Vec n, float ior) {
float cosi = constrain(dot(d, n), -1, 1);
float etai = 1, etat = ior;
Vec nn = n;
if (cosi < 0) {
cosi = -cosi;
} else {
float tmp = etai; etai = etat; etat = tmp;
nn = mul(n, -1);
}
float eta = etai / etat;
float k = 1 - eta * eta * (1 - cosi * cosi);
if (k < 0) return reflect(d, n);
return add(mul(d, eta), mul(nn, eta * cosi - sqrt(k)));
}
float fresnel(Vec d, Vec n, float ior) {
float cosi = constrain(dot(d, n), -1, 1);
float etai = 1, etat = ior;
if (cosi > 0) {
float tmp = etai; etai = etat; etat = tmp;
}
float sint = etai / etat * sqrt(max(0, 1 - cosi * cosi));
if (sint >= 1) return 1;
float cost = sqrt(max(0, 1 - sint * sint));
cosi = abs(cosi);
float Rs = ((etat * cosi) - (etai * cost)) / ((etat * cosi) + (etai * cost));
float Rp = ((etai * cosi) - (etat * cost)) / ((etai * cosi) + (etat * cost));
return (Rs * Rs + Rp * Rp) * 0.5;
}
// ======================
// 背景
// ======================
Vec sky(Vec d) {
float t = 0.5 * (d.y + 1);
return new Vec(lerp(1, 0.5, t), lerp(1, 0.7, t), lerp(1, 1.0, t));
}
// ======================
// 構造体
// ======================
class Vec {
float x, y, z;
Vec(float a, float b, float c) { x = a; y = b; z = c; }
}
class Ray {
Vec o, d;
Ray(Vec a, Vec b) { o = a; d = b; }
}
class Hit {
Vec p, n, col;
int mat; // 0=床, 1=金属, 2=ガラス
float t; // 交差距離
Hit(Vec a, Vec b, Vec c, int m, float tt) {
p = a; n = b; col = c; mat = m; t = tt;
}
}
// ======================
// ベクトル演算
// ======================
Vec add(Vec a, Vec b) { return new Vec(a.x + b.x, a.y + b.y, a.z + b.z); }
Vec sub(Vec a, Vec b) { return new Vec(a.x - b.x, a.y - b.y, a.z - b.z); }
Vec mul(Vec a, float s) { return new Vec(a.x * s, a.y * s, a.z * s); }
Vec mul(Vec a, Vec b) { return new Vec(a.x * b.x, a.y * b.y, a.z * b.z); }
float dot(Vec a, Vec b) { return a.x * b.x + a.y * b.y + a.z * b.z; }
Vec norm(Vec a) {
float inv = 1.0 / sqrt(dot(a, a));
return new Vec(a.x * inv, a.y * inv, a.z * inv);
}
float length(Vec a) {
return sqrt(dot(a, a));
}
// 交差誤差回避のためのオフセット
Vec offsetPoint(Vec p, Vec n, Vec dir) {
float side = (dot(dir, n) >= 0) ? 1.0 : -1.0;
return add(p, mul(n, side * 1e-3));
}
メモ:ドンキPC(P8)で18FPS
