Hi folks. I will talk about the progress I’ve made since last time and how the blog post style will be changed.
Less Emphasis on the Well-covered Subjects on the Internet
When I read my own posts from the past, I noticed they have a lot of duplicate info with popular tutorials out there. So in the future posts, I will minimize the effort I spend on talking about popular subjects and talk more about the unspoken quarks and side-effect of them.
If the player’s PC can’t handle high resolution shadow map texture, then alias will occur on the edges of the shadow, which is not good. One way to deal with the jagged shadow edges is PCF. It blurs shadow’s edges by sampling neighbor texels’ depth test results and blend them together. The way I am blending is to precompute some poisson disk samples, then for each pixel that I’m testing against, rotate the disk by a random amount, then use that disk to sample the neighbor texels.
Result of random poisson sampling PCF:
Incorrectness of PCF
What most tutorials missed is that PCF causes self-intersecting errors if its sampling area too big. By taking neighbor depth samples from the shadow map texture, you are comparing depths that don’t belong to you. Reason why PCF works at all is because most shadow mapping system use a bias value, and that small bias leave some room for error, so that PCF of a small area works most of the time.
Diagram describing why PCF doesn’t work if the sample area is too big:
Cascaded Shadow Map Seams
Cascaded shadow map seams is another common artifact most tutorials ignore. By seam, I mean a substantial difference in shadow quality. I briefly mentioned this in my first blog post and said I would just leave it like this. Well, I lied. Turned out the artifact caused by this is quite noticeable and I was forced to fix it.
This is the CSM seam in action (I tweaked the cascade size a bit to exaggerate the artifact):
My solution to this problem is to blend the edges of shadow cascades. When the shadow map is being rendered, I extend the near plane for each cascades a little bit to overlap with the preceding cascades. And when a fragment end up being in that overlapped region, I can take the depth test results from both cascades, then linearly interpolate them based on where that fragment point is.
Here’s CSM seam being blended:
Besides shadow, there isn’t a substantial amount of improvements in renderer. When I shade the scene, I only apply ambient occlusion factor to ambient terms now, meaning only surfaces covered in shadow will have any ambient occlusion. I also gave ambient light a bluish tint, since most of the ambient light comes from atomsphere light in outdoor scenes.
World Editor Improved
Before finishing up the manipulator widget for my world editor, I edit the scene by typing in numbers to change an entity’s size or position. But now, I can do all of that by dragging the mouse and pressing a couple of hotkeys:
Editor in action:
Surprisingly, I didn’t find any useful material that talks about how to do this kind of entity manipulator seriously on the internet, so I came up with my own hacks.
For scaling, I project the entity position onto screen space, and record the first mouse position. Then depending on the mouse distance from the entity position in screen space, I calculate the shrinking/growing size.
For translation along XZ plane, I project a ray through where the mouse is pointing at into the 3d scene. That ray will hit the XZ plane (hopefully). If it hits, then the first hit position is recorded, and let’s call it FirstHitP. In each subsequent frames, collision between mouse ray and XZ plane is recorded, let’s call that HitP. We can take (HitP - FirstHitP) as the temporary translation vector for that entity. It works pretty well for XZ plane translation, and you can easily see how this can be applied to translations along other dimensions too.
Rotation takes the same idea of projecting mouse rays and hitting the plane. It just takes vectors and dot product them to find the difference angle. Then that angle is used to produce a quaternion to concatenate with entity’s local orientation (also a quaternion).
Yep. I no longer need windows.h header in my platform layer. Inspired by Casey’s hint on removing windows.h in handmade hero, I pulled out all the struct definitions and function definitions into my own custom header, which is only 700 lines. This reduced the game’s compile time by one whole second.
Last but not Least, New Assets
A picture is worth a thousand words:
The point of importing more detailed assets into the game at this point is to field test Monter’s renderer, making sure that it looks good for both low-poly and high-detail models. The result isn’t half bad. That being said, this is placeholder art, and better models will be made!