Jump to content

  • Log In with Google      Sign In   
  • Create Account

Vilem Otte

Member Since 11 May 2006
Offline Last Active Jun 27 2016 01:24 PM

#5220136 From scratch vs Unity

Posted by Vilem Otte on 30 March 2015 - 03:21 AM

I don't know if one should really write anything from a scratch, since there are a lot of open source projects around. Maybe rather than making one yourself, modify already existing ones or at least assemble one from already existing projects.

 

No.....! In most cases (and I've worked on such projects), where large parts are taken from another projects (open source, etc.) ends up as huge mess. As a first programmer in that project, I was trying to get away ASAP.

 

"The management" automatically states that when you grab code from another open-source project, you know EVERYTHING about it (this is wrong - but hey, try to explain them when they don't listen). So, after few weeks of messages from management (where they wanted large scale changes that would take days in project you written on your own, they wanted them in days in source you just used), where I tried to explain that it can't be done that fast (and no human being is able to know everything about 5 or 6 large open-source projects you downloaded & build 2 weeks ago & be able to do large scale modifications); I ended up quitting.

 

I'm not saying you shouldn't use open-source at all, but explain this to management (and hope they will listen)




#5202181 Writing my own pathtracer on mobile device

Posted by Vilem Otte on 06 January 2015 - 03:50 AM

About saving the image and your issue.

 

There are multiple reasons why you can get the issue:

1.) Due to asynchronous and highly parallel nature of the code, the accumulator buffer is re-written while you are reading from it inside the loop. See following diagram:

 

| Thread 1 | Thread 2 |

|----------|          |

|  Render  |          |

|     |    |          |

|     |    |----------|

|     |    | GetBitmap|

|     |    |     |    |

|     |    |     |    |

|----------|     |    |

|  Render  |     |    |

|     |    |     |    |

|     |    |     |    |

|     |    |----------|

|     |    | saveImage|

|     |    |     |    |

|----------|     |    |

 

Now, the GetBitmap doesn't read the correct data (they are re-written while GetBitmap reads them. The proper solution for this is to use Mutex lock on the shared resources (which is accumulator buffer and samples - as both can be changed during rendering phase).

 

First of all you need an instance of Mutex object (it is one of the core java objects) to synchronize your code; that is accessible from render and getBitmap functions.

 

Your render and getBitmap will then look like:

public void render() {
    try {
        mutex.acquire();
        try {
            ... // The code of render will be here (or at least the part of code where you write into accumulator and samples - assuming you write to samples too)
        }
        finally {
            mutex.release();
        }
    }
    catch (InteruptedException ex) {
        ...
    }
}

...

public Bitmap getBitmap() {
    ... // Here you create bitmap object
    try {
        mutex.acquire();
        try {
            ... // The nested loop from getBitmap goes here
        }
        finally {
            mutex.release();
        }
    }
    catch (InteruptedException ex) {
        ...
    }
    // Here you return rBitmap
}

This way (using Mutex object), you synchronize your code to work like this:

 

 

| Thread 1 | Thread 2 |

|----------|          |

|  Render  |          |

|     |    |          |

|     |    |          |

|     |    |          |

|     |    |          |

|     |    |          |

|----------|          |

|          |----------|

|          | GetBitmap|

|          |     |    |

|          |     |    |

|          |     |    |

|          |     |    |

|          |     |    |

|          |----------|

|----------| saveImage|

|  Render  |     |    |

|     |    |     |    |

|     |    |     |    |

|     |    |     |    |

 

Of course this is only going to help you when those functions are actually processed on multiple threads (and I assume they do).

 

Another possible reason would be out-of-range colors (when your code is sequential, it is most likely related to the divisions or type-casts), in this part of the code:
int r = (int)(accumulator[i*3+0] / (float)samples);
int g = (int)(accumulator[i*3+1] / (float)samples);
int b = (int)(accumulator[i*3+2] / (float)samples);

Make sure that r, g and b are indeed in range from 0 to 255 (just Log.v it - to see whether there isn't something weird happening). Although it would most likely make the issue be throughout whole image, not just parts.




#5201596 Writing my own pathtracer on mobile device

Posted by Vilem Otte on 03 January 2015 - 02:05 PM

My apologise man, I made a stupid mistake there.

 

What we want when computing the dx and dy are the differences between two rays on X, respectively Y axes (on viewport). Now, why do we do that, we take base vector, which is the one directly in the centre of the screen and then we take ray to one pixel right from centre and to one pixel up from centre. The code doesn't do that.

float xone = (((float)width * 0.5f + 1.0f) / (float)width) * 2.0f - 1.0f;
float yone = ((((float)height * 0.5f + 1.0f) / (float)height) * 2.0f - 1.0f) * aspect;

These are the fixed xone and yone vectors. I intentionally kept the computation more intensive (you can actually optimise it to 2/width, respectively (2/height)*aspect.

 

Now it should work properly.

 

And btw. Happy New Year to whole community here smile.png




#5200808 Writing my own pathtracer on mobile device

Posted by Vilem Otte on 30 December 2014 - 09:01 AM

Hm, still the same result.

 

Did you mean this:

Vector3D direction = new Vector3D(xdir + r1 * dxSize, ydir + r2 * dySize, zdir);

instead of this:

Vector3D direction = new Vector3D(xdir + r1 * dxSize, ydir + r2 * dxSize, zdir);

? Even if you did, it is also not working.

 

 

Actually...

Vector3D direction = new Vector3D(xdir + r1 * dxSize, ydir + r2 * dySize, zdir);

direction.normalize();

 

But I think I forgot some division (that is why your rays are "spread" that much (they shouldn't be).




#5200547 Writing my own pathtracer on mobile device

Posted by Vilem Otte on 29 December 2014 - 08:25 AM

Uh.... please normalize this direction vector:

Vector3D direction = new Vector3D(xdir + r1 * dxSize, ydir + r2 * dxSize, zdir);

 

I gotta go to work right now, I'll check the math later today (maybe there was some mistake in the code I posted).




#5200502 What is better? TransformFeedback or OpenCL Kernel?

Posted by Vilem Otte on 29 December 2014 - 02:08 AM

+1 for samoth, although...

 

The synchronization (if done correctly) works for all the devices from major vendors and is not that expensive. Generally...

 

If you need to do something more complex with the data before you output them, go with OpenCL, otherwise using transform feedback might be perfectly fine.

 

To your question exactly:

What would be faster? It depends, if you use constant number of matrices (weights) per vertex, then OpenGL version might be faster. For variable number of weights (theoretically infinite), the computation itself might be highly efficient in OpenCL (it won't be a simple implementation though, because if you mess one thing there it will get horribly slow), most likely better than doing that through OpenGL (but I will not guarantee that, as you would need to benchmark ... you might also get different results on different devices, well... I guess you know how this work).

 

To your second question, OpenCL can work directly with OpenGL vertex buffer objects (and textures). You just need to "acquire" them prior to using them in OpenCL and "release" them prior to using them back again in OpenGL. I've been using OpenCL kernels for quite a long time now and I haven't seen (if done properly) any performance hits by this one (and yes, you can also use double-buffering to overcome this problem - you're working on first buffer in OpenCL and displaying second one (that you generated in the previous frame), that way you most likely won't see any performance hit at all). ... it will cost you more memory though!




#5200500 Writing my own pathtracer on mobile device

Posted by Vilem Otte on 29 December 2014 - 01:54 AM

Antialiasing

Actually this is not as hard when you think about it - for primary rays each ray occupies approximately same "pyramid" - so you just need to get dx and dy (these vectors will have direction of your right, respectively up vector of camera, their length will be the size of this pyramid along these vectors (which are basically X and Y if you projected them into screen-space).

 

Their calculation is straight-forward. Your ray-direction is computed as:

// You can initialize all these values before your rendering loop, they don't change for different primary rays
float xone = (1.0 / (float)width) * 2.0f - 1.0f;
float yone = ((1.0 / (float)height) * 2.0f - 1.0f) * aspect;
float zdir = 1.0f / (float)tan(fov);
Vector3D base = new Vector3D(0, 0, zdir).normalize();
Vector3D xvec = new Vector3D(xone, 0, zdir).normalize();
Vector3D yvec = new Vector3D(0, yone, zdir).normalize();
Vector3D dx = xvec - base;
Vector3D dy = yvec - base;

// As we know our camera right vector and up - this can be further simplified to
float dxSize = dx.length();
float dySize = dy.length();

// Inside your rendering loop
// And for the AA rays you actually work like this
for (int aa = 0; a < AA_SAMPLES; a++) {
    float r1 = Random().GetFloat() * 0.5; // Random().GetFloat() here returns random value between -1.0 and 1.0
    float r2 = Random().GetFloat() * 0.5; // Random().GetFloat() here returns random value between -1.0 and 1.0
    Vector3D direction = new Vector3D(xdir + r1 * dxSize, ydir + r2 * dxSize, zdir);
    Ray ray = new Ray(Camera, direction);
    color.add(Trace(ray, 1));
}
return color.divide(AA_SAMPLES);

Explicit Path Tracing

To answer questions:

1.) 4D vectors are specific to my code (I took an example directly from my code), you don't need them, the code is written in OpenCL code (which works really close to the actual hardware), the thing is, that on that level you care about your type size, memory alignment, etc. (e.g. these things are all about optimization), this is something you can't really do with Java, so feel free to use your Vector3D instead.

 

2.) EPS is a constant here - it prevents your hitpoint to end inside the intersection (you had the problem - it is the one with numeric precision - and solved it with 0.99f - in this code I've used the same, but just used preprocessor directive (in Java you would use something different, like public static float EPS = 0.99f; ... if you know C or OpenCL language, my equivalent is #define EPS 0.99f - they are not entirely the same (from the compiler perspective or language theory perspective), but they will work the same)

 

EDIT:

If anyone reads the post now, there is stupid mistake I noted few posts below, there should be:

float xone = (((float)width * 0.5f + 1.0f) / (float)width) * 2.0f - 1.0f;
float yone = ((((float)height * 0.5f + 1.0f) / (float)height) * 2.0f - 1.0f) * aspect;

For explanation, see few posts below. My apologise again.




#5199626 Writing my own pathtracer on mobile device

Posted by Vilem Otte on 22 December 2014 - 08:21 PM

Well... I've been re-reading that code today in the morning and something caught my eye...

 

Your ray generation for AA rays is wrong!

Camera.x = Camera.x + (col + 0.5f) / 2.0f;
Camera.y = Camera.y + (row + 0.5f) / 2.0f;

Your 'Camera' is actually your origin (which must be shared between all the rays, you shouldn't move your camera.

 

A little theory - how antialiasing work ... well... let us start better, with simple question, why do we have aliasing?

1.) Let's draw an image - the following rectangles are pixels, and points are the positions where we shoot a ray:

┌─────┬─────┬─────┐

│          │    

│  ■  │  ■  │  ■ 

│     │     │    

─┼─┤

│     │     │    

│  ■  │  ■  │  ■ 

│     │     │    

─┴─┘

 

For 2x2 anti-aliasing we can use for example this (uniform sampling)

┌─────┬─────┬─────┐

│ ■ ■ ■ ■ │ ■ ■

│     │     │    

│ ■ ■ │ ■ ■ │ ■ ■

─┼─┤

│ ■ ■ │ ■ ■ │ ■ ■

│     │     │    

│ ■ ■ │ ■ ■ │ ■ ■

─┴─┘

 

Or this (Random sampling)

┌─────┬─────┬─────┐

         

│         

     │  

─┼─┤

      │  

  │   

│    │     

─┴─┘

 

There is one very important note, all the rays originate from the same point (so you will not move camera), but they have different ray direction vector.

 

How the computation works - each of your pixel is actually 4-sided pyramid, with apex in your camera origin - all you need is to compute 4 boundary vectors (minimum and maximum ray directions), all antialiasing samples will fall between the bounds of these (you can distribute them uniformly, randomly, or on some well-guessed spots (Quincunx AA (better than uniform in a box), poisson (one of the best), etc.).




#5198609 Writing my own pathtracer on mobile device

Posted by Vilem Otte on 16 December 2014 - 03:11 PM

Why your image turned black (respectively is black and white).

 

CColor class uses 1 byte to store each color channel (0-255), you are summing 4 values of range (0-255), you have overflow! You have to take care while working with data types - your have to sum colors F.e. in 32-bit value per channel. E.g.:

int r = 0;
int g = 0;
int b = 0;

for(int row = 0; row < 2; row++) {
    for(int col = 0; col < 2; col++){
        Vector3D direction = new Vector3D(xdir, ydir, zdir).normalize();
        Ray ray = new Ray(Camera, direction);
        CColor c = Trace(ray,1);
        r = r + (int)c.r;
        g = g + (int)c.g;
        b = b + (int)c.b;
    }
}

r = r / 4;
g = g / 4;
b = b / 4;

return new CColor(r, g, b);

You can also use floats instead of int (or technically even short - 16-bit integers - will work). That is why you saw the issue you saw.

 

Ad. CColor - it stores the color value in 1 integer - 1 byte for red, 1 byte for green, 1 byte for blue and 1 byte for alpha; technically you can initialize it with float, but it will still store channel just in 1 byte. This is why I'd recommend creating your own class for holding color (in our software we use "float4" - 4 floats packed in class). Why? Simply because we need more range than 1 byte per channel (and using of CColor-like type may be confusing - you can use it, for output - but not for computations).




#5198422 Writing my own pathtracer on mobile device

Posted by Vilem Otte on 15 December 2014 - 03:51 PM

Congratulations, you've just built your first path tracer that can converge. It still has some serious problems (apart from problems when handling large scale scenes), when you overcome these it will start becoming useful smile.png.

  1. Convergence in directly lit areas takes ages, convergence in shadowed areas too.
  2. Your ray generation when you hit surface is... well, naive is the best word describing it (we denote techniques (or algorithms) as naive, when they produce correct result (and they are simple), but they are definitely not efficient)
  3. Allowing for different shapes and/or materials
  4. Allowing for TONs of shapes and materials in scene

Okay, brace for informations!

 

1. Explicit path tracing

 

The general problem of path tracing is this: If the path we're following doesn't hit the light, the contribution of this path is zero. The smaller your lights get, the lower probability of hitting them we have (and for perfect point lights we are at zero probability of hitting the light!).

 

But, don't worry - we don't write some black magic code to have fast path tracers for direct lights. The actual idea is quite simple, if at each given step in path we can connect to (any) light the path will have some contribution to the result (aka, we separate direct lighting from indirect lighting). I've did the full derivation in my Bachelor thesis few years ago (for full derivation, see chapter 3.2 http://is.muni.cz/th/396530/fi_b/Bachelor.pdf, you don't need to remember that though ... shameless self-promotion ph34r.png) - the whole idea works like this, at each step in path we sample one (random) light, compute direct illumination from it (note, we need to cast shadow ray to determine visibility between current path step hitpoint and random point on that light) and continue in the path (if we accidentally hit the light, we have to discard that value!).

 

This way it is quite easy to achieve very good looking images in few samples per pixel ... there will still be problems (caustics still takes quite long to converge, dark room standing next to lit room, etc.) - they also have solutions (Bi-Directional Path Tracing).

 

Alright, I'm talking enough for this - time for some code:

float4 trace(Ray r, Scene s) {
    // Calculate next step on path
    RayResult res = s.findClosestIntersection(r);
    float4 hitPoint = r.origin + r.direction * res.distance * EPS;
    float3 normal = s.objects[res.hitID].getNormal(res.barycentric);
    float3 color= s.objects[res.hitID].getColor();

    float4 result = float4(0.0, 0.0, 0.0, 0.0);

    // Explicit step
    if(s.objects[res.hitID].isLight() == false) {
        int lightID = s.getRandomLight();
        float4 randomLightPoint = s.objects[lightID].getRandomPosition();
        Ray shadow = new Ray(hitPoint, (randomLightPoint - hitPoint).normalize());
        RayResult shadowResult = s.findClosestIntersection(shadow);
        if (s.objects[shadowResult.hitID].isLight() == true) {
            result = dot(normal, shadow.getDirection()) * attenuation * color * s.objects[shadowResult.hitID].getColor();
        }
    }

    // Next step, beware, you have to correctly weight depending on your PDF!
    return result + weight * color * trace(r.generateNextPathStep(), s);
}

2. Sampling....

 

Assuming you are still reading smile.png ... there is a way how to heavily implement areas where there is only indirect illumination. Now a little theory - before I described a way you use for generating your ray directions, the way works, but it is bad - you are working with perfectly diffuse materials (as for now, once you just into reflective you will have to change your ray generator to work for them), now the current generator will work for reflected rays too, but it has to (randombly) pick exactly the ray in direction of perfect reflection of incident ray (btw. probability of doing that is equal to 0). That is why we use different ray generator for reflected rays.

 

Where am I getting? All your samples should be weighted by cosine of angle between ray and normal of the surface (to keep energy conservation and of course to be physically correct). Now, as each sample is multiplied by the cosine of that angle - it means that rays at grazing (high angles - almost parallel with the surface they are hitting) have very, very low weight (their contribution is almost zero). Technically we want to generate more rays in direction similar to normal and less rays in directions close to be perpendicular to normal.

 

For further description you can also look inside my thesis (Chapter 3.3). There is also a code provided for faster uniform random number generator (and thus ray direction generator), and for cosine-weighted random number generator (better for diffuse surfaces). Of course for each different shading model you have another "ideal" ray generator. This technique is called importance sampling (you compute mainly those samples that are important).

 

3. Shapes & Materials

 

This is the part where you most likely get very soon, I'd advice to implement previous 2 before that (1. will allow you to see the resulting image very fast, thus increasing your speed in debugging shapes and materials; 2. (if implemented correctly & in a clever way) will allow you to use different sampling methods for different materials, which is very, very important for any useful path tracer.

 

4. Acceleration Structures

 

Once you have 1, 2, 3, and 4 - it is time to jump into large scenes. I won't dive into them now, but to give you little preview:

 

gallery_102163_282_2527673.png

 

This Crytek Sponza image is what I've rendered just now on NVidia Mobility GeForce 720M (which is low end), at full laptop display resolution (1366x768 ... minus something on window borders), frame rate 12 fps; On Radeon R7 (the power of desktop) next to it runs at realtime frame rates (I didn't do the benchmark though) ... that is why acceleration structures are so important.

 

Note, there is some aliasing, because - no AA filter is running in that image, no texture filter is running in that image (and yes, it decreases performance), no packet tracing or other kind of hack is used, the transparent materials are root of all evil (I have to terminate ray and create new one), acceleration structure is SplitBVH, ray tracing runs on OpenCL, the resulting buffer is showed using OpenGL and PBO.




#5198358 Writing my own pathtracer on mobile device

Posted by Vilem Otte on 15 December 2014 - 11:27 AM

Uhm, I'm not sure why you don't get exceptions with that code (as you should get them, there are few things done wrong + you won't see progressive at all afaik) ... time to refresh my java memory:

private float[] accumulator;
private int samples;

// You need 1 float value per channel!
public void RestartProgressiveRendering() {
    this.accumulator = new float[this.canvas.getWidth() * this.canvas.getHeight() * 3];
    this.samples = 0;
}

public void RenderOneStep() {
    int width = this.canvas.getWidth();
    int height = this.canvas.getHeight();
    int i = 0;
    for (int x = 0; x < width; x++) {
        for (int y = 0; y < height; y++) {
            CColor sample = ComputeSample(x, y, width, height);
            // The accumulator is used to sum all the samples since last RestartProgressiveRendering, thus you 
            // have to either use +=, or write it this-like
            accumulator[i * 3 + 0] = accumulator[i * 3 + 0] + sample.r;
            accumulator[i * 3 + 1] = accumulator[i * 3 + 1] + sample.g;
            accumulator[i * 3 + 2] = accumulator[i * 3 + 2] + sample.b;
            i++;
        }
    }

    samples++;
}

public Canvas DisplayResult() {
    int width = this.canvas.getWidth();
    int height = this.canvas.getHeight();
    Paint p = new Paint();
    int i = 0;
    for (int x = 0; x < width; x++) {
        for (int y = 0; y < height; y++) {
            // Here you get each single channel from accumulator and divide by pixels
            this.canvas.drawPoint(Color.rgb((int)(accumulator[i * 3 + 0] / (float)samples), 
                (int)(accumulator[i * 3 + 1] / (float)samples), 
                (int)(accumulator[i * 3 + 2] / (float)samples)));
            i++;
        }
    }

    ...
}

Just few notes on optimization:

  • in public void RestartProgressiveRendering method, you shouldn't re-allocate the buffer (even though allocations sometimes seems to be cheaper in Java virtual machines than in compiled languages, it isn't as cheap as zero-outing the buffer). The (re)allocation should be put into separate method called Resize (you should call that when user F.e. rotates screen)
     
  • Using canvas for this is in my opinion not performance wise. Technically you want to use OpenGL ES PBO (which is available for ES 3.0), for older versions there is GL_OES_EGL_image which works almost the same (for transforming data from CPU memory to GPU memory and thus better for rendering).



#5198242 Writing my own pathtracer on mobile device

Posted by Vilem Otte on 14 December 2014 - 10:40 PM

Alright, for some reason I woke up very early in the morning... so let's get started:

 

For each pixel you want: c=1/n sum_0^n x_n

 

Where:

  • c is the resulting color of the pixel
  • n is the number of samples rendered
  • x_n is the sample

The most obvious and easiest way to perform progressive rendering is to use a buffer where you accumulate samples, like:

Buffer accumulator;
int samples;

...

RestartProgressiveRendering()
{
    accumulator.clear();
    samples = 0;
}

RenderOneStep()
{
    for each pixel (i)
    {
        accumulator[i] += ComputeSample(i);
    }
    samples++;
}

DisplayResult()
{
    for each pixel (i)
    {
        resultPixel[i] = accumulator[i] / (float)samples;
    }
}

Now, this approach works (note, the shown code is written like pseudo-code, you can do a ton of optimizations in there using parallelization and moving that ugly slow division prior to for loop and use multiplication inside).

 

Each time you move something in your scene (or your camera has moved) you have to call ResetProgressiveRendering. There are several problems with this approach:

  1. You have to hold 32-bit floating point buffer for accumulator (or 16-bit floating point, or 16-bit integer ... but you will hit precision problems early ... for 16-bit integer it will be as soon as 256 samples per pixel, for 16-bit fp probably a bit later (it depends!) ... 32-bit fp should be okay)
  2. 32-bit fp RGB buffer means ... width * height * 4 * 3 bytes of data at least, that is 4 times more than for 8-bit integer RGB buffer; it is not such a big deal on PC, but on android devices you don't want to waste most of the memory on output buffer

There is another approach, where you can also always keep the buffer you're already showing and divide at the point where you add new sample into buffer. Time for pseudo-code:

Buffer result;
int samples;

...

RestartProgressiveRendering()
{
    result.clear();
    samples = 0;
}

RenderOneStep()
{
    for each pixel (i)
    {
        result[i] = result[i] * samples / (float)(samples + 1) + ComputeSample(i) / (float)(samples + 1);
    }
    samples++;
}

DisplayResult()
{
    result[i].show();
}

Note, you must pre-compute samples / (float)(samples + 1) and 1.0f / (float)(samples + 1) ... most compilers are not intelligent enough to pre-compute these parts of the equation and you would be doing a lot more divisions (for 1000x1000 pixels it is 2M divisions per frame (from Intel Optimization Manual on modern Core i7 single fp division eats 24 clocks, multiplication 1 or 2 clocks (can't remember correctly) ... this means that by not pre-computing these you do at least 22 more clocks 2 milion times per frame ... thats 44M clocks wasted -> just to give you little insight into the optimization - and why it heavily matters here)

 

This solution has an advantage, if you do correct casting, result can be 8-bit integer RGB buffer - that means less memory, less fillrate used and that means more speed!

 

Of course it also has disadvantages:

  1. If you're not careful, you will lose precision (very quickly), otherwise you will jus lose it (definitely a lot faster than in the previous case for 32-bit fp solution). Although, if your path tracing algorithm is good enough (F.e. 4 samples per pixel at time, doing bi-directional path tracing ,etc.) it may be enough to combine just several frames of progressive resulting in smooth image (with smooth caustics!) ... quite good, don't you think?
  2. As the buffer where you sum samples is your result buffer, it may cause some troubles with tone-mapping or such (in case you combine your samples wrongly - because those inside result buffer will be already after tone mapping (in case of tone mapping)) ... or you can use F.e. 16-bit fp RGB buffer instead, and do tone mapping after everything (which will work well too!).

It is up to you which one will you select, honestly on Android I'd prefer going the way that uses the least memory and the least fillrate (and the least processing power), as there isn't that much computing power as on the PC. Note, inside NTrace project we have a path tracer (actually there is more path tracing algorithms, they use different sampling strategy, etc.) that uses 2nd solution on PCs and it works well (if I remember correctly we got quite good results with 8-bit integer targets there too!).




#5198225 Writing my own pathtracer on mobile device

Posted by Vilem Otte on 14 December 2014 - 07:55 PM

What you generally want is a buffer where you accumulate samples and divide by number of sample count. I will elaborate more tomorrow as it is 3am here atm and there would be tons of mistakes from my side. wink.png




#5197702 Writing my own pathtracer on mobile device

Posted by Vilem Otte on 11 December 2014 - 07:04 PM

I'm still at work today (I've got a demo day tomorrow with one application, so I will be most likely sleeping for just few hours). But as we're running tests that take very long time, so...

 

Why you see black/colored pixels

 

The hint you want had already been mentioned:

You cast a ray from camera origin to sphere - you compute your hitpoint as "HitPoint = Origin + Distance * Direction", which will most likely get you INSIDE the sphere (due to precision of floating point numbers).

 

Now, a quick hack is using something like "HitPoint = Origin + Distance * 0.99 * Direction", this isn't a real solution, but this hack works 99% of time and for start it is absolutely valid to use it. Technically you would like the constant to be relative to your distance (floats are quite precise between 0.0 and 1.0, less precise in thousands, and even less precise when you get to millions, billions and up ... the higher your value (in absolute value) is, the worse the problem is, and the constant offset also needs to be higher).

 

For a quick view into precision problem - go check this site https://randomascii.wordpress.com/2012/03/08/float-precisionfrom-zero-to-100-digits-2

 

E.g. modify your hitpoint calculation as:

HitPoint = ray.origin.add(ray.direction.multiply(distance * 0.99f));

This way you should see something. (It won't be perfect and lots of rays miss, you will need better sampling than just randomly shoot rays - once you got this running, I will describe few ways how to improve it).




#5197109 Writing my own pathtracer on mobile device

Posted by Vilem Otte on 09 December 2014 - 02:22 AM

Your random number generator and logic behind it is wrong. Sadly I have to go off from PC in few minutes so I won't be able to give full answer and some background why is that (I will do that later).

 

As for now - your random direction should be normalized (unless you normalize it in ray constructor - which I don't think you do). There are few rules about the "random" direction and generally random number generation behind - you should use different random number generators for different surfaces. As of now, go with:

// Initialization of RNG, try to do this only once for whole random direction generator object - SEED is some number (F.e. 123456 ... or current timestamp, or anything :) )
Random rng = new Random(SEED);

...

// Your random direction into sphere generation code

Vector3D v = new Vector3D();
v.x = rng().NextDouble() * 2.0 - 1.0;
v.y = rng().NextDouble() * 2.0 - 1.0;
v.z = rng().NextDouble() * 2.0 - 1.0;
v.normalize();

...

// Your random direction into hemisphere
Vector3D v = GenerateRandomDirectionIntoSphere();
if (v.dot(normal) < 0.0)
{
    // Where negate does return new Vector3D(-v.x, -v.y, -v.z);
    v = v.negate();
}

The process of "how" you generate random numbers determines how good is your path tracer (I will elaborate later ;) ).






PARTNERS