No announcement yet.

Witcher 3-like Per-Instance Tree Fading

  • Filter
  • Time
  • Show
Clear All
new posts

    Witcher 3-like Per-Instance Tree Fading

    (Pardon the low fps)


    For a while now I wanted to emulate an effect found in The Witcher 3 - trees disappear as you move your camera close to them. I had a similar effect already done before, using the camera position and the pixel position and just doing a distance-based fade on those. The problem with that was - your camera would partially hide the tree. So if your fade distance was 100 units, you'd have a 100 unit big sphere mask hiding pixels. Any part of the tree that was outside of that radius would still be visible.

    To rectify that I sat back down at the drawing board and came up with a solution. Mind you, it's probably not optimal and I'm sure a lot of people here could whip up something more optimized, but it's still a neat effect that I haven't seen that often so I decided to share. I'll go through it step-by-step.

    First problem: Getting the position of foliage instances

    Your usual ObjectPositionWS node, if used with foliage, returns the position of the containing InstancedStaticMeshActor, i.e. the geometric center of all your foliage instances. In order to get the per-foliage position, it has to be piped through the vertex shader, like so:

    That solves that issue. Second comes a problem that took me a bit longer to crack.

    Second problem: Getting the scale / height of the foliage

    Turns out there is no easy way to get the height of the foliage, as stuff like ObjectScale and ObjectRadius don't work properly with painted foliage. Luckily, Ryban Brucks solved the issue with the convenient FoliageScaleFactor material function. Less luckily - that function is broken in the current iteration of the engine.

    The function works by taking the 0,0,0 local position and the 0,0,1 local position of the foliage instance, converting them to world space and taking their distance. Once you have that you can divide a nominal foliage height by it to get the height scale of your foliage. You'll notice however that the transform nodes convert from Local to Local, which is a bug. Simply create your own FoliageScaleFactorFixed material function (or modify the engine one), copy all of that and change the transform nodes to Local -> World.

    You'll also notice that you have to pass in a height. This will have to be a parameter in your material, so you'll have to enter this value for each foliage that you have. The static mesh editor shows the approximate size of a 3D model, I used the height given there .

    Right-o, so now we got the foliage location and its height. With some math magic it is now possible to create our volume mask. In my case it's going to be a rough cylinder mask.

    I've split my logic into the vertical and horizontal mask. I then add them together to form the final result. I'll start with the horizontal one since it's simpler.

    The two lines coming in from the left are the camera position and the foliage position calculated above. I mask out the Z since I only care about the XY plane at this point. I simply measure the XY distance of my camera from my tree and blend between FadeStartDistance and (FadeStartDistance + FadeLength) to get a 0-1 mask.

    This now leaves the vertical portion of the masking. In essence it is simple:

    1. For a tree of height h, take its mid-point height Y and the camera vertical position X.
    2. Now subtract the two to get the vertical distance between the foliage's mid-point and the camera. Take the absolute value of this to get X'.
    3. Now do the same blend as above but offset the FadeStartDistance by the half-height of the foliage, resulting in a blend between (FadeStartDistance + HalfHeight) and (FadeStartDistance + HalfHeight + FadeLength).

    The image below has these steps outlined.

    Once we have both our masks we combine them with a simple add and a clamp. This way, if we're within the vertical "transparent zone", if the horizontal one is still outside, the resulting opacity will still be 1.

    As you can see I run it through a dither in the end for prettier results. I also have "Enable Opacity Mask Dither" turned on on my material.

    That's essentially it! Plug this in with the rest of your foliage material, punch in the correct values and you should have properly disappearing foliage. Hope someone finds this useful. If you have any questions let me know and I'll try to help as best I can.
    Last edited by DamirH; 03-28-2017, 08:39 PM.


    As it was late last night I didn't realize that I could do a few small improvements to the fading effect. Namely, I've added a different setting for the top and bottom part of the fade region, allowing me to specify a wider fade area for the tops of the trees than for the trunks. Also, I've moved the whole thing into 2 custom nodes (1 with and 1 without the TopFade modification). The whole thing now looks like this:

    Click image for larger version

Name:	FoliageFade.PNG
Views:	1
Size:	205.1 KB
ID:	1125679

    You can actually just grab this entire material function here:
    Just paste that into the material function editor.


      Hi, I know it's a really old post but I'm trying to achieve a per instance scaling of foliage.. Do you think your solution would can be changed to allow per instance scaling (using a scalar as function input)


        Of course. The only thing for that that you really need is the first part:

        However this has since been streamlined and you can just take "Object Position" and push it through a "Vertex Interpolator" node and it will work just fine. Then you need to do is get the camera location, subtract it from the instance position, take the length of that vector, divide it by some fade distance and you have your scale lerp. In order to actually scale an object via a shader, you have to use the WPO and push all the vertices toward the object position (meaning a normalized Absolute Position - Object Position multiplied by some push distance, multiplied by your scale lerp).

        I am not at my PC right now so I can't create an example for you but this video should help you out:

        Instead of using the Time sine wave though, you want to use the vertex interpolated camera vector stuff I talked about at the start.


          Thanks for pointing out the video; I saw it few days ago and it's actually that video that led me to your post.
          There seems to be a subtility I cannot sort out for foliage instances though.

          Subtracting the world position leads to incorrect result; here is my current graph:

          But the result is also incorrect as the foliage would deform and not shrink even if I only use the up vector.. ideally the foliage would flatten to there base but i cannot figure out what's missing..
          I use a sphere mask to lerp the wpo because I want to affect a zone of the world and not the distance to camera.

          Click image for larger version

Name:	Capture.PNG
Views:	358
Size:	153.8 KB
ID:	1717725


            I'm afraid that math is wrong. You are basically subtracting the pixel position from the object position, not taking into account your scale reference point (be it the camera or some fixed world position). First and foremost though, you don't need to use the custom UV trick anymore, there's a new node called VertexInterpolator which does all of that nasty stuff automatically. Just use ObjectPosition and push it through a VertexInterpolator.

            Actually come to think of it, WPO is calculated in the vertex shader so ObjectPosition might actually work for WPO out of the box, without any fiddling around.

            If you want your foliage to squish down, what you need to is this:

            -Take your pixel position (Absolute World Position Not Including WPO)
            -Subtract your object position from it (should work out of the box in WPO as discussed)
            -Take only your Z value
            -Multiply by your sphere mask
            -Append it to a 0,0 vector (So you have a Vector3 of 0,0,YourValue)
            -Plug it into WPO


              Worked out of the box... I don't know how i ended up overcomplicating the code so much...
              For reference here is the working graph: Click image for larger version  Name:	Capture.PNG Views:	0 Size:	135.3 KB ID:	1717874
              Thanks a lot for your help!!
              Last edited by thebenjiman; 02-06-2020, 03:22 AM.


                If you want it only in the Z axis you want to take the result of that lerp and plug it into the Z component of a "Make Float3" node.