Writing my own pathtracer on mobile device

Started by
55 comments, last by IsItSharp 9 years, 3 months ago

Hello forum and graphic geeks biggrin.png ,

i just registered myself on this wonderful site where kind of everything is posted i am interested in (games, graphics, programming). So the christmas holidays are imminent and i am currently searching for some programming stuff i could learn in the free days off from work.

Because i am really into android programming currently and i also really love the idea of pathtracing (i did a lot of CGI with Blender and LuxRender) i thought about writing a simple pathtracer for mobile devices (it don't have to be fast! smile.png)

Sadly i am a complete math idiot. Some months ago i started the first project "Try to write a pathtracer". After reading documents for hours and trying to understand the math calculations behind the letters i gave up. So in the next weeks i hope i have enough endurance to go through this second try and finally understand it (maybe it helps me in the future).

Currently i am working with Android Studio and a Galaxy Note 2 with Android 4.4.3 (Cyanogenmod).

There will probably be a lot of questions and i hope you will answer them ph34r.png

So here are some points i already understood of programming a pathtracer:

1. I need an abstract class from which the basic objects inherit (such as a Sphere and a Plane). Every object has its own definitions (radius, center point, etc.) and its own "checkIntersect" function

2. I need different classes, such as a Vector3D and a Ray (which has a origion Vector3D and a direction Vector3D)

3. Then i have to loop through the pixels of the image and generate a ray. Now i will loop through all basic objects and check if the ray intersects with one of these objects

4. if an object is in front of the intersected one (no idea how i could check this?) i skip this ray

5. if the ray hits the object i have to calculate the reflection and the resulting color depending on the object color (i will start with just a diffuse material). From that i have to calculate a second reflection ray which will also travel through the scene (sadly no idea how to calculate that second ray sad.png

6. If a given MAX_BOUNCE is reached, the ray disappears

These are the rough points i have in my mind for the topic "pathtracing".

So any tips you may have for me? I really appreciate all your answers smile.png

I will try to keep the latest progress updated.

Advertisement

Welcome to the forums.

I'm glad there is another fan of ray tracing and another brave one to try writing them. Although for the beginning, allow me to share few things I've learned (mostly the hard way).

First of all, you need to know at least some math (3D math -> vectors, solving equations, etc.). I would recommend you to try naive ray tracing first (just primary rays, then try whitted ray tracing) - and next you can dive into more complex stuff. Be sure to check out few ways how to optimize your renderer (BVHs, KDTrees, Grids, Nested Grids, etc.), and don't forget also to optimize your intersection routines (you will need them precise and fast for path tracing). Once you've got this, you got basics to jump into wonderful world of global illumination.

Android is indeed an interesting plaform, I'm though not sure how fast you will get without native code (Java is slow - but for learning, why not ... you can also always move to native code later - and optimize from there).

Anyways, I wish you best of luck in wonderful world of infinitely bouncing rays, feel free to ask any further question, I'll try to answer... to your questions so far:

1.) Currently I'm working mostly with high performance ray tracing (where we decided to use just single type of primitive - triangle). Although I've worked with CPU-based general ray tracers, the approach worked like this:

There was a Primitive class - it had several virtual functions (including the one for Ray-Primitive test, and also GetAABB (Axis-aligned bounding box) which was used when building KD-Tree for the scene). Triangle, Sphere, Plane, etc. all these inherited Primitive class and implemented those functions. This approach has some overhead (V-tables), but it is the most clean way to allow multiple primitives inside your scene.

2.) Yes - in my current ray tracing project I have "Math" part of project, which contains "Numeric" folder - where you have all numeric classes needed -> float4 (4 component vector), mat4 (4x4 matrix), quat (quaternion - to represent spatial rotations).

Apart from that I also have "Geometric" folder - where I do have all primitives (Ray, Plane, Axis-Aligned Bounding Box, Sphere, Triangle, etc. - note. even though I use only triangles inside the scene it is important to note, that the others are also used inside the project).

Even though it is not absolutely necessary to distinguish the Math and Geometric structures, I recommend to do so (as your ray tracer will grow it can become bloody mess and then you will need refactoring).

3. and 4.) For naive ray tracing this is correct - like:


for (y = 0 ... height)
    for (x = 0 ... width)
        shadePixel(x, y);

Now, in naive (very simple ray tracer) your shadePixel function will look like:


shadePixel(x, y)
    d = INFINITY;
    id = -1;
    hit = false;
    for (i ... numOfObjects)
        if (id' = intersect(object[i], ray[x, y], &d'))
            if (d' < d & d' > 0)
                d = d'
                id = id'
                hit = true
    if (hit == true)
        pixel[x, y] = object[id].color

This is very basic shadePixel function - it guarantees that you always write out color of closest hit along the ray (that is non-negative).

I recommend to do 5. and 6.) once you have at least basic functionality like the above written. Hint - you can use recursion inside shadePixel, you just have to calculate secondary ray direction and origin (origin is a hitpoint, direction depends on what material have you hit).

If you update the progress (and possibly show results and some parts of code - I can give you further hints ;).

My current blog on programming, linux and stuff - http://gameprogrammerdiary.blogspot.com

Hi Vilem Otte,

thank you very much for your detailed answer. Because everyone on the internet is talking about, how easy it is to write a pathtracer (climax is a pathtracer in 99 lines of C and i don't understand a single line of code) i first thought: well, it can't be that hard, even for me. But sadly, it is kind of complicated.

To the data storage topic:

do i really need a KD-Tree if i only use primitives such as spheres and planes?


do i really need a KD-Tree if i only use primitives such as spheres and planes?

If you only have a few, no. You can start out with a test scene of 5-6 spheres and won't need any special data structure to store the spheres. Once you get over a few hundred spheres (or triangles) though you quickly begin to realize how important it is for larger scenes!

“If I understand the standard right it is legal and safe to do this but the resulting value could be anything.”

The key to usually anything is understanding the basics, without the basic mathematical knowledge and understanding behind path-tracing you are doomed to blind cut-paste/copy-paste -> find tutorial/sample code and then back to copy and pasting again. All too often the same phrase have been mentioned, I want to do X but I do not comprehend the theory/principles underlying X. Then how are you going to implement something you don't understand, and what are you going to do when you run into issues during implementation. My advice, spend the time up front to understand the basics behind path-tracing, math and all and you will find that this will go a long way when you decide to start your implementation.

[background=#fafbfc]I want to do X but I do not comprehend the theory/principles underlying X.[/background]


Yes, that's the problem sad.png

[background=#fafbfc]Then how are you going to implement something you don't understand, and what are you going to do when you run into issues during implementation.[/background]


As i wrote at my first post, i hoped you guys could help me out with some things?

[background=#fafbfc]My advice, spend the time up front to understand the basics behind path-tracing, math and all[/background]


The best method to learn something is if you practice it a lot (e.g. writing a pathtracer). "Learning by doing"

So here comes my first understanding problem:

Based on this letter: http://www.csee.umbc.edu/~olano/435f02/ray-sphere i tried to calculate the intersection for the sphere.

So far tried to calculate a, b, c and the discriminant. As it says in the text:

if the discriminant is below zero, there is not intersection. If it is 0, there is one intersection. If it is 1 there are two intersection (entry and exit).

So do i have to calculate the length of the resulting discriminant vector (and then check the stuff) or should i check if the coordinates of the vector are below zero?

My code looks like this so far (based on
b2 - 4 a c
a = d * d
b = 2 d * (p0 - pc)
c = (p0 - pc) * (p0 - pc) - r2):
        public override float Intersect(Ray ray)
        {
            Vector3D a = ray.direction.multiply(ray.direction);
            Vector3D twoDirection = ray.direction.multiply(new Vector3D { x = 2, y = 2, z = 2 });
            Vector3D b = twoDirection.multiply(ray.origin.subtract(this.center));
            Vector3D c = (ray.origin.subtract(this.center)).multiply(ray.origin.subtract(this.center)).subtract(
                          new Vector3D(this.radius*this.radius, this.radius*this.radius, this.radius*this.radius)
                          );
            Vector3D discriminant = b.multiply(b).subtract(new Vector3D(4,4,4).multiply(a).multiply(c));

        }
Edit:

Okay, i got it biggrin.png

Is this correct?


        public override float Intersect(Ray ray)
        {
            float a = ray.direction.Dotproduct(ray.direction);
            float b = 2.0f * ray.direction.Dotproduct(ray.origin.subtract(this.center));
            float c = ray.origin.subtract(this.center).Dotproduct(ray.origin.subtract(this.center)) - (this.radius * this.radius);

            float discriminant = (b * b) - (4.0f * a * c);

            if (discriminant < 0) //no hit
                return -1.0f;
            else if (discriminant == 0) //one hit
                return (-b + (float)Math.Sqrt(discriminant)) / 2.0f * a;
            else
            {
                float t1 = (-b - (float)Math.Sqrt(discriminant)) / 2.0f * a;
                float t2 = (-b + (float)Math.Sqrt(discriminant)) / 2.0f * a;

                return Math.Min(t1, t2); //get the smallest value
            }
        }

So any tips here?

No one here who wants to check the last function?

Probably you want to completely exclude results less than zero (i.e. if t1 is less than 0, return t2, and vice versa, and if they are both positive then return the smallest one). That will also handle cases where your ray starts inside the sphere, as well. Otherwise I haven't checked but the equation looks correct, it's just a standard quadratic. Also make sure your direction vectors are normalized.

Also since you seem to be using C# (correct me if I'm wrong) you would do yourself a favour to code proper arithmetic operators for your vector class rather than using .subtract, .add, etc... that'll make your code much easier to read and write.

“If I understand the standard right it is legal and safe to do this but the resulting value could be anything.”

@Bacterius

Thanks for your reply smile.png

Also since you seem to be using C# (correct me if I'm wrong) you would do yourself a favour to code proper arithmetic operators for your vector class rather than using .subtract, .add, etc... that'll make your code much easier to read and write.


It is C# yes. I want to write the pathtracer step by step in c# because i am more familiar with this language. After i finished a code block (e.g. the intersection for the sphere) i will transfer it to Java for Android.

I didn't over overwrite the arithmetic operators because it is not possible in java.

Probably you want to completely exclude results less than zero (i.e. if t1 is less than 0, return t2, and vice versa, and if they are both positive then return the smallest one). That will also handle cases where your ray starts inside the sphere, as well. Otherwise I haven't checked but the equation looks correct, it's just a standard quadratic


So like this?


    public float intersect(Ray ray){
        Vector3D v = ray.origin.sub(this.center);

        float a = ray.direction.dot(ray.direction);
        float b = 2.0f * ray.direction.dot(v);
        float c = v.dot(v) - (this.radius * this.radius);

        float discriminant = (b*b) - (4.0f * a * c);

        if(discriminant > 0){
            float x1 = (-b - (float)Math.sqrt(discriminant) / (2.0f * a));
            float x2 = (-b + (float)Math.sqrt(discriminant) / (2.0f*a));

            return Math.min(x1,x2);
        }
        else if(discriminant == 0)
            return (-b + (float)Math.sqrt(discriminant) / (2.0f * a));
        else
            return -1.0f;
    }

Also make sure your direction vectors are normalized.


So like this?


Ray ray = new Ray(new Vector3D(0.0f,0.0f,0.0f), new Vector3D(5.0f,5.0f,1.0f).normalize());

Normalize function:


    public Vector3D normalize(){
        float magnitude = magnitude();
        return new Vector3D(this.x / magnitude, this.y / magnitude, this.z / magnitude);
    }

Magnitude function:


    public float magnitude(){
        return (float)Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z);
    }

This topic is closed to new replies.

Advertisement