0
\$\begingroup\$

I've been trying to implement Volumetric Lighting using the code from this tutorial but I've run into some issues, even after basically copypasting the shader code.

I'll just show you what's going wrong.

Blinn-Phong God Rays

As you can see, the models seem to be "projected" in the space. At first I tried tinkering with the various variables that the shader requires, but to no avail. In the screenshot I used 0.5f for the decay, exposure, weight and density, with a sample amount of 64.

Any ideas on what might be causing this?

Draw method:

    public override void Draw(GameTime gameTime, SpriteBatch spriteBatch)
    {
        base.Draw(gameTime, spriteBatch);

    //Set the rendertarget to draw the Blinn-Phong shaded scene to
        GraphicsDevice.SetRenderTarget(renderTarget);
        GraphicsDevice.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, Color.Black, 1.0f, 0);

        Material material = new Material();
        material.DiffuseColor = Color.Red;
        material.AmbientColor = Color.Red;
        material.AmbientIntensity = 0.2f;
        material.SpecularColor = Color.White;
        material.SpecularIntensity = 2.0f;
        material.SpecularPower = 25.0f;

    //Draw all the models
        foreach (ModelData m in models)
        {
            ModelMesh mesh = m.Model.Meshes[0];
            Effect e = mesh.Effects[0];

            e.CurrentTechnique = e.Techniques[m.Technique];
            material.SetEffectParameters(e);
            this.camera.SetEffectParameters(e);
            e.Parameters["World"].SetValue(world * m.Transform);
            e.Parameters["WorldInverseTransposed"].SetValue(Matrix.Transpose(Matrix.Invert(world * m.Transform)));
            e.Parameters["CameraEye"].SetValue(new Vector4(this.camera.Eye, 0));
            // TODO: LightSource Color + Intensity
            e.Parameters["LightSources"].SetValue(lightPositions);

            mesh.Draw();
        }

    //Restore the rendertarget to the backbuffer and clear it.
        GraphicsDevice.SetRenderTarget(null);
        GraphicsDevice.Clear(Color.Black);

    //Pass the standard variable to the spriteBatch vertex shader.
    //The vertex shader isn't used (for as far as I can tell at least) but because the standard
    //vertex shader for spriteBatch is compiled in 2_0 and the God Ray shader in 3_0 I had to implement it
    //manually in order to compile it in 3_0.
        Matrix projection = Matrix.CreateOrthographicOffCenter(0,
        GraphicsDevice.Viewport.Width, GraphicsDevice.Viewport.Height, 0, 0, 1);
        Matrix halfPixelOffset = Matrix.CreateTranslation(-0.5f, -0.5f, 0);
        postProcessing.Parameters["MatrixTransform"].SetValue(halfPixelOffset * projection);

    //Setup all the required data for the shader
        postProcessing.CurrentTechnique = postProcessing.Techniques["Technique1"];
        Vector3 lightPosition = Vector3.Transform(lightPositions[0], world * camera.ViewMatrix * camera.ProjectionMatrix);
        postProcessing.Parameters["lightPosition"].SetValue(new Vector2(lightPosition.X, lightPosition.Y));
    postProcessing.Parameters["Tex"].SetValue(renderTarget);
        postProcessing.Parameters["exposure"].SetValue(0.5f);
        postProcessing.Parameters["decay"].SetValue(0.5f);
        postProcessing.Parameters["weight"].SetValue(0.5f);
        postProcessing.Parameters["density"].SetValue(0.5f);

    //Draw the renderTarget to the screen at the size of the viewport so it fits the screen
        spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Additive, SamplerState.PointWrap, DepthStencilState.Default, RasterizerState.CullNone, postProcessing);
        spriteBatch.Draw(renderTarget, new Rectangle(spriteBatch.GraphicsDevice.Viewport.X, spriteBatch.GraphicsDevice.Viewport.Y, spriteBatch.GraphicsDevice.Viewport.Width, spriteBatch.GraphicsDevice.Viewport.Height), Color.White);
        spriteBatch.End();

    //Restore some of the changes spriteBatch.Begin() made to the graphics device so the 3D render won't break
        GraphicsDevice.BlendState = BlendState.Opaque;
        GraphicsDevice.DepthStencilState = DepthStencilState.Default;
        GraphicsDevice.SamplerStates[0] = SamplerState.LinearWrap;
    }

Shader:

sampler2D renderTarget;

#define SAMPLE_AMOUNT 64

Texture2D Tex;
float2 lightPosition;
float exposure;
float decay;
float weight;
float density;

SamplerState State = sampler_state
{
    Texture = <Tex>;
    MipFilter = Point;
    MinFilter = Linear;
    MagFilter = Linear;
    AddressU = Wrap;
    AddressV = Wrap;
};

float4x4 MatrixTransform;

//The default vertex shader of the spriteBatch, implemented manually so it can be compiled in 3_0
void SpriteVertexShader(inout float4 color    : COLOR0,
                        inout float2 texCoord : TEXCOORD0,
                        inout float4 position : SV_Position)
{
    position = mul(position, MatrixTransform);
}

//Copypasted from the tutorial, only changed some variable names
float4 main(float2 texCoord : TEXCOORD0) : COLOR0  
{  
  // Calculate vector from pixel to light source in screen space.  
   half2 deltaTexCoord = (texCoord - lightPosition.xy);  
  // Divide by number of samples and scale by control factor.  
  deltaTexCoord *= 1.0f / SAMPLE_AMOUNT * density;  
  // Store initial sample.  
   half3 color = tex2D(State, texCoord);  
  // Set up illumination decay factor.  
   half illuminationDecay = 1.0f;  
  // Evaluate summation from Equation 3 NUM_SAMPLES iterations.  
   for (int i = 0; i < SAMPLE_AMOUNT; i++)  
  {  
    // Step sample location along ray.  
    texCoord -= deltaTexCoord;  
    // Retrieve sample at new location.  
   half3 sample = tex2D(State, texCoord);  
    // Apply sample attenuation scale/decay factors.  
    sample *= illuminationDecay * weight;  
    // Accumulate combined color.  
    color += sample;
    // Update exponential decay factor.  
    illuminationDecay *= decay;  
  }  
  // Output final color with a further scale control factor.  
   return float4( color * exposure, 1);  
}  

technique Technique1
{
    pass Pass1
    {
    VertexShader = compile vs_3_0 SpriteVertexShader();
        PixelShader = compile ps_3_0 main();
    }
}
\$\endgroup\$
0
\$\begingroup\$

Keep in mind that this effect only works well if you are facing the light source because essentially it is the lightsource which you have to do a radial blur on. In your scene the light is neither facing the camera nor is the lightsource visible itself (or maybe just out of the view frustum). For the best results you have to render your light volume with colors on and the rest of your scene black so that solid objects will block incoming light (their colour value is not accumulated into the blur).

By the way, I use 35,0.90f,0.2f,0.999f,0.2f values for NUM_SAMPLES,Density,Weight,Decay,Exposure parameters with the same effect and it looks quite convincing with the rendering technique described above.

\$\endgroup\$
  • \$\begingroup\$ Ah, I see, it was the combination of a badly placed lightsource and my models being colored what was causing the problems. However, in order to make this work with a black background I'd have to make my light source visible somehow, like in image (b) from the tutorial I linked. Some cursory googling and looking around on the stackexchange here indicates that it's no easy matter to generate something like that. Since neither the tutorial nor your answer mentions it at all I figured that I'm overlooking something, but what? \$\endgroup\$ – sjasogun Jun 22 '14 at 17:15
  • \$\begingroup\$ You could draw a glowing yellow orb on a billboard (camera facing quad) on the light position. When drawing your volumetric light, draw everything with black color but your orb so only that will be visible on your blurred rendertarget. Blend that additively with your scene at the end to get the final result. ;) \$\endgroup\$ – János Turánszki Jun 22 '14 at 19:38
  • \$\begingroup\$ I tried your method, but the same projection-like effect occurs, only in grayscale. Also, all of the projections are parallel. I have updated the pastebin with my Draw method: link. Any ideas of what be causing this persisting problem? \$\endgroup\$ – sjasogun Jun 24 '14 at 17:02
  • \$\begingroup\$ Figured it out: Screen-space projection using the WorldViewProjection matrix doesn't work when applying it to a Vector3 object: Create a Vector4 object first that has 1 as its w-value. \$\endgroup\$ – sjasogun Jun 26 '14 at 17:38
  • \$\begingroup\$ Yes if you have 1 in the w of the vector, your translation in the matrix will be applied 1 time to that vector. If you have zero, then the vector will only rotate and scale. \$\endgroup\$ – János Turánszki Jun 26 '14 at 18:04

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.