Hello guys. I got some time so I'm doing another itsy bitsy weekend-long procedural generation project. This time it's trees. Trees are complicated to generate but as usual, I'm aiming pretty low with a lot of self imposed constraints to make the project doable in couple of days.

I'll be taking inspiration from Japanese illustrator Hiroshi Nagai. Some of you that are into retrowave aesthetic may be familiar with his work:

I'll be trying to replicate this bubbly style of trees. with as little resources as possible. Obviously it won't be as good as the illustrations because I intend to cut quite a few corners 🙂. The plan is to boil everything down to just a single billboard shader that does all the work. This shader could end up on somewhat involved side. We'll see how close I can get it to ape Nagai's beautifully stylized trees.

I'm starting with some handmade sketches.
Here are basic shapes I'd like the system to cover:

And here's a schematic of a 2d distance field composition that I'm hoping will be able to configure itself into various shapes shown above, driven by some input parameters and billboard proportions.

Up next: making a basic sdf shader that draws tree silhouettes 😉

I'm starting with a quad running a shader.

The shader draws two circles representing bottom and top parts of the crown. Radii are adjustable via uniforms. Both circles automatically position themselves properly when the quad is scaled non-proportionally. Each sticks to a corresponding quad border. Circles are drawn using simple circular distance fields.

Next, the unneeded parts of circles are masked out and a trapezoid with sides tangential to circles in inserted inbetween. This now forms a configurable smooth blob that will represent the main crown silhouette.

The idea is for billboard to fake volume and react properly to a directional light source. For this to happen the shader will have to calculate some realtime normals.

Luckily, our two circles and a trapezoid can be viewed as orthographic projections of two spheres and a cone. Using sphere/cone equations and a bit of trigonometry, we can reverse engineer 3d normals from 2d distance fields.

With normals set, I let Godot render the lit billboard in wrapped lambert diffuse mode. This is only to test if normals are correctly calculated. Illumination info will be later used in more roundabout way.

Hm, looks like gif's palette couldn't handle the smoothness of normal map gradients 🙂

    xyz Hm, looks like gif's palette couldn't handle the smoothness of normal map gradients

    Yep, the gif has limited colors.

    Excellent work! 👍

    • xyz replied to this.

      Interesting approach! Funny how (even simple) normals are enough to give an illusion of 3D shape

      • xyz replied to this.

        Tomcat Yep, the gif has limited colors.

        Excellent work! 👍

        Thanks. I'd expect the gif exporter I'm using to do some dithering but it looks like there is none. I should probably look for a better capturing tool.

        LoipesMas Interesting approach! Funny how (even simple) normals are enough to give an illusion of 3D shape

        Yeah it looks surprisingly 3d. When there is no camera orbiting involved, it's indistinguishable from a full blown mesh.

        Moving on. Here's a turnaround of several billboards with cast shadows and a bit of AO. I'm glad Godot is handling billboard cast shadows as expected, turning billboards towards the light when rendering shadow maps.
        The illusion is quite satisfactory. One could easily be fooled into thinking those are real meshes if there was no occasional z swapping between billboards.

        Since those are not y-billboards as is often the case with tree billboards, but rather full 360 billboards always facing the camera, there's still one thing that needs to be handled - the height foreshortening when camera is looking at steep angles. I'm doing this in my trademark sloppy-fakery-just-barely-good-enough style 🙂 The top circle that forms the crown is just slided down towards the bottom circle, depending on the camera angle. This is far from real perspective projection but I think it'll do for this purpose.

        For this type of billboard, normals are given in screen space. To keep the lighting continuity when viewing from steep angles, they need to be shifted up/down depending on the angle of view. This angle can be taken per pixel but I'm doing one average angle for the whole billboard. Again, good enough for this project yet saves some precious gpu cycles.

          xyz Impressive! Curious, How's the performance compared to rendering traditional poly-meshes?

          • xyz replied to this.

            MikeCL

            Thanks!

            I didn't actually benchmark it but I guess it should be faster than meshes. It's only quads after all and the shader code in not very expensive thus far. The additional benefit is that with distance fields you get "infinite" smoothness at any scale.

            However I'm using billboards not for performance reason but because I think the stylization aimed for here is easier to do with 2d distance fields than with full 3d geometry.

            Yesterday I spent a day in the forest instead of working on this. Can't help it, I always prioritize hiking and road trips over staring at the screen. So technically this isn't a weekend project anymore. The deadline has been broken. Who would have thunk it! 🙂 But I digress.

            Now for the tricky part. The plan is to use some kind of procedural stippling to render the texture of the trees. I'm starting with a polka dot pattern shader. It's composed of two rows of tiled circular distance fields. The rows are shifted in such a way that circles form a honeycomb pattern. This will ensure maximum surface coverage.

            Furthermore, circle radii can be controlled per cell by a rng or a noise texture. Likewise for the center offset, to break the distribution uniformity a bit.

            And, of course, the color can be varied per cell too. All together now for a bit of fun:

            The idea here is to drive some of these parameters with ramped/noised diffuse and possibly a specular illumination term.

            Putting the two shaders together.
            In most of his trees, Nagai consistently uses 4 easily distinguishable stippling layers:

            • dark (almost black) silhouette layer, for parts that are completely in shadow.
            • dark green layer for partially occluded/shadowed parts
            • lighter green layer for lit parts
            • some warm color layer for flowers/fruit.

            So I tried to replicate that by layering the stippling shader. Density of dots is driven by Lambertian illumination. Eeach layer can remap the Lambert term differently to get different dot density falloff. I tweaked this manually aiming to match the appearance of original illustrations.

            Size and offset of dots can also be adjusted per layer to make the whole thing less uniform.

              xyz That looks fantastic! I love the style that your approach produces! 😍

              @MikeCL Thanks, glad you like it 🙂

              Tuning the shader further... The main visual problem was that stipple dots were abruptly popping in and out of existence when camera moved around. This was kind of unpleasant to look at. I had to mitigate it by introducing gradual dot scaling when a dot is still on the "dark side" of the shadow terminator. The closer it gets to the terminator the bigger it is, reaching full size right when it crosses to the light side. This introduces smaller dots that are not really seen in the reference illustration but it was a cost of making it look decent in motion.

              To get a bit of clumping I added simple normal alteration calculation driven by Godot's cellular noise texture. A poor man's normal map 🙂 Natural looking clumps and branch rhythms from the reference are way out of shader's (and my) capabilities, so this will have to do.

              Michael33 Thanks!

              I think I'm done with the shader for this iteration. It could be further improved but I'm leaving that for some other time. The final thing I added is the trunk. I initially planned to make it a separate object but why complicate when it can be squeezed into the same billboard with two lines of shader code. It could also be normal mapped but I figured the plain silhouette fits the overall style better.

              The last thing to do is test how this all looks en masse by applying it to multimesh instance and create a whole bunch of them. I just need to decide which parameters will be the best candidates to vary per instance. Stay tuned... 🙂

              After all this astonishing job is stupid for me to say... I have observed there is a glitch in the shadow, just where the trunk finds the floor. I assume you already know 🙂

              • xyz replied to this.

                d2clon Thanks! There's quite a few glitches as this is a work in progress, but the thing you mentioned is caused simply by the approximative nature of billboards - they are just texture mapped planes that turn towards the camera or a light source when rendering the shadow map. The latter actually causes this artefact. It could be remedied in several ways but I won't go into it in this limited scope project.

                I've set up instanced rendering. Still no real scripted parametrization. The existing randomization is done "automatically" by blindly varying some properties using billboard's world position as a seed for rng in the shader itself. I also added some atmospherics for the presentation value 🙂

                  This thread looks to me like watching a magician in a performance show. I am like >8O

                  • xyz replied to this.

                    xyz Looks amazing! I would love to see a character running around in that environment. Or perhaps like a camera flyby. Fantastic work! 🤩

                    • xyz replied to this.

                      d2clon This thread looks to me like watching a magician in a performance show. I am like >8O

                      Ha ha! Thanks! Enjoy the show 😉

                      MikeCL Looks amazing! I would love to see a character running around in that environment. Or perhaps like a camera flyby. Fantastic work! 🤩

                      Thank you! I'll put a quick video up shortly. Until then here's another still image with a bit different ambience.