From 4c2b8f709cd8eda495c8cac9f16d024603598790 Mon Sep 17 00:00:00 2001 From: Martin Christen Date: Fri, 9 Sep 2016 12:00:19 +0200 Subject: [PATCH] added jupyter tutorial --- jupyter/Tutorial.ipynb | 1266 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1266 insertions(+) create mode 100644 jupyter/Tutorial.ipynb diff --git a/jupyter/Tutorial.ipynb b/jupyter/Tutorial.ipynb new file mode 100644 index 0000000..a7ffdd3 --- /dev/null +++ b/jupyter/Tutorial.ipynb @@ -0,0 +1,1266 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "![Logo](https://github.com/martinchristen/pyRT/blob/master/docs/img/pyRT_64.png?raw=true)\n", + "\n", + "\n", + "# Tutorial: An introduction to pyRT (\"Pirate\") using the Jupyter Notebook\n", + "\n", + "FHNW - University of Applied Sciences and Arts Northwestern Switzerland\n", + "Martin Christen, martin.christen@fhnw.ch, @MartinChristen" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "pyRT is in development. Everything is (pre-)alpha!\n", + "\n", + " pip install pyrt --upgrade\n", + " \n", + " \n", + "pyRT doesn't have any dependencies, but it is recommended to install the following modules:\n", + "\n", + " pip install pillow --upgrade\n", + " pip install numpy --upgrade\n", + " pip install moviepy --upgrade\n", + " pip install jupyter –upgrade\n", + "\n", + "Basically, it is up to you how to display images, pyRT only creates lists with RGB Values etc." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "Source code is available on github:\n", + "https://github.com/martinchristen/pyRT\n", + "\n", + "The project is also on Twitter: @PythonRayTracer" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "# Part 1: Using pyrt to Draw\n", + "\n", + "pyRT also contains some functionatly to create images using Standard functions to draw points, lines, circle, rectangles. This has nothing todo with ray tracing, but it is fun and an easy way to get started to \"Image Generators\".\n" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": true, + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [], + "source": [ + "from pyrt.renderer import RGBImage\n", + "from pyrt.math import Vec2, Vec3\n", + "import numpy as np\n", + "import random" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "collapsed": true, + "slideshow": { + "slide_type": "subslide" + } + }, + "outputs": [], + "source": [ + "w = 320\n", + "h = 240\n", + "image = RGBImage(w, h)" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "collapsed": true, + "slideshow": { + "slide_type": "subslide" + } + }, + "outputs": [], + "source": [ + "for i in range(5000):\n", + " position = Vec2(random.randint(0, w - 1), random.randint(0, h - 1))\n", + " color = Vec3(random.uniform(0, 1), random.uniform(0, 1), random.uniform(0, 1))\n", + " \n", + " image.drawPoint(position, color)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Save image to file and display it" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from IPython.display import Image\n", + "from PIL import Image as PImage\n", + "\n", + "im = PImage.new(\"RGB\", (w, h))\n", + "im.putdata(image.data)\n", + "im.save(\"rendering.png\")\n", + "Image(\"rendering.png\")" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "image = RGBImage(w, h)" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "for i in range(500):\n", + " image.drawLine(Vec2(random.randint(0, w - 1), random.randint(0, h - 1)),\n", + " Vec2(random.randint(0, w - 1), random.randint(0, h - 1)),\n", + " Vec3(random.uniform(0, 1), random.uniform(0, 1), random.uniform(0, 1)))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Output image without saving to file" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from IPython.core.display import HTML\n", + "import base64\n", + "from io import BytesIO\n", + "\n", + "im = PImage.new(\"RGB\", (w, h))\n", + "im.putdata(image.data)\n", + "buffer = BytesIO()\n", + "im.save(buffer, format=\"JPEG\")\n", + "img_str = str(base64.b64encode(buffer.getvalue()), encoding=\"ascii\")\n", + "HTML('')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Personally I prefer this way of displaying an image, so let's create a display function for future images:\n" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "from IPython.core.display import HTML\n", + "import base64\n", + "from io import BytesIO\n", + "\n", + "def display(imagedata, w, h):\n", + " im = PImage.new(\"RGB\", (w, h))\n", + " im.putdata(imagedata)\n", + " buffer = BytesIO()\n", + " im.save(buffer, format=\"PNG\")\n", + " img_str = str(base64.b64encode(buffer.getvalue()), encoding=\"ascii\")\n", + " return HTML('')" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "image = RGBImage(w, h)\n", + "for i in range(100):\n", + " image.drawCircle(Vec2(random.randint(0, w - 1), random.randint(0, h - 1)),\n", + " random.randint(3, 100),\n", + " Vec3(random.uniform(0, 1), random.uniform(0, 1), random.uniform(0, 1)))\n", + " \n", + "display(image.data,w,h)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As a last example on how to \"draw\" directly, we use the \"drawRectangle\" method to - surprise - draw some rectangles." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "image = RGBImage(w, h)\n", + "\n", + "for i in range(100):\n", + " image.drawRectangle(Vec2(random.randint(0, w - 1), random.randint(0, h - 1)),\n", + " random.randint(1, w / 2), random.randint(1, h / 2),\n", + " Vec3(random.uniform(0, 1), random.uniform(0, 1), random.uniform(0, 1)))\n", + " \n", + "display(image.data,w,h)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Part 2: An introduction to 3D Graphics\n", + "\n", + "This part shows how we can draw in 3D. It introduces the submodules camera and geometry." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "from pyrt.renderer import RGBImage\n", + "from pyrt.math import Vec2, Vec3\n", + "from pyrt.camera import PerspectiveCamera\n", + "from pyrt.geometry import Triangle, Vertex" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "let's create a 320x240 image again:" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "w = 320\n", + "h = 240\n", + "image = RGBImage(w, h)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now we create a camera: the image plane has the same size like out output image (320x240) and we define a field of view of 60 degrees." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "camera = PerspectiveCamera(w,h,60)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This camera has its origin in (0,0,0) and points along the z-axis. This is not really what we want. So we can set where it is positioned and where it looks at.\n", + "\n", + " camera.setview(position, lookat, upvector)\n", + " \n", + "position: camera position\n", + "lookat: where it looks at\n", + "upvector: for the orientation of the camera" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "camera.setView(Vec3(0,-10,0), Vec3(0,0,0), Vec3(0,0,1))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "we can access the projection matrix and the view matrix using:\n", + "\n", + " camera.projection\n", + " camera.view\n", + " \n", + "These are 4x4 matrices." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Projection:\n", + "[[1.2990381056766582, 0.0, 0.0, 0.0]\n", + "[0.0, 1.7320508075688776, 0.0, 0.0]\n", + "[0.0, 0.0, -1.0002000200020003, -0.20002000200020004]\n", + "[0.0, 0.0, -1.0, 0.0]]\n", + "View:\n", + "[[1.0, 0.0, -0.0, -0.0]\n", + "[0.0, 0.0, 1.0, -0.0]\n", + "[0.0, -1.0, 0.0, -10.0]\n", + "[0.0, 0.0, 0.0, 1.0]]\n" + ] + } + ], + "source": [ + "print(\"Projection:\")\n", + "print(camera.projection)\n", + "print(\"View:\")\n", + "print(camera.view)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Thew view-projection matrix can be created by multiplying the two matrices. Please note that multiplications are done from right to left.\n", + "\n", + "So we create the view-projection matrix using:" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[[1.2990381056766582, 0.0, 0.0, 0.0]\n", + "[0.0, 0.0, 1.7320508075688776, 0.0]\n", + "[0.0, 1.0002000200020003, 0.0, 9.801980198019804]\n", + "[0.0, 1.0, 0.0, 10.0]]\n" + ] + } + ], + "source": [ + "vp = camera.projection * camera.view\n", + "\n", + "print(vp)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now we create a triangle. Triangles consists of 3 Vertices. A Vertex can have attributes like position, color, normal, ...). \n", + "\n", + "For this demo we only care about positions." + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "t = Triangle(Vertex(position=(-5, 1, 0)),\n", + " Vertex(position=(0, 1, 5)),\n", + " Vertex(position=(5, 1, 0)))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now we multiply every vertex position of the triangle (t.a.position, t.b.position, t.c.position) with the view-projection matrix.\n", + "\n", + "This results in normalized device coordinates (NDC) in the range (-1,-1,-1) to (1,1,1)" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "at = vp * t.a.position\n", + "bt = vp * t.b.position\n", + "ct = vp * t.c.position" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The NDC are now transformed to image coordinates. The division by z is a perspective transformation." + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "a_screenpos = Vec2(int(w * 0.5*(at.x + 1.) / at.z), int(h * 0.5*(at.y + 1.)/ at.z))\n", + "b_screenpos = Vec2(int(w * 0.5*(bt.x + 1.) / bt.z), int(h * 0.5*(bt.y + 1.)/ at.z))\n", + "c_screenpos = Vec2(int(w * 0.5*(ct.x + 1.) / ct.z), int(h * 0.5*(ct.y + 1.)/ at.z))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "And now we display the triangle by drawing the edges:" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 20, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "color = Vec3(1,1,1)\n", + "image.drawLine(a_screenpos, c_screenpos, color)\n", + "image.drawLine(c_screenpos, b_screenpos, color)\n", + "image.drawLine(b_screenpos, a_screenpos, color)\n", + "\n", + "display(image.data,w,h)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Part 3: Ray Tracing\n", + "\n", + "With this knowledge we know how 3D graphics basically work. Now we use ray-tracing to create the same triangle as in the last example.\n", + "\n", + "First we import the requires (sub-)modules" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "from pyrt.math import *\n", + "from pyrt.scene import *\n", + "from pyrt.geometry import Triangle, Vertex\n", + "from pyrt.camera import PerspectiveCamera\n", + "from pyrt.renderer import SimpleRT" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Then we create our camera" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "w = 320\n", + "h = 240\n", + "\n", + "camera = PerspectiveCamera(w, h, 60)\n", + "camera.setView(Vec3(0,-10,0), Vec3(0,0,0), Vec3(0,0,1))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The next step is to create a scene.\n", + "A scene consists of all objects you want to display. We just add a triangle to the scene." + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "scene = Scene()\n", + "scene.add(Triangle(Vertex(position=(-5, 1, 0)),\n", + " Vertex(position=(0, 1, 5)),\n", + " Vertex(position=(5, 1, 0))))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now the scene has to know which camera we use for rendering, so we just set the camera:" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "scene.setCamera(camera)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now we specify the raytracer. At the moment there is only one (reference) implementation of a raytracer, called **SimpleRT**" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "# Creating Renderer: Simple Raytracer\n" + ] + } + ], + "source": [ + "engine = SimpleRT()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now we render the scene and display the result" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "# RENDER STATISTICS###############################\n", + "TIME FOR RENDERING: 2.3619461059570312s\n", + "NUMBER OF PRIMARY RAYS: 76800\n", + "NUMBER OF SECONDARY RAYS: 0\n", + "NUMBER OF SHADOW RAYS: 0\n", + "RAYS/s: 32515.559862396432\n", + "##################################################\n" + ] + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 26, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "imgdata = engine.render(scene)\n", + "display(imgdata,w,h)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We create a new scene and this time we add some colors to the vertices:" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "scene = Scene()\n", + "\n", + "scene.add(Triangle(Vertex(position=(-5, 1, 0), color=(1,0,0)),\n", + " Vertex(position=(0, 1, 5), color=(0,1,0)),\n", + " Vertex(position=(5, 1, 0), color=(0,0,1))))\n", + "\n", + "scene.setCamera(camera)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "and render again..." + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "# RENDER STATISTICS###############################\n", + "TIME FOR RENDERING: 2.359083890914917s\n", + "NUMBER OF PRIMARY RAYS: 76800\n", + "NUMBER OF SECONDARY RAYS: 0\n", + "NUMBER OF SHADOW RAYS: 0\n", + "RAYS/s: 32555.01014430431\n", + "##################################################\n" + ] + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 28, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "imgdata = engine.render(scene)\n", + "display(imgdata,w,h)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can also create a scene with 2 triangles and render it:" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "# RENDER STATISTICS###############################\n", + "TIME FOR RENDERING: 3.6194040775299072s\n", + "NUMBER OF PRIMARY RAYS: 76800\n", + "NUMBER OF SECONDARY RAYS: 0\n", + "NUMBER OF SHADOW RAYS: 0\n", + "RAYS/s: 21218.962667581676\n", + "##################################################\n" + ] + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 29, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "scene = Scene()\n", + "\n", + "scene.add(Triangle(Vertex(position=(-5, 1, 0), color=(1,1,1)),\n", + " Vertex(position=(0, 1, 5), color=(0,1,1)),\n", + " Vertex(position=(5, 1, 0), color=(1,1,1))))\n", + "\n", + "scene.add(Triangle(Vertex(position=(5, 1, 0), color=(1,1,1)),\n", + " Vertex(position=(0, 1, -5), color=(1,1,0)),\n", + " Vertex(position=(-5, 1, 0), color=(1,1,1))))\n", + "\n", + "scene.setCamera(camera)\n", + "\n", + "imgdata = engine.render(scene)\n", + "display(imgdata,w,h)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Instead of triangles we can also use spheres. Let's also look at materials. One material type is \"PhongMaterial\" where you can define the material of the object by specifying its color, its shinines, its reflectivity etc." + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "from pyrt.geometry import Sphere\n", + "from pyrt.material import PhongMaterial" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "scene = Scene()\n", + "\n", + "scene.add(Sphere(center=Vec3(0.,0.,0.), radius=3., material=PhongMaterial(color=Vec3(1.,0.,0.))))\n", + "\n", + "scene.setCamera(camera)" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "# RENDER STATISTICS###############################\n", + "TIME FOR RENDERING: 2.091775894165039s\n", + "NUMBER OF PRIMARY RAYS: 76800\n", + "NUMBER OF SECONDARY RAYS: 0\n", + "NUMBER OF SHADOW RAYS: 0\n", + "RAYS/s: 36715.214193944885\n", + "##################################################\n" + ] + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 32, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "imgdata = engine.render(scene)\n", + "display(imgdata,w,h)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "however, without light this object doesn't really look like a 3D-Object. So let's also add a point light. This light type is somewhat like a light bulb." + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "from pyrt.light import PointLight" + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "# RENDER STATISTICS###############################\n", + "TIME FOR RENDERING: 2.503495931625366s\n", + "NUMBER OF PRIMARY RAYS: 76800\n", + "NUMBER OF SECONDARY RAYS: 0\n", + "NUMBER OF SHADOW RAYS: 0\n", + "RAYS/s: 30677.101979605963\n", + "##################################################\n" + ] + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 34, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "scene = Scene()\n", + "\n", + "scene.addLight(PointLight(Vec3(-1,-8,1)))\n", + "scene.add(Sphere(center=Vec3(0.,0.,0.), radius=3., material=PhongMaterial(color=Vec3(1.,0.,0.))))\n", + "\n", + "scene.setCamera(camera)\n", + "\n", + "imgdata = engine.render(scene)\n", + "display(imgdata,w,h)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now we're ready to create a larger scene. Let's create 4 spheres on top of a plane created using two triangles. Every piece should have a different material, so let's create materials first." + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "floormaterial = PhongMaterial(color=Vec3(0.5,0.5,0.5))\n", + "sphere0material = PhongMaterial(color=Vec3(1.,0.,0.), reflectivity=0.5)\n", + "sphere1material = PhongMaterial(color=Vec3(0.,1.,0.), reflectivity=0.5)\n", + "sphere2material = PhongMaterial(color=Vec3(0.,0.,1.), reflectivity=0.5)\n", + "sphere3material = PhongMaterial(color=Vec3(1.,1.,0.), reflectivity=0.5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Lets create another view, from more above looking to (0,0,0)" + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "camera = PerspectiveCamera(w, h, 45)\n", + "camera.setView(Vec3(0.,-10.,10.), Vec3(0.,0.,0.), Vec3(0.,0.,1.))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now we create and add a light and geometries:" + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "# Create a scene\n", + "scene = Scene()\n", + "\n", + "# Add a light to the scene\n", + "scene.addLight(PointLight(Vec3(0,0,15)))\n", + "\n", + "\n", + "# Add \"floor\"\n", + "A = Vertex(position=(-5.0, -5.0, 0.0))\n", + "B = Vertex(position=( 5.0, -5.0, 0.0))\n", + "C = Vertex(position=( 5.0, 5.0, 0.0))\n", + "D = Vertex(position=(-5.0, 5.0, 0.0))\n", + "\n", + "scene.add(Triangle(A,B,C, material=floormaterial))\n", + "scene.add(Triangle(A,C,D, material=floormaterial))\n", + "\n", + "# Add 4 spheres\n", + "scene.add(Sphere(center=Vec3(-2.5,-2.5,1.75), radius=1.75, material=sphere0material))\n", + "scene.add(Sphere(center=Vec3( 2.5,-2.5,1.75), radius=1.75, material=sphere1material))\n", + "scene.add(Sphere(center=Vec3( 2.5, 2.5,1.75), radius=1.75, material=sphere2material))\n", + "scene.add(Sphere(center=Vec3(-2.5, 2.5,1.75), radius=1.75, material=sphere3material))\n", + "\n", + "# Set the camera\n", + "scene.setCamera(camera)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "now we are ready to render as usual:" + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "# RENDER STATISTICS###############################\n", + "TIME FOR RENDERING: 8.909627914428711s\n", + "NUMBER OF PRIMARY RAYS: 76800\n", + "NUMBER OF SECONDARY RAYS: 0\n", + "NUMBER OF SHADOW RAYS: 0\n", + "RAYS/s: 8619.888589918117\n", + "##################################################\n" + ] + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 38, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "imgdata = engine.render(scene)\n", + "display(imgdata,w,h)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This is quite a boring image. We can tell the renderer to support shadows:" + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "# Creating Renderer: Simple Raytracer\n", + "# Shadow Enabled\n", + "# RENDER STATISTICS###############################\n", + "TIME FOR RENDERING: 11.061001062393188s\n", + "NUMBER OF PRIMARY RAYS: 76800\n", + "NUMBER OF SECONDARY RAYS: 0\n", + "NUMBER OF SHADOW RAYS: 55280\n", + "RAYS/s: 11941.053007314586\n", + "##################################################\n" + ] + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 39, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "engine = SimpleRT(shadow=True)\n", + "imgdata = engine.render(scene)\n", + "display(imgdata,w,h)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "And we can enable multiple iterations for rays. So a ray doesn't stop at an object if the material is reflecting." + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "# Creating Renderer: Simple Raytracer\n", + "# Shadow Enabled\n", + "# Iterations: 3\n", + "# RENDER STATISTICS###############################\n", + "TIME FOR RENDERING: 13.609395027160645s\n", + "NUMBER OF PRIMARY RAYS: 76800\n", + "NUMBER OF SECONDARY RAYS: 26456\n", + "NUMBER OF SHADOW RAYS: 62923\n", + "RAYS/s: 12210.608896894535\n", + "##################################################\n" + ] + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 40, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "engine = SimpleRT(shadow=True, iterations=3)\n", + "imgdata = engine.render(scene)\n", + "display(imgdata,w,h)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.5.0" + } + }, + "nbformat": 4, + "nbformat_minor": 1 +}