Version 0.4.1 of the website just dropped! Read the release notes

How I made the music video for “gaze upon the stars”

Published on

37 minute read

Heads up!

Like the last one, this article may also be a bit rough around the edges, perhaps even more so. Again, any and all feedback is welcome!

Last time, I talked about how I created one of my best songs, “gaze upon the stars”. Today, I’m at last here to discuss its music video.

If you’re wondering what took me so long this time, the hot weather here prevented me from doing too much on my PC. It tends to turn into a space heater under load, and the space it’s heating happens to be very small, so it quickly spirals out of control. I’ve also been having some sleep issues thanks to loud neighbours. But without further ado, let’s finally dive into how the music video was made!

The animation was created in Unreal Engine (version 5.7.4, to be exact), which happens to be the same engine Home Paige uses. I had previously tried both Unity and Godot, but neither of them really ended up working out. But that’s not to say Unreal doesn’t have its flaws either. Not by a long shot, in fact…

Hey, just so you know, whenever I say UE or Unreal, I’m referring to Unreal Engine.

Tangent: Unreal Engine woes

Hey!

This section is not important for the rest of the article, so if you don’t care, just skip to the next section here.

Still here? Alright, then let me take you on a bit of a detour to talk about some of the issues I’ve experienced with Unreal.

Linuxn’t

Unreal Editor 5.7 is the first version to use Wayland on Linux by default, and it might just have the worst Wayland support I’ve ever seen in the over 2 years I’ve been daily driving Linux. And trust me, I’ve seen some stinkers. There’s a post about it on the Unreal Engine forums if you want to learn more.

To fix this, I first had to launch Unreal Engine with the SDL_VIDEODRIVER=x11 environment variable, to force it to go back to X11 and run under Xwayland. That alone wasn’t enough though, because it broke a bunch of other things instead. Thankfully I found a fix in that same forum thread , although it’s still far from perfect.

Since then, that forum post has been locked, saying the issues are fixed in UE 5.8. However, after updating to UE 5.8.2, I can confirm that this is not in fact the case. While mouse interactions seem to be improved (except sometimes when a notification pops up in the bottom), the viewport navigation doesn’t work. As such, I still have to use the workarounds above. So close, yet so far away…

Miscellaneous Instabilities

There have also been some other issues. For instance, every time I launch the project, the grass just doesn’t exist, and I have to reopen the level to fix it.

This seems to have been patched in 5.8!

Or sometimes when I save certain things, the terrain in the distance just freaks out. I somehow didn’t have a screenshot of that, so I reproduced it just for you!

I haven’t had this happen in 5.8 either, so perhaps this one is fixed too.

Unreal Engine has also crashed on me several times, most notably when switching from the cinematic viewport to the normal one. In fact, it still does that in 5.8. Quite annoying when you’re working on cinematic stuff like this, don’t you think?

Unexpected Blackout

Finally, in the process of writing this article (dear god, that was like 3 months ago already…), Unreal Editor decided it was no longer going to render the viewport (specifically in Lit mode). That’s what inspired me to write this section in the first place, although as it turns out, it may not have been Unreal’s fault.

I tried many things to resolve this, including:

I then looked in the Discord server for my distro (Nobara), and found someone else who also started running into this issue after an update on 14 May. Armed with the knowledge that it was likely a driver or kernel issue, I switched to an experimental build of the Mesa driver, and lo and behold, it worked!

I looked on the forums recently, and saw a lot of people having the same issues running Unreal Engine 5.8 on Linux. Those posts further confirmed my theory that it was in fact an issue with Mesa.

Ironically, the build it installed was still a month old (from 20 May, to be exact). That’s likely because Nobara hadn’t been getting package updates for a while, since version 44 was in the works. Now that said version is out, the stable driver works too.

And actually, I started getting severe graphical glitches in Unlit mode instead. In fact, they still happened as recently as August 25, although now it seems to have gone away after upgrading to UE 5.8. Well, this is just a certified Linux moment…

Concluding this Tangent

As you can see, Unreal Engine does have its fair share of issues, even more so on Linux. A bit unfortunate considering how good the Linux desktop is getting these days, although supporting it can be quite a hassle at times. There were plenty more smaller issues, but they’re not really worth mentioning here, and I need to stop complaining by now.

Despite all of these problems though, Unreal’s tools for creating content like this compensate for that just enough for me to keep using it.

Chapter 2: The Music Video

In this article, I’ll go through some of the assets, materials, techniques and other stuff I used to make the music video. Keep in mind this was my first time using Unreal, so there is a fair bit of jank involved. That… isn’t exactly unusual for me, to be honest.

Should you be interested, I’ve once again made the project files for this music video (and any future ones) freely available, this time on GitLab , under the MIT licence. Note that they have been updated to Unreal Engine 5.8.2.

Normally, I’d put stuff like this on GitHub (like what I did with this website), but they have a hard limit on Git LFS bandwidth , which is just unacceptable for a project like this. GitLab doesn’t mention this anywhere, so they’re likely a bit more lenient. Should the repository become unavailable for whatever reason though, I’ll try to keep a backup version on my Nextcloud share .

I also want to warn you that things may get rather technical in places. My apologies for that. Hopefully you don’t mind too much. But with all of that aside, let’s go over how the music video was made, shall we?

Gazing upon the Stars

Since the song is titled “gaze upon the stars”, obviously the sky needed some stars to gaze upon. And the default sky you start with in UE5 doesn’t have any of them, which is unfortunate.

A lot of YouTube tutorials recommend dropping in the BP_Sky_Sphere blueprint, which is likely left over from an older version of Unreal Engine. Sure, that one does have stars, but they’re not animated. And that’s just lame. They do offer another solution, the Celestial Vault plugin, but it said no to my lighting setup, so I couldn’t use it.

While writing this, I accidentally found another material that’s part of Unreal Engine, called M_Procedural_Sky_Night. I’m not sure what the origin of it is exactly, but it does have animated stars, although the effect is so subtle it might as well not be there. It also has its flaws, and it wouldn’t have helped me with the constellations anyway, so I’m still happy that I went down this rabbit hole.

I initially used a Fab asset called Good Sky to handle the sky, which did have clearly animated stars. However, when I posted an early version of the music video in the Home Paige Discord server (because that’s what I do now), people wanted more. They wanted shooting stars and constellations. And Good Sky wasn’t going to cut it for that. So I ended up doing it myself, by making my own sky material.

The Path to Stars

Fun fact!

I used a very similar approach to create the animated starry background of this very website using WebGL. The source code for that is available on GitHub , under the MPL-2.0 licence.

Can’t see said background? Then make sure the Immersive Mode and Background preferences are enabled.

Of course, in order to make a night sky, you need some stars. And because I wanted to make them procedural, I used some noise for that. But noise works with a position, so I needed to get that first. Good Sky did actually come in handy, as its skydome mesh comes with texture coordinates that made this very easy.

In order to get those coordinates scaled down and moving, I wrote this material function that I called MF_NoiseCoords, so I could re-use it later:

Now feed it some parameters, and out comes a position that you can give to a noise function. Notice it also outputs the adjusted scale; this will come in handy later. But first, stars. Interestingly, I found that Unreal Engine’s gradient noise gave me the best results:

Hey!

The Sequencer Time node you see in these graphs comes from another named reroute. I talk about it later in the article. If you’re not planning to use the Sequencer, just use the normal Time node.

Twinkle, Twinkle

Cool. Now we have moving stars. But here on Earth, stars don’t just appear to move, they also twinkle . So how do we do that? Why, with more noise, of course!

The graph is essentially the same as the above, just with different parameters. So instead of adding another yet image (this article already has so many of them, as you’ll see later), here’s a complete list of the things I changed for the twinkle noise:

You then multiply the stars with this new noise, et voilà, twinkling stars! Okay, there’s a bit more going on afterwards, but I’ll save that for the end of this section.

Colouring them in

Stars are also not just pure white . They can have a whole range of temperatures, resulting in colours from orange to white to blue. It is pretty subtle though, which is why people often just make stars white. Most of us simply don’t care all that much.

I do care, though. And that means it’s time to add another layer of noise in the mix. I definitely didn’t use the most correct approach for this, but it’s good enough for government work:

There is a lot of dodgy math involved here to try and get a more reasonable colour temperature range.

And here’s the function for getting the colour from a temperature (MF_ColorFromTemperature), based on this algorithm from Tanner Helland . It probably didn’t need to be this complex, but whatever.

Again, you multiply the final colour noise with the twinkling stars to make them coloured. Simple! The end result is barely noticeable though, and in hindsight I could’ve saturated the colours a bit. Oh, well. It’s still technically better than just plain white.

Paige in the Sky

I mentioned before that I got a request to add constellations to the sky. To be more specific, they wanted ones depicting some of the Home Paige characters. Now that I have a custom sky material, it shouldn’t be that hard, right…?

Well, there is a problem, and it’s that I’m not particularly good at drawing. And with the way I implemented constellations, I’d have to draw them myself, since I don’t know anyone that could do it for me.

The images I ended up referencing were no more than 128 × 128 pixels in size, which is just abysmal for trying to convert to dots and lines. I really wasn’t making this easy for myself. I persevered though, and after a week or so I ended up with this. It’s pretty scuffed in places, but thankfully that doesn’t really matter.

I just found out I forgot some things, like part of Sophy’s hair and possibly the stars for Oselle’s mouth. Oops. Good thing you can’t really tell in the final music video…

In case you’re not familiar with Home Paige… what are you even doing? …Oh, my bad, I got carried away there. What I wanted to say is that Sophy is the one in the bottom right corner, and Oselle’s in the bottom left. I might as well go full circle while I’m at it, so in the top left we have Paige, and finally in the top right corner is Oliver.

The Constellation Function

You may have noticed the constellation texture contains 4 of them, and that it’s actually split into 3 separate layers. That way I can customize them more easily inside of Unreal Engine. However, that does mean it’ll take a bit more effort to get them into the sky.

First, another function. Since the constellations only cover a small portion of the sky, we need to start by manipulating the UVs properly, as well as creating a mask. Here’s how I did that:

All functions used here are built into Unreal Engine.

Next up, we sample the layer textures, change their intensities and combine them together. While there are 4 layers here, we only have 3, so the last one is just a black texture. You can omit it from the graph if you don’t need a 4th layer.

Finally, we mask away the portion we don’t need, adjust the brightness and contrast with a custom function (more on that later), and accumulate it with the previous constellations. We also combine the masks together; you’ll see why in just a moment.

Using Said Function

Now that we have our constellation function (appropriately called MF_Constellation), we just need to put it to use. Let’s start off with the common inputs, most importantly the UVs.

Here’s where we need that scale output from the MF_NoiseCoords function; we can use it together with the Star Speed parameter we previously defined for the stars, in order to match their speed perfectly.

Lastly, we call the function with a bunch of parameters to get our combined constellation output. This involves a lot of copy-pasting, so I won’t show the full graph here.

Completing the Material

Neat! We’ve got everything we need to finish off the sky material now. You may say we haven’t talked about the shooting stars yet, but those aren’t actually part of the skybox, so be patient. And no, there’s no moon either. It would’ve been offscreen anyway.

Here we take everything we’ve made up until now, adjust their brightness and combine them. We can use the accumulated constellation mask to hide the stars near constellations, so that they’re a bit more clear. We also bring in the atmosphere produced by the level’s SkyAtmosphere.

The brightness function (MF_BrightnessSettings) used here and in the constellation function is pretty simple:

One more thing. In order to be able to use our new material as a skybox, we need to change 2 material properties:

And that’s it! We finally have our procedurally generated starry sky material. There are just a few things left to talk about…

Shooty Starry Particles

Remember, I was also asked to add shooting stars (aka meteors ). Putting these directly into the sky material isn’t a good idea, so instead, let’s talk particles. In Unreal Engine, these are handled by the Niagara VFX system .

I’m not going to dive into the details here, as that would take way too long. This article is long enough as is. Just know that I used the Radial Burst template as a starting off point, though it was modified quite a bit. For instance, I disabled its sprite renderer, as it was causing ugly artefacts in renders.

One neat thing I did is calculate the spawn rate specifically so that the meteors would appear more or less in time with the beat:

The spawn rate is perhaps a bit high, so just pretend it’s a meteor shower or something. I also whipped up another material to get the ribbon trails to look how I want them to:

That’s all I wanted to say about the shooting stars. We just have one more topic related to the sky left to discuss.

Puffs of Cloud

Naturally, the clouds aren’t part of the sky material either. In fact, they’re just Unreal Engine’s default volumetric clouds. I did, however, tweak the settings to make them not as dense, as well as much less detailed. Remember, this is Home Paige, not (insert realistic looking Unreal Engine game here).

These clouds do have an effect on the overall lighting, which results in some of the images in this article having different brightnesses than each other. You can also notice the brightness changing in the music video. Without the clouds though, the shadows simply become too dark.

Oh yeah, the clouds are also tinted purple. This was actually done somewhere in the lighting settings. But with that, our night sky is complete! We can finally move on to the next subject, which is the scenery.

Making a Scene

Having a night sky is nice, but it would be even nicer if there were a few more things to look at. I started by spending quite a while trying to create a nature scene which I both liked well enough and would not make our low poly Paige look too out of place. Eventually I settled on the Cozy Nature pack on Fab , which I then used to create this landscape:

I did end up tweaking a bunch of the materials from this addon, although whether I did a good job of that is debatable.

Light it Up

No scene is complete without lighting. For this, I threw in something special by making the darker tones a bit purply. That helps to give the lighting some character. The light level ended up a bit darker than I would’ve liked though, which is unfortunately something that I tend to do quite often. I am a dark mode user with sensitive eyes, after all.

Of course, you can’t really have an Unreal Engine project in 2026 without post-processingpost-processing. The 2 main ones we’ve got here are bloom

…and a touch of extra vignette. Nothing too fancy.

Oh, technically lens flares were also enabled, but they were turned down so much they might as well have not been there. Not like they would’ve fit in all that well…

Foreground Fill-In

All of that is cool and all, but where is Paige supposed to sit? On the ground? Well, funny you say that. I originally wanted Paige to lay down on a picnic towel, but my lacklustre animation skills couldn’t find a good pose for her. So instead… park bench.

If you thought my animation skills were lacklustre, you haven’t seen my 3D modelling skills. Or more precisely, my total lack thereof. As such, I had to resort to the next best thing: a free Sketchfab model1.

I also added a recycling bin on the other side, also from Sketchfab2. Why, you may ask?

  1. A park needs to have a place to deposit your rubbish into, and…
  2. …you know… Recycling Bin? The place that Home Paige is set in? I can make references too, you know.

The one I picked looked very out of place, though. One of the things I did is making its colour and normal map 4 times smaller, which lowers the detail quite a bit. The colours were also way too dark, but thankfully, Unreal Engine’s got our back. We can simply adjust the texture’s brightness curve, and that makes it look better. I also tweaked the material settings slightly.

Honestly, I could’ve gone a bit further than this, because it still looks a bit too detailed and dark. But let’s move on to the park bench, which was still looking rather bright in comparison. I adjusted the bench’s material colour to match them a bit better:

And the end result is… fine. Nothing to write home about. I just wanted something serviceable to accompany the song I created, so it’s not a big deal.

A Slab of Concrete…ish

Because the terrain isn’t exactly flat, trying to place objects on it would have them clip into the ground. And that’s no good. To resolve this, I added a grey slab. That on its own is boring however, so let’s construct it out of concrete instead.

For some reason, I thought it was a good idea to build my own procedurally generated concrete-like material. Was that really necessary? Maybe not, but it did make me learn how to use Unreal’s material editor, which in turn helped me create the custom sky.

Yes, I did in fact make this concrete material before the sky. The more you know.
Stack the Layers

We start off with – you guessed it! – more noise. There are a few layers here, so let’s go over each of them one by one.

  1. We start off with a course, primary noise layer.
  2. That layer is then exponentiated a ton and then inverted, in order to turn it into a few occasional speckles.
  3. Then we have a finer secondary noise layer for some extra detail.
  4. Finally, that gets multiplied with the primary noise to break it up a bit, and is then blended with the speckles to get our final, combined noise layer.

There are 2 more material functions at play here. The first (MF_Noise) is a simple helper function to make some noise:

And the second (MF_Sigmoid) is a crude little sigmoid function ; a function that gets closer to 1 as the input increases, but never actually reaches it.

Technically it should do the same for negative numbers but approaching -1 instead, but I just skipped it since there are no negative numbers to be seen here. If there were, something must have gone seriously wrong.

The factor defaults to 1 if that input is disconnected. By the way, to properly handle negative numbers, you should just be able to put an Abs node between the input value and the Add node.

Materialization

Now it’s just a matter of turning that noise into a decent-looking material. If we use our combined noise layer to blend between some material values, it’s already starting to look like something:

But we can do better than that. You see, the noise function we’ve been using also puts out 3D vectors that are well suited for normal maps. We can take the vectors from our primary noise layer, and only use them where there are speckles using the built-in FlattenNormal function.

If we plug that into the material’s Normal input, those dark spots will get a pseudo-3D effect, making them more pronounced:

Perhaps it’s still looking on the plain side though. No worries, we have more noise where that came from. With this, we can add a bit of texture to it:

Hey, that’s not so bad. There’s definitely still room for improvement, but I can work with this. And we’ve always got loads of parameters to tweak, too.

But where’s Paige?

You’re saying we made it this far into the article, and we haven’t even discussed our main protagonist here (apart from the constellations, but that doesn’t count)? That’s just unacceptable. Let’s fix that.

I want to preface this by saying I almost certainly used a completely wrong approach to get her into Unreal Engine, because as you remember, Home Paige is already an Unreal Engine game. But it should be obvious by now that we don’t really do things the correct way here.

Importing the Model

Alright, let’s talk about what I did. I started with specifically this version of a Blender rig I found in the Home Paige Discord server . Because it was really meant to be used in Blender, it has lots of flaws that made this job much harder than it likely needed to be.

This is the same rig I used in my old Unity project. And yes, I’m aware there’s a newer version of the rig, but that one was somehow even harder to work with, so I went back to this one.

The best results I got for importing it into Unreal Engine was by exporting a .glb file from Blender. However, if I recall correctly, it still had a bunch of material issues that needed correcting. Thankfully that didn’t take all too long, mainly since I had an already working version in Unity that I could reference.

I also made some tweaks to the star material, so that they glow slightly orange. It works quite well with the bloom, I’d say.

Rigging it Up

Because I have absolutely no clue on how to use Blender, I wanted to create a rig inside of Unreal Engine, so I could use that to animate her instead. To be honest, I could’ve just learned a bit of Blender to create the animation there instead, especially because this Unreal Engine rig likes to break sometimes.

Anyway, I started by creating a control rig, with controls for every bone that actually has a meaningful effect:

I then selected every control (except for the purple face selector ones, we’ll get there when we get there) and dragged them into the Forwards Solve graph, creating an item array.

You’ll have undoubtedly noticed that each control’s name specifically ends with -- and a bone name. The rig I used also had generic names for them, which made this naming process quite tedious. But now I can use those carefully crafted control names to retrieve the correct bone for each control in the item array, and transform that bone accordingly:

This is a bit of a cursed approach, but it mostly works. Now we can start animating Paige inside Unreal Engine, with the same technology used to create the music video itself: the Sequencer ! Simply create an actor track for Paige, and add the newly created control rig to that track.

For the music video, I created a very simple sitting loop. As I stated before, animating is not one of my strong suits, and I just wanted something serviceable. If you want to see Paige animations that are actually good, I’d strongly recommend checking out what Minimin is cooking .

It’s Sequencer Time!

We’ve now discussed just about everything you can see, so let’s get to animating the music video. Unreal Engine conveniently has some really powerful tools dedicated to stuff like this, which is what led me to try it out in the first place.

Camera-nimation

If we want to make a video, we need a camera. Just like real life. Unlike real life however, in Unreal Engine, we can materialize a camera out of thin air with the press of a button. Meanwhile, us mere mortals have to spend our hard-earned money on one. Although these days we’re just carrying around smartphones in our pockets that happen to have decent cameras…

I made it so that this camera would very slowly move and rotate over time. So slowly, in fact, that it probably took you a while to notice it was even moving. And if you didn’t notice at all, try scrubbing through the video. That should make it obvious.

One cool trick we can do with this new camera is animating the exposure compensation to create a fade effect, like so:

Make sure to use a curve like the one shown here, especially for longer fades. Otherwise, it’ll stay dark for too long, which doesn’t feel natural.

Doing it this way will make the darker parts of the image fade out faster, while bright objects (like Paige’s star antennae ) will remain visible for longer. Just like a real camera!

Staying in Sync

Because we’re making a music video, it would be nice if we could synchronize things to the Sequencer, to better line things up with the music. That’s going to take some more work, though.

Avoiding Audio Anomalies

The Sequencer allows us to drag any imported audio file directly into it. That’s quite neat, however one problem I ran into is that the audio would de-sync more and more while playing. Not exactly ideal when you’re trying to time stuff to the beat.

You can set the Sequencer’s clock source to Audio to try and combat this:

0:00 / 0:00

And while this does fix the de-syncing, for some reason, it makes the audio crackly instead. Thankfully I found a solution to this on the forums, namely the console command Sequencer.Audio.UseAudioClockForAudioDesync 1. Combining this with the audio clock makes it behave pretty much perfectly for audio-sensitive workflows like this.

Fun fact!

You can throw this command in a configuration file if you want it to be enabled by default. I ended up adding it to the project’s Config/DefaultEngine.ini file:

[ConsoleVariables]
Sequencer.Audio.UseAudioClockForAudioDesync = 1
Sequencer-driven Materials

Normally, if you want a material to evolve over time, you would use the appropriately named Time node, which just outputs the world time in seconds. However, I wanted the sky to move in sync with the Sequencer, so that I could make all constellations show up properly in the music video.

I tried looking up how to do this, but the only thing I found back then was that you had to use what’s called a material parameter collection to animate materials. Now, that may have been the case back in the day, but while researching a similar topic in late August, I accidentally found out that you CAN animate material parameters .

However, I didn’t know this at the time, and in fact, using a material parameter collection has one big benefit: you’re able to re-use it across multiple materials. As such, it is likely still the best approach for this use case.

So, what I did is I created one of these collections, containing nothing more than a scalar Time parameter. It’s so simple I don’t even need to show it to you. Then I added it to the Sequencer and made it so that it linearly increases by 1 every second. That way, it would effectively hold the sequence’s current time in seconds.

Now it’s just a matter of referencing this in any materials we want to stay in sync with the sequence, and using it in place of the Time node. This is where that Sequencer Time node originates from in previous graphs.

The Remainders

Particle systems, like the one that handles our shooting stars, also need a bit of tweaking to get them synchronized with the Sequencer. The Unreal Engine documentation fortunately already has this case covered, but to summarize it essentially boils down to making the emitter deterministic and adding a lifecycle track for it in the sequence.

Something that I haven’t been able to synchronize yet though are the clouds, which is annoying, because they affect the overall lighting. I’ll have to try to figure that out later. Or I could just stop using the default volumetric clouds.

Exporting the Video

Phew. We finally have our music video. Now we just need to render it. Surely it won’t be that bad, right…? *sighs* Yeah, no.

Rendering it

I used Unreal’s Movie Render Queue for this. It has a nice feature where we can set the resolution and framerate to whatever we want. That allows us to do test renders at a lower resolution and framerate to speed them up significantly, and then switch to a higher one for our final render (in this case, 3840 × 2160 at 60 fps, aka 4K60).

However, we also have to choose an export format. While it shows an option to export an H.264 video, on Linux, this option simply errors out saying it is not supported. And in fact, this applies to all the video export modes. Once again, us Linux users are being left in the dark.

Not all hope is lost, though. We can still export an image sequence. For the first test renders I did, I used a simple .png sequence. But even at a reduced resolution and framerate, it was still taking up tens of gigabytes of disk space, which is not ideal. There is an option to use a .jpg sequence, but I didn’t trust it at the time because it’s lossy.

Instead, I switched to an .exr sequence with DWAB compression. While this is also lossy, you honestly can’t even tell. That still ended up with a size of about 20 gigabytes, but it’s definitely less than what a PNG sequence would’ve been at 4K 60fps. And we also get some… advantages?

You see, these EXR images store 16-bit linear colours. That’s fine for professional work, especially because you get much more brightness detail than the 8-bit images from PNG/JPG sequences, even with the lossy compression. They may even be suitable for HDR purposes, but I wasn’t able to figure that out.

One big downside though is that if you try to use them directly, the end result will be extremely dark. You have to tonemap them yourself. It also takes a lot of time for the CPU to decode these images, slowing down the encoding process. Speaking of which…

Encoding it

It’s time to turn this image sequence into a proper video. Unreal Engine does have an option to use a command-line encoder , but I believe I couldn’t get hardware acceleration working this way. Instead, I just let it export the image sequence, and then executed FFmpeg directly:

ffmpeg -hide_banner -y \
	-hwaccel vaapi -hwaccel_output_format vaapi -vaapi_device /dev/dri/renderD128 \
	-framerate 60 -i './stargazer-music-video_final/%04d.exr' -i 'f78 - gaze upon the stars.flac' \
	-c:a copy -c:v hevc_vaapi -b:v 50M -tier 1 -vf 'zscale=t=iec61966-2-1,format=nv12,hwupload' \
	'stargazer-v2_final.mkv'

I’m guessing most of you aren’t familiar with FFmpeg, and you’re probably looking at this command, going: “…huh? What does that do?” So, let’s dissect this one step at a time.

  1. -hide_banner hides the incredibly big banner that it likes to output every time, and -y always overwrites the output file if it already exists, instead of asking you first. These are just for convenience, so don’t worry about it.
  2. -hwaccel vaapi -hwaccel_output_format vaapi -vaapi_device /dev/dri/renderD128 tells FFmpeg that we want to use VAAPI for hardware acceleration. This is what worked for me on Linux with my AMD GPU. For more information, refer to the FFmpeg wiki .
  3. -framerate 60 specifies the input framerate. This is required when you’re dealing with an image sequence.
  4. -i './stargazer-music-video_final/%04d.exr' passes said image sequence as an input, using a pattern.
  5. -i 'f78 - gaze upon the stars.flac' then specifies the original song as an input. While you can export audio from Unreal Engine, it is not necessary, since the only audio track is the song itself. It would introduce another layer of instability, too.
    (If you’re wondering, this is a 48 kHz, 24-bit file.)
  6. -c:a copy tells FFmpeg to not encode the audio, and just add it to the video container directly. FLAC audio is ideal for music videos anyway, since it’s lossless. That way there will be slightly less quality loss.
  7. -c:v hevc_vaapi -b:v 50M -tier 1 sets the video codec to HEVC (H.265), with the High tier, a target bitrate of 50Mbps (which was overkill, it needed less than 20Mbps), and using the VAAPI hardware acceleration from earlier.
    While CRF would be better, it’s unfortunately not supported by VAAPI. And yes, my GPU does support AV1, but apparently it gives worse quality or something? I don’t remember. It’s been almost 4 months, you know.
  8. -vf 'zscale=t=iec61966-2-1,format=nv12,hwupload' specifies some video filters:
    1. zscale=t=iec61966-2-1 is the important one. It’s what maps the linear values from our EXR sequence to sRGB, which looked the most accurate to me, although it’s actually still a bit off.
    2. format=nv12,hwupload is some more VAAPI hardware acceleration stuff. Something about how to upload the frames to the GPU, I believe.
  9. Now we’re just left with 'stargazer-v2_final.mkv', which is the output path. Here we’re using an MKV (Matroska) container, but that shouldn’t really matter. MP4 is probably fine, too.

You can see I used hardware acceleration for this. While that might be a bit worse for quality, YouTube is going to compress it so much that it’s not too big of a deal. Doing this also takes a lot of load off the CPU, which was already at 100% usage the entire time from just decoding those EXR images. I don’t even want to imagine what encoding a 4K60 video on top of that would be like.

And at last, we have a video file! Which, by the way, is about 50 times smaller than that image sequence, coming in at just 400 megabytes. And that includes the FLAC audio. Now we just upload it to YouTube, and…

…as it turns out, we’ve got a bit of a problem. You see, YouTube uses the BT.709 colour space, and when it got those sRGB colours, it decided to mess with them. This resulted in the video looking darker than it’s supposed to. It’s most noticeable in parts of the image that were already dark to begin with.

PNG exported from Unreal Engine (likely has different lighting, and don’t ask me why the camera angle changed…)

Screenshot of local video file

Screenshot from YouTube

Thankfully we can just barely call that a feature. It is nighttime, after all. This is definitely not ideal though, and I’ll need to resolve this by the time I finish the next music video. I will probably just go back to a PNG sequence, or maybe even a JPEG one. The quality loss may not have been so bad after all.

Bonus: A Heroic Image

Before we wrap up, I wanted to say a few things about this very article’s hero image. That’s the big image at the top, if you didn’t know.

Facilitating Facial Animations

In the music video, we just stare at Paige’s back the entire time. However, the hero image has her face in it, and that default face wouldn’t be sufficient. So, I had to implement facial animations.

I followed this tutorial for animating materials using a control rig. I won’t go into detail here, so watch that tutorial if you want to learn more. This approach calls for very specific material parameters, so I had to create my own material for each facial feature, all with unique parameter names. Thankfully it’s not that difficult. Here’s what one of them looks like:

Keep in mind each spritesheet for Paige’s facial features has a different number of sprites in them. The B value of the Divide node needs to be changed depending on how many of them it has. Here it’s 16 because the mouth texture has 16 × 16 sprites (even though most of them are invalid), but for the eyes it needs to be 8, and 4 for the eyebrows.

I might as well show the control rig graph for the facial features while I’m at it. It basically just adds " X" or " Y" to the control name, and adjusts the curve with that name (and therefore material parameter).

Composing the Image

I again used the Sequencer to create it, since it lets me tweak everything in the scene while (poorly) posing Paige at the same time, without messing up the base map.

For this image, I brought in 2 additional models: the Unreal Engine cinematic camera model, as well as a tripod, again from Sketchfab3. Because Paige cannot hold a camera that heavy. The scaling is most likely a bit off, but who cares, it’s just for a silly little blog post cover image.

Something I also did was cheat with the lighting, as the settings from the music video made things look very bad from this particular angle:

Old lighting

New lighting

I noticed there were some shadow artefacts though, especially with Paige’s hair. It’s almost unnoticeable at this resolution, but trust me, it’s there, and my perfectionist self couldn’t resist. This resulted in me falling in a bit of a rabbit hole trying to find the right shadow settings.

The first thing I tried that made a difference was ray traced shadows. My GPU surprisingly had no problem with that, but unfortunately, the tripod did. Again, it’s hard to tell at this resolution, but it’s there.

Before

With ray traced shadows

Another thing I tried was disabling virtual shadow maps. It’s supposed to be some fancy technology to make shadows look crisper (i.e. more like the ray traced shadows above), but it was in fact what was causing the artefacts. Disabling it did of course make the shadows a lot smoother, but that honestly has such a vibe, so I kept it. Take a look:

With virtual shadow maps

The final image, without them

The last thing I want to mention is the depth of field effect. I actually wanted it to be even more present, but apparently, if you go too far, we’re back to artefact land. This was about the best I could do.

Exporting it

Now that everything is set up, we can simply use the Quick Render feature to export an image.

The Quick Render dropdown

Going into the settings lets us tweak the image resolution. I went with 2700 × 2160 here; going any wider would be redundant with the way this hero image is composed.

I also used a custom render graph. It’s essentially the same as the default graph, but with 2 changes. First is that it exports a .png instead of a .jpg. Not that it’s needed, since the quality loss is really only noticeable if you’re pixel peeping (which means zooming in and staring closely at the pixels).

Second is that there’s an extra Sampling Method node to increase the temporal samples. This helps to reduce most of the remaining artefacts, mainly around the grass, since its wind effect rudely keeps going through the warm-up frames. Again, perfectionist here. This does make it take longer to export, but because it’s just a single frame, it’s not a big deal.

The changed nodes have been highlighted in yellow.

The Finishing Touches

After exporting the image from Unreal Engine, I brought it into Inkscape to create the final hero image. Similar to part 1 of the blog post, it’s split into 2 sections: the left half is a screenshot from the music video, while the right half is the brand-new image we just created. And to give it a bit of flavour, rather than just being a straight cut, it’s diagonal instead.

Fun fact!

If you combine the left half from this part’s hero image with the right half of part 1’s, you get the full music video shot I used:

I had to go edit part 1’s hero image just for that, because the screenshot used to be offset…

Conclusion

Wow. That surely must’ve been… a lot to take in for you. I don’t really know what to say any more, other than thank you for persevering through that over half an hour of yapping. See you next time! Which better not be in 4 months again…

Below are the obligatory attributions for the Sketchfab models I used. Hopefully the models don’t disappear like the last time I used a model from there.

Footnotes

  1. Park Bench by plezzo (CC-BY licence)

  2. Park Trash Can by Pippa (CC-BY licence)

  3. Camera Tripod Slik 38T4 by Cymen05 (CC-BY licence)

Comments

Discussion title: Blog post: Stargazer lo-fi cover part 2 (music video)