Coder Social home page Coder Social logo

Comments (6)

KnewHow avatar KnewHow commented on May 21, 2024 1

Thank you very much, I've got it!

from tinyraytracer.

ssloy avatar ssloy commented on May 21, 2024 1

Thank you so much @DamianCoventry for your help!

from tinyraytracer.

DamianCoventry avatar DamianCoventry commented on May 21, 2024

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.

from tinyraytracer.

DamianCoventry avatar DamianCoventry commented on May 21, 2024

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 :-)

from tinyraytracer.

KnewHow avatar KnewHow commented on May 21, 2024

afterwards

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

from tinyraytracer.

DamianCoventry avatar DamianCoventry commented on May 21, 2024

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

from tinyraytracer.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.