diff --git a/index.html b/index.html
index 1deccb1d7c7a780af5e10fd0d8ebfa503d1f01bc..d2a125a6c4a6830a12284dd3fbc28d067f07a5a9 100644
--- a/index.html
+++ b/index.html
@@ -15,6 +15,6 @@
     <script type="module" src="/src/cg/perspectiveDivide.ts"></script>
     <script type="module" src="/src/cg/walkToVec.ts"></script>
     <script type="module" src="/src/cg/basis.ts"></script-->
-    <script type="module" src="/src/cg/raytracing1.ts"></script>
+    <script type="module" src="/src/cg/raytracing2.ts"></script>
   </body>
 </html>
diff --git a/src/cg/animation1 frames/render.frame.0.png b/src/cg/animation1 frames/render.frame.0.png
new file mode 100644
index 0000000000000000000000000000000000000000..9023bea0ce683eb56f8a9149f59ec7f4e52bcf80
Binary files /dev/null and b/src/cg/animation1 frames/render.frame.0.png differ
diff --git a/src/cg/animation1 frames/render.frame.1.png b/src/cg/animation1 frames/render.frame.1.png
new file mode 100644
index 0000000000000000000000000000000000000000..601be26d15159e44b40b002fade435a4c1f3f713
Binary files /dev/null and b/src/cg/animation1 frames/render.frame.1.png differ
diff --git a/src/cg/animation1 frames/render.frame.10.png b/src/cg/animation1 frames/render.frame.10.png
new file mode 100644
index 0000000000000000000000000000000000000000..71574d343c1b746990b6ae5905acea9f1fd3138e
Binary files /dev/null and b/src/cg/animation1 frames/render.frame.10.png differ
diff --git a/src/cg/animation1 frames/render.frame.2.png b/src/cg/animation1 frames/render.frame.2.png
new file mode 100644
index 0000000000000000000000000000000000000000..31d95714c83e04a363b103829f200131feed2055
Binary files /dev/null and b/src/cg/animation1 frames/render.frame.2.png differ
diff --git a/src/cg/animation1 frames/render.frame.4.png b/src/cg/animation1 frames/render.frame.4.png
new file mode 100644
index 0000000000000000000000000000000000000000..cd4c48b9649df640d173da01bd606b19477b12c3
Binary files /dev/null and b/src/cg/animation1 frames/render.frame.4.png differ
diff --git a/src/cg/animation1 frames/render.frame.5.png b/src/cg/animation1 frames/render.frame.5.png
new file mode 100644
index 0000000000000000000000000000000000000000..4d95157b2786dd0940b2157907367580d10848f9
Binary files /dev/null and b/src/cg/animation1 frames/render.frame.5.png differ
diff --git a/src/cg/animation1 frames/render.frame.6.png b/src/cg/animation1 frames/render.frame.6.png
new file mode 100644
index 0000000000000000000000000000000000000000..c8166ae26ad816c30733c678f101af0d6f2bfc0d
Binary files /dev/null and b/src/cg/animation1 frames/render.frame.6.png differ
diff --git a/src/cg/animation1 frames/render.frame.7.png b/src/cg/animation1 frames/render.frame.7.png
new file mode 100644
index 0000000000000000000000000000000000000000..3f996840ce41307bd287e561c6001b6844abb5ec
Binary files /dev/null and b/src/cg/animation1 frames/render.frame.7.png differ
diff --git a/src/cg/animation1 frames/render.frame.8.png b/src/cg/animation1 frames/render.frame.8.png
new file mode 100644
index 0000000000000000000000000000000000000000..f294eaa6ad42e03a33ead3ad9a7b8a3e0d44f8d3
Binary files /dev/null and b/src/cg/animation1 frames/render.frame.8.png differ
diff --git a/src/cg/animation1 frames/render.frame.9.png b/src/cg/animation1 frames/render.frame.9.png
new file mode 100644
index 0000000000000000000000000000000000000000..33829f286078b48109a8275d78c56148cf7bf47a
Binary files /dev/null and b/src/cg/animation1 frames/render.frame.9.png differ
diff --git a/src/cg/animation1.ts b/src/cg/animation1.ts
new file mode 100644
index 0000000000000000000000000000000000000000..9815ef8b9a23fe5a53fa4b15fa7e51893145bbb6
--- /dev/null
+++ b/src/cg/animation1.ts
@@ -0,0 +1,53 @@
+/*import Framebuffer from "./framebuffer";
+
+const width = 100
+const height = 100
+const framebuffer = new Framebuffer(width, height);
+
+let currentFrame = 0
+
+// Step from 0 to 1
+for (let i = 0; i <= 1; i += 0.1) {
+    framebuffer.clear()
+    
+    // remap 0 1 to -1 1
+    const remapped = i * 2 - 1; console.info(remapped)
+
+    // fit remapped i into actual values
+    // and use absolute value from remapped    
+    const val = Math.round((height-1) * Math.abs(remapped));
+
+    framebuffer.draw(50, val, [255, 0, 0])
+    
+    framebuffer.update();
+    framebuffer.save("frame." + ++currentFrame)
+}*/
+
+import { easeOutBounce } from "./helper";
+import Framebuffer from "./framebuffer";
+
+const width = 100
+const height = 100
+const framebuffer = new Framebuffer(width, height);
+
+let currentFrame = 0
+
+
+for (let i = 0; i <= 1; i += 0.1) {
+    framebuffer.clear();
+
+    // remap 0 1 to -1 1
+    //const remapped = i * 2 - 1; console.info(remapped)
+
+    // fit remapped i into actual values
+    // and use absolute value from remapped    
+    //const val = Math.round((height-1) * Math.abs(remapped));
+
+    const c = height * easeOutBounce(i)
+    framebuffer.draw(width * i, Math.floor(c), [255, 0, 0])
+
+    framebuffer.update();
+    framebuffer.save("frame." + ++currentFrame)
+    
+}
+
diff --git a/src/cg/framebuffer.ts b/src/cg/framebuffer.ts
new file mode 100644
index 0000000000000000000000000000000000000000..fb7e2eddd5b8995b5ff7d8590f57755d05cf3668
--- /dev/null
+++ b/src/cg/framebuffer.ts
@@ -0,0 +1,164 @@
+export type Color3 = [number, number, number];
+export type Vector3 = [number, number, number];
+
+export interface ICoord2D {
+    x: number,
+    y: number
+}
+
+export default class Framebuffer {
+    private canvas: HTMLCanvasElement
+    private ctx
+    private buffer
+    private color: Color3
+    private saveLink: HTMLAnchorElement
+    private saveButton: HTMLButtonElement
+    private logField: HTMLUListElement
+
+    constructor(width = 100, height = 100) {
+
+        // Remove stored images
+        for (var key in sessionStorage) {
+            if (key.indexOf("render.") === 0) {
+                sessionStorage.removeItem(key);
+            }
+        }
+
+        this.color = [0, 0, 0]
+        const appEl = document.getElementById("app");
+        appEl.style.display = "flex";
+        appEl.style.height = "100vh";
+        appEl.style.width = "100vw";
+        appEl.style.flexDirection = "column";
+        appEl.style.justifyContent = "center";
+        appEl.style.alignItems = "center";
+
+        this.canvas = document.createElement("canvas");
+        this.canvas.id = "framebuffer";
+        this.canvas.width = width;
+        this.canvas.height = height;
+        this.canvas.style.borderWidth = "2px"
+
+        const header = document.createElement("h2");
+        header.innerText = "framebuffer: " + width + "x" + height;
+        appEl.appendChild(header);
+        appEl.appendChild(this.canvas);
+
+        this.ctx = this.canvas.getContext("2d");
+
+        if (!this.ctx) {
+            console.warn("No canvas 2d context!")
+            return
+        }
+
+        // Setup save link
+        this.saveLink = document.createElement("a");
+        this.saveLink.id = "save";
+
+        this.saveLink.style.display = "none";
+        appEl.appendChild(this.saveLink);
+
+        // Save button
+        this.saveButton = document.createElement("button");
+        this.saveButton.innerText = "Download Frames";
+        appEl.appendChild(this.saveButton);
+
+        this.saveButton.addEventListener("click", () => this.download())
+
+        // Log 
+        this.logField = document.createElement("ul");
+        this.logField.id = "log";
+        appEl.appendChild(this.logField);
+
+        // Get pixel data
+        this.buffer = this.ctx.getImageData(0, 0, this.canvas.width, this.canvas.height);
+
+    }
+
+    
+    draw(x: number, y: number, color: Color3 = [80, 80, 80], parameters?: {}): void {
+
+        const defaults = {
+        }
+
+        const params = { ...defaults, ...parameters }
+        this.color = color;
+
+        if (!this.buffer) return;
+
+        // The first (red) component for this pixel
+        var offset = 4 * x + (this.buffer.width * 4) * y;
+
+        const redIndex = offset;
+        const greenIndex = redIndex + 1;
+        const blueIndex = greenIndex + 1;
+        const alphaIndex = blueIndex + 1;
+
+        this.buffer.data[redIndex] = this.color[0];
+        this.buffer.data[greenIndex] = this.color[1];
+        this.buffer.data[blueIndex] = this.color[2];
+        this.buffer.data[alphaIndex] = 255;
+    }
+
+
+    update() {
+        if (!this.ctx) return;
+
+        this.ctx.putImageData(this.buffer, 0, 0);
+    }
+
+    save(name: string) {
+        sessionStorage.setItem("render." + name, this.canvas.toDataURL("image/png").replace("image/png", "image/octet-stream"));
+
+    }
+
+    download() {
+        for (var key in sessionStorage) {
+            if (key.indexOf("render.") === 0) {
+                sessionStorage.getItem(key)
+                console.info("Downloading", key)
+                this.saveLink.setAttribute('download', key + '.png');
+                this.saveLink.setAttribute('href', sessionStorage[key]);
+                this.saveLink.click();
+            }
+        }
+    }
+
+     log(content: string) {
+         this.logField.innerText = content;
+     }
+
+    clear() {
+        this.ctx.reset();
+        this.buffer = this.ctx.getImageData(0, 0, this.canvas.width, this.canvas.height);
+        this.update();
+    }
+
+    // Type definition for a 3D vector
+
+
+    // Function to convert from raster space to screen space
+    rasterToScreen(x: number, y: number, width: number, height: number, distance: number): Vector3 {
+    // Convert from raster space to normalized device coordinates (NDC)
+    const ndcX = (x + 0.5) / width;
+    const ndcY = (y + 0.5) / height;
+
+    // Convert from NDC to screen space
+    const screenX = 2 * ndcX - 1;
+    const screenY = 1 - 2 * ndcY;
+
+    // Return the screen space coordinates as a Vector3
+    return [screenX, screenY, distance];
+    }
+
+    /* Example usage:
+    const rasterX = 50;
+    const rasterY = 50;
+    const canvasWidth = 100;
+    const canvasHeight = 100;
+
+    const screenCoords = this.rasterToScreen(this.rasterX, this.rasterY, this.canvasWidth, this.canvasHeight);
+    console.log(screenCoords); // Output: [0, 0, 0]*/
+
+    
+}
\ No newline at end of file
diff --git a/src/cg/helper.ts b/src/cg/helper.ts
new file mode 100644
index 0000000000000000000000000000000000000000..6f146336d1b2dd0dfb9142b5bd5cfda8cfd6f54d
--- /dev/null
+++ b/src/cg/helper.ts
@@ -0,0 +1,14 @@
+export function easeOutBounce(x: number): number {
+    const n1 = 7.5625;
+    const d1 = 2.75;
+    
+    if (x < 1 / d1) {
+        return n1 * x * x;
+    } else if (x < 2 / d1) {
+        return n1 * (x -= 1.5 / d1) * x + 0.75;
+    } else if (x < 2.5 / d1) {
+        return n1 * (x -= 2.25 / d1) * x + 0.9375;
+    } else {
+        return n1 * (x -= 2.625 / d1) * x + 0.984375;
+    }
+    }
\ No newline at end of file
diff --git a/src/cg/raytracing1.ts b/src/cg/raytracing1.ts
index 97636d3ec4c93038a9813b1ff662819e8d4fddcb..9b44583d99891c7fdf06226d45d70f341078bec4 100644
--- a/src/cg/raytracing1.ts
+++ b/src/cg/raytracing1.ts
@@ -4,8 +4,8 @@ import { vecDotProduct, vecMultiplyScalar, vecSubtract } from "./utils";
 const pg = new Playground();
 
 const sphere = {
-    position: [0, .5, -3], //C - Center of sphere
-    radius: 0.5
+    position: [0, .5, -4], //C - Center of sphere
+    radius: 2
 }
 pg.visCamera(-1);
 pg.gridXZ()
@@ -14,7 +14,7 @@ const o = [0, 0, 0]; //O - Origin
 const co = vecSubtract(o, sphere.position) // O - C
 const rsq = sphere.radius * sphere.radius;
 
-const step = 1 / 8
+const step = 1/8
 
 for (let yCoord = -1; yCoord <= 1; yCoord +=  step) {
 
diff --git a/src/cg/raytracing2.ts b/src/cg/raytracing2.ts
new file mode 100644
index 0000000000000000000000000000000000000000..4fec61b39b6946645072af844d217227607852df
--- /dev/null
+++ b/src/cg/raytracing2.ts
@@ -0,0 +1,61 @@
+import Framebuffer from "./framebuffer";
+import { ISphere, Vec3, raySphereIntersect } from "./utils";
+
+const width = 200
+const height = 200
+const framebuffer = new Framebuffer(width, height);
+const imagePlaneDist = -1
+
+const tNear = 1;
+const tFar = 1000;
+
+const spheres = [
+    {
+        position: [0, 0, -3],// <--- 
+        radius: 2,// <--- 
+        color: [255,0,0]// <--- 
+    },
+    {
+        position: [3,2,-3],// <--- 
+        radius: 2,// <--- 
+        color: [0,255,0]// <--- 
+    },
+    //... add more spheres if you like
+]
+
+const o:Vec3 = [0,0,0]//<--- the camera/viewer origin
+
+// Loop over framebuffer pixels
+for (let x = 0; x <= width; x++) {
+    for (let y = 0; y <= height; y++) {
+
+        const v = framebuffer.rasterToScreen(x, y, width, height, imagePlaneDist)// <--- convert raster to screen space 
+
+        let closestSphere = null;
+        let closestIntersection = 9999;
+
+        for (let i = 0; i < spheres.length; i++) {
+
+            const [t1, t2] = raySphereIntersect(v, o, spheres[i] as ISphere)// <--- Calculate intersections
+
+            if (t1 < closestIntersection && tNear < t1 && t1 < tFar) {
+                closestIntersection = t1;
+                closestSphere = spheres[i]
+            }
+
+            if (t2 < closestIntersection && tNear < t2 && t2 < tFar) {
+                closestIntersection = t2;
+                closestSphere = spheres[i];
+            }
+        }
+        if (!closestSphere) {
+            framebuffer.draw(x,y,[0,0,50]) // <--- Draw a background color
+        } else {
+            framebuffer.draw(x,y,closestSphere.color) // <--- Draw color of closest sphere
+        }
+
+    }
+
+}
+framebuffer.update();
+framebuffer.save("spheres.");
\ No newline at end of file
diff --git a/src/cg/utils.ts b/src/cg/utils.ts
index 3441189d55ab570eff9a14df0e15637362918b24..1a4beb8a8ab71bc9f2674b95c9b9f9c0b9f372f6 100644
--- a/src/cg/utils.ts
+++ b/src/cg/utils.ts
@@ -167,6 +167,12 @@ export type Matrix4 = [
     number, number, number, number
 ];
 
+export interface ISphere{
+    position: Array<number>,
+    radius: number,
+    color?: Array<number>
+}
+
 export function multVec3Matrix4(v: Vec3, m: Matrix4):Vec3 {
     const v4: Vec4 = [v[0], v[1], v[2], 1]
 
@@ -206,4 +212,22 @@ export function matrix3ToMatrix4(m: Matrix3): Matrix4 {
     ]
     return m4;
 }
+
+export function raySphereIntersect(v: Vec3, o: Vec3, sphere: ISphere): [number, number] {
+    const ov = vecSubtract(v, o); // V - O
+
+    const co = vecSubtract(o, sphere.position) // O - C
+    const rsq = sphere.radius * sphere.radius;
+
+        const a = vecDotProduct(ov, ov); // a = (V - O)*(V _ O)
+        const b = 2* vecDotProduct(ov, co); // b = 2 * ((V - O)*(O - C))
+        const c = vecDotProduct(co, co) - rsq; // c = ((O - C)*(O - C))-r^2
+ 
+        const discriminant = (b*b) - (4*a*c); // b^2 - 4*a*c
+
+        const t1 = (-b + Math.sqrt(discriminant)) / (2 * a)
+        const t2 = (-b - Math.sqrt(discriminant)) / (2 * a)
+
+    return [t1, t2];
+}
 //
\ No newline at end of file