Hi there!
Well after having a real hard time finding a SIMPLE example for casting light shafts I finally managed to port the Volumetric Lighting Tutorial by Nicolas Menzel to monogame.
His turorial is based on the GPUGems 3 article “Volumetric Light Scattering as a Post-Process” by Kenny Mitchell from EA
Surely there are lots of examples, but mostly in XNA4 or too complicated for me as a beginner.
The source by Nicolas isn’t that simple, too, as there’s lot of unused stuff - but I finally got it running!
So I put the project to gitHub and want to give a quick description, hoping that it may help others desperate like me finding a simple solution.
I’ve splitted the project into several parts - with the uncleared original port, also containing all the unused original functions and as a first step a cleared, basic version with only the necessary stuff inside. I think I will extend this basic version from time to time for a while…
The original consisted of 2 projects I merged into one.
I’m using a different model, as I had problems with the original gears and the cube textures. I found a nice sphere with breakouts at cgtrader
I also added a controls-form for easily manipulating the light shaft parameters.
Project structure:
- Game1.cs - the main game class
- Controls… - the control form class
- PostScreenEffects.cs - class for the post screen effect handling
As a raw order for generating a light shaft effect one could say:
- Generate several render targets for the different steps
- render the models with textures and other lighting (this step can be anywhere before steps 8,9)
- render an occlusion mask of the scene
- render the background texture containing the lightsource (the flare) and the occlusion mask to a new render target (blending)
- downscale the blended target from step 4 and apply a linear filter
- apply the lightshaft effect to this downscaled target
- upscale the result
- combine the targets (step 2 and step 7) to the final target
- draw the final target to screen
- Parameters:
LightMapPosition - the position of the flare texture in texture position format (0…1)
LightMapOffset - in the beginning, I used this to center the effect over the flare - meanwhile I managed to calculate the center by:
(int)((2*LightMapPosition.X + LightMapOffset.X) * GraphicsDevice.Viewport.Width - _BackgroundTexture.Width / texFactor / 2), (int)((2*LightMapPosition.Y + LightMapOffset.Y) * GraphicsDevice.Viewport.Height - _BackgroundTexture.Height / texFactor / 2),
texFactor - a scaling factor for the flare texture
texSampleSize - the size of the downscaled lightshaft texture for applying the effect
ModelExposure - lets You fade out the model - so You may see the occlusion mask behind
- Lightshaft effect parameters (description by Kenny Mitchell):
-LightShaftExposure - controls the overall intensity of the post-process
-LightShaftWeight - controls the intensity of each sample
The exposure and weight factors are simply scale factors. Increasing either of these increases the overall brightness of the result.
-LightShaftDecay - dissipates each sample’s contribution as the ray progresses away from the light source. This exponential decay factor practically allows each light shaft to fall off smoothly away from the light source.
-LightShaftDensity - control over the separation between samples. If we increase the density factor, we decrease the separation between samples, resulting in brighter light shafts covering a shorter range.
- Basic configuration
The control-form “controls” contains some sliders for manipulating the lightshaft effect parameters.
The implementation is quite simple. In order to access the parameters of the main game class by the form, You need to make them public and add some getters ans setters like
public float LightShaftExposure { get; set; } = 0.351f;
The form is initialized and opened by:
controls = new Controls(this);
controls.Show();
The important thing here is to pass a reference to the main game class with ‘this’.
With this reference You then are able to access the parameters from the control class.
(Controls.cs)
Game1 _game1;
public Controls(Game1 game1)
{
_game1 = game1;
…
}
…
_game1.LightMapPosition …
(I went here more into detail, because I’ve often seen questions about how to do this.)
Another important thing is to set the graphics feature level to “HiDef”.
graphics.GraphicsProfile = GraphicsProfile.HiDef;
Otherwise, You will get an exception like this when compiling the project:
SharpDX.SharpDXException: "HRESULT: [0x80070057], Module: [General], ApiCode: [E_INVALIDARG/Invalid Arguments], Message: Falscher Parameter.
Also, I’ve seen this very often in many forums and it’s really ugly as it is not obvious what the problem is…
The render targets are set up in the function SetupRenderTargets()
Maybe one could reduce the number of them to two or three, but I think it provides more clarity to have one RT for each step.
- _RenderTargetColor - contains the scene with just the models with lighting and material
- _RenderTargetMask - contains the occlusion mask - the scene is drawn black with the models being fully opaque
- _RenderTargetMaskedBackground - the flare texture (light source) is drawn to the occlusion mask
- _RenderTargetLinearFilter - the _RenderTargetMaskedBackground downscaled with some linear filter applied
- _RenderTargetShaftsSample - the _RenderTargetLinearFilter with the lightshaft shader being applied
- _RenderTargetShaftsFull - the upscaled result of the _RenderTargetShaftsSample
- _RenderTargetFinal - the last step: combining _RenderTargetColor with _RenderTargetShaftsFull
- Shaders
For the main game class there are 2 shaders:
- BlackShader.fx - the occlusion shader - the scene is drawn in black - only the models are opaque
float4( 0, 0, 0, 1 );
- ModelMaterial.fx - the scene with the models and normal lighting. I applied a simple diffuse lighting and specular lighting effect. The camera position is fixed at Z=180. This might be something that is worth to improve…like adding texture, movable camera…
The postScreenEffects contains 2 shaders:
- LinearFilter.fx - I’m not really sure if it’s necessary to apply this shader like it has been done here… It seems to me to be some kind of simple “blurring” shader. It could be that one could achieve the same result with the integrated sampler states…?
- LightShafts.fx - the heart of this - the code is completely adopted from the GPUGems article
So far, for this moment… hope to add some more details next time!
Cheers,
Stephan
Update: [21.06.18] v2
- added the option to set the scale of the lightshaft sampling texture - this shows the differences when downscaling the sample texture and applying the effect to different sizes.
- Also added a little gimmick, so that You can draw the scene to the desktop background (this might not work on Win7 - maybe only on Win8/10)
- Changed NUM_SAMPLES parameter in LightShafts.fx to input variable. This lets You test different samplerates.
In order to get this working I had to change the shader profile to 5.0. Also I had to add an unroll to the for-loop parameter….At moment it is set to 400 for testing - there’s one anoying drawback: it takes quite long to compile. You can change the unroll value to e.g. 100 → [unroll (100)] if it takes toooo long to compile - but don’t forget that this also changes the maximum of samples.