rafael-fuente / Python-Raytracer

A basic Ray Tracer that exploits numpy arrays and functions to work reasonably fast.
BSD 3-Clause "New" or "Revised" License
466 stars 44 forks source link

Scene Scripting Language #1

Closed sparthir closed 3 years ago

sparthir commented 4 years ago

So... many many many years ago I used to use POVRay and the thing I liked about it is the scenes were scripted in their own files.

You could not only describe the scene but also use variables to start doing animations with batch files. I wrote an DOS based animation tool in Turbo Pascal that did all sorts of weird and wonderful things. Things like moving objects (the camera and lights are counted as an object) along bezier curves, catmull-rom spline curves etc.

So anyway to get to the point it would really make this far more accessible if a text file could be used to describe a scene. So things like objects, textures, camera, lights etc could be defined in a far simpler file that is called by the Python code.

I'd be happy to help with how it should look and do testing. Perhaps a simplified version of the POVRay scene files could be a guide.

Another thing that can be done of course is (as you will see in the example below) separating out textures into their own files for modification. :)

For example a basic scene would look like this:

//EXAMPLE OF SPHERE

//Files with predefined colors and textures
#include "colors.inc"
#include "glass.inc"
#include "golds.inc"
#include "metals.inc"
#include "stones.inc"
#include "woods.inc"

//Place the camera
camera {
  sky <0,0,1>           //Don't change this
  direction <-1,0,0>    //Don't change this  
  right <-4/3,0,0>      //Don't change this
  location <30,10,1.5> //Camera location
  look_at <0,0,0>     //Where camera is pointing
  angle 15      //Angle of the view--increase to see more, decrease to see less
}

//Ambient light to "brighten up" darker pictures
global_settings { ambient_light White }

//Place a light--you can have more than one!
light_source {
  <10,-10,20>   //Change this if you want to put the light at a different point
  color White*2         //Multiplying by 2 doubles the brightness
}

//Set a background color
background { color White }

//Create a "floor"
plane {
  <0,0,1>, 0            //This represents the plane 0x+0y+z=0
  texture { T_Silver_3A }       //The texture comes from the file "metals.inc"
}

//Sphere with specified center point and radius
//The texture comes from the file "stones.inc"
sphere { <0,0,1.5>, 1 texture {T_Stone1} }
rafael-fuente commented 4 years ago

I think that implementing a parser would add lot unnecessary messy code to this project. just pov-ray parser, https://github.com/POV-Ray/povray/tree/master/source/parser has more code that this repository and it's written in C++.

One of the strengths of Python is that is highly readable language and it can be easily mixed , so I think that writting a scene in pure Python is an advantage, not a disadvantage. In fact, some modern Rendering/Modeling engines like Blender use Python as scripting language.

Another thing that can be done of course is (as you will see in the example below) separating out textures into their own files for modification. :)

It can easily done. Just define materials in another file: This is Example 1 written in that way https://github.com/rafael-fuente/Python-Fast-Raytracer/blob/master/Example1.py

custom_materials.py:

gold_metal = Material(diff_color = rgb(1., .572, .184), n = vec3(0.15+3.58j, 0.4+2.37j, 1.54+1.91j), roughness = 0.0, spec_c = 0.2, diff_c = 0.8, max_ray_depth = 3)
bluish_metal = Material(diff_color = rgb(0.0, 0, 0.1), n = vec3(1.3+1.91j, 1.3+1.91j, 1.4+2.91j), roughness = 0.2,spec_c = 0.5, diff_c = 0.3, max_ray_depth = 3)

img = Image.open(Path("images/checkered_floor_linear_sRGB.png"))
checkered_floor = np.asarray(img)/256.
checkered_floor_material = Material(texture = checkered_floor, repeat = 2.0, diff_color = rgb(1., 1., 1.), n = vec3(1.5, 1.5, 1.5), roughness = 0.2, spec_c = 0.5, diff_c = 0.5, max_ray_depth = 3)

custom_skyboxes.py:

img = Image.open(Path("images/miramar_linear_sRGB.png"))
miramar = np.asarray(img)/256.

img = Image.open(Path("images/stormydays_linear_sRGB.png"))
stormydays = np.asarray(img)/256.

my_scene.py:

# Import the necessary modules

Sc = Scene(ambient_color = rgb(0.05, 0.05, 0.05))

angle = -np.pi/2 * 0.3
Sc.add_Camera(Camera(screen_width = 400 ,screen_height = 300, position = vec3(2.5*np.sin(angle), 0.25, 2.5*np.cos(angle)  -1.5 ), look_at = vec3(0., 0.25, -3.)))

Sc.add_DirectionalLight(Ldir = vec3(0.52,0.45, -0.5),  color = rgb(0.15, 0.15, 0.15))

Sc.add_Surface(Sphere(material = gold_metal, center = vec3(-.75, .1, -3.),radius =  .6))
Sc.add_Surface(Sphere(material = bluish_metal, center = vec3(1.25, .1, -3.), radius = .6))

Sc.add_Surface(Plane(material = checkered_floor_material,  center = vec3(0, -0.5, -3.0), width = 6.0,height = 6.0, pu = vec3(1.0, 0, 0), pv = vec3(0, 0, -1.0)))

Sc.add_SkyBox(stormydays)
Sc.render()

Have you tried jupyter notebook? It helps to make this type of things more easily and neatly.

About the animations I think is a good idea and it's very easy to do. render() method from scene.py return a PIL image, and PIL package has a nice implemetation to make a gif. Just with one line of code:

frames[0].save('animation.gif', format='GIF', append_images=frames[1:], save_all=True, duration=10, loop=0)

where "frames" is a list of PIL image instances.

Anyway, i'm going to implement a nice way to make animations in the engine Thanks for the suggerences. Any more suggestions are welcome! :)

sparthir commented 4 years ago

Yes. Actually having a closer look at the extensive code you've created I see what you mean.

I was thinking along the lines of something that already has a basic parser (JSON or similar) for scene construction but actually you're probably right that Python will be good enough.

I think my point is removing the engine as much as possible from the scene is a good idea for clarity for someone wanting to create images or animations. also reduces having to duplicate code.

Having those materials as separate files might be really nice. For example materials_wood.py, materials_metal.py etc makes it easier for people to submit pull requests for new. Same for other things like say objects (spheres, cones, cylinders, cubes, toroids etc).

I like abstracting those things like this into separate files so it's easier to manage and also for contributors.

I haven't used Jupyter yet. I use VS Code for most things these days. :)

rafael-fuente commented 4 years ago

Ok, this is what the next update will have:

sparthir commented 4 years ago

Very cool!

I'm super geeking out about this right now. Shame I'm not that good at Python or I could be much better help. Also I'm not sure how some of the maths is done since it's not my strong suit. :/

I just know that from a scene builders perspective the cleaner the code is to make a scene up the easier it is to manage.

rafael-fuente commented 4 years ago

I uptaded the entire repository. I added new options like antialiasing, camera lens and soap bubble material. Scene scripting is now much more clean and rendering performance has improved.