Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Something I don't understand in your code #24

Closed
KnewHow opened this issue Mar 19, 2021 · 6 comments
Closed

Something I don't understand in your code #24

KnewHow opened this issue Mar 19, 2021 · 6 comments

Comments

@KnewHow
Copy link

KnewHow commented Mar 19, 2021

I have read whole part one article of you tiny ray tracer, some code I'm still not understanding. Can you give me a solution.
the one is checkerboard color with white and light yellow, why you use material.setDiffuse((int(.5 * hit.x + 1000) + int(.5 * hit.z)) & 1 ? vec3f(.3, .3, .3) : vec3f(.3, .2, .1)); // TODO understand to get? the whole code is flowing:

 float checkerboard_t = std::numeric_limits<float>::max();
  if(std::fabs(d.y) > 1e-3) {
        float cb_t= -(orig.y + 4) / d.y; // the checkerboard plane has equation y = -4, cb_t is ray intersect with the checkerboard plane
        vec3f cb_hit_point = orig + cb_t * d; // the point the ray intersect with checkerboard plane
        if(cb_t > 0 && std::fabs(cb_hit_point.x) < 10 && 
            cb_hit_point.z < -10 && cb_hit_point.z > -30 && cb_t < sphere_t) {
                checkerboard_t = cb_t;
                hit = cb_hit_point;
                normal = vec3f(0, 1, 0);
              why
            }
    } 

another problem is calculate the background with environment map with following code, I don't understand this formulate.
I if have time, I hope you can do some explain for me. Thank you very much!

 int a = std::max(0, std::min(env.getWidth() -1, static_cast<int>((atan2(d.z, d.x) / (2 * M_PI) + .5)* env.getWidth()))); // TODO understand why
        int b = std::max(0, std::min(env.getHeight() -1, static_cast<int>(acos(d.y) / M_PI * env.getHeight()))); // TODO understand why
        return env.getPixelColor(a, b); // background color
@DamianCoventry
Copy link

Hi KnewHow,

Regarding the call to material.setDiffuse(), the key feature to achieve is a checkerboard pattern. One of the tools ssloy used for the job is the bitwise AND (&) operator. Let's isolate that code here:

(int(.5 * hit.x + 1000) + int(.5 * hit.z)) & 1

When you AND any integer by 1 you're effectively asking "is this number odd or even?". This works because the least significant bit of an integer represents the value 0 or 1, and therefore also indicates an even or odd value respectively.

The next tool used is the ternary operator ?:. Passing this operator the result of the bitwise AND allows the code to choose either of the two colours, based on whether the result is even or odd.

If we supply this formula with a series of values, say, 1,2,3,4,5,... etc, you can see we achieve the on/off pattern of a checkboard due to the nature of sequential numbers being even and odd.

This is why the hit.x and hit.z are being cast to integers and used as input to this formula -- we can guarantee that the numbers are a changing series due to knowing the application's core loop is walking across the rows and columns of the output image.

So why the +1000? Good question, I'm glad you asked. The gotcha here is the checkboard rectangle spans the 0 value on the x axis. In fact, ssloy has the code written to go from -10 on the x axis to +10 on the x axis. In terms of passing integers from -10 to +10 through the above formula, you arrive at a problem because there is no 0 value, 0 is the absence of a value. When traversing from -x to +x you go from -1, to +1 and obviously they're both odd numbers. So you ruin the nice the checkboard pattern when you cross the 0 coord.

The easiest way out is to shift the numbers away from the origin just for this formula, hence, ssloy adds 1000. Chances are high in this case we won't come anywhere near 0 on the x axis with such a large value.

So why doesn't he add +1000 to the z value too? You're asking some great questions. Because the checkerboard is on the -z axis. It doesn't span 0 like the x axis does. ssloy placed the checkboard at -10 to -30 on the z axis.

@DamianCoventry
Copy link

DamianCoventry commented May 15, 2021

To your other question, let's consider this code:

int a = std::max(0, std::min(env.getWidth() -1, static_cast<int>((atan2(d.z, d.x) / (2 * M_PI) + .5)* env.getWidth())));
int b = std::max(0, std::min(env.getHeight() -1, static_cast<int>(acos(d.y) / M_PI * env.getHeight())));
return env.getPixelColor(a, b); // background color

In a nutshell, this is spherical environment mapping. ssloy has thrown in boundary checks for the image so that env.getPixelColor() doesn't access an invalid pixel. Let's remove those and concentrate on the guts of it.

a = (int)(atan2(d.z, d.x) / (2 * M_PI) + .5) * env.getWidth()
b = (int)acos(d.y) / M_PI * env.getHeight()

Spherical environment mapping is all about converting a unit vector (that is, a vector whose length is 1) to UV texture coords. This is ideal for the case where your camera is inside a sphere and looking around. That's what ssloy is simulating in this tinyraytracer.

This works when you map an image to the sphere that has already been distorted beforehand. Either with an expensive digital camera, or modified afterwards using Photoshop. The image that ssloy has supplied is one such image. That is what's going on in the above formulae -- the code has made the assumption that the input data (the image) have already been distorted and now it's time to convert back to sphere.

To make the formulae more clear, let's rewrite them in terms of UV texture coords.

float texCoordU = atan2(d.z, d.x) / (2 * M_PI) + .5;
float texCoordV = acos(d.y) / M_PI;
int pixelX = (int)(texCoordU * (float)env.getWidth());
int pixelY = (int)(texCoordV * (float)env.getHeight());

Typically the values texCoordU and texCoordV are in the range (0,1) so that when we multiply them with the width and height of the image we're choosing a value within the bounds of the pixel data. The std::min/std::max handle this case above.

Let's look at the Y coord first as it's easier to explain. We know that d.y is a unit vector, therefore it's value will be in the range (-1,1). We want our tex coord to be in the range (0,1). We need a simple way to linearly map a value from the former range to the latter. The formula used here is

acos(d.y) / M_PI

This works because acos accepts values in the range (-1,1) and returns a value (θ) that is 0 ≤ θ ≤ π. This is why ssloy divides by PI afterwards -- it converts the return value into the range (0,1). Great! So we're done, if the value passed is 1 then we get a 0 back. If the value passed in is -1, we get PI back. Divide by PI and we've got a suitable value for a tex coord.

On to the other bit:

atan2(d.z, d.x) / (2 * M_PI) + .5

The idea is similar here. We need to produce a valid tex coord in the range (0,1). The trick is we have two input variables: x and z. This formula achieves the conversion using atan2(). The neat thing about atan2() is that it essentially tells you which way a vector is facing. If it helps, you could think of it as passing atan2 a 2D vector and it telling you which way it's facing from 0° up to 359°.

Of course atan2 actually uses radians, and instead of atan2 returning values in the range 0-359, it returns values in the −π < θ ≤ π range. That is why ssloy divides by 2*PI, because it converts the range of possible return values from -0.5 to +0.5. And that is why he adds 0.5 to the result -- it shifts the range of possible values nicely into (0,1). Once again, we have a nice tex coord.

Hopefully this helped you understand what is going on in the code :-)

@KnewHow
Copy link
Author

KnewHow commented May 15, 2021

Thank you very much, I've got it!

@KnewHow KnewHow closed this as completed May 15, 2021
@ssloy
Copy link
Owner

ssloy commented May 15, 2021

Thank you so much @DamianCoventry for your help!

@KnewHow
Copy link
Author

KnewHow commented May 17, 2021

afterwards

Your explanation is very professional! Now, I have completely understand! Thank you very much!

@DamianCoventry
Copy link

You're welcome. I'm glad you've got it now. Happy to have helped :-)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants