【问题标题】:HLSL Depth buffer precision lossHLSL 深度缓冲精度损失
【发布时间】:2017-02-15 03:31:58
【问题描述】:

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

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

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

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

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

  • 如果(像素深度 > 光深度) 丢弃(不要对其施加光)

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

有些地图的图块的 z 索引范围为 0.00001f-1.0f,如果能够在某些图块之间绘制一些灯光,例如

  • 平铺 1 - 0.00020f
  • 光 - 0.00021f
  • 平铺 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/////////////////////////////////////////
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 位的精度。 如果没有,在我想要的任何两个瓷砖之间放置灯光的最佳做法是什么?

提前谢谢你

【问题讨论】:

  • 请将代码的相关部分添加到此问题中。不要链接到外部内容。似乎这只是不适当的 z 剪裁平面的问题(顺便说一句,您使用的是透视投影还是正交投影)。我相信您可以更合理地设置它们。
  • 对不起,我觉得把代码放在pastebin上会更好,因为它很多,马上改。
  • 想一想:为什么你使用half4 Depth : COLOR0; 而不是浮动的结果深度?一半的精度很差。
  • 我已经尝试了所有可能的组合,half4 是最后一个,希望能出现某种奇迹。我从浮动开始。
  • 过去一周我一直在努力完成这项工作,但没有任何结果。尝试更改渲染目标 Surface 格式、混合状态、精灵排序模式,启用和禁用 DepthStencilState,但结果是相同的。我能想到的最后一件事是近/远平面,但我不确定如何使用我当前的代码进行设置,以及 spritebatch 是否允许我想做的事情。

标签: c# .net xna monogame hlsl


【解决方案1】:

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

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

答案很可能是,因为 z-buffer 不是线性的,所以它们的近处比远处的精度更高。

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

这两种通用的解决方案是……

  1. 缩短您的远距离。
  2. 编码非线性深度(查找普通 z 缓冲区如何存储深度,但它基本上是 log/1-exp)。

【讨论】:

  • 抱歉回复晚了,stack 没有通过电子邮件通知我。如何减少远距离或夹子操作公差?我正在查看教程、文章等等,但我不确定如何使用我的相机代码来实现或更改它。干杯
  • "clip(abs(depth - lightPosition.z)
  • 不幸的是,它没有任何区别,我尝试过使用更大的值和更小的方式,甚至删除整个 loc。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-07-13
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多