Documentation:Tutorial Section 3.8

From POV-Wiki
Revision as of 16:00, 6 March 2009 by WikiSysop (talk | contribs) (→‎What is a condition and how do I make one?)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search

This document is protected, so submissions, corrections and discussions should be held on this documents talk page.

Do Not Use Jitter Or Crand

One last piece of basic information to save frustration. Because jitter is an element of anti-aliasing, we could just as easily have mentioned this under the INI file settings section, but there are also forms of anti-aliasing used in area lights, and the new atmospheric effects of POV-Ray, so now is as good a time as any.

Jitter is a very small amount of random ray perturbation designed to diffuse tiny aliasing errors that might not otherwise totally disappear, even with intense anti-aliasing. By randomizing the placement of erroneous pixels, the error becomes less noticeable to the human eye, because the eye and mind are naturally inclined to look for regular patterns rather than random distortions.

This concept, which works fantastically for still pictures, can become a nightmare in animations. Because it is random in nature, it will be different for each frame we render, and this becomes even more severe if we dither the final results down to, say 256 color animations (such as FLC's). The result is jumping pixels all over the scene, but especially concentrated any place where aliasing would normally be a problem (e.g., where an infinite plane disappears into the distance).

For this reason, we should always set jitter to off in area lights and anti-aliasing options when preparing a scene for an animation. The (relatively) small extra measure quality due to the use of jitter will be offset by the ocean of jumpies that results. This general rule also applies to any truly random texture elements, such as crand.

INI File Settings

Okay, so we have a grasp of how to code our file for animation. We know about the clock variable, user declared clock-relative variables, and the phase keyword. We know not to jitter or crand when we render a scene, and we are all set build some animations. Alright, let's have at it.

The first concept we will need to know is the INI file settings, Initial_Frame and Final_Frame. These are very handy settings that will allow us to render a particular number of frames and each with its own unique frame number, in a completely hands free way. It is of course, so blindingly simple that it barely needs explanation, but here is an example anyway. We just add the following lines to our favorite INI file settings

  Initial_Frame = 1
  Final_Frame = 20

and we will initiate an automated loop that will generate 20 unique frames. The settings themselves will automatically append a frame number onto the end of whatever we have set the output file name for, thus giving each frame an unique file number without having to think about it. Secondly, by default, it will cycle the clock variable up from 0 to 1 in increments proportional to the number of frames. This is very convenient, since, no matter whether we are making a five frame animated GIF or a 300 frame MPEG sequence, we will have a clock value which smoothly cycles from exactly the same start to exactly the same finish.

Next, about that clock. In our example with the rolling ball code, we saw that sometimes we want the clock to cycle through values other than the default of 0.0 to 1.0. Well, when that is the case, there are setting for that too. The format is also quite simple. To make the clock run, as in our example, from 0.0 to 2.0, we would just add to your INI file the lines

Initial_Clock = 0.0
Final_Clock = 2.0

Now, suppose we were developing a sequence of 100 frames, and we detected a visual glitch somewhere in frames, say 51 to 75. We go back over our code and we think we have fixed it. We would like to render just those 25 frames instead of redoing the whole sequence from the beginning. What do we change?

If we said make Initial_Frame = 51, and Final_Frame = 75, we are wrong. Even though this would re-render files named with numbers 51 through 75, they will not properly fit into our sequence, because the clock will begin at its initial value starting with frame 51, and cycle to final value ending with frame 75. The only time Initial_Frame and Final_Frame should change is if we are doing an essentially new sequence that will be appended onto existing material.

If we wanted to look at just 51 through 75 of the original animation, we need two new INI settings

Subset_Start_Frame = 51
Subset_End_Frame = 75

Added to settings from before, the clock will still cycle through its values proportioned from frames 1 to 100, but we will only be rendering that part of the sequence from the 51st to the 75th frames.

This should give us a basic idea of how to use animation. Although, this introductory tutorial does not cover all the angles. For example, the last two settings we just saw, subset animation, can take fractional values, like 0.5 to 0.75, so that the number of actual frames will not change what portion of the animation is being rendered. There is also support for efficient odd-even field rendering as would be useful for animations prepared for display in interlaced playback such as television (see the reference section for full details).

With POV-Ray 3 now fully supporting a complete host of animation options, a whole fourth dimension is added to the raytracing experience. Whether we are making a FLIC, AVI, MPEG, or simply an animated GIF for our web site, animation support takes a lot of the tedium out of the process. And do not forget that phase and clock can be used to explore the range of numerous texture elements, as well as some of the more difficult to master objects (hint: the julia fractal for example). So even if we are completely content with making still scenes, adding animation to our repertoire can greatly enhance our understanding of what POV-Ray is capable of. Adventure awaits!

While-loop tutorial

Usually people who have never programmed have great difficulties understanding how simple while-loops work and how they should be used. When you get into nested loops, the problem is even worse.

Sometimes even people who have programmed a bit with some language get confused with POV-Ray's while-loops. This usually happens when they have only used a for-loop which in itself has an index variable (which is often even incremented automatically).

What a while-loop is and what it is not

A while-loop in POV-Ray is just a control structure which tells POV-Ray to loop a command block while the specified condition is true (ie. until it gets false).

That is, a while-loop is like this:


The commands between #while and #end are run over and over as long as the condition evaluates to true.

A while-loop is not a for-loop nor any kind of loop which has an index variable by itself (which may be incremented automatically in each loop).

The while-loop does not care what the conditions are between the parentheses (as long as they evaluate to some value) or what does the block between #while and #end contain. It will just execute that block until the condition becomes false.

The while-loop does not do anything else. You can think about it as a kind of "dumb" loop, which does not do anything automatically (and this is not necessarily a bad thing).

How does a single while-loop work?

The while-loop works like this:

  1. If the condition between the parentheses evaluates to false, jump to the command after the #end statement. If the condition evaluates to true, just continue normally.
  2. At the #end statement jump to the #while statement and start again.

That is:

  • When POV-Ray gets to the #while statement it evaluates the condition between parentheses.
  • If the statement evaluated to true then it will just continue normally with the next command.
  • However, if the statement evaluated to false, POV-Ray will skip the entire body of the loop and continue from the command after the #end statement.
  • At an #end statement POV-Ray will just jump back to the corresponding #while-statement and then continue normally (ie. testing the condition and so on).

Note that nowhere there is any mention about any index variable or anything else that could be used to automatically end the loop or whatever. As said, it is just a "dumb" loop that continues forever if necessary, only testing the statement between the parentheses (but it is not interested in what it is, only in its evaluated value).

Although one could easily think that this kind of "dumb" loop is bad and it should be more "intelligent" and better, the fact is that this kind of "dumb" loop is actually a lot more flexible and versatile. It allows you to make things not possible or very difficult to do with an "intelligent" for-loop with automatic index variables.

How do I make a while-loop?

It depends on what you are trying to make.

The most common usage is to use it as a simple for-loop, that is, a loop which loops a certain number of times (for example 10 times) with an index value getting successive values (for example 1, 2, 3, ..., 10).

For this you need to first declare your index identifier with the first value. For example:

#declare Index = 1;

Now you want to loop 10 times. Remember how the condition worked: The while-loop loops as long as the condition is true. So it should loop as long as our 'Index' identifier is less or equal to 10:

#while(Index <= 10)

When the 'Index' gets the value 11 the loop ends, as it should.

Now we only have to add 1 to 'Index' at each loop, so we should do it at the end of the loop, thus getting:

#declare Index = 1;
#while(Index <= 10)

  (some commands here)

  #declare Index = Index + 1;

The incrementation before the #end is important. If we do not do it, 'Index' would always have the value 1 and the loop would go forever since 1 is always less or equal to 10.

What happens here?

  1. First POV-Ray sets the value 1 to 'Index'.
  2. Then it sees the #while statement and evaluates what is between the parentheses: Index <= 10
  3. As 'Index' has the value of 1 and 1 <= 10, the condition evaluates to true.
  4. So, it just continues normally. It executes the commands following the #while statement (denoted in the above example as "(some commands here)").
  5. Then it arrives normally to the last #declare command in the block. This causes the value 2 to be assigned to 'Index'.
  6. Now it arrives the the #end command and so it just jumps to the #while.
  7. After that it executes the steps 2-6 again because also 2 is less or equal to 10.
  8. After this has been done 10 times, the value 11 is assigned to 'Index' in the last command of the block.
  9. Now, when POV-Ray evaluates the condition it sees that it is false (because 11 is not less or equal to 10). This causes POV-Ray to jump to the command after the #end statement.
  10. The net effect of all this is that POV-Ray looped 10 times and the 'Index' variable got successive values from 1 to 10 along the way.

If you read carefully the above description you will notice that the looping is done in a quite "dumb" way, that is, there is no higher logic hidden inside the loop structure. In fact, POV-Ray does not have the slightest idea how many times the loop is executed and what variable is used to count the loops. It just follows orders.

The higher logic in this type of loop is in the combination of commands we wrote. The effect of this combination is that the loop works like a simple for-loop in most programming languages (like BASIC, etc).

What is a condition and how do I make one?

A condition is an expression that evaluates to a boolean value (ie. true or false) and is used in POV-Ray in #while-loops and #if-statements.

A condition is mainly a comparison between two values (although there are also some other ways of making a condition, but that is not important now). For example:

1 < 2  is true
1 > 2  is false
1 = 1  is true
1 = 2  is false

and so on.

Usually it makes no sense to make comparisons like those. However, when comparing identifiers with some value or two identifiers together it starts to be very useful. Consider this:

#if(version < 3.1)
  #error "Wrong version!"

If the identifier called 'version' has a value which is less than 3.1 the #error line will be executed. If 'version' has a value which is 3.1 or greater, the #error line is just skipped.

You can combine conditions together with the boolean operators & (and) and | (or). You can also invert the value of a condition with ! (not).

For example, if you want something to be done when 'a' is less than 10 and 'b' is greater or equal to 20, the condition would be:

a<10 & b>=20

For more information about these comparison operators, see the 'Float operators' section of the POV-Ray documentation.

What about loop types other than simple for-loops?

As POV-Ray does not care what the condition is and what we are using to make that condition, we can use the while-loop in many other ways.

For example, this is a typical use of the while-loop which is not a simple for-loop:

#declare S = seed(0);
#declare Point = <2*rand(S)-1, 2*rand(S)-1, 2*rand(S)-1>;
#while(vlength(Point) > 1)
  #declare Point = <2*rand(S)-1, 2*rand(S)-1, 2*rand(S)-1>;

What we are doing here is this: Take a random point between <-1, -1, -1> and <1, 1, 1> and if it is not inside the unit sphere take another random point in that range. Do this until we get a point inside the unit sphere.

This is not an unrealistic example since it is very handy.

As we see, this has nothing to do with an ordinary for-loop:

  • It does not have any "index" value which gets consecutive values during the loop.
  • We do not know how many times it will loop. In this case it loops a random number of times.
  • For-loops are usually used to get a series of things (eg. objects). At each loop another instance of that thing is created. Here, however, we are only interested in the value that results after the loop, not the values inside it.

As we can see, a while-loop can also be used for a task of type "calculate a value or some values until the result is inside a specified range" (among many other tasks).

By the way, there is a variant of this kind of loop where the task is: "Calculate a value until the result is inside a specified range, but make only a certain number of tries. If the value does not get inside that range after that number of tries, stop trying". This is used when there is a possibility for the loop for going on forever.

In the above example about the point inside the unit sphere we do not need this because the random point will surely hit the inside of the sphere at some time. In some other situations, however, we cannot be so sure.

In this case we need a regular index variable to count the number of loops. If we have made that amount of loops then we stop.

Suppose that we wanted to modify our point searching program to be completely safe and to try only up to 10 times. If the point does not hit the inside of the sphere after 10 tries, we just give up and take the point <0,0,0>.

#declare S = seed(0);
#declare Point = <2*rand(S)-1, 2*rand(S)-1, 2*rand(S)-1>;
#declare Index = 1;
#while(Index <= 10 & vlength(Point) > 1)
  #declare Point = <2*rand(S)-1, 2*rand(S)-1, 2*rand(S)-1>;
  #declare Index = Index + 1;

#if(vlength(Point) > 1)
  #declare Point = <0,0,0>

What did we do?

  • We added an 'Index' value which counts the amount of loops gone so far. It is quite similar to the index loop of a simple for-loop.
  • We added an extra condition to the while-loop: Besides testing that the point is outside the unit sphere it also tests that our index variable has not bailed out. So now there are two conditions for the loop to continue: The 'Index' value must be less or equal to 10 and the 'Point' has to be outside the unit sphere. If either one of them fails, the loop is ended.
  • Then we check if the point is still outside the unit sphere. If it is, we just take <0,0,0>.

Btw, sometimes it is not convenient to make the test again in the #if statement. There is another way of determining whether the loop bailed out without successful termination or not: Since the loop ends when the 'Index' gets the value 11, we can use this to test the successfulness of the loop:

#if(Index = 11)
  (loop was not successful)

What about nested loops?

Even when one masters simple loops, nested loops can be a frightening thing (or at least hard to understand).

Nested loops are used for example for creating a 2D array of objects (with rows and columns of objects), etc. For example if you want to create a 10x20 array of spheres in your scene, a nested loop is the tool for it.

There is nothing special about nested loops. You only have to pay attention to where you initialize and update your index variables.

Let's recall the form of a simple for-loop:

#declare Index = initial_value;
#while(Index <= final_value)

  [Something here]

  #declare Index = Index + index_step;

The [Something here] part can be anything. If it is another while-loop, then we have nested loops. The inner loop should have the same structure as the outer one.

Note that proper indentation helps us distinguishing between the loops. It is always a good idea to use a good indentation scheme:

#declare Index1 = initial_value1;
#while(Index1 <= final_value1)

   #declare Index2 = initial_value2;
   #while(Index2 <= final_value2)

      [Something here]

      #declare Index2 = Index2 + index2_step;

   #declare Index1 = Index1 + index1_step;

It is a common mistake for beginners to break this structure. For example it is common to declare the 'Index2' before the first #while. This breaks the for-loop structure and thus does not work. If you follow step by step what POV-Ray does, as explained earlier, you will see why it does not work. Do not mix the structures of the inner and the outer loops together or your code will simply not work as expected.

So, if we want to make our 10x20 array of spheres, it will look like this:

#declare Index1 = 0;
#while(Index1 <= 9)

   #declare Index2 = 0;
   #while(Index2 <= 19)

      sphere { <Index1, Index2, 0>, .5 }

      #declare Index2 = Index2 + 1;

   #declare Index1 = Index1 + 1;

Note how we now start from 0 and continue to 9 in the outer loop and from 0 to 19 in the inner loop. This has been done to get the sphere array start from the origin (instead of starting from <1, 1, 0>). Of course we could have made the 'Index1' and 'Index2' go from 1 to 10 and from 1 to 20 respectively and then created the sphere in this way:

  sphere { <Index1-1, Index2-1, 0>, .5 }

Although you should not mix the loop structures together, you can perfectly use the values of the outer loop in the inner loop (eg. in its condition). For example, if we wanted to create a triangular array of spheres instead of a rectangular one (that is, we create only half of the spheres), we could have made the inner #while like this:

  #while(Index2 < Index1*2)

('Index2' will go from 0 to the value of 'Index1' multiplied by 2.)

There is no reason why we should limit ourselves to just two nested loops. There is virtually no limit how many loops you can nest. For example, if we wanted to create a box-shape filled by spheres rows, colums and depth, we just make three nested loops, one for the x-axis, another for the y-axis and the third for the z-axis.

Mixed-type nested loops

It is perfectly possible to put a for-loop inside a non-for-loop or vice-versa. Again, you just have to be careful (with experience it gets easier).

For example, suppose that we want to create 50 spheres which are randomly placed inside the unit sphere.

So the distinction is clear: First we need a loop to create 50 spheres (a for-loop type suffices) and then we need another loop inside it to calculate the location of the sphere. It will look like this:

#declare S = seed(0);
#declare Index = 1;
#while(Index <= 50)

   #declare Point = <2*rand(S)-1, 2*rand(S)-1, 2*rand(S)-1>;
   #while(vlength(Point) > 1)
      #declare Point = <2*rand(S)-1, 2*rand(S)-1, 2*rand(S)-1>;

   sphere { Point, .1 }

   #declare Index = Index + 1;

There are some important things to note in this specific example:

  • Although this is a nested loop, the sphere is not created in the inner loop but in the outer one. The reason is clear: We want to create 50 spheres, so the sphere creation has to be inside the loop that counts to 50. The inner loop just calculates an appropriate location.
  • The seed value 'S' is declared outside all the loops although it is used only in the inner loop. Can you guess why? (Putting it inside the outer loop would have caused an undesired result: Which one?)

Other things to note

There is no reason why the index value in your simple for-loop should step one unit at a time. Since the while-loop does not care how the index changes, you can change it in whichever way you want. Eg:

#declare Index = Index - 1;  Decrements the index (be careful with the
                             while loop condition)

#declare Index = Index + 0.2;  Increases by steps of 0.2

#declare Index = Index * 2;  Doubles the value of the index at each step.


- Be careful where you put your while-loop.

I have seen this kind of mistake more than once:

#declare Index = 1;
#while(Index <= 10)
   {  threshold 0.6
      sphere { <Index, 0, 0>, 2, 1 }
   #declare Index = Index + 1;

You will probably see immediately the problem.

This code creates 10 blobs with one component each. It does not seem to make much sense. Most probably the user wanted to make one blob with 10 components.

Why did this mistake happen? It may be that the user (more or less subconsciously) thought that the while-loop must be the outermost control structure and does not realize that while-loops can be anywhere, also inside objects (creating subcomponents or whatever).

The correct code is, of course:

{  threshold 0.6

   #declare Index = 1;
   #while(Index <= 10)

      sphere { <Index, 0, 0>, 2, 1 }

      #declare Index = Index + 1;

The essential difference here is that it is only the sphere code which is run 10 times instead of the whole blob code. The net result is the same as if we had written the sphere code 10 times with proper values of 'Index'.

Be also careful with the placement of the braces. If you put them in the wrong place you can end up accidentally repeating an opening or a closing brace 10 times. Again, a proper indentation usually helps a lot with this (as seen in the above example).

- Tip: You can use while-loops in conjunction with arrays to automatize the creation of long lists of elements with differing data.

Imagine that you are making something like this:

  {  [0.00 rgb <.1,1,.6>]
     [0.05 rgb <.8,.3,.6>]
     [0.10 rgb <.3,.7,.9>]
     [0.15 rgb <1,.7,.3>]
     and so on

It is tedious to have to write the same things over and over just changing the index value and the values in the vector (even if you use copy-paste to copy the lines).

There is also one very big problem here: If you ever want to add a new color to the color map or remove a color, you would have to renumber all the indices again, which can be extremely tedious and frustrating.

Would not it be nice to automatize the creation of the color map so that you only have to write the vectors and that is it?

Well, you can. Using a while-loop which reads an array of vectors:

#declare MyColors = array[20]
   {  <.1,1,.6>, <.8,.3,.6>, <.3,.7,.9>,
      <1,.7,.3>, ...


   {  #declare LastIndex = dimension_size(MyColors, 1)-1;
      #declare Index = 0;
      #while(Index <= LastIndex)

         [Index/LastIndex rgb MyColors[Index]]

         #declare Index = Index + 1;

Now it is easy to add, remove or modify colors in your color map. Just edit the vector array (remembering to change its size number accordingly) and the while-loop will automatically calculate the right values and create the color map for you.

SDL tutorial: A raytracer

You know you have been raytracing too long when ...
... You've been asked how you did that thing you did, by the author of the raytracer you used to do it.
-- Alex McLeod


A raytracer made with POV-Ray sounds really weird, doesn't it? What is it anyways? POV-Ray is already a raytracer in itself, how can we use it to make a raytracer? What the...?

The idea is to make a simple sphere raytracer which supports colored spheres (with diffuse and specular lighting), colored light sources, reflections and shadows with the POV-Ray SDL (Scene Description Language), then just render the image created this way. That is, we do not use POV-Ray itself to raytrace the spheres, but we make our own raytracer with its SDL and use POV-Ray's raytracing part to just get the image on screen.

What obscure idea could be behind this weirdness (besides a very serious case of YHBRFTLW...)? Why do not just use POV-Ray itself to raytrace the spheres a lot faster and that is it?

The idea is not speed nor quality, but to show the power of the POV-Ray SDL. If you know how to make such a thing as a raytracer with it, we can really grasp the expressive power of the SDL.

The idea of this document is to make a different approach to POV-Ray SDL teaching. It is intended to be a different type of tutorial: Instead of starting from the basics and give simple and dumb examples, we jump right into a high-end SDL code and see how it is done. However, this is done in a way that even beginners can learn something from it.

Another advantage is that you will learn how a simple sphere raytracer is done by reading this tutorial. There are lots of misconceptions about raytracing out there, and knowing how to make one helps clear most of them.

Although this tutorial tries to start from basics, it will go quite fast to very "high-end" scripting, so it might not be the best tutorial to read for a completely new user, but it should be enough to have some basic knowledge. Also more advanced users may get some new info from it.

Note: in some places some mathematics is needed, so you would better not be afraid of math.

If some specific POV-Ray SDL syntax is unclear you should consult the POV-Ray documentation for more help. This tutorial explains how they can be used, not so much what is their syntax.

The Phase Keyword The idea and the code

This document is protected, so submissions, corrections and discussions should be held on this documents talk page.