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

GIパストレーサsmallptのprocessing移植

Posted at

はじめに

上記サイトにsmallpt99(99行のsmall path tracing)があります。
これをProcessingに変換します。

環境

M1 Mac E2Core/P2Core
Processing 4.4.10

結果

Threads: 8 SAMPLES: 256
time(ms) = 15056
total rays = 739339043
rays / second = 4.9105944E7
5000万 ray/s
※バチクソにハマったとき

コード

オリジナル含め、壁は巨大な球の一部となっています。
image.png
300行こえています。

ray.pde
// =====================================
// smallpt風 Cornell Box Path Tracer
// - float に統一
// - Thread でマルチスレッド
// - 上下反転
// =====================================

int W = 320;
int H = 240;
int SAMPLES = 256;           // 1ピクセルのサンプル数
int THREADS;                 // CPU コア数
int MAX_DEPTH = 16;          // 再帰の最大深さ(保険)
final float S = 0.001f;      // シーン全体を 1/1000 に縮小 doubleからfloat精度にした都合で

PImage img;

// カメラ(スレッドから参照)
Ray cam;
Vec cx, cy;

// ray計算
long rayCount = 0;             // 全スレッド合計のray本数
Object rayLock = new Object(); // 合算用ロック(1スレッド1回だけ使う)


// -------------------- setup / draw --------------------
void setup() {
  size(320, 240);
  pixelDensity(1);
  img = createImage(W, H, RGB);

  THREADS = 8;//Runtime.getRuntime().availableProcessors();
  println("Threads:", THREADS, "  SAMPLES:", SAMPLES);

  cam = new Ray(
    new Vec(50f*S, 52f*S, 295.6f*S),
    new Vec(0f, -0.042612f, -1f).norm()  // 方向はそのまま
    );
  cx = new Vec(W * 0.5135f / H, 0, 0);
  cy = cx.cross(cam.d).norm().mul(0.5135f);

  println("Rendering start...");
  renderMultiThread();
  println("Rendering finished.");
}

void draw() {
  image(img, 0, 0);
}

// -------------------- ベクトル / レイ --------------------
class Vec {
  float x, y, z;
  Vec(float x_, float y_, float z_) {
    x=x_;
    y=y_;
    z=z_;
  }
  Vec() {
    this(0, 0, 0);
  }

  Vec add(Vec b) {
    return new Vec(x+b.x, y+b.y, z+b.z);
  }
  Vec sub(Vec b) {
    return new Vec(x-b.x, y-b.y, z-b.z);
  }
  Vec mul(float b) {
    return new Vec(x*b, y*b, z*b);
  }
  Vec mult(Vec b) {
    return new Vec(x*b.x, y*b.y, z*b.z);
  }

  Vec norm() {
    float n = sqrt(x*x + y*y + z*z);
    return mul(1.0f / n);
  }
  float dot(Vec b) {
    return x*b.x + y*b.y + z*b.z;
  }
  Vec cross(Vec b) {
    return new Vec(
      y*b.z - z*b.y,
      z*b.x - x*b.z,
      x*b.y - y*b.x
      );
  }
}

class Ray {
  Vec o, d;
  Ray(Vec o_, Vec d_) {
    o=o_;
    d=d_;
  }
}

enum Refl_t {
  DIFF, SPEC, REFR
}

// -------------------- 球 --------------------
class Sphere {
  float rad;
  Vec p, e, c;
  Refl_t refl;
  Sphere(float rad_, Vec p_, Vec e_, Vec c_, Refl_t refl_) {
    rad = rad_;
    p   = p_;
    e   = e_;
    c   = c_;
    refl= refl_;
  }

  float intersect(Ray r) {
    Vec op = p.sub(r.o);
    float eps = 1e-4f;
    float b = op.dot(r.d);
    float det = b*b - op.dot(op) + rad*rad;
    if (det < 0) return 0;
    det = sqrt(det);
    float t;
    if ((t = b - det) > eps) return t;
    if ((t = b + det) > eps) return t;
    return 0;
  }
}

// -------------------- シーン --------------------
Sphere[] spheres = {
  new Sphere(1e5f*S, new Vec((1e5f+1f)*S, 40.8f*S, 81.6f*S), new Vec(), new Vec(.75f, .25f, .25f), Refl_t.DIFF),
  new Sphere(1e5f*S, new Vec((-1e5f+99f)*S, 40.8f*S, 81.6f*S), new Vec(), new Vec(.25f, .25f, .75f), Refl_t.DIFF),
  new Sphere(1e5f*S, new Vec(50f*S, 40.8f*S, 1e5f*S), new Vec(), new Vec(.75f, .75f, .75f), Refl_t.DIFF),
  new Sphere(1e5f*S, new Vec(50f*S, 40.8f*S, (-1e5f+170f)*S), new Vec(), new Vec(), Refl_t.DIFF),
  new Sphere(1e5f*S, new Vec(50f*S, 1e5f*S, 81.6f*S), new Vec(), new Vec(.75f, .75f, .75f), Refl_t.DIFF),
  new Sphere(1e5f*S, new Vec(50f*S, (-1e5f+81.6f)*S, 81.6f*S), new Vec(), new Vec(.75f, .75f, .75f), Refl_t.DIFF),
  new Sphere(16.5f*S, new Vec(27f*S, 16.5f*S, 47f*S), new Vec(), new Vec(1, 1, 1).mul(.999f), Refl_t.SPEC),
  new Sphere(16.5f*S, new Vec(73f*S, 16.5f*S, 78f*S), new Vec(), new Vec(1, 1, 1).mul(.999f), Refl_t.REFR),
  new Sphere(600f*S, new Vec(50f*S, (681.6f-.27f)*S, 81.6f*S), new Vec(12, 12, 12), new Vec(), Refl_t.DIFF)
};

// -------------------- 共通ユーティリティ --------------------
float clamp(float x) {
  return x<0?0:x>1?1:x;
}
int   toInt(float x) {
  return int(pow(clamp(x), 1.0f/2.2f)*255.0f+0.5f);
}

// 最近ヒット
int intersectScene(Ray r, float[] tOut) {
  float inf = 1e20f;
  float t = inf;
  int id = -1;
  for (int i=0; i<spheres.length; i++) {
    float d = spheres[i].intersect(r);
    if (d!=0 && d<t) {
      t = d;
      id= i;
    }
  }
  tOut[0] = t;
  return id;
}

// -------------------- マルチスレッドワーカー --------------------
class RenderWorker extends Thread {
  int id;
  int seed;  // スレッド専用 RNG 状態
  long localRayCount = 0;   // このスレッドが使ったrayの本数

  RenderWorker(int id_) {
    id = id_;
    seed = 1234567 + id * 12345;   // 適当な初期値
  }

  // 線形合同法 RNG(0〜1)
  float rnd() {
    seed = seed * 1664525 + 1013904223;
    int bits = (seed >>> 9) & 0x007FFFFF;    // 上位 23bit
    return bits / (float)(1<<23);
  }

  Vec radiance(Ray r, int depth) {
    localRayCount++;
    if (depth > MAX_DEPTH) return new Vec();

    float[] tArr = new float[1];
    int idHit = intersectScene(r, tArr);
    if (idHit == -1) return new Vec();  // miss

    Sphere obj = spheres[idHit];
    Vec x  = r.o.add(r.d.mul(tArr[0]));
    Vec n  = x.sub(obj.p).norm();
    Vec nl = (n.dot(r.d)<0) ? n : n.mul(-1);
    Vec f  = obj.c;

    float p = max(f.x, max(f.y, f.z));

    depth++;
    if (depth > 5) {
      if (rnd() < p) {
        f = f.mul(1.0f/p);
      } else {
        return obj.e;
      }
    }

    if (obj.refl == Refl_t.DIFF) {
      float r1  = TWO_PI * rnd();
      float r2  = rnd();
      float r2s = sqrt(r2);

      Vec w = nl;
      Vec u = ((abs(w.x) > 0.1f ? new Vec(0, 1, 0) : new Vec(1, 0, 0)).cross(w)).norm();
      Vec v = w.cross(u);

      Vec d = u.mul(cos(r1)*r2s)
        .add(v.mul(sin(r1)*r2s))
        .add(w.mul(sqrt(1.0f-r2)))
        .norm();

      return obj.e.add(f.mult(radiance(new Ray(x, d), depth)));
    }

    if (obj.refl == Refl_t.SPEC) {
      Vec reflDir = r.d.sub(n.mul(2.0f * n.dot(r.d)));
      return obj.e.add(f.mult(radiance(new Ray(x, reflDir), depth)));
    }

    // 屈折
    Ray reflRay = new Ray(x, r.d.sub(n.mul(2.0f * n.dot(r.d))));
    boolean into = n.dot(nl) > 0.0f;

    float nc = 1.0f;
    float nt = 1.5f;
    float nnt = into ? nc/nt : nt/nc;
    float ddn = r.d.dot(nl);
    float cos2t = 1.0f - nnt*nnt*(1.0f - ddn*ddn);

    if (cos2t < 0) {
      return obj.e.add(f.mult(radiance(reflRay, depth)));
    }

    Vec tdir = r.d.mul(nnt)
      .sub(n.mul((into?1.0f:-1.0f)*(ddn*nnt+sqrt(cos2t))))
      .norm();

    float a  = nt-nc;
    float b  = nt+nc;
    float R0 = (a*a)/(b*b);
    float c  = 1.0f - (into ? -ddn : tdir.dot(n));
    float Re = R0 + (1.0f - R0)*pow(c, 5.0f);
    float Tr = 1.0f - Re;
    float P  = 0.25f + 0.5f*Re;
    float RP = Re / P;
    float TP = Tr / (1.0f - P);

    if (depth > 2) {
      if (rnd() < P) {
        return obj.e.add(f.mult(radiance(reflRay, depth).mul(RP)));
      } else {
        return obj.e.add(f.mult(radiance(new Ray(x, tdir), depth).mul(TP)));
      }
    }

    return obj.e.add(
      f.mult(
      radiance(reflRay, depth).mul(Re)
      .add(radiance(new Ray(x, tdir), depth).mul(Tr))
      )
      );
  }

  public void run() {
    for (int y = id; y < H; y += THREADS) {
      int row = (H-1-y) * W;   // 上下反転
      for (int x = 0; x < W; x++) {
        Vec col = new Vec();

        for (int sy=0; sy<2; sy++) {
          for (int sx=0; sx<2; sx++) {
            Vec sub = new Vec();
            for (int s=0; s<SAMPLES; s++) {
              float r1 = 2.0f * rnd();
              float dx = (r1 < 1.0f) ? sqrt(r1)-1.0f : 1.0f - sqrt(2.0f-r1);
              float r2 = 2.0f * rnd();
              float dy = (r2 < 1.0f) ? sqrt(r2)-1.0f : 1.0f - sqrt(2.0f-r2);

              float u = (((sx+0.5f+dx)/2.0f + x) / (float)W - 0.5f);
              float v = (((sy+0.5f+dy)/2.0f + y) / (float)H - 0.5f);

              Vec d = cx.mul(u)
                .add(cy.mul(v))
                .add(cam.d)
                .norm();

              Vec sample = radiance(new Ray(cam.o.add(d.mul(140.0f*S)), d), 0);
              sub = sub.add(sample.mul(1.0f/SAMPLES));
            }
            col = col.add(new Vec(
              clamp(sub.x),
              clamp(sub.y),
              clamp(sub.z)
              ).mul(0.25f));
          }
        }

        int idx = row + x;
        img.pixels[idx] = color(
          toInt(col.x),
          toInt(col.y),
          toInt(col.z)
          );
      }
    }
    // このスレッド分のrayを合計に足す
    synchronized (rayLock) {
      rayCount += localRayCount;
    }
  }
}

// -------------------- レンダリング起動 --------------------
void renderMultiThread() {
  img.loadPixels();

  RenderWorker[] ws = new RenderWorker[THREADS];
  for (int i=0; i<THREADS; i++) {
    ws[i] = new RenderWorker(i);
    ws[i].start();
  }
  for (int i=0; i<THREADS; i++) {
    try {
      ws[i].join();
    }
    catch(Exception e) {
    }
  }

  img.updatePixels();
  
  // 結果表示(起動からの経過時間でOKという指定)
  long dt = millis();
  println("time(ms)      = " + dt);
  println("total rays    = " + rayCount);
  println("rays / second = " + (rayCount * 1000.0 / dt));
}
0
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
0
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?