# I'm following a tutorial but I don't get the same result; why are the raytraced shadows not working?

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.

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{

Vec3   p; //position

public Sphere(Vec3 p, double rad) {
this.p = p;
}

@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.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();

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);
}

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)) {
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);
}
}


10/19/2021 update:

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

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;
}
}

• 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. Commented Oct 19, 2021 at 3:28

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.