光线追踪渲染器故障排除:渲染图像与预期不匹配

问题描述 投票:0回答:1

我一直致力于使用矢量、光线、材质、灯光、球体、场景和渲染引擎的类在 Python 中实现基本的光线追踪渲染器。但是,我得到的渲染图像与我的预期不符,我无法确定问题所在。

代码

python raytracing
1个回答
0
投票
from math import sqrt,pi
from PIL import Image as img2ppm
class Vector:
    def __init__(self, x=0.0, y=0.0, z=0.0):
        self.x = x
        self.y = y
        self.z = z
    def __str__(self):
        return "({}, {}, {})".format(self.x, self.y, self.z)
    def dot_product(self, other):
        return self.x * other.x + self.y * other.y + self.z * other.z
    def magnitude(self):
        return sqrt(self.dot_product(self))
    def normalize(self):
        return self / self.magnitude()
    def __add__(self, other):
        return Vector(self.x + other.x, self.y + other.y, self.z + other.z)
    def __sub__(self, other):
        return Vector(self.x - other.x, self.y - other.y, self.z - other.z)
    def __mul__(self, other):
        assert not isinstance(other, Vector)
        return Vector(self.x * other, self.y * other, self.z * other)
    def __rmul__(self, other):
        return self.__mul__(other)
    def __truediv__(self, other):
        assert not isinstance(other, Vector)
        return Vector(self.x / other, self.y / other, self.z / other)
def from_hex(hexcolor):
    return Vector(int(hexcolor[1:3], 16) / 255.0, int(hexcolor[3:5], 16) / 255.0, int(hexcolor[5:7], 16) / 255.0)
class Image:
    def __init__(self, width, height):
        self.width = width
        self.height = height
        self.pixels = [[None for _ in range(width)] for _ in range(height)]
    def set_pixel(self, x, y, col):
        self.pixels[y][x] = col
    def write_ppm(self, img_fileobj):
        Image.write_ppm_header(img_fileobj, height=self.height, width=self.width)
        self.write_ppm_raw(img_fileobj)
    def write_ppm_header(img_fileobj, height=None, width=None):
        img_fileobj.write("P3 {} {}\n255\n".format(width, height))
    def write_ppm_raw(self, img_fileobj):
        def to_byte(c):
            return round(max(min(c * 255, 255), 0))
        for row in self.pixels:
            for color in row:
                img_fileobj.write("{} {} {} ".format(to_byte(color.x), to_byte(color.y), to_byte(color.z)))
            img_fileobj.write("\n")
class Ray:
    def __init__(self, origin, direction):
        self.origin = origin
        self.direction = direction.normalize()
class RenderEngine:
    MAX_DEPTH = 5
    MIN_DISPLACE = 0.0001
    def render(self, scene, img_fileobj):
        width = scene.width
        height = scene.height
        x0 = -1.0
        x1 = +1.0
        xstep = (x1 - x0) / (width - 1)
        aspect_ratio = float(width) / height
        y0 = -1.0 / aspect_ratio
        y1 = +1.0 / aspect_ratio
        ystep = (y1 - y0) / (height - 1)
        camera = scene.camera
        pixels = Image(width, height)
        for j in range(height):
            y = y0 + j * ystep
            for i in range(width):
                x = x0 + i * xstep
                ray = Ray(camera, Vector(x, y) - camera)
                pixels.set_pixel(i, j, self.ray_trace(ray, scene))
        pixels.write_ppm(img_fileobj)
    def ray_trace(self, ray, scene, depth=0):
        color = Vector(0, 0, 0)
        dist_hit, obj_hit = self.find_nearest(ray, scene)
        if obj_hit is None:
            return color
        hit_pos = ray.origin + ray.direction * dist_hit
        hit_normal = obj_hit.normal(hit_pos)
        color += self.color_at(obj_hit, hit_pos, hit_normal, scene)
        if depth < self.MAX_DEPTH:
            new_ray_pos = hit_pos + hit_normal * self.MIN_DISPLACE
            new_ray_dir = (ray.direction - 2 * ray.direction.dot_product(hit_normal) * hit_normal)
            new_ray = Ray(new_ray_pos, new_ray_dir)
            color += (self.ray_trace(new_ray, scene, depth + 1) * obj_hit.material.reflection)
        return color
    def find_nearest(self, ray, scene):
        dist_min = None
        obj_hit = None
        for obj in scene.objects:
            dist = obj.intersects(ray)
            if dist is not None and (obj_hit is None or dist < dist_min):
                dist_min = dist
                obj_hit = obj
        return (dist_min, obj_hit)
    def color_at(self, obj_hit, hit_pos, normal, scene):
        material = obj_hit.material
        obj_color = material.color_at(hit_pos)
        to_cam = scene.camera - hit_pos
        specular_k = 50
        color = material.ambient * from_hex("#FFFFFF")
        for light in scene.lights:
            to_light = Ray(hit_pos, light.position - hit_pos)
            color += (obj_color* material.diffuse* max(normal.dot_product(to_light.direction), 0))
            half_vector = (to_light.direction + to_cam).normalize()
            color += (light.color* material.specular* max(normal.dot_product(half_vector), 0) ** specular_k)
        return color
class Light:
    def __init__(self, position, color=from_hex("#FFFFFF")):
        self.position = position
        self.color = color
class Material:
    def __init__(
        self,color=from_hex("#FFFFFF"),ambient=0.05,diffuse=1.0,specular=1.0,reflection=0.5,):
        self.color = color
        self.ambient = ambient
        self.diffuse = diffuse
        self.specular = specular
        self.reflection = reflection
    def color_at(self, position):
        return self.color
class Sphere:
    def __init__(self, center, radius, material):
        self.center = center
        self.radius = radius
        self.material = material
    def intersects(self, ray):
        sphere_to_ray = ray.origin - self.center
        # a = 1
        b = 2 * ray.direction.dot_product(sphere_to_ray)
        c = sphere_to_ray.dot_product(sphere_to_ray) - self.radius * self.radius
        discriminant = b * b - 4 * c
        if discriminant >= 0:
            dist = (-b - sqrt(discriminant)) / 2
            if dist > 0:
                return dist
        return None
    def normal(self, surface_point):
        return (surface_point - self.center).normalize()
class ChequeredMaterial:
    def __init__(
        self,color1=from_hex("#FFFFFF"),color2=from_hex("#000000"),ambient=0.05,diffuse=1.0,specular=1.0,reflection=0.5,):
        self.color1 = color1
        self.color2 = color2
        self.ambient = ambient
        self.diffuse = diffuse
        self.specular = specular
        self.reflection = reflection
    def color_at(self, position):
        if int((position.x + 5.0) * 3.0) % 2 == int(position.z * 3.0) % 2:
            return self.color1
        else:
            return self.color2
class Scene:
    def __init__(self, camera, objects, lights, width, height):
        self.camera = camera
        self.objects = objects
        self.lights = lights
        self.width = width
        self.height = height
CAMERA = Vector(0, -0.35, -1)
OBJECTS = [Sphere(Vector(0, 10000.5, 1),10000.0,ChequeredMaterial(color1=from_hex("#420500"),color2=from_hex("#e6b87d"),ambient=0.2,reflection=0.2,),),Sphere(Vector(0.75, -0.1, 1), 0.6, Material(from_hex("#0000FF"))),Sphere(Vector(-0.75, -0.1, 2.25), 0.6, Material(from_hex("#803980"))),]
LIGHTS = [Light(Vector(1.5, -0.5, -10), from_hex("#FFFFFF")),Light(Vector(-0.5, -10.5, 0), from_hex("#E6E6E6")),]
engine = RenderEngine()
with open(r'D:\puray-master\image.ppm', "w") as img_fileobj:
    engine.render(Scene(CAMERA,objects=OBJECTS,lights=LIGHTS,width=300,height=300), img_fileobj)
img2ppm.open(r'D:\puray-master\image.ppm').show()
© www.soinside.com 2019 - 2024. All rights reserved.