An OSL starfield shader for Blender Cycles

Inspired by a BlenderHD video I implemented a simple starfield shader in OSL. The distribution of stars can be controlled by a density map and the color of the stars follows (very roughly) the probability of colors for visible stars. As I will show in an example, adding dust clouds and twinkling can be done with procedural textures.

Starfield example

The image below is a still from a short animation.

(It is pretty dark, for cinematic effects you probably would choose a far brighter setup)
The stars are generated with the starfield shader and their distribution is controlled by a noise texture. The faint green glow of stellar dust and the faint orange color on the horizon are generated with a separate noise and gradient texture respectively. (example node setup at the end)
The animation features additional twinkling that is not visible in the still image. This is effected by blending time animated 4d simplex noise.
(In the animation the contrast/lightness was autocorrected for a better view)

The code

The code below contains two functions: one that calculates a probability dependent voronoi distribution and the shader itself. The code is pretty straight forward but note that the Falloff value should not be set too high to prevent artifacts in the star shapes when seen in close-up. (These are due to the fact that we divide space in cells and the fact that we only considered the nearest neighboring cells for our voronoi distribution. Taking into account further neighbors would solve this but impact the performance of the shader in a rather drastic manner).
The outputs of the shader consist of a color and a factor. The color is the emmisive power in W/m2. This could be used as an input to an emmision closure but in fact it might be simpler just to normalize this and use it as a color directly (as is done in all examples). The factor is just a value which is 1.0 for the center of the stars aand decreases exponentially when further away.
point voronoi3dp(point p, float density, output float d2)
{
 int xx, yy, zz, xi, yi, zi;

 xi = (int)floor(p[0]);
 yi = (int)floor(p[1]);
 zi = (int)floor(p[2]);
 
 float dbest = 1e10;
 point pbest = 1e10;
 vector dz = vector(7,111,19);
 for (xx = xi - 1; xx <= xi + 1; xx++) {
  for (yy = yi - 1; yy <= yi + 1; yy++) {
   for (zz = zi - 1; zz <= zi + 1; zz++) {
    vector ip = vector(xx, yy, zz);
    if(cellnoise(ip)< density){
     point  vp = ip + cellnoise(ip+dz);
     vector dp = p-vp;
     float  d  = dot(dp,dp);
     if (d < dbest) {
      dbest = d;
      pbest = vp;
     }
    }
   }
  }
 }
 d2=dbest;
 return pbest;
}

shader stars(
 point Pos = P,
 float Stardensity = 0.001,
 float Scale = 1000.0,
 float Falloff = 10,
 
 output color col = 0.0,
 output float fac = 0.0
){
 point p = Pos * Scale;
 
 float d;
 voronoi3dp(p,Stardensity,d);
 fac = exp(-d*Falloff);
 if(d<100 br="">  col = blackbody(3500.0+6000.0*cellnoise(p+vector(17,17,17)));
 }
}

Example node setup

The node setup used to generate both the still image and the animation is shown below. It's fairly large but not really complicated.

(click to enlarge)
Each texture contribution is ordered in a horizontal row of nodes. From top to bottom: the starfield (dark blue, note the normalization of the color), the twinkling (light blue), the dust (green) and the horizon glow (yellow). The glare was added in the compositor along with some contrast enhancement (for the video sample I added extra contrast enhancement in an external program). An example .blend file bundled with the stars.osl script and the simplexnoise.osl script is available as a .zip file on GitHub. (click View Raw button to download)

100k visits, hurrah!



This blog has reached the 100k visits mark just a couple of hours ago. Not bad for something that started just a few months ago :-) Apparently you all find it worthwhile reading material so I will definitely continue writing. (and to be honest, I feel flattered by all the nice comments I get, the Blender community is a really friendly bunch of people :-) So a big thank you to you and onward to the next milestone!

A new tree add-on part VI: speed improvements for complex trees

Trees that contain many branchpoints take a progressively longer time to generate as they become more complex. This is due to the time it takes to determine which branch segment is closest to any endpoint. The code up to version 0.0.7 did a linear scan through all the branch segments for each endpoint and while this was still fast enough for moderately sized trees (the time it takes doesn't explode exponentially because each iteration removes endpoints within kill distance of a branch) it took many seconds for a tree with thousand endpoints, short internode lengths and a short kill distance. In other words it got slow when the trees became interesting.

Version 0.0.8 (available on Github, click view raw to download) adds a significant speed up for larger trees (3-6 times depending on parameter settings). Quite complex tree skeletons can now be generated in a few seconds instead of half a minute or more (depending on the power of your pc of course. And remember, this is a pure python implementation so having multiple cores makes no difference, it's the raw speed of a single core that counts). Please note that because endpoints within kill distance are removed in a different order, trees generated in version 0.0.8 are not identical to trees generated in previous versions but their general appearance stays the same.

For the technically inclined: this speed up is realized by storing all branch segments in a kd-tree, so looking for a nearest neighbor now scales as the log of the number of branch segments instead of taking linear time. This is a very noticeable difference once your tree grows to hundreds or even thousands of branch segments. The kd-tree is a pure python implementation in its own, separate module kdtree.py that might be interesting for other applications as well.

A new tree add-on part V: speeding up skinning of the tree skeleton

one of the issues that bothered me about my new tree add-on was that applying the skin modifier to flesh out the branch skeleton was incredibly slow, especially for larger trees. I therefore decided i'd try writing my own skinning routine and the results are quite promising: a factor 10 or more for moderately sized trees.


The new version (0.0.7) is as usual available on GitHub. How to use and install this add-on is documented in previous articles: I II III IV.
The new skinning method is the default if you uncheck the no modifiers checkbox but if you like you can still select Blenders skin modifier from the drop down as shown on the right. (This can also be done later because we leave the skeleton itself intact; as it only consists of edges it won't show up in renders but you could use it to add any modifier later on).

Timings

The table below lists some timings for my computer, all values were left to their defaults except for the number of endpoints:
Timings in seconds for generating a tree skeleton
Endpoints No modifier Own skinning Blenders skinmod
100 0.02 0.04 0.46
200 0.04 0.07 0.74
400 0.08 0.16 1.51
400 0.56 0.87 14.43

The last line shows the results for a fair sized, bushy tree (400 endpoints, but with the internode length and kill distances reduced to 0.5 and 2.5 respectively and with 3000 extra endpoints per 1000 iterations). The difference between less than a second and more than 14 seconds is the difference between comfortable modelling and frustration, and there is still some room for improvement because the code is still riddled with print statements.
There are three factors that help to speed up the skinning operation compared to blenders built-in skin modifier:
  • unlike the modifier we do not have to make a copy of the mesh,
  • we also generate a lot less geometry it seems and
  • we don't have to deal with special cases: the skin modifier can handle any geometry while our skinning just has to deal with straight edges or simple forks.

Results

When we apply a subsurface modifier to our skinned branches the result is already quite acceptable although I see room for improvement, specifically in the shape of the forks. As you can see there is some bulging were the branches join and this is especially noticeable when there is either a very sharp anngle between the branches or when both branches bend out of plane. Fortunately the highlighted 5-gon lends itself to all kinds of angle dependant adaptations so I expect I can tweak this further to get smoother results.
The focus for the next minor release will be on bug fixing and code cleanup to prepare for a full 1.0.0 release. This will be accompanied by better (hopefully: -) docs as well.

A new tree add-on part IV: adding objects and fixing bugs

version 0.0.6 provides new functionality to add additional objects to the tree besides leaves. This might be helpful in creating trees with blooms or fruit.

adding arbitrary objects

If you click on the add object button you can not only select an object to duplicate but you can also set a number of additional parameters that control the distribution of this additional object analogous to the parameters that control the distribution of the leaves. [Sorry, no sample image yet. The code is, as always, available on GitHub ]
Check for earlier articles in this series the archive section in the sidebar on the right.
Besides this extra functionality version 0.0.6 fixes two bugs: selecting a saved preset might give an error so now we exclude the various group properties from saved presets (after all a named group might not exist at all in another scene) and in certain situations many identical internodes might be produced. This is not only difficult to skin but results in bunches of leaves as well. This might be a flaw in the original algorithm that we solve by checking whether a new internode doesn't occupy the same position as any existing one.
That last one is tricky and happens when a newly calculated branchpoint is not within killing distance of the endpoints originally directing it and further away from those endpoints than the original branchpoint causing the same branchpoint to be generated again each iteration. I've illustrated this situation in the diagram below:

(original branchpoint in red, new branchpoint in purple, endpoints in green. Here the original distances (purple lines) are larger than the kill distance and the new distances (blue lines) even more so.) I wonder if there is a better way to prevent this than just increasing the kill distance (which would result in a very sparse tree) or checking a new branchpoint against all previous ones like we do now (which is quite expensive and rather inelegant).

A new tree add-on part III: multiple trunks

Version 0.0.5 of my new tree add-on adds functionality to specify a group of starting points facilitating the creation of hedgerows or twinned trees.

multiple trunks

When you want to create a hedgerow or a small copse of trees combining multiple trees might not lead to an acceptable result because branches might cross and the shape is not quite the way you want it to be. Also the amount of work needed to create a hedge of thirty plants can add up. I therefore added the possibility to define any number of starting points by refering to the locations of objects in a group.

Part of the scene setup used the create the example image is shown on the left. 
The empties (represented by the green cone shapes) are all in the same group and their locations are used as starting points.


The parameter settings were as shown on the right.  As usual the code is available as a zip-file on GitHub. More info on how to use the tree add-on can be found in two previous posts: A new tree add-on and A new tree add-on part II: better workflow

Future directions

I noticed that the mesh created by the skin and subsurface modifiers contains not only a huge amount of faces but also contains quite a precentage of vertices (20% or so) that are so close together that they practically overlap. Generating such a mesh takes a considerable amount of time so I think I will try to come up with a skinning method that is suitable for our tree trunks but hopefully somewhat faster.

New tree addon part II: a better workflow

Version 0.0.4 of my new tree add-on is focused on defining the crown shape with groups of objects

usability enhancements

In the previous version of the add-on the markers or endpoints that represent the resources that the growing branches compete for were distributed inside an invisible ellipsoid. The resulting distribution could me made visible with the show markers option but from a usability point of view that was not ideal. likewise the su bstraction of markers from the distribution for those markers that were in the set of currently selected objects was less than intuitive, the more so since producing a tree would deselectsll objects forcing the user to select them again when trying for a different tree shape.

In this new version we allow for different way to define a crown shape using groups. If you select the use groups option you can choose three different groups:

    the Crown group
    any markers generated will lie inside the objects that are part of this group.
    the Shadow group
    any markers generated will not lie in any objects in this group. This group is optional.
    the Exclusion group
    growing branches will not penetrate into any objects in this group but stop. This group is also optional.
The shape of the marker cloud is determined by the combined shapes of the objects in the crown group minus those in the shadow group. If you use a shadow group it must be different from the crown group otherwise no markers would be produced at all (although there is a safeguard in the add-on to prevent this). If you use an exclusion group this should differ from the crown group as well although it might be the same as the shadow group.

workflow

The workflow now becomes pretty straight forward:

  • create a crowngroup, for example a bunch of different icospheres,
  • assign anything casting a shadow to a shadow group (here a box),
  • optional: assign anything impenetrable to an exclusion group (or reuse the shadow group),
  • position the 3d cursor at the place where the bole of the tree should start,
  • click update tree (here shown with the parameter settings that produce the tree in the screenshot),
Of course you can alter the other options as well. Don't forget to disable any objects for rendering that you used just to define a group otherwise you won't see much of your tree.

Code availability

This release is version 0.0.4 and avaible as a zipfile on GitHub