Create a gist now

Instantly share code, notes, and snippets.

@yhara /ray.rb
Created Feb 21, 2017

# 1. とりあえず色を出す
IMAGE_WIDTH = 256
IMAGE_HEIGHT = 256
IMAGE_DEPTH = 256
class Vec
def initialize(x, y, z)
@x, @y, @z = x, y, z
end
attr_reader :x, :y, :z
def vnormalize!
len = vlength()
if len > 1.0e-17
r_len = 1.0 / len
@x *= r_len
@y *= r_len
@z *= r_len
end
self
end
def vlength
Math.sqrt(@x * @x + @y * @y + @z * @z)
end
end
class Ray
def initialize(origin, dir)
@origin, @dir = origin, dir
end
attr_reader :dir
end
puts("P3")
puts("#{IMAGE_WIDTH} #{IMAGE_HEIGHT}")
puts("#{IMAGE_DEPTH-1}")
# t: 0 ~ 1
def color(t)
t = 0 if t < 0
t = 1 if t > 1
ret = IMAGE_DEPTH * t.to_f
return (ret == IMAGE_DEPTH ? (IMAGE_DEPTH-1) : ret).to_i
end
IMAGE_HEIGHT.times do |row|
IMAGE_WIDTH.times do |col|
x = col.to_f / (IMAGE_WIDTH / 2) - 1.0
y = (IMAGE_HEIGHT-row).to_f / (IMAGE_HEIGHT / 2) - 1.0
ray = Ray.new(Vec.new(0.0, 0.0, 5.0),
Vec.new(x, y, -1.0).vnormalize!)
#p x: x, y: y, dir: ray.dir
r, g, b = color(ray.dir.x), color(ray.dir.y), color(ray.dir.z)
print("#{r} #{g} #{b} ")
end
end
# 2. 白い円を出す
IMAGE_WIDTH = 256
IMAGE_HEIGHT = 256
IMAGE_DEPTH = 256
class Vec
def initialize(x, y, z)
@x, @y, @z = x, y, z
end
attr_reader :x, :y, :z
def vsub(b)
Vec.new(@x - b.x, @y - b.y, @z - b.z)
end
def vdot(b)
@x * b.x + @y * b.y + @z * b.z
end
def vnormalize!
len = vlength()
if len > 1.0e-17
r_len = 1.0 / len
@x *= r_len
@y *= r_len
@z *= r_len
end
self
end
def vlength
Math.sqrt(@x * @x + @y * @y + @z * @z)
end
end
class Ray
def initialize(origin, dir)
@origin, @dir = origin, dir
end
attr_reader :origin, :dir
end
class Sphere
def initialize(radius, position, color)
@radius, @position, @color = radius, position, color
end
attr_reader :radius, :position, :color
def intersect?(ray)
rs = ray.origin.vsub(@position)
b = rs.vdot(ray.dir)
c = rs.vdot(rs) - @radius * @radius
d = b * b - c
return d > 0 && (-b - Math.sqrt(d)) > 0
end
end
puts("P3")
puts("#{IMAGE_WIDTH} #{IMAGE_HEIGHT}")
puts("#{IMAGE_DEPTH-1}")
# t: 0 ~ 1
def color(t)
t = 0 if t < 0
t = 1 if t > 1
ret = IMAGE_DEPTH * t.to_f
return (ret == IMAGE_DEPTH ? (IMAGE_DEPTH-1) : ret).to_i
end
def print_col(c)
print("#{color(c.x)} #{color(c.y)} #{color(c.z)} ")
end
BLACK = Vec.new(0, 0, 0)
sphere = Sphere.new(1.0, Vec.new(0, 0, 0), Vec.new(1, 1, 1))
IMAGE_HEIGHT.times do |row|
IMAGE_WIDTH.times do |col|
x = col.to_f / (IMAGE_WIDTH / 2) - 1.0
y = (IMAGE_HEIGHT-row).to_f / (IMAGE_HEIGHT / 2) - 1.0
ray = Ray.new(Vec.new(0.0, 0.0, 5.0),
Vec.new(x, y, -1.0).vnormalize!)
c = if sphere.intersect?(ray)
sphere.color
else
BLACK
end
print_col(c)
end
end
# 3. 球に影をつける
IMAGE_WIDTH = 256
IMAGE_HEIGHT = 256
IMAGE_DEPTH = 256
class Vec
def initialize(x, y, z)
@x, @y, @z = x, y, z
end
attr_reader :x, :y, :z
def vadd(b)
Vec.new(@x + b.x, @y + b.y, @z + b.z)
end
def vsub(b)
Vec.new(@x - b.x, @y - b.y, @z - b.z)
end
def vmul(t)
Vec.new(@x * t, @y * t, @z * t)
end
def vdot(b)
@x * b.x + @y * b.y + @z * b.z
end
def vnormalize!
len = vlength()
if len > 1.0e-17
r_len = 1.0 / len
@x *= r_len
@y *= r_len
@z *= r_len
end
self
end
def vlength
Math.sqrt(@x * @x + @y * @y + @z * @z)
end
end
class Ray
def initialize(origin, dir)
@origin, @dir = origin, dir
end
attr_reader :origin, :dir
end
# 交点
class Isect
def initialize(hit=false)
@hit = hit
@hit_point = Vec.new(0, 0, 0)
@normal = Vec.new(0, 0, 0)
@color = Vec.new(0, 0, 0)
end
attr_accessor :hit, :hit_point, :normal, :color
end
class Sphere
def initialize(radius, position, color)
@radius, @position, @color = radius, position, color
end
attr_reader :radius, :position, :color
def intersection(ray)
rs = ray.origin.vsub(@position)
b = rs.vdot(ray.dir)
c = rs.vdot(rs) - @radius * @radius
d = b * b - c
if d > 0 && (t = -b - Math.sqrt(d)) > 0
i = Isect.new(true)
i.hit_point = ray.origin.vadd(ray.dir.vmul(t))
i.normal = i.hit_point.vsub(@position).vnormalize!
d = clamp(Vec.new(1, 1, 1).vnormalize!.vdot(i.normal), 0.1, 1.0)
i.color = @color.vmul(d)
else
i = Isect.new(false)
end
i
end
end
puts("P3")
puts("#{IMAGE_WIDTH} #{IMAGE_HEIGHT}")
puts("#{IMAGE_DEPTH-1}")
def clamp(t, min, max)
if t < min
min
elsif t > max
max
else
t
end
end
# t: 0 ~ 1
def color(t)
t = 0 if t < 0
t = 1 if t > 1
ret = IMAGE_DEPTH * t.to_f
return (ret == IMAGE_DEPTH ? (IMAGE_DEPTH-1) : ret).to_i
end
def print_col(c)
print("#{color(c.x)} #{color(c.y)} #{color(c.z)} ")
end
BLACK = Vec.new(0, 0, 0)
sphere = Sphere.new(1.0, Vec.new(0, 0, 0), Vec.new(1, 1, 1))
IMAGE_HEIGHT.times do |row|
IMAGE_WIDTH.times do |col|
x = col.to_f / (IMAGE_WIDTH / 2) - 1.0
y = (IMAGE_HEIGHT-row).to_f / (IMAGE_HEIGHT / 2) - 1.0
ray = Ray.new(Vec.new(0.0, 0.0, 5.0),
Vec.new(x, y, -1.0).vnormalize!)
i = sphere.intersection(ray)
print_col(i.color)
end
end
# 4. 床を出す
IMAGE_WIDTH = 256
IMAGE_HEIGHT = 256
IMAGE_DEPTH = 256
class Vec
def initialize(x, y, z)
@x, @y, @z = x, y, z
end
attr_reader :x, :y, :z
def vadd(b)
Vec.new(@x + b.x, @y + b.y, @z + b.z)
end
def vsub(b)
Vec.new(@x - b.x, @y - b.y, @z - b.z)
end
def vmul(t)
Vec.new(@x * t, @y * t, @z * t)
end
def vdot(b)
@x * b.x + @y * b.y + @z * b.z
end
def vnormalize!
len = vlength()
if len > 1.0e-17
r_len = 1.0 / len
@x *= r_len
@y *= r_len
@z *= r_len
end
self
end
def vlength
Math.sqrt(@x * @x + @y * @y + @z * @z)
end
end
class Ray
def initialize(origin, dir)
@origin, @dir = origin, dir
end
attr_reader :origin, :dir
end
# 交点
class Isect
def initialize(hit=false)
@hit = hit
@hit_point = Vec.new(0, 0, 0)
@normal = Vec.new(0, 0, 0)
@color = Vec.new(0, 0, 0)
@distance = 1.0e+30
end
attr_accessor :hit, :hit_point, :normal, :color, :distance
end
class Sphere
def initialize(radius, position, color)
@radius, @position, @color = radius, position, color
end
attr_reader :radius, :position, :color
def intersect!(ray, isect)
rs = ray.origin.vsub(@position)
b = rs.vdot(ray.dir)
c = rs.vdot(rs) - (@radius * @radius)
d = b * b - c
if d > 0
t = -b - Math.sqrt(d)
if t > 0 && t < isect.distance
isect.hit_point = ray.origin.vadd(ray.dir.vmul(t))
isect.normal = isect.hit_point.vsub(@position).vnormalize!
isect.color = @color.vmul(clamp(LIGHT.vdot(isect.normal), 0.1, 1.0))
isect.distance = t
end
end
end
end
class Plane
def initialize(position, normal, color)
@position, @normal, @color = position, normal, color
end
def intersect!(ray, isect)
d = -(@position.vdot(@normal))
v = ray.dir.vdot(@normal)
t = -(ray.origin.vdot(@normal) + d) / v
if t > 0 && t < isect.distance
isect.hit_point = ray.origin.vadd(ray.dir.vmul(t))
isect.normal = @normal
d = clamp(LIGHT.vdot(isect.normal), 0.1, 1.0)
m = isect.hit_point.x % 2
n = isect.hit_point.z % 2
d *= 0.5 if ((m > 1 && n > 1) || (m < 1 && n < 1))
f = 1.0 - [isect.hit_point.z.abs, 25.0].min * 0.04
isect.color = @color.vmul(d * f)
isect.distance = t
end
end
end
def clamp(t, min, max)
if t < min
min
elsif t > max
max
else
t
end
end
# t: 0 ~ 1
def color(t)
t = 0 if t < 0
t = 1 if t > 1
ret = IMAGE_DEPTH * t.to_f
return (ret == IMAGE_DEPTH ? (IMAGE_DEPTH-1) : ret).to_i
end
def print_col(c)
print("#{color(c.x)} #{color(c.y)} #{color(c.z)} ")
end
BLACK = Vec.new(0, 0, 0)
LIGHT = Vec.new(0.577, 0.577, 0.577)
puts("P3")
puts("#{IMAGE_WIDTH} #{IMAGE_HEIGHT}")
puts("#{IMAGE_DEPTH-1}")
plane = Plane.new(Vec.new(0, -1, 0), Vec.new(0, 1, 0), Vec.new(1, 1, 1))
t = 0
sphere1 = Sphere.new(0.5, Vec.new( 0.0, -0.5, Math.sin(0)), Vec.new(1, 0, 0))
sphere2 = Sphere.new(1.0, Vec.new( 2.0, 0.0, Math.cos(t*0.666)), Vec.new(0, 1, 0))
sphere3 = Sphere.new(1.5, Vec.new(-2.0, 0.5, Math.cos(t*0.333)), Vec.new(0, 0, 1))
IMAGE_HEIGHT.times do |row|
IMAGE_WIDTH.times do |col|
x = col.to_f / (IMAGE_WIDTH / 2) - 1.0
y = (IMAGE_HEIGHT-row).to_f / (IMAGE_HEIGHT / 2) - 1.0
ray = Ray.new(Vec.new(0.0, 2.0, 6.0),
Vec.new(x, y, -1.0).vnormalize!)
i = Isect.new(false)
sphere1.intersect!(ray, i)
sphere2.intersect!(ray, i)
sphere3.intersect!(ray, i)
plane.intersect!(ray, i)
print_col(i.color)
end
end
# 5. 反射させる
IMAGE_WIDTH = 512
IMAGE_HEIGHT = 512
IMAGE_DEPTH = 256
class Vec
def initialize(x, y, z)
@x, @y, @z = x, y, z
end
attr_reader :x, :y, :z
def vadd(b)
Vec.new(@x + b.x, @y + b.y, @z + b.z)
end
def vsub(b)
Vec.new(@x - b.x, @y - b.y, @z - b.z)
end
def vmul(t)
Vec.new(@x * t, @y * t, @z * t)
end
def vmulti(b)
Vec.new(@x * b.x, @y * b.y, @z * b.z)
end
def vdot(b)
@x * b.x + @y * b.y + @z * b.z
end
def vcross(b)
Vec.new(@y * b.z - @z * b.y,
@z * b.x - @x * b.z,
@x * b.y - @y * b.x)
end
def vnormalize!
len = vlength()
if len > 1.0e-17
r_len = 1.0 / len
@x *= r_len
@y *= r_len
@z *= r_len
end
self
end
def vlength
Math.sqrt(@x * @x + @y * @y + @z * @z)
end
def reflect(normal)
self.vadd(normal.vmul(-2 * normal.vdot(self)))
end
end
class Ray
def initialize(origin, dir)
@origin, @dir = origin, dir
end
attr_reader :origin, :dir
end
# 交点
class Isect
def initialize()
@hit = 0
@hit_point = Vec.new(0, 0, 0)
@normal = Vec.new(0, 0, 0)
@color = Vec.new(0, 0, 0)
@distance = 1.0e+30
@ray_dir = Vec.new(0, 0, 0)
end
attr_accessor :hit, :hit_point, :normal, :color, :distance, :ray_dir
end
class Sphere
def initialize(radius, position, color)
@radius, @position, @color = radius, position, color
end
attr_reader :radius, :position, :color
def intersect!(ray, isect)
rs = ray.origin.vsub(@position)
b = rs.vdot(ray.dir)
c = rs.vdot(rs) - (@radius * @radius)
d = b * b - c
t = -b - Math.sqrt(d.abs)
if d > 0 && t > EPS && t < isect.distance
isect.hit_point = ray.origin.vadd(ray.dir.vmul(t))
isect.normal = isect.hit_point.vsub(@position).vnormalize!
isect.color = @color.vmul(clamp(LIGHT.vdot(isect.normal), 0.1, 1.0))
isect.distance = t
isect.hit += 1
isect.ray_dir = ray.dir
end
end
end
class Plane
def initialize(position, normal, color)
@position, @normal, @color = position, normal, color
end
def intersect!(ray, isect)
d = -(@position.vdot(@normal))
v = ray.dir.vdot(@normal)
t = -(ray.origin.vdot(@normal) + d) / v
if t > EPS && t < isect.distance
isect.hit_point = ray.origin.vadd(ray.dir.vmul(t))
isect.normal = @normal
d = clamp(LIGHT.vdot(isect.normal), 0.1, 1.0)
m = isect.hit_point.x % 2
n = isect.hit_point.z % 2
d *= 0.5 if ((m > 1 && n > 1) || (m < 1 && n < 1))
f = 1.0 - [isect.hit_point.z.abs, 25.0].min * 0.04
isect.color = @color.vmul(d * f)
isect.distance = t
isect.hit += 1
isect.ray_dir = ray.dir
end
end
end
def clamp(t, min, max)
if t < min
min
elsif t > max
max
else
t
end
end
# t: 0 ~ 1
def color(t)
t = 0 if t < 0
t = 1 if t > 1
ret = IMAGE_DEPTH * t.to_f
return (ret == IMAGE_DEPTH ? (IMAGE_DEPTH-1) : ret).to_i
end
def print_col(c)
print("#{color(c.x)} #{color(c.y)} #{color(c.z)} ")
end
# 光の方向
LIGHT = Vec.new(0.577, 0.577, 0.577)
EPS = 0.0001
# 最大反射回数
MAX_REF = 4
PLANE = Plane.new(Vec.new(0, -1, 0), Vec.new(0, 1, 0), Vec.new(1, 1, 1))
t = 0
SPHERE1 = Sphere.new(0.5, Vec.new( 0.0, -0.5, Math.sin(0)), Vec.new(1, 0, 0))
SPHERE2 = Sphere.new(1.0, Vec.new( 2.0, 0.0, Math.cos(t*0.666)), Vec.new(0, 1, 0))
SPHERE3 = Sphere.new(1.5, Vec.new(-2.0, 0.5, Math.cos(t*0.333)), Vec.new(0, 0, 1))
def intersect!(ray, i)
SPHERE1.intersect!(ray, i)
SPHERE2.intersect!(ray, i)
SPHERE3.intersect!(ray, i)
PLANE.intersect!(ray, i)
end
puts("P3")
puts("#{IMAGE_WIDTH} #{IMAGE_HEIGHT}")
puts("#{IMAGE_DEPTH-1}")
IMAGE_HEIGHT.times do |row|
IMAGE_WIDTH.times do |col|
x = col.to_f / (IMAGE_WIDTH / 2) - 1.0
y = (IMAGE_HEIGHT-row).to_f / (IMAGE_HEIGHT / 2) - 1.0
ray = Ray.new(Vec.new(0.0, 2.0, 6.0),
Vec.new(x, y, -1.0).vnormalize!)
i = Isect.new
intersect!(ray, i)
if i.hit > 0
dest_col = i.color
temp_col = Vec.new(1, 1, 1).vmulti(i.color)
(1...MAX_REF).each do |j|
q = Ray.new(i.hit_point.vadd(i.normal.vmul(EPS)),
i.ray_dir.reflect(i.normal))
intersect!(q, i)
if i.hit > j
dest_col = dest_col.vadd(temp_col.vmulti(i.color))
temp_col = temp_col.vmulti(i.color)
end
end
else
dest_col = Vec.new(ray.dir.y, ray.dir.y, ray.dir.y)
end
print_col(dest_col)
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment