darwinsenior
darwinsenior

Reputation: 496

Any idea how to build a more complete raytracer in Haskell?

I am currently take a course on production graphics and since we are spend the whole semester building a raytracer I would like to experiment as much as possible. I used to use C++ but it gets really messy at some point. And I trust haskell as a very good language for prototyping, and clean code. However, I would really appreciate some advice as now I could not find a way for data representation.

Here is currently what I thought.

A raytracer in general is a scene as an environment so I might use a monad (build upon state monad probably) to connect different part in the tracer.

Camera is the simplest. It has Position and Direction and can be moved around and rotate.

The other parts become tricky. I wish to have Screen which could either generate pinhole or depth of field, and a list of lights that could be environment, point light, area light or directional light. The objects in the scene is made up by material and geometry where material might have phong or glossy or transparent so on and so forth, and geometry could be sphere triangle plane or a bunch of them (I am thinking bhv or kdtree as representation)

So, if I am in C++, I am pretty sure I will use inheritance to express such polymorphism. However, I am stuck on how should I represent that in haskell.

My initial attempt (if I do not have to consider secondary ray at all) is to use typeclass. So I have something like this

class Geometry geo where
    intersect :: geo -> Ray -> Intersection
class Material mat where
    shade :: (Light l) => mat -> Intersection -> l -> Color

And so I define my Objects in the following way. I looked up and I tend to use RankNTypes (I think I made a mistake here)

data Object = forall . geo mat (Geometry geo, Material mat) => Object geo mat 

Then I found I could not go on if I plan to implement the following function.

raytrace_objects :: (Material mat) => Ray -> [Objects] -> (Intersection, mat)
raytrace_objects ray objects =
  foldl reduce (farest_intersection, blackbody) objects
  where
    reduce (Object geo mat) old@(old_inter, _) =
      if new_inter < old_inter
        then (new_inter, mat)
        else old
      where
        new_inter = intersect ray geo

And blackbody just return black for the shading function

Suddenly I found that it is impossible!!!! mat has to match the material type whatever the nearest objects has. And it is not decidable during the static analysis. So, I am stuck. Is there any suggestion how I could surpass the haskell's type system or is there a better way to do it?

Upvotes: 0

Views: 248

Answers (1)

sapanoia
sapanoia

Reputation: 789

As a beginner you should try to avoid language extensions, because they can mislead you to less ideomatic solutions.

In Haskell, you sometimes rely on functions for polymorphism where you would use inheritance in other languages. Consider this simple sketch, which prefers functions over "objects":

type Geometry = Ray -> Intersection
type Material = UV -> Color

data Object = Object { objGeometry :: Geometry
                     , objMaterial :: Material
                     }

blackbody :: Material
blackbody uv = RGB 0 0 0

sphere :: Double -> Geometry
sphere radius ray = ...

Of course, to get reasonable performance, this would need to be extended with bounding boxes and other optimizations. Still, it shows that you can approach the problem from a completely different side.

About your implementation

This looks more like a proper type for Object:

data Object geo mat = Object ...

The constraint (Geometry geo, Material mat) => should instead go on the functions which handle Object, and only when they actually need it.

raytrace_objects will not work this way, because the objects parameter is of type list ([]). All elements in a list must be of the same type. This type is fixed at compile time. Object geo mat is not a plain type, because the parameters are not yet known. So there cannot be any actual values of this type. Object Sphere Wood might be a suitable type. Thus, [Object Sphere Wood] is a list that can only contain wooden spheres.

Food for thought and search engines: Object geo mat (which is a short notation for forall geo mat. Object geo mat) is a higher-order type which has kind * -> * -> * . [x] has kind * -> * , and [Int] has kind * .

This is a key difference between algebraic data types with type-classes in contrast to object-oriented programming: The Haskell compiler needs to know all types at compile time, whereas OO languages typically only need to know an interface and do not care about the actual runtime type.

Higher-order types and type-classes are more like templates in C++.

Further pointers

You could solve your problem at hand with heterogenous lists (e.g. HList from Hackage), which work more like OO lists in the sense that they can accept objects of varying types. Or you try more idiomatic solutions like the one outlined above.

Upvotes: 1

Related Questions