1
\$\begingroup\$

I followed this tutorial called "Ray Tracing in One Weekend" by Peter Shirley. And I implemented the java version of it for studying Ray Tracing.

Everything was all right until Diffuse Materials, but anti-aliasing was okay though.

Please point out my mistakes.

I have tried gamma color correction but the shadows are still invisible. Here's the source code:

In case I screwed up my Sphere intersection code etc., here are the classes:

Sphere class:

public class Sphere extends Hitable{

    double r; //radius
    Vec3   p; //position
    
    public Sphere(Vec3 p, double rad) {
        this.p = p;
        this.r = rad;
    }
    
    @Override
    boolean hit(Ray r, double t_min, double t_max, hit_record rec) {
        Vec3 oc = r.getOrigin().sub(p);
        double a = r.getDirection().lengthSquared();
        double half_b = oc.dot(r.getDirection());
        double c = oc.lengthSquared()-this.r*this.r;
        double discriminant = half_b*half_b-a*c;
        if(discriminant<0) return false;
        double sqrtd = Math.sqrt(discriminant);
        
        double root = (-half_b - sqrtd)/a;
        if(root < t_min || t_max < root) {
            root = (-half_b + sqrtd)/a;
            if(root < t_min || t_max < root)
                return false;
        }
        
        rec.t = root;
        rec.p = r.at(rec.t);
        Vec3 outward_normal = (rec.p.sub(this.p)).div(this.r);
        rec.set_face_normal(r, outward_normal);
        return true;
    }
}

Vec3 class:

public class Vec3 {

    private double x, y, z;
    
    public Vec3(double x, double y, double z) { 
        this.x = x;
        this.y = y;
        this.z = z;
    }

    double x() { return x; }
    double y() { return y; }
    double z() { return z; }
    double r() { return x; }
    double g() { return y; }
    double b() { return z; }
    double lengthSquared() { return x*x+y*y+z*z; }
    double length() { return Math.sqrt(lengthSquared()); }
    double dot(Vec3 v) { return x*v.x()+y*v.y()+z*v.z(); }
    Vec3 cross(Vec3 v) { return new Vec3(y*v.z()-z*v.y(), z*v.x()-x*v.z(), x*v.y()-y*v.x()); }
    Vec3 unitVector() { return this.div(this.length()); }
    Vec3 neg() { return new Vec3(-x,-y,-z); }
    Vec3 add(Vec3 v) { return new Vec3(x+v.x(), y+v.y(), z+v.z()); }
    Vec3 mul(Vec3 v) { return new Vec3(x*v.x(), y*v.y(), z*v.z()); }
    Vec3 sub(Vec3 v) { return new Vec3(x-v.x(), y-v.y(), z-v.z()); }
    Vec3 div(Vec3 v) { return new Vec3(x/v.x(), y/v.y(), z/v.z()); }
    Vec3 add(double t) { return new Vec3(x+t, y+t, z+t); }
    Vec3 mul(double t) { return new Vec3(x*t, y*t, z*t); }
    Vec3 sub(double t) { return new Vec3(x-t, y-t, z-t); }
    Vec3 div(double t) { return new Vec3(x/t, y/t, z/t); }
    public String toString() { return "("+x+","+y+","+z+")"; }
}

Ray class:

public class Ray {

    private Vec3 origin, direction;
    
    Vec3 getOrigin()     { return origin; }
    Vec3 getDirection()  { return direction; }
    Vec3 at(double t) { return origin.add(direction.mul(t)); } 
    
    public Ray(Vec3 origin, Vec3 direction) {
        this.origin = origin;
        this.direction = direction;
    }
}

Main class:

import java.awt.Canvas;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Toolkit;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import javax.imageio.ImageIO;
import javax.swing.JFrame;

import rt.Hitable.hit_record;

public class Main {

    public static void main(String[] args)
    {
        JFrame frame = new JFrame("Raytracer");
        Canvas canvas = new Canvas();           
        frame.add(canvas);
        frame.setSize(Toolkit.getDefaultToolkit().getScreenSize());
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setLocationRelativeTo(null);
        frame.setResizable(false);
        frame.setVisible(true);
        
        canvas.createBufferStrategy(2);
        Graphics g2 = canvas.getBufferStrategy().getDrawGraphics();
        
        int w = 640;        //render width
        int h = 480;        //render height
        int sample = 100;   //samples
        int depth = 50;     //depth
        
        BufferedImage display = new BufferedImage(w,h,BufferedImage.TYPE_INT_RGB);
        Camera cam = new Camera();
        
        HitableList world = new HitableList();
        world.add(new Sphere(new Vec3(0,0,-1), 0.5));
        world.add(new Sphere(new Vec3(0,-100.5,-1), 100));
        
        long last = System.nanoTime();
        
        for(int j=h-1;j>0;--j)
        {
            System.out.println("scanlines remaining: "+j);
            
            for(int i=0;i<w;++i)
            {
                Vec3 col = new Vec3(0,0,0);
                
                for(int s=0;s<sample;++s) {
                     double u = (double)(i+Math.random()*1)/(display.getWidth()-1);
                     double v = (double)(j+Math.random()*1)/(display.getHeight()-1);
                     Ray r = cam.getRay(u, v);
                     col   = col.add(hit(r,world,depth));
                }
                
                double r = col.r(),
                       g = col.g(),
                       b = col.b();
                
                double scale = 1.0/sample;
                //r *= scale;
                //g *= scale;
                //b *= scale;
                
                r = Math.sqrt(scale*r);
                g = Math.sqrt(scale*g);
                b = Math.sqrt(scale*b);
                
                int ir = (int)(256*clamp(r, 0, 0.999));        // R component
                int ig = (int)(256*clamp(g, 0, 0.999));        // G component 
                int ib = (int)(256*clamp(b, 0, 0.999));        // B component
                display.setRGB(i, h-j-1, (ir<<16)+(ig<<8)+ib); // plot pixel to the screen.
            }
        }
        
        
        long now = System.nanoTime();
        double time = ((now-last)/1000000000.0);
        System.out.println("done, time taken: " + time);
        
        g2.setFont(new Font("Consolas", Font.PLAIN, 32));
        g2.setColor(Color.BLACK);
        
        try {
            ImageIO.write(display, "PNG", new File("./gallery/"+time+"--"+new SimpleDateFormat("dd-MM-yyyy").format(new Date())+"--"+w+"x"+h+"--"+sample+".png"));
        } catch (IOException e) {
            e.printStackTrace();
        }
        
        while(true)
        {
            g2.drawImage(display, 0, 0, frame.getWidth(), frame.getHeight(), null);
            g2.drawString(""+time, 50, 50);
            canvas.getBufferStrategy().show();
        }
    }
    
    static Vec3 random_in_unit_sphere() {
        while(true) {
            Vec3 p = randomVec3(-1,1);
            if(p.lengthSquared() >= 1.0)  {
                continue;
            }
            return p;
        }
    }
    
    static Vec3 randomVec3(double min, double max)
    {
        double scale = max-min;
        return new Vec3((Math.random()*scale)+min, (Math.random()*scale)+min, (Math.random()*scale)+min);
    }

    static double clamp(double x, double min, double max) {
        if(x < min) return min;
        if(x > max) return max;
        
        return x;
    }
    
    static Vec3 hit(Ray r, Hitable world, int depth) {
        hit_record rec = new hit_record();
        
        if(depth <= 0)
            return new Vec3(0,0,0);
        
        if(world.hit(r, 0, Double.POSITIVE_INFINITY, rec)) {
            Vec3 target = rec.p.add(rec.normal).add(random_in_unit_sphere());
            return hit(new Ray(rec.p, target.sub(rec.p)), world, depth-1).mul(0.5);
        }
        
        Vec3 unit_direction = r.getDirection().unitVector();
        double t = 0.5*(unit_direction.y()+1.0);
        return new Vec3(1,1,1).mul(1-t).add(new Vec3(0.5,0.7,1).mul(t));
    }
}

problematic results 10/19/2021 update: update 2

\$\endgroup\$
3
  • \$\begingroup\$ Please (re)post your source code as text (and formatted as code blocks), not as screen shots which are difficult to read. \$\endgroup\$ Oct 18, 2021 at 14:40
  • \$\begingroup\$ @AcmeNerdGames like this? \$\endgroup\$
    – Yusuf
    Oct 18, 2021 at 14:47
  • \$\begingroup\$ Yes, much better \$\endgroup\$ Oct 18, 2021 at 15:12

2 Answers 2

2
\$\begingroup\$

The randomVec3() function used in random_in_unit_sphere() doesn't match the one in the tutorial. A standard unit sphere is centred at the origin with a radius of 1, and your current implementation doesn't give you either of those properties.

I don't know if that's the only problem, but fixing that should help.

Try something like this, which is closer to the code in the tutorial:

static Vec3 randomVec3(double min, double max)
{
    double scale = max-min;
    return new Vec3((Math.random()*scale)+min, (Math.random()*scale)+min, (Math.random()*scale)+min);
}

static Vec3 random_in_unit_sphere()
{
    while(true)
    {
        Vec3 p = randomVec3(-1.0, 1.0);
        if(p.lengthSquared() >= 1.0)
        {
            continue;
        }
        return p;
    }
}
\$\endgroup\$
2
  • \$\begingroup\$ I still don't get the shadows but it's much faster and better somehow now. I added the classes in case I screwed up some of my codes. \$\endgroup\$
    – Yusuf
    Oct 19, 2021 at 3:28
  • \$\begingroup\$ Thank you for your advice, Adam. \$\endgroup\$
    – Yusuf
    Oct 25, 2021 at 14:19
1
\$\begingroup\$

I found the solution.

It was actually in the HitableList class, where I defined this intersection function;

@Override
    boolean hit(Ray r, double t_min, double t_max, hit_record rec) {
        hit_record temp_rec = new hit_record();
        boolean hitanything = false;
        double closest_so_far = t_max;
        
        for(Hitable h : this.objects) {
            if(h.hit(r, t_min, closest_so_far, temp_rec)) {
                hitanything = true;
                closest_so_far = temp_rec.t;
                rec = temp_rec;
            }
        }
        
        return hitanything;
    }

(Spoiler: I forgot Java is pass-by-value) This function actually does not assign the HitRecord passed by the parameter to the temp_rec, so leaving the HitRecord default (in effect, normal is set to (0,0,0), leaving no shadows behind). So I changed it to:

@Override
    public boolean intersect(Ray ray, double tmin, double tmax, HitRecord record) {
        
        for(Hitable h : objects) {
            if(h.intersect(ray, tmin, tmax, record)) {
                return true;
            }
        }
        
        return false;
    }   

So here is the result: Thanks, everyone for helping me in this thread. Result

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .