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 Better Way
What we are trying to create is a scattering of individual points of light with the following properties:
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:
mesh
object to hold the stars;translate
directive to center the star field at the camera's location (so that the stars will appear to move with the camera);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
Post a Comment