1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

レイトレーシング

Last updated at Posted at 2025-11-29

image.png

ガラス球の面積が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

1
0
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
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?