1
votes

As the subject implies, i'm losing precision or not having enough when i'm using the depth buffer on other render targets.

Currently, my rendering goes like this :

  • draw everything
  • draw again to depth buffer. (this is a rendertarget that uses z index as the red channel)
  • draw lights (here i pass the depth buffer created above so lights are drawn in front or behind the objects and it's here i'm not getting enough precision)
  • combine both render targets.

I'm not sure, or rather, i know i'm doing something wrong but don't no where or how.

I want to be able to, inside the lights shader, to do this :

  • if (pixel depth > light depth) discard (don't apply light to it)

but with the precision loss or some other problem, i'm not able to make this comparison with values smaller than 0.01.

Some maps have tiles that have a z index range of 0.00001f-1.0f and it would be useful to be able to draw some lights in between some tiles such as

  • tile 1 - 0.00020f
  • light - 0.00021f
  • tile 2 - 0.00022f

I was able to achieve much higher precision with some rgba-float encoding/decoding but this is super heavy and in lower setups it just messes everything up and lights don't draw in the correct order.

Here's the scene draw code:

RenderTarget2D colorRT;
RenderTarget2D depthRT;
RenderTarget2D shadowRT;
RenderTarget2D finalRT;

protected void LoadContent()
{
    colorRT = new RenderTarget2D(GraphicsDevice, width, height);
    finalRT = new RenderTarget2D(GraphicsDevice, width, height);
    depthRT = new RenderTarget2D(GraphicsDevice, width, height, false, SurfaceFormat.Single, DepthFormat.Depth24);
    shadowRT = new RenderTarget2D(GraphicsDevice, width, height, false, SurfaceFormat.Single, DepthFormat.None);
}

protected override void Draw()
{
    DrawColorMap();
    DrawDepthMap();
    GenerateShadowMap();
    DrawCombinedMaps();

    spriteBatch.GraphicsDevice.SetRenderTarget(null);
    spriteBatch.GraphicsDevice.Clear(Color.Transparent);

    //draw finalRT
}

private void DrawColorMap()
{
    spriteBatch.GraphicsDevice.SetRenderTarget(colorRT);
    spriteBatch.GraphicsDevice.Clear(Color.Transparent);

    spriteBatch.Begin(SpriteSortMode.FrontToBack, BlendState.AlphaBlend, SamplerState.LinearClamp, DepthStencilState.None, RasterizerState.CullCounterClockwise, null, map.camera.GetTransformation() * Resolution.getTransformationMatrix());

    map.Draw(spriteBatch, mapsTex, scroll, map.camera.GetTransformation());

    spriteBatch.End();
}

private void DrawDepthMap()
{
    GraphicsDevice.SetRenderTarget(depthRT);
    GraphicsDevice.Clear(Color.White);

    greyEffect.Parameters["World"].SetValue(map.camera.GetTransformation() * Resolution.getTransformationMatrix());

    spriteBatch.Begin(SpriteSortMode.FrontToBack, BlendState.Opaque, SamplerState.LinearClamp, DepthStencilState.None, RasterizerState.CullCounterClockwise, greyEffect);

    map.Draw(spriteBatch, mapsTex, scroll, map.camera.GetTransformation());

    spriteBatch.End();
}

private Texture2D GenerateShadowMap()
{
    GraphicsDevice.SetRenderTarget(shadowRT);
    GraphicsDevice.Clear(new Color(0, 0, 0, 0));
    GraphicsDevice.BlendState = BlendState.AlphaBlend;
    GraphicsDevice.DepthStencilState = DepthStencilState.None;

    foreach (var light in map.lights)
    {
        if (light != null)
        {
            if (!light.IsEnabled) continue;

            Vertices[0].Position = new Vector3(map.camera.WorldToScreen2(new Vector2(light.Location.X, light.Location.Y) - new Vector2(scroll.X, scroll.Y), map.camera.GetTransformation()), light.Location.Z);
            Vertices[1].Position = new Vector3(map.camera.WorldToScreen2(new Vector2(light.Location.X + light.LightDecay, light.Location.Y) - new Vector2(scroll.X, scroll.Y), map.camera.GetTransformation()), light.Location.Z);
            Vertices[2].Position = new Vector3(map.camera.WorldToScreen2(new Vector2(light.Location.X, light.Location.Y + light.LightDecay) - new Vector2(scroll.X, scroll.Y), map.camera.GetTransformation()), light.Location.Z);
            Vertices[3].Position = new Vector3(map.camera.WorldToScreen2(new Vector2(light.Location.X + light.LightDecay, light.Location.Y + light.LightDecay) - new Vector2(scroll.X, scroll.Y), map.camera.GetTransformation()), light.Location.Z);

            VertexBuffer.SetData(Vertices);
            spriteBatch.GraphicsDevice.SetVertexBuffer(VertexBuffer);

            _lightEffect.CurrentTechnique = _lightEffectTechniquePointLight;
            _lightEffect.Parameters["DepthMap"].SetValue(depthRT);
            _lightEffect.CurrentTechnique.Passes[0].Apply();

            // Add Belding (Black background)
            spriteBatch.GraphicsDevice.BlendState = BlendBlack;

            spriteBatch.GraphicsDevice.DrawUserPrimitives(PrimitiveType.TriangleStrip, Vertices, 0, 2);
        }

    return shadowRT;
}

private void DrawCombinedMaps()
{
    GraphicsDevice.SetRenderTarget(finalRT);
    GraphicsDevice.Clear(Color.LightSkyBlue);

    _lightCombinedEffectParamColorMap.SetValue(colorRT);
    _lightCombinedEffectParamShadowMap.SetValue(shadowRT);

    spriteBatch.Begin(0, BlendState.Opaque, null, DepthStencilState.None, RasterizerState.CullCounterClockwise, _lightCombinedEffect);

    spriteBatch.Draw(colorRT, new Rectangle(0, 0, viewPortStored.Width, viewPortStored.Height), Color.White);

    spriteBatch.End();
}

Depth buffer shader & Lighting shader:

/////////////////////////////////////////DEPTH BUFFER SHADER/////////////////////////////////////////
float2 offset;
float scale;
float2 screenSize;
float4x4 World;

struct VertexShaderInput
{
    float4 Position : POSITION0;
    float2 TexCoord : TEXCOORD0;
};

struct VertexShaderOutput
{
    float4 Position : POSITION0;
    float2 TexCoord : TEXCOORD0;
    float2 Depth : TEXCOORD1;
};

struct PixelShaderOutput
{
    half4 Depth : COLOR0;
};

VertexShaderOutput MyVertexShader(VertexShaderInput input)
{
    VertexShaderOutput output;

    // Half pixel offset for correct texel centering.
    input.Position.xy -= 0.5;

    // Viewport adjustment.
    input.Position.xy = input.Position.xy / screenSize;
    input.Position.xy -= offset;

    input.Position.xy *= float2(2, -2);
    input.Position.xy -= float2(1, -1);

    output.Position = input.Position;
    output.TexCoord = input.TexCoord;
    output.Depth.x = output.Position.z;
    output.Depth.y = output.Position.w;

    return output;
}

PixelShaderOutput PointLightShader(VertexShaderOutput input)
{
    PixelShaderOutput output;

    output.Depth = input.Depth.x / input.Depth.y;

    return output;
}
/////////////////////////////////////////END DEPTH BUFFER SHADER/////////////////////////////////////////

/////////////////////////////////////////LIGHTING SHADER/////////////////////////////////////////
float screenWidth;
float screenHeight;
float4 ambientColor;

float lightStrength;
float lightDecay;
float3 lightPosition;
float4 lightColor;
float lightRadius;
float lightCenterStrenght;

float3 coneDirection;
float coneAngle;
float coneDecay;

float scale;
float2 offset;

Texture DepthMap;
sampler DepthMapSampler = sampler_state {
    texture = <DepthMap>;

    AddressU = CLAMP;
    AddressV = CLAMP;
};

struct VertexShaderInput
{
    float4 Position : POSITION0;
    float4 TexCoord : TEXCOORD0;
};

struct VertexShaderOutput
{
    float4 Position : POSITION0;
    float4 TexCoord : TEXCOORD0;
    float4 ScreenPosition : TEXCOORD1;
};

VertexShaderOutput MyVertexShader(VertexShaderInput input)
{
    VertexShaderOutput output;

    // Half pixel offset for correct texel centering.
    input.Position.xy -= 0.5;

    // Viewport adjustment.
    input.Position.xy = input.Position.xy / float2(screenWidth, screenHeight);
    input.Position.xy -= offset;

    output.ScreenPosition = input.Position;

    input.Position.xy *= float2(2, -2);
    input.Position.xy -= float2(1, -1);

    output.Position = input.Position;
    output.TexCoord = (lightDecay * 2 * input.TexCoord) / scale;
    //Output.Color = color;

    return output;
}

float4 PointLightShader(VertexShaderOutput input) : COLOR0
{
    //float2 texCoord = 0.5f * (float2(input.ScreenPosition.x,-input.ScreenPosition.y) + 1);
    //texCoord *= lightDecay * 2;
    ////allign texels to pixels
    //texCoord -= 0.5;

    //input.ScreenPosition.xy /= input.ScreenPosition.w;

    float depth = tex2D(DepthMapSampler, input.ScreenPosition.xy).r;
    clip(abs(depth - lightPosition.z) < 0.05 ? 1 : -1);
    //float4 position;
    //position.xy = input.ScreenPosition.xy;
    //position.z = depth;
    //position.w = 1;
    //position /= position.w;

    float coneAttenuation;

    float4 shading;
    float2 pixelPosition = input.TexCoord.xy;
    float2 lightPos = float2(lightDecay, lightDecay) * scale;
    float2 lightDirection = (pixelPosition - lightPos) / scale;

    //THIS ADDS THE CIRCLE IN THE CENTER OF THE LIGHT.
    float distance;
    if (lightCenterStrenght > 0)
        distance = (1 / length(pixelPosition - lightPos)) * lightStrength;
    else
        distance = lightStrength;

    coneAttenuation = saturate(1.0f - length(lightDirection) / lightDecay);

    shading = distance * coneAttenuation * lightColor;

    //if (depth >= 1 * 256)
        //return float4(255, 255, 255, coneAttenuation * lightStrength);
    /*
        if (realDepth >= 1)
            return float4(255, 255, 255, coneAttenuation * lightStrength);*/

    return float4(shading.rgb, coneAttenuation * lightStrength);
}

float4 SpotLightShader2(float2 TexCoord : TEXCOORD0, float2 screenPos : TEXCOORD1) : COLOR0
{
    float4 depth = tex2D(DepthMapSampler, screenPos);
    float realDepth = 0;

    float3 shading = float3(0, 0, 0);
    float coneAttenuation;

    float lightDepth = lightPosition.z;

    if (realDepth < lightDepth)
    {
        float2 pixelPosition = TexCoord;

        float2 lightVector = normalize((pixelPosition - float2(lightDecay, lightDecay) * scale) / scale);
        // cosine of the angle between spotdirection and lightvector
        float SdL = dot(coneDirection, -lightVector);

        if (SdL > coneAngle)
        {
            float3 lightPos = float3(lightPosition.x, lightPosition.y, lightPosition.z);
            float2 lightVector = (pixelPosition - float2(lightDecay, lightDecay) * scale) / scale;
            lightVector = normalize(lightVector);

            float3 coneDirectionTemp = coneDirection;
            //coneDirectionTemp.z = 50.0f;
            float spotIntensity = pow(abs(SdL), coneDecay);

            float2 lightDirection = (pixelPosition - float2(lightDecay, lightDecay) * scale) / scale;
            float3 halfVec = float3(0, 0, 1);

            float amount = max(dot(1, lightVector), 0);

            coneAttenuation = saturate(1.0f - length(lightDirection) / lightDecay);

            float2 reflect = normalize(2 * amount * 1 - lightVector);

            float2 r = normalize(2 * dot(lightVector, 1) * 1 - lightVector);
            float2 v = normalize(mul(normalize(coneDirectionTemp), 1));
            float dotProduct = dot(r, v);

            //float4 specular = light.specPower * light.specularColor * max(pow(dotProduct, 10), 0) * length(inColor);
            float specular = min(pow(saturate(dot(reflect, halfVec)), 10), 1);

            shading = lightColor * lightStrength;
            shading += specular * 1;
            shading += amount * 0.5;
            shading *= coneAttenuation * spotIntensity;
        }
    }
    else
        shading = 0;

    return float4(shading.r, shading.g, shading.b, coneAttenuation * lightStrength);
}
/////////////////////////////////////////END LIGHTING SHADER/////////////////////////////////////////

Camera code :

public class Camera
{
    public Matrix Transform;
    public Matrix PositionTransform = Matrix.CreateTranslation(new Vector3(0, 0, 0f));
    public Matrix GetTransformation()
    {
        Transform = Matrix.CreateScale(new Vector3(zoom, zoom, 1)) * PositionTransform;

        return Transform;
    }

       public Vector2 WorldToScreen(Vector2 worldPosition, Matrix m)
    {
        return Vector2.Transform(worldPosition, Resolution.getTransformationMatrix());
    }

    public Vector2 WorldToScreen2(Vector2 worldPosition, Matrix m)
    {
        return Vector2.Transform(worldPosition, m * Resolution.getTransformationMatrix());
    }
}

Offset is just a Vector2 acting as a scroll.

here's a screenshot with the problem, the light should be between the lamp and the banner (banner in front covering the light)

If there's some important part of the code missing to help me, let me know.

To sum up, i'd like to be able to have as much precision as 8 decimal points when comparing the depth map values to the light's depth value if it's even possible. If not, what would be the best practice to have lights in between whatever two tiles i want?

Thank you in advance

1
Please add the relevant part of the code to this question. Do not link to external content. It seems it is just a matter of inappropriate z-clipping planes (btw, are you using a perspective or orthographic projection). I am sure that you can set them up more reasonably.Nico Schertler
Sorry, thought it would be better to put the code on pastebin since it's a lot, will change it right away.Tiago Vilaça
Just a quick thought: Why do you use half4 Depth : COLOR0; and not float for the resulting depth? Half has a very bad precision.Gnietschow
I've tried every possible combination, half4 was the last one, hoping for some kind of miracle. I began with float though.Tiago Vilaça
I've been trying to make this work for the past week with no results. Tried to change the render targets Surface formats, the blendstate, the spritesortmode, enable and disable the DepthStencilState, but the outcome was the same. The last thing i could think of that could make this work is the near/far plane, but i'm not sure how to set up with my current code and if spritebatch even allows for what i want to do.Tiago Vilaça

1 Answers

0
votes

Edit: Actually i think i see the issue. Your clip operation has a tolerance that is way bigger than the difference in depth you posted for the objects in your scene.

If you are thinking, "my z-buffer is only 24 bits and I don't see it z-fighting, so why is my 32-bit depth buffer?"

The answer is likely to be, because z-buffers are not linear, they have more precision close than far.

Keep in mind that SurfaceFormat.Single is not actually supported on all platforms, so you might want to avoid writing a deferred engine that relies on it.

The two general solutions are either..

  1. Reduce your far distance.
  2. Encode non-linear depth (lookup how normal z-buffers store depth, but its basically a log/1-exp).