Breakdown: Creating a Detailed and Vibrant Scene with Unreal Engine and Substance 3D
Karl Aldskogius discussed the workflow behind the Sunset Garden project, explaining how this project allowed them to experiment with different workflows, how they created the stones, and detailing how they achieved the appearance of the wisteria.
Introduction
Hello. My name is Karl Aldskogius. I'm 22 years old, and I'm from Sweden. I'm a self-taught Artist with an interest in most art forms, but with a special love for environment art. For most of my sentient life, I've been interested in art. I've made jewelry, wood sculptures, paintings, etc.
My introduction to game art and environment art was kinda random, like a YouTube recommendation or something like that. However, I had done some 3D work previously in the form of banners for Minecraft YouTubers, which I suppose made me take a second look when I first learned about art related to games.
I'm not completely sure what made me stick with environment art, but I started looking at tutorials and reading articles every day. After a practice project making a hand-painted sword, I decided to take on this more ambitious project.
As it was my first big project focused on what I actually wanted to do, I decided to tackle it in a slightly different way. Usually, you would track the time for each part and try to optimize your efficiency so that you can create the best art in the shortest time possible. This is, of course, the way you probably should think, as this is how it works when you are working in the industry. You have to create the best art possible under harsh constraints such as performance limitations, full explorability on the player's end, and time constraints.
This was my first real project, however, so I decided to completely scrap the time-constraint part of it and just experiment as much as possible at every stage of the process. What this meant in practice was a lot of scrapped content, a lot of detail focused on places the viewer wouldn't even be able to see, and, of course, a very long development time. In terms of the project itself, it might've been a bit of a waste of time, but in terms of my overall development as an artist, I think it was the correct route to go down.
Everyone is different, and I think it's vital to realize and accept how you work as a person in order to develop in the most optimal way. I realized that I was very prone to procrastination and all that stuff, so I just developed ways to progress while simultaneously procrastinating. One thing that helped me was creating a personal Discord where I could put images I took on walks, random thoughts I had, tutorials I found, links to specific Discord messages I saw, and just random stuff that I thought would come to use later.
Basically, I tried to do stuff that would help me actually do good work once I had the energy to work, when I had no energy to work. A hidden gem not known by that many people is Bilibili. It's like Chinese YouTube, and they have all the stuff that's on normal YouTube but under a better algorithm. In addition to that, there's Chinese content that's only on there, and that stuff is some of the best content I've ever found.
Stone Kits and Tileables
For the stones and stone wall tileables, I started by just making a simple stone kit using a standard high-to-low workflow in ZBrush, Maya, and then Substance 3D Painter. For the texturing, I tried to keep the stones very simple visually because it's gonna be used as a kit and also as part of a tileable texture, which means that you want to avoid having too much uniqueness per stone, as that will stand out a lot once it gets repeated.
At this stage, you've got a complete kit ready to be used in-engine: low-poly, high-poly, and textures. With this in hand, you can move on to the next step in the process of "converting" the stone kit into a tileable version.
When baking the stones, you will have two groups set up, something like the top part of the image. You want to come back and rearrange them so that each stone is in its own group containing both the low-poly and the high-poly instead. After this, we move into ZBrush, where we arrange the stones into a tileable (there are many tutorials on this, so just look something up).
After that, we export everything back into Maya, where we have to separate the stones back into the original two low-poly and high-poly groups instead of having one group per stone containing both the low-poly and the high-poly. The reason we did that was just to make sure that they perfectly overlapped at all times without having to worry too much about it.
Now we bring everything back into ZBrush (lots of jumping back and forth), and the only thing you really have to do at this stage is to import the Base Color and Roughness that you got from Substance 3D Painter when texturing your unique stone kit.
Keep in mind to click the Flip V button with the texture selected to make it work correctly in ZBrush. Then, with the low-poly tileable selected, you just go down to the Texture Map menu and apply the texture. You should then see the texture being applied correctly.
The tutorials on ZBrush tileables will explain the whole thing with canvas size, zooming out, the focus plane, etc., but at this point you can just focus on the focus plane, align the camera, and go into the Texture menu to save out a texture using Grab Unshaded Doc or Grab Shaded Doc if you used a flat-shaded material (this can be useful for inspecting the textures more easily while working).
Now we have the Color and the Roughness, so all we need to grab is the height, which is gonna be really simple because of our setup. All you do is hide the low-poly group and show the high-poly group. Then you go into Alpha and click GrabDoc, and that's basically it.
Congratulations, now we basically have a setup where you just import the texture using unique UVs, and it spits out a tileable version that's completely identical because it's created using all the same components that the unique meshes are using. Now it's time to take the Base Color, Roughness, and Height and move into Substance 3D Designer, where we will generate the Ambient Occlusion and the normal map.
That is as simple as putting down two nodes, quite literally, but I wanted to do some more work to the base color and the roughness. As I mentioned before, I chose to keep the textures for the stone kit very simple and without any contrasty spots that would stick out, because those would become very obvious when repeated.
Now is the time to add in some variation, and what I did was add some shadowing under the stones, some highlights on top, some color variation per stone, and lastly some surface damage. This is still pretty tame, but there's more detail that will come later, such as RGB masks and world-aligned detail textures.
The chain in terms of texturing goes as follows:
- Unique stone kit — small components, repeated a lot, needs low contrast and low detail in the texture.
- Base tileable — a medium component in the scope of the whole scene. It can have more unique elements in the texture because it does not repeat as much.
- RGB masks — zero repetition. Try to push as much uniqueness as possible, as not only does that add more visual interest, but it also brings the underlying repeating elements together better and helps to hide repetitiveness.
The whole setup for the tileable converter thing was a little annoying, but once in place, it's very quick to use, which is why I decided to create another version that had grout between the stones. For that, all I had to do was go to a place in the chain of processes where the tileable was in place, but the stones were grouped per stone. Then I just scaled each stone down uniformly, and that kept the tiling intact. After that, I saved out the textures again and reused my Substance 3D Designer setup, with the only difference being that I also added some grout.
RGB Masks
My RGB mask stuff is nothing revolutionary, but its importance cannot be overstated, as you can see from the before-and-after comparison.
Step one is to plan out which parts of your scene you want to share the same texture. For me, that was kinda arbitrary, but you want to get some idea of what texel density you want to go for and then just group together different assets in a way that makes sense and auto-unwrap them until you roughly reach that desired texel density.
In my example, you can see that I'm bringing in quite large modules, which drastically helps reduce how many textures you will need in the end. Once you have everything planned, just create a second UV channel for the module in question and then, in my opinion, just auto-unwrap it. Substance 3D Painter is kinda annoying with how it deals with multiple UV channels, so I just swapped them and made the auto-UV'd second UV the first UV. After the texturing, I swapped them back.
It is, of course, possible to just leave them swapped, but it will probably end up being more annoying in the long run to deal with that every time in-engine. You also need to handle normals on the second UV in a different manner, which makes it even more annoying and could potentially add more instructions (I'm not actually sure about the performance hit when doing this, but it's overall more annoying at the very least).
In Substance 3D Painter, make sure to bake out all the maps that you would need to use all the generators. Tick the box "Use Low-Poly Mesh As High-Poly Mesh" above the box where you usually add your high-poly mesh. As for texturing, it's of course super dependent on what you are making, as usual, but for me I knew I wanted some sort of generic grungy directional detail that would work nicely with my clean tileable. Because of the scale difference in terms of standout detail, it helps hide repetition from the tileable and unify everything better.
I also wanted some top-to-bottom gradients that would help ground different elements and create better visual separation between them. As shown in the example, I didn't just use one gradient for the whole module; I added two stacked on top of each other that start and end at different points that create the separation I wanted.
The third effect is just an AO-based effect that has been blurred, histogrammed, and warped a bunch of times to get those smooth, lava-lampy transitions. For this effect, I also did two layers to make the transition less harsh, but I didn't want to simply blur it, so I did it like this instead.
In the shader, you can also separate different parts based on their grayscale value, and this becomes really easy and gives predictable results if you author the texture with distinct jumps in grayscale value, like here.
You can essentially treat them like different masks if you want and drive cool effects in the shader using this approach. I actually did this for the edge of the roof, where it's kinda broken, to create a "hand-painted AO" looking effect.
Do keep in mind what type of effect you want to drive in the shader when authoring the RGB masks because, other than how you make them visually, there's also some other stuff to keep in mind when it comes to the Substance 3D Painter layer stack and the engine shader.
For me, I only used the RGB masks to tint the underlying surface, so for that you would want each layer to be set to Linear Dodge because you want each layer to be there regardless of whether they overlap or not. If you forget to change that and use the default blend mode for every layer, then you kinda defeat the purpose of even having an RGB mask.
If there is zero overlap between the channels, you could've just put it all inside a single grayscale channel and then given them different values to separate them that way. The power of the different channels comes from the fact that they can overlap each other, but that only works if you actually add them together.
This is not always the case, though, and I'm not gonna go into every "it depends" case, but the point is to always think ahead so that you make sure to explore every cool idea you might come up with instead of just doing things without thinking, because then you'll miss out on cool ideas, and we don't want that.
Modeling
Most of my scene is made using a plank, stone, and broken stone kit that I assemble into different props or mix with very simple modeling, utilizing tileable textures.
Some general tips would be to not assemble everything using individual pieces, but instead make some bigger modules that you build with and then merge and edit from there. An example in my scene would be the stairs, where I made some different stone sections that fit nicely together and then copied those to create longer strips. I assembled these to get the general shape, and for the bent part I measured the distance it would occupy.
Then I took one of these strips, made it that length, and used the Bend modifier to fit it in. At this stage, I have a decent base, and now I start deleting some awkward stones, changing some around to avoid repetition, and completely redo some edge-case sections.
Another thing to note is how I have even quads on some surfaces that don't need them for the silhouette, but that's just to make the vertex painting not look terrible, as it works by interpolating between vertices, which looks weird with uneven topology. So basically, plan and model accordingly.
Most of my foliage was placed inside Unreal Engine using the Foliage Tool, but for the ivy, I noticed in the reference that it had a really distinct shape, with some nice bends and a very pleasing silhouette. Because of that, I assembled it in Maya so that I could bend it into shape using Soft Select, which lets me have full control over everything.
Quick mention of how I made the stones. I just took my existing clean stone kit and picked some pieces out. Then I brought in a Megascans mesh that I used to boolean parts out until I got a nice-looking shape. After that, I sculpted over the booleaned areas to unify the art style. I don't have to go into much detail because this video explains it very well:
Texturing was just the same as my original kit, but I changed it up slightly for the broken-off parts of the stone.
Shaders
I'll now go into more of the things I did inside Unreal Engine.
Wall Shader
This one is pretty crazy in terms of how overkill and weird it is. I spent a lot of time in here just doing various experiments, etc., so I wouldn't exactly take this as advice on how to make the best and most performant shader. There are, however, some cool bits of logic that you could extract and do something else with, so I thought I'd try to show some of that off.
Apologies if it's straight up impossible to understand what's happening, but I'll try to dissect some elements of it and kinda isolate them as you would with a Material Function that you could reuse in different materials (only that I didn't, and all I have now is a huge spaghetti mess).
Here's how I did my vertex-paintable moss. I tried highlighting the lines that are actually in use, as there's a bunch of other effects that interact with the moss, etc., but aren't used for the actual moss itself, and then also connections used for other effects overlapping with the moss part that confuse when trying to sight-read the graph.
The way to read it is that you basically start at the top left, where a Height Blend using the moss Height and another random Height map (in this case, my dirt ground height) is done. Then, using the alpha result, you do some alpha offset stuff. That then goes down to the very bottom left, where it's used as the alpha in a Lerp between the moss BC and a value of -0.05.
A thing to keep note of with all the alpha offset stuff is that it truly does work in a pretty weird way. A Lerp between the BC and -0.05 doesn't sound like it would darken the BC a little, but it does. If you were to Lerp between the BC and a darkened version of the BC, it wouldn't work at all. You also can't saturate the alpha in the Lerp to get it to behave normally, but it's kinda whatever. Just copy my values or play around with it as I did and see what happens.
Anyways, this goes up into a Height Lerp, which is where I've noted that step 2 is done. You then continue to the right, and the Height Lerp there is the exact same one; I just split it into two images. All the stuff here is basically just more alpha offset stuff, etc., but after that final Lerp, the surface is completed, and everything after this is just overlays and other effects that I want to be applied on top of everything in the material.
By the way, I added a warning, but that Vector3 looks like a normal dark green even though it has the values -0.05, 0.01, and -0.05. Here's the result. The effect you get is a standard moss that you can vertex paint, but it also has darkened edges and a gradient falloff outside of it to both fake some depth and ground it a little with the green tinting.
Another tip I can give is that the gradient going from the Saturate in step 1 down to the Lerp in step 2 can also be used later on inside a Lerp to blend between the regular normal and a flat normal. Since it's basically a gradient going around the edge of the vertex paint and sloping outwards, modifying the normals here will give the effect of it having height, kinda like how a perturb normal setup would, but this looks smoother.
For the stone surface, I don't have anything that special before I start adding overlays and RGB masks; it's mostly just the Standard Base Color and Roughness controls, etc. The only other thing I added was a Detail Normal for small dents that I tiled quite aggressively so that it only shows up when you're very close to the surface. I will attach an image of the graph, but it is truly hopeless to try to decipher it.
It is not very complicated in practice, though. Add a detail normal and give it separate tiling controls so that you can tile it at a different rate compared to the stones, and then just plop in a BlendAngleCorrectedNormals node. Give both inputs separate Flatten Normal controls with parameters so that you can control the normal strength independently.
A little trick I did was to add a Split Components node to get a single channel out of the detail normal. Then I did some math operations on that to refine the mask, and I used that mask to Lerp between the stone base color and a darkened version. Doing it this way gives some nice variation in the coloring instead of applying a solid mask per dent, and you also don't have to use additional textures.
Here is the RGB mask part of the shader. It may look a little cluttered, but it's actually a very simple setup. Just give some generic controls for the mask and then plug that into a Lerp that blends between the unaffected surface and that same surface with controls for multiplying and adding color. After the first channel is done, you just do the same thing for the second, but this time you take the result of the first one and use that in the Lerp together with the clean surface, and then repeat the process.
The Roughness uses the same Lerp setup, but with the Standard Roughness and the modified Roughness values instead. The weird alpha offset setup can be disregarded, as that is only for a weird setup I had with my roof for the blue channel.
In this part of the shader, I just overlay these painterly textures that I made in Photoshop. There's not that much to say other than that I control the color and roughness with the same parameter, which is a Vector4. For all of these effects, I also have a Static Switch Parameter so that I can turn things on and off, which is handy because I use this as a master material.
It's a pretty limited master material because of all the niche effects, etc., but I still had to turn things on or off depending on the material instance, so they needed to be there. And here's everything showcased.
I utilized the alpha offset meta again for my landscape material. It's pretty straightforward, and it gives a pretty cool result on paths by darkening the edges and making them feel more integrated into the scene.
Small Flowers
My smallest flowers are essentially just dots without any texturing done to them. I give them their color by blending some world-aligned noises and lerping between different colors. Then I give the stalks the color of the ground using the RVT sample, while the petals get their color from the world-aligned setup.
Depth Fade Door Shader
This is another simple but effective shader that utilizes Fresnel to fake a dim light inside the house. Because of how Fresnel works, it looks like the light is deep inside the house instead of hovering at the same depth as the door. The Depth Fade handles the harsh transition that you would normally get from jamming a plane into the door and makes it look like it sits much deeper inside the house. The Sine node at the top is just there to slowly pulsate the light a little for some added interest.
Window Shader.
I downloaded a free interior cube map online and edited it in Photoshop to make it look more like the reference. Basically just blurring it a lot and changing the colors.
Trees
I can start by saying that the shading and all that is basically just the same as this tutorial, so follow that for the general look:
It is not the same, as I did some other modifications and watched some other tutorials as well, but it's nothing that really made a significant difference in the end. What I am going to focus on, however, are the different things I experimented with to try to get a new and more unique look for my trees.
My thinking was basically: let me try as many weird workflows and ideas as I can, and then in the end I will at least have learned some new things regardless of how it looks.
Tree Trunk
Not much to say about this one, as it's basically not even seen, but it's just something quickly thrown together in SpeedTree that has a tileable texture on it and also vertex-paintable moss.
Canopy Normals
As you probably know, it is pretty common to transfer the normals from a sphere, or from a shrink-wrapped sphere, to the canopy to get smoother shading so that you can circumvent the harsh shadows that you might otherwise get. This tutorial shows it off pretty well:
In my findings, not transferring the normals looked weird most of the time, but transferring them from a sphere looks too flat. So what I came up with was to first shrink-wrap a sphere onto my canopy, then remesh it for clean quads, then smooth it out a bit, and finally sculpt some blobby shapes onto it.
They come through very nicely in the normals and give a lot of shape to what would've otherwise been completely flat, while still maintaining that nice soft shading. This was probably the most important factor for the overall look of the trees.
Random Leaf Alpha
If you've ever seen a tree tutorial like the one linked above, you know that the cards are set up so that each card takes up the full 0–1 space. I wanted to play around with the idea of utilizing a texture atlas to offset the UVs randomly by set increments so that I could have many different leaf shapes while still maintaining the overall setup.
A little disclaimer is that there are more efficient ways to do this and that my solution is more of an opportunity to experiment with different things as opposed to just making it in the best way possible. Anyways, I started by creating a setup in Houdini that randomly assigns a value between 0 and 1 to the alpha channel of the vertex colors for each card. The value between 0 and 1 is assigned in 0.1 increments, so group 1 is 0.1, group 2 is 0.2, etc.
I also have a slider in there that just blasts away cards at random so that I can go into Unreal Engine, check how it looks, and then lower the number of cards until I see the visuals start breaking. With that setup done, I went into the FlipBook function and made a modified version of it. The only difference was that I disconnected the Time node and put the vertex alpha into the input that previously had the time node connected to it. So it flips through the atlas based on vertex alpha instead of doing it over time.
The result is that each card in the canopy gets a different leaf alpha. The thing I'm lerping it with using the red channel is just a manually selected leaf alpha from the atlas that is very soft around the outlines, which I use on the hero tree to the left of the house. I manually paint in that leaf where I want less detail, which in turn makes the areas where the detail remains pop out more.
Wisteria
The wisteria is obviously an integral part of the scene, which is why I put off making it for quite some time out of fear that my attempt wouldn't live up to what I had imagined. After lots of back and forth, thinking, and experimentation, I arrived at a result that I'm quite satisfied with. The essence of it all boils down to trial and error, but there are still a few tips and pointers that I could give that helped me get a satisfactory result.
The first step would be to throw together a decent recipe for the primary root generation as well as the secondary roots so that you have some decent warping, etc., to work with. Then I just threw chopped-up parts of my scene into SpeedTree to use as colliders for my wisteria to grow around.
After that, it comes down to manual editing and a lot of forces to guide the roots in a visually pleasing manner. I identified some silhouettes of high importance that I needed to make sure to capture, such as the root going from the first leg of the pergola and curving around onto the top of it.
I art-directed those parts very strictly, but once I had them in place, I could just handle the tertiary branches completely procedurally and guide them with forces so that they would fill up the space I wanted.
Basically:
- Primary roots= most attention and manually art-directed for strong visual language.
- Secondary roots= act as a skeleton for the general shape of the whole plant.
- Tertiary branches= act as the volume of the plant.
After that, add in some flowers here and there, and you should be done. A nice tip I got that saved me from the depths of frustration and despair was to use the Season Light force to remove flowers that hang too low. I wanted the branches and leaves to hang down so that I could get that full and voluminous effect, but it looked weird when the flowers grew that low, and I couldn't easily remove them with how my wisteria was set up.
The shader in Unreal Engine is mostly the same as my other vegetation, but a perk of SpeedTree is that you get some nice wind for free, so I just used that for my wisteria. Other than that, I added some vertex paint controls that let me hue-shift the wisteria in either direction by having the base vertex color set to 0.5. Going higher or lower would then shift the hue in either direction. I did the same thing for lightening and darkening.
This way, you get four controls from two channels, which then lets me bring in the AO from SpeedTree in the third remaining channel, which I used for various effects such as basic AO, but also for influencing the SSS. Another thing to keep in mind is that SpeedTree has some very cool options for manipulating the normals of your asset. I cross-checked between SpeedTree and Unreal Engine and played around with the settings until it looked as good as it could.
Root Decals
When looking at some references from Overwatch, I noticed that they put root decals behind all of their ivy. I compared it to my wisteria and ivy and realized that the roots were the thing that made the whole setup look interesting rather than just a green blob without any breakup for readability.
Looking at the Overwatch example, I drew up some shapes in Photoshop and made sure that they had those interesting curves that end in pointy bits and then flow back into curves again, if that makes sense. In Unreal Engine, I placed them in interesting spots around my ivy and then turned off Receive Decals on the vegetation so that the decals would end up behind it.
Foliage
For my foliage shader, the main thing I wanted to make sure of was that it had some nice swaying wind instead of solely relying on Unreal Engine's standard SimpleGrassWind function. Luckily, there is a nice tutorial on this by PrismaticaDev that I implemented to get that sorted:
I later brought in some SimpleGrassWind as well, but at a very low strength, which I blended with the swaying wind to get some further breakup. Some plants benefit from certain effects more than others, so I did not give every plant the same master material. For example, some plants used a top-to-bottom gradient that I got from the BoundingBoxBased_0-1-UVW function, and some larger plants with "bushy" shapes had baked AO as well.
Some of the visuals on the plants come from a post-process material, so I'll explain that first and then move on to how all of my post-process effects are set up and how they work.
Post-Process Materials
The effect itself is just the part circled in blue. A simple way to think about what it does is to imagine looking at the diffuse buffer and then overlaying that on top of your normal viewport using a Lighten blend mode. The green part is how the effect is masked so that it only affects the flowers, and this is a concept that will come up again in most of my post-process materials.
The setup relies on Custom Depth Stencils. This is a feature that you can enable per mesh, which places it in the stencil buffer. You can then read from that buffer in post-process materials and use it to mask effects. In this case, I chose the value 9, which means that the If node will return 9 if the Custom Depth Stencil value is 9, and 0 if it isn't. You then clamp that result to either 0 or 1, which is used as the alpha in the Lerp.
Here is my Kuwahara filter post-process material. The Kuwahara filter is based on this video, and what I did was basically just add my Custom Depth Stencil masking to it, as shown in the previous example.
The difference here is that I'm stacking a lot of different stencil values together, and that is because many effects can use the Kuwahara filter but still need different values because of other effects. My water uses the Kuwahara filter, and my background trees do as well. They don't use the same strength, though, so they need different values for me to differentiate between them.
At the bottom of the graph, there is a confusing little section that returns a different RADIUS value for the Kuwahara filter based on the stencil value. It will always default to the RADIUS parameter, which is set to 2 for every possible value except 3 and 5. Stencil value 3 will return a radius of 3, and stencil value 5 will return a radius of 1.
This could be seen as a bit of an upgrade to the last example because now I can not only turn effects on or off depending on the mesh, but also control the strength of the Kuwahara filter in addition to completely masking it out.
My last post-process material is my outline material. I spent quite a bit of time looking around online for nice outline material, but weirdly enough, it felt like everything I found used solid-colored outlines. When I tested those in my scene, it looked very odd to have black outlines around everything. Luckily, it was really easy to change, and I think the results look pretty nice.
The differently colored outlines give the whole scene a more unique art style and feel, and also really help separate stacked elements visually.
Outline and foliage post-process showcased in isolation.
The last thing to talk about when it comes to post-process materials and Custom Depth Stencil masking is this situation that I've tried to highlight above. On the tree, we can either completely remove the outlines or have them enabled everywhere, but what if we wanted outlines on the tree and not where it intersects with the grass?
The outlines actually come from the grass, but we can't remove the outlines on the grass because they create some nice separation against the wall behind it. We just don't want them on the tree because, in my opinion, that softer transition works better in this case. What I did to solve this problem was create a sort of post-process material remover volume.
This is just an empty translucent material that has Allow Custom Depth Write enabled. Then, on the mesh that you are applying this material to, you just have to untick Render In Main Pass. The last step is to choose a value for its Custom Depth Stencil. This could be anything, but for me I set it up so that it removes all post-process material effects.
VFX
Why make a 3D environment instead of a painting? For me, an important factor is the movement of the image and the life that it brings to the artwork. This was a part that I felt I really needed to nail, and surprisingly it came together without much of a headache, as most of the scene is covered in foliage and a nice wind sway setup was all that was needed to bring life to about 90% of the scene.
However, I did add a lot of other things to increase the liveliness of the scene, and I'll quickly go over the things I implemented. I added some birds traveling along a spline, animated through vertex colors and sine waves. I also used a simple WPO sine-wave setup for the boat to make it move up and down subtly.
To accommodate the moving boat, I also added some WPO to the water. A little problem I ran into was that I needed the waves to be super subtle because of the entrance structure connected to the water. It reaches down very close to the water surface, and if I used stronger waves, they would clip through it and look weird.
I could have just kept the waves weak, but then you wouldn't really notice them. What I did instead was add two different wave strengths that I lerped between using vertex colors. The default was the weaker one, and then I painted in the stronger one around the pillar in the water to the right.
This pillar is very tall, so you can make the waves as strong as you want and have them contrast against it so that the viewer actually notices the waves. It just so happens that the perfect place to draw the viewer's eye was also where the boat was, so I got that visual relationship for free. The only thing other than that worth going into any detail on would be the god rays.
The god rays are simply placed cards with some shader animation that I positioned throughout the scene to make the lighting feel less uniform. The only place where there was any real thought behind their placement was around the boat. I placed a god ray through the arch and behind the boat to create some nice separation between the boat and the elements behind it, while also drawing some extra attention to that area.
Other than that, I added some floating pollen, wind whispers, cloud shadows using a Light Function, butterfly clusters, and chimney smoke. For the showcase video, I also added some subtle camera shake to help break up the CG feel.
Lighting
Lighting the scene was a real challenge for me and something that spanned the entire project. Whenever I was procrastinating on getting work done on something more self-contained, like a stone kit, a Designer material, or something similar, I would hop into Unreal and either play around with shaders or try to improve the lighting.
My workflow when lighting was basically to pull up everything affecting the overall image and crank every setting like a madman until I found something that improved the feel, even if only a little. I would often get stuck and frustrated, close everything down, and move on to tinkering with shaders instead, which is why my lighting process was very iterative and sporadic.
Like with everything else, it's of utmost importance to ask for a lot of feedback, but that was taken to a whole new level when it came to lighting, at least for me. Looking back at some of the older lighting setups I had, I now notice that they look really weird. But I remember being completely blind to that at the time, and other people's feedback was my only saving grace.
Conclusion
I feel like I've made this whole thing way longer than it needed to be, but as an article-reading warrior, I remember scouring endless articles looking for that specific shader setup, little workflow technique, or whatever it might've been. Because of that, I decided to just pour as much of what I did into this breakdown as possible so that maybe someone finds something in here that's useful for them.
My aim with this breakdown was for someone to find a cool technique to implement in their own work, or to help a beginner artist kickstart their art journey using some of the tips that helped me kickstart mine. Lastly, I think most people already know this, but I'll say it anyway: make sure to seek advice from skilled professionals. People are far more willing to help than you could ever imagine.
Big shoutouts to:
- Experience Points Discord
- Dinusty Discord
- Stylized Station
- SBECord
- Waramaug
- 3nz1o
...and many more.
Thanks for reading!