Star Field Generator

A lot of POV-Ray users are fans of science fiction, and for this reason will want to create scenes set in space, or if not a fan of science fiction will want an appropraite background for scenes set at night. Whatever the reason, a method for adding a starry backdrop to a scene is a useful addition to your POV-Ray library.
A lot of the attempts that have been released make use of a POV-Ray texture, usually a marble pattern that is mostly black with with just enough white (or other light colors) to create the appearance of stars against a black background. This works because it uses the pattern as essentially a random number generator, with a high threshold for turning a pixel on. This can work but for animation work it has several drawbacks:
  • A pattern that generates a scattering of stars at one resolution will have different results at another resolution, most likely fewer stars at lower resolutions, and bands of white matching the underlying pattern used to simulate the effect at higher resolutions.
  • The randomness of the effect means that the slightest turn of the camera will cause a completely different set of stars to appear.
  • A Better Way
    What we are trying to create is a scattering of individual points of light with the following properties:
  • Each star covers a consistent number of pixels;
  • Each star's appearance is consistent with the resolution of the scene and the orientation of the camera, that is, we can turn the camera and/or scale the image, and as long as a star is within the camera view and is not occluded by other scene geometry, it will be rendered in the scene;
  • The implementation will not greatly impact rendering performance.
  • With all of this in mind, what I've developed for my own use is a script that generates a mesh object which simulates the stars. The key is to decide the overall shape of the mesh that will simulate the star, and to place it so that at least one of the rays cast during the render will hit it.
    The Shape
    Since the mesh object uses triangles, it is tempting use just one triangle to represent each star, on the idea that if the triangle is small enough, only one or two rays will hit it. My own experience is that if the triangle is small enough to cover only one or two pixels, it is also small enough to occasionally slip between the rays of two adjacent pixels, and be missed entirely. In a still image the star won't be missed, but in an animation the star will randomly blink, which is not desired. The code here models each star as an octagon, which requires only 6 triangles to model. An octagon can span several pixels before its actual shape is apparent.
    In the worst-case scenario, the greatest on-screen radius that a disc can have and still escape being hit by at least one ray equals
    sqrt(1 / 2) * (j + 1) ,
    where j is the amount of jitter you have set for the render. A disc that is smaller than this has a non-zero chance of being missed in the ray-tracing and thus not being rendered. For a hexagon, the minimum distance from any vertex to the center is a slightly larger value; it must be at least
    (1 / 2 + sqrt(1/12)) * (j + 1)
    in order to guarantee that at least one ray will hit it, and for an octagon-shaped shape the center-to-vertex distance will be
    sqrt(2 - sqrt(2)) * (j + 1) .
    If your anti-aliasing threshold is set to zero, then every sub-pixel is sampled, and you can get away with a smaller star size (specifically, you can divide the values above by the subdivision level), and this will have the result of stars that are fainter than full brightness. The final code will show how you can use this to vary the brightness of the stars.
    If you define your camera using the relatively idiot-proof method I describe in an earlier post, then you can place the star of the minimum size at a distance of the smaller of the two image dimensions, multiplied by the current zoom value, and it will be precisely positioned to appear as a point of light.
    The next bit of coding is to randomly place the star. Since the rotation of the star around the placement axis doesn't matter, we can quickly generate a random unit vector and build two vectors that are normal to it and each other, and use these to construct the vertices of the star at the appropriate distance:
    #local dStar = min(image_width, image_height) * sCamZ; // sCamZ is the zoom value for my
                                                           // camera
    
    // POV-Ray does not expose the anti-aliasing settings to the scene descripton language,
    // so you will have to set the jitter before calling the file.
    #ifndef(Jitter)
      #local Jitter = 0;
    #end
    
    #local rStar = sqrt(2 - sqrt(2)) * (Jitter + 1); // We are making octagon-shaped stars.
    
    #ifndef(Seed) // A default random number seed in case the caller didn't define one
      #local Seed = 0;
    #end
    
    #local rsA = seed(Seed);
    
    #local temp = rand(rsA) * 2 - 1;
    
    #local zStar = vrotate(<0, sqrt(1 - temp * temp), temp>, z * rand(rsA) * 360) * dStar;
    #local xStar = vnormalize(vcross(zStar, <zStar.y,zStar.z,-zStar.x>)) * rStar;
    #local yStar = vnormalize(vcross(zStar, xStar)) * rStar;
    
    triangle { xStar + zStar, (xStar + yStar) * sqrt(.5) + zStar, yStar + zStar }
    triangle { xStar + zStar, yStar + zStar, (-xStar + yStar) * sqrt(.5) + zStar }
    triangle { xStar + zStar, (-xStar + yStar) * sqrt(.5) + zStar, -xStar + zStar }
    triangle { xStar + zStar, -xStar + zStar, (-xStar - yStar) * sqrt(.5) + zStar }
    triangle { xStar + zStar, (-xStar - yStar) * sqrt(.5) + zStar, -yStar + zStar }
    triangle { xStar + zStar, -yStar + zStar, (xStar - yStar) * sqrt(.5) + zStar }
    Now that I've shown you the basics of putting one star into the scene, fleshing this out into something that you can fire and forget involves the following additions:
  • A loop to make multiple stars;
  • Code to provide the mesh object to hold the stars;
  • A translate directive to center the star field at the camera's location (so that the stars will appear to move with the camera);
  • Optional scaling so that you can ensure that the stars appear behind the other objects in the scene;
  • A place to apply pigmenting to the stars (because all-white is boring);
  • Other optional code so that when you test a scene at a lower resolution you can force the stars in a high-resolution render to have the same apparent size.
  • This code has some values not defined here, because in my scene files I have already defined them as part of my camera set-up, prior to invoking the file:
  • pCamL: This is the location of the camera.
  • sCamZ: This is the zoom value of the camera.
  • vCamD: This is the direction vector of the camera.
  • // Provide a default star count if the user does not provide one.
    #ifndef (StarCount)
      // Believe it or not, this results in less than 400 stars in the field
      // of view, when I use my usual camera values.
      #local StarCount = 10000;
    #end
    
    #ifndef(Seed)
      #local Seed = 0;
    #end
    
    #local rsA = seed(Seed);
    
    // This value allows the user to force higher-resolution renders to have the
    // same relative star size as during lower-resolution test renders, but if
    // the user doesn't want the feature then this default effectively shuts it
    // off.
    #ifndef(MinResolution)
      #local MinResolution = min(image_width, image_height);
    #end
    
    // The default behavior is that off-screen stars are culled.
    #ifndef (KeepHiddenStars)
      #local KeepHiddenStars = false;
    #end
    
    #if (KeepHiddenStars)
      // This value causes all stars to be added to the scene
      #local dClipPlane = -1;
    #else
      #local temp = min(image_width, image_height) * 2;
      // This value causes all off-camera stars to be culled
      #local dClipPlane = sCamZ / vlength(<image_width / temp, image_height / temp, sCamZ>);
    #end
    
    // This is the default (that is, off) for the option to support fainter stars.
    // For best results, the anti-alias threshold needs to be zero when using this
    // option, and the value should be set no lower than one divided by the minimum
    // anti-aliasing level. I'd have made this a simple on/off switch, but POV-
    // Ray does not expose the anti-aliasing settings to the SDL.
    #ifndef (Faintness)
      #local Faintness = 1; // All stars will be equal luminance.
    #end
    
    #local dStar = min(image_width, image_height, MinResolution) * sCamZ;
    
    mesh {
    #for (iI, 1, StarCount) // The StarCount loop
    
    #ifndef(Jitter)
      #local Jitter = 0;
    #end
    
    #local rStar = sqrt(2 - sqrt(2)) * (Faintness + rand(rsA) * (1 - Faintness)) * (1 + Jitter);
    
    #local temp = rand(rsA) * 2 - 1;
    
    #local zStar = vrotate(<0, sqrt(1 - temp * temp), temp>, z * rand(rsA) * 360);
    
    // The bounding check
    #if (vdot(zStar, vCamD) > dClipPlane) 
    
    #local zStar = zStar * dStar;
    #local xStar = vnormalize(vcross(zStar, <zStar.y,zStar.z,-zStar.x>)) * rStar;
    #local yStar = vnormalize(vcross(zStar, xStar)) * rStar;
    
    triangle { xStar + zStar, (xStar + yStar) * sqrt(.5) + zStar, yStar + zStar }
    triangle { xStar + zStar, yStar + zStar, (-xStar + yStar) * sqrt(.5) + zStar }
    triangle { xStar + zStar, (-xStar + yStar) * sqrt(.5) + zStar, -xStar + zStar }
    triangle { xStar + zStar, -xStar + zStar, (-xStar - yStar) * sqrt(.5) + zStar }
    triangle { xStar + zStar, (-xStar - yStar) * sqrt(.5) + zStar, -yStar + zStar }
    triangle { xStar + zStar, -yStar + zStar, (xStar - yStar) * sqrt(.5) + zStar }
    #end // of the bounding check
    #end // of the StarCount loop
    
    texture {
    #ifdef (StarPigment) // allows the user to make the stars different colors
      pigment { StarPigment }
    #else
      pigment { rgb 1 } // otherwise they're all white
    #end
      finish { ambient 1 diffuse 0 }
    }
    
    #ifdef (StarScaling)
      scale StarScaling
    #end
    
      translate pCamL
    } // Close of the mesh object
    

    Comments

    Popular posts from this blog

    Transition Macros

    Titling Made Simple