HLSL深度缓冲区精度损失

时间:2017-02-15 03:31:58

标签: c# .net xna monogame hlsl

正如主体所暗示的那样,当我在其他渲染目标上使用深度缓冲时,我正在失去精度或者没有足够的精度。

目前,我的渲染是这样的:

  • 画一切
  • 再次绘制深度缓冲区。 (这是一个使用z索引作为红色通道的rendertarget)
  • 绘制灯光(这里我传递了上面创建的深度缓冲区,因此在对象的前面或后面绘制了光线,而且在这里我得不到足够的精度)
  • 合并两个渲染目标。

我不确定,或者更确切地说,我知道我做错了什么但不知道在哪里或如何。

我希望能够在灯光着色器内执行此操作:

  • if(像素深度>光照深度) 丢弃(不要对它施加光照)

但由于精度损失或其他一些问题,我无法与小于0.01的值进行比较。

某些地图的平铺指数范围为0.00001f-1.0f,因此能够在某些地块之间绘制一些灯光非常有用,例如

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

我能够通过一些rgba-float编码/解码获得更高的精度,但这是超级重的,在较低的设置中它只会混淆一切并且灯光不能以正确的顺序绘制。

这是场景绘制代码:

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&照明着色器:

/////////////////////////////////////////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/////////////////////////////////////////

相机代码:

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只是一个用作滚动的Vector2。

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

如果缺少一些重要的代码来帮助我,请告诉我。

总而言之,如果甚至可以将深度贴图值与灯光的深度值进行比较,我希望能够获得与8个小数点一样多的精度。 如果没有,那么在我想要的任何两块瓷砖之间设置灯光的最佳做法是什么?

提前谢谢

1 个答案:

答案 0 :(得分:0)

编辑:其实我觉得我看到了这个问题。剪辑操作的容差大于为场景中的对象发布的深度差异。

如果你在考虑,“我的z-buffer只有24位,我看不到z-fighting,那为什么我的32位深度缓冲区?”

答案可能是,因为z缓冲区不是线性的,它们的精度比远处更接近。

请记住,并非所有平台都支持SurfaceFormat.Single,因此您可能希望避免编写依赖它的延迟引擎。

两种通用解决方案是......

  1. 减少远距离。
  2. 编码非线性深度(查找正常的z-buffers存储深度,但基本上是log / 1-exp)。