2009-07-17

レイトレーサーを作るシリーズ:

視線との交点から光源までの間にさえぎるものがなかったら光源計算をする。

if (visible(pos, light.pos)) {
col = col.add(light.shading(pos, normal, material));
}

影をつけるためにシーンに複数の物体を登録できるようにしてたらやたら長くなった…。

boolean rendererd = false;
int renderY = 0;
int LineBuf[];

// 値を範囲内に収める
float clamp(float val, float min, float max) {
if (val < min) {
return min;
} else if (val > max) {
return max;
} else {
return val;
}
}

int fcolor(float r, float g, float b) {
int ir = (int)(clamp(r, 0, 1) * 255.99);
int ig = (int)(clamp(g, 0, 1) * 255.99);
int ib = (int)(clamp(b, 0, 1) * 255.99);
return color(ir, ig, ib);
}

// 線分と球の交差判定
float intersect_line_sphere(vec orig, vec dir, vec sphere_center, float sphere_radius) {
vec c = sphere_center;
vec s = orig;
vec d = dir;
float r = sphere_radius;
vec v = s.sub(c);

float dd = sq(v.dot(d)) - (v.dot(v) - sq(r));
if (dd < 0) {
return -1; // not intersect
} else {
float sqdd = sqrt(dd);
float t1 = -v.dot(d) - sqdd;
return t1; // maybe intersect
}
}

// 線分と無限平面の交差判定
float intersect_line_plane(vec orig, vec dir, float a, float b, float c, float d) {
float h = orig.x * a + orig.y * b + orig.z * c + d;
if (h < 0) {
return -1;
} else {
float inner = dir.x * a + dir.y * b + dir.z * c;
if (inner >= 0) {
return -1;
} else {
return h / (-inner);
}
}
}

// マテリアル
class Material {
vec diffuse = new vec(0, 0, 0);
vec emissive = new vec(0, 0, 0);

Material(float difr, float difg, float difb) {
diffuse.set(difr, difg, difb);
emissive.set(0, 0, 0);
}
};

// 物体
class Object {
Material material;

float intersect_line(vec pNrm, vec orig, vec dir) {
return -1;
}
}

// 球
class Sphere extends Object {
vec center;
float radius;

Sphere(vec _center, float _radius, Material _material) {
center = _center;
radius = _radius;
material = _material;
}

float intersect_line(vec normal, vec orig, vec dir) {
float t = intersect_line_sphere(orig, dir, center, radius);
if (t >= 0) {
vec pos = new vec(orig.add(dir.scale(t)));
normal.set(pos.sub(center).normal());
}
return t;
}
}

// 無限平面
class Plane extends Object {
float a, b, c, d; // 平面方程式の係数

Plane(float _a, float _b, float _c, float _d, Material _material) {
a = _a;
b = _b;
c = _c;
d = _d;
material = _material;
}

float intersect_line(vec normal, vec orig, vec dir) {
float t = intersect_line_plane(orig, dir, a, b, c, d);
if (t >= 0) {
normal.set(a, b, c);
}
return t;
}
}

// 点光源
class PointLight {
vec pos;
vec col;

PointLight(vec _pos, vec _col) {
pos = _pos;
col = _col;
}

vec shading(vec hitpos, vec normal, Material material) {
vec lv = pos.sub(hitpos).normal();
float f = max(normal.dot(lv), 0);
return new vec(f * material.diffuse.x * col.x, f * material.diffuse.y * col.y, f * material.diffuse.z * col.z);
}
}

// 交差情報
class Intersect {
float t = -1;
vec normal = new vec(0, 0, 0);
Object obj = null;

boolean is_enable() { return t >= 0; }
}

// シーン
class Scene {
ArrayList objs;
ArrayList lights;

Scene() {
objs = new ArrayList();
lights = new ArrayList();
}

void add_obj(Object obj) {
objs.add(obj);
}

void add_light(PointLight light) {
lights.add(light);
}

// 一番近くで交差するオブジェクトを探す
void find_nearest_intersect_with_ray(Intersect isect, vec orig, vec dir) {
isect.t = -1;
vec normal = new vec(0, 0, 0);
for (int i=objs.size(); --i>=0; ) {
Object obj = (Object)objs.get(i);
float t = obj.intersect_line(normal, orig, dir);
if (t >= 0 && (isect.t < 0 || t < isect.t)) {
isect.t = t;
isect.normal.set(normal);
isect.obj = obj;
}
}
}

// 目標物までの間に何かに当たるか?
boolean intersect_anything(vec orig, vec target) {
vec diff = target.sub(orig);
float len = diff.length();
vec dir = diff.scale(1.0 / len);
vec normal = new vec(0, 0, 0);
for (int i=objs.size(); --i>=0; ) {
Object obj = (Object)objs.get(i);
float t = obj.intersect_line(normal, orig, dir);
if (t >= 0 && t < len) {
return true;
}
}
return false;
}

// シェーディング
int shading(vec pos, vec normal, Material material) {
vec col = new vec(0, 0, 0);
for (int i=lights.size(); --i>=0; ) {
PointLight light = (PointLight)lights.get(i);
if (visible(pos, light.pos)) {
col = col.add(light.shading(pos, normal, material));
}
}
return fcolor(col.x, col.y, col.z);
}

// 見えるか?
boolean visible(vec pos, vec target) {
return !intersect_anything(pos, target);
}
}

vec eyepos = new vec(0, 0, -1);
Scene scene;

void setup() {
size(256, 256);
LineBuf = new int[width];

scene = new Scene();
scene.add_obj(new Sphere(new vec(0, 0, 0), 0.5, new Material(1, 1, 1)));
scene.add_obj(new Plane(0, 1, 0, 0.5, new Material(1, 1, 1)));
scene.add_light(new PointLight(new vec( 500, 1000, -500), new vec(1, 0, 0)));
scene.add_light(new PointLight(new vec(-500, 1000, -500), new vec(0, 1, 0)));
scene.add_light(new PointLight(new vec( 0, 1000, +100), new vec(0, 0, 1)));
}

void draw() {
if (rendererd) { return; }

render_line(LineBuf, width, height, renderY);
copy_line(LineBuf, renderY);
if (++renderY >= height) {
rendererd = true;
}
}

void copy_line(int[] img, int y) {
loadPixels();
for (int i = 0; i < width; ++i) {
pixels[y * width + i] = img[i];
}
updatePixels();
}

void render_line(int[] img, int width, int height, int iy) {
float w2 = width / 2;
float h2 = height / 2;
float xofs = w2 - 0.5;
float yofs = h2 - 0.5;
float py = -((iy - yofs) / h2); // flip Y

vec normal = new vec(0, 0, 0);
Intersect isect = new Intersect();
for (int j=0; j<width; ++j) {
float px = (j - xofs) / w2;
vec raydir = (new vec(px, py, 1)).normal();

scene.find_nearest_intersect_with_ray(isect, eyepos, raydir);
if (isect.is_enable()) {
vec pos = eyepos.add(raydir.scale(isect.t));
img[j] = scene.shading(pos, isect.normal, isect.obj.material);
} else {
img[j] = color(0, 0, 0);
}
}
}
  • Javaで関数から複数の値を返したいときは、クラスを作ってやらないといけないとか…メンドイ。