Shadowmapping - Z-Werte in Textur sind alle um konstanten Wert verschoben



  • Hi Leute,

    ich versuche gerade Shadowmapping bei mir zu implementieren.

    Meine Quelle für OpenGL: http://www.opengl-tutorial.org/intermediate-tutorials/tutorial-16-shadow-mapping/

    Meine Quelle für DirectX: http://richardssoftware.net/Home/Post/37

    Schritt 1: Ich rendere die Szene aus Sicht der Lichtquelle und erstelle eine Tiefentextur.

    So definiere ich die Tiefentextur bei OpenGL:

    GL.GenTextures(1, depthTexture);
                GL.BindTexture(TextureTarget.Texture2D, depthTexture[0]);
                GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.DepthComponent24, width, height, 0, OpenTK.Graphics.OpenGL.PixelFormat.DepthComponent, PixelType.Float, IntPtr.Zero);
                GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMinFilter.Nearest);
                GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Nearest);
                GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int)TextureWrapMode.ClampToEdge);
                GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)TextureWrapMode.ClampToEdge);
    

    So definiere ich die Textur bei DirectX:

    bool use24Bit = true;
    
    var depthTex = new Texture2D(m_device, new Texture2DDescription()
                {
                    Width = width,
                    Height = height,
                    MipLevels = 1,
                    ArraySize = 1,
                    SampleDescription = new SampleDescription(1, 0),
                    Format = use24Bit ? Format.R24G8_Typeless : Format.R32_Typeless,
                    Usage = ResourceUsage.Default, //Default = GPU RW, Immutable = GPU RO, DYNAMIC (CPU WO, GPU RO), STAGING (CPU RW, GPU RW)
                    BindFlags = BindFlags.DepthStencil | BindFlags.ShaderResource,
                    CpuAccessFlags = CpuAccessFlags.None,
                    OptionFlags = ResourceOptionFlags.None
                });
    
    depthStencilView = new DepthStencilView(m_device, depthTex, new DepthStencilViewDescription()
                {
                    Format = use24Bit ? SlimDX.DXGI.Format.D24_UNorm_S8_UInt : SlimDX.DXGI.Format.D32_Float, //Erlaubte Werte: DXGI_FORMAT_D16_UNORM, DXGI_FORMAT_D24_UNORM_S8_UINT, DXGI_FORMAT_D32_FLOAT, DXGI_FORMAT_D32_FLOAT_S8X24_UINT, DXGI_FORMAT_UNKNOWN (typeless nicht erlaubt); If the format chosen is DXGI_FORMAT_UNKNOWN, then the format of the parent resource is used.
                    Flags = DepthStencilViewFlags.None,
                    Dimension = DepthStencilViewDimension.Texture2D,
                    MipSlice = 0,
    
                });
    
    shaderResourceView = new ShaderResourceView(m_device, depthTex, new ShaderResourceViewDescription
                {
                    Format = use24Bit ? Format.R24_UNorm_X8_Typeless : Format.R32_Float,
                    Dimension = ShaderResourceViewDimension.Texture2D,
                    MipLevels = 1,
                    MostDetailedMip = 0
                });
    

    So definiere ich die Textur beim CPU-Rasterizer(Eigenes Zeug):

    float[][] texture; -> Bekommt den Z-Wert der Normalisierten Device-Koordinaten
    

    Wenn ich in die Texturen gerendert habe, lese ich diese nun wie folgt aus:

    Vertexshader OpenGL:

    vec4 ShadowCoord = (ShadowMapMatrix * vec4(gl_in[i].gl_Position.xyz,1));
    

    Vertexshader DirectX:

    float4 shadowPos = mul(float4(input.pos.xyz, 1.0), ShadowMatrix);
    

    Vertexshader CPU:

    float[] shadowPos = Matrix.MatrixVektorMult(shadowMatrix, vertex.pos.Float4f)
    

    Pixelshader OpenGL:

    vec4 shadowCoord = ShadowCoord / ShadowCoord.w;
    float t = (1 - vec4(texture2D(ShadowMap, shadowCoord.xy)).r) * 100;
    //float t = (1 - shadowCoord.z) * 100;
    out_frag_color = vec4(t,t,t,1);
    

    Pixelshader DirectX:

    float3 shadowPos = clamp(input.shadowPos.xyz / input.shadowPos.w, 0, 1);
    shadowPos.y = 1 - shadowPos.y;
    
    float t = (1 - ShadowTexture.Sample(TextureFilterScharf, shadowPos.xy).r) * 100;
    //float t = (1 - shadowPos.z) * 100;
    
    return float4(t,t,t,1);
    

    Pixelshader CPU:

    Vektor shadowPos = GrafikHelper.Clamp(new Vektor(shadowPosHome[0] / shadowPosHome[3], shadowPosHome[1] / shadowPosHome[3], shadowPosHome[2] / shadowPosHome[3]), 0, 1);
    
    float shadowTexelZ = ReadDepthValue(prop.textureDeck3, shadowPos);
    return GrafikHelper.VektorToColor(GrafikHelper.Clamp(new Vektor(1, 1, 1) * (1 - shadowTexelZ) * 100, 0, 1));
    //return GrafikHelper.VektorToColor(GrafikHelper.Clamp(new Vektor(1, 1, 1) * (1 - shadowPos.z) * 100, 0, 1));
    

    So. Dadurch erhalte ich folgendes Bild:

    http://fs1.directupload.net/images/150418/yfxvgjmu.jpg

    Meine Fragen:

    Kann es sein, dass bei OpenGL die Textur-Nullpunkt links unten liegt und bei DirectX der Textur-Nullpunkt links oben?

    Warum sind die Texturwerte bei DirectX alle niedriger (Objekte scheinen weniger Abstand zur Lichtquelle zu haben) (Bild erscheint heller)?

    Bei DirectX werden die Tiefeninformationen im UNorm-Format 24-Bit gespeichert. Siehe hier für Erklärung: https://msdn.microsoft.com/en-us/library/windows/desktop/dd607323%28v=vs.85%29.aspx

    Wie siehts es bei OpenGL aus? Haben die für ihre 24-Bit-Depth-Texture das gleiche Format?

    Bei DirectX gibt es etwas, das nennen die DepthBias. Das kann man beim RasterizerState einstellen. MSDN-Erklärung: https://msdn.microsoft.com/en-us/library/windows/desktop/cc308048(v=vs.85).aspx

    Mein Verständnis dafür: Z-Werte werden alle um ein konstanten Wert verrückt, wenn man das macht. Stimmt das? Hat OpenGL das auch? Ist das eine Sinnvolle Idee, um Shadow-Akne zu verhindern?

    Ihr müsst mir helfen^^ Ihr seht ja selbst, wie ich hier übelst rumeier 😃



  • Mir ist noch eine komische Sache aufgefallen. Ich habe mir mal die Depth-Textur direkt ausgeben lassen.

    http://fs1.directupload.net/images/150419/gew9f9d6.jpg

    Dabei habe ich folgende Min-Max-Texturwerte festgestellt:

    OpenGL:

    0,4976378 (das doppelte wäre 0,9952756)
    0,499023259 (das doppelte wäre 0,998046518)

    DirectX:

    0,995275557
    0,998046339

    CPU:

    0,997636259
    0,9990242

    So habe ichs bei OpenGL ausgelesen:

    byte[] data = new byte[4 * tex.Width * tex.Height];
    GL.GetTexImage(TextureTarget.Texture2D, 0, global::OpenTK.Graphics.OpenGL.PixelFormat.DepthComponent, PixelType.Float, data);
    

    Immer 4 Byte gehören zu ein Texel. So habe ich den float-Wert daraus berechnet:

    int unormDepth = data[i + 2] << 16 | data[i + 1] << 8 | data[i + 0];
    float depth = ConvertUnsignedNormalizedIntegerToFloat(unormDepth , 24)
    float ConvertUnsignedNormalizedIntegerToFloat(int f, int bitCount)
            {
                int maxInt = 1 << bitCount;
                return (float)f / maxInt;
            }
    

    Bei DirectX habe ich auch die Bytes aus der Textur gelesen:

    DataBox dataBox = device.ImmediateContext.MapSubresource(depthTexture2D, 0, MapMode.Read, MapFlags.None);
    byte[] data = new byte[dataBox.SlicePitch];
    dataBox.Data.Read(data, 0, data.Length);
    device.ImmediateContext.UnmapSubresource(depthTexture2D, 0);
    

    Dann die gleiche Formel wie bei OpenGL, um UNorm nach float zu konvertieren.

    Warum ist bei OpenGL der Textur-Wert nur halb so groß? Alle Zahlen weichen minimal voneinander ab. Warum?

    Hat hier jemand noch eine Idee, wie ich das Problem, wieso die gelesenen DirectX-Depth-Texturwerte von OpenGL abweichen, eingrenzen kann?

    Bei DirectX kann man im Pixelshader den Depth-Outputwert über SV_DEPTH auch selber setzen. Wie macht man das bei OpenGL? Über layout(location = 0) out vec4 out_frag_color; kann man ja nur festlegen, in welches Rendertarget (Color) ich ausgebe. Wie lautet die Notation, um aufs Depth-Target schreibend zuzugreifen?


  • Mod

    XMAMan schrieb:

    Kann es sein, dass bei OpenGL die Textur-Nullpunkt links unten liegt und bei DirectX der Textur-Nullpunkt links oben?

    ja, dem ist so.

    Warum sind die Texturwerte bei DirectX alle niedriger (Objekte scheinen weniger Abstand zur Lichtquelle zu haben) (Bild erscheint heller)?

    kann es sein dass du immer eine andere projektionsmatrix nutzt?

    Bei DirectX werden die Tiefeninformationen im UNorm-Format 24-Bit gespeichert. Siehe hier für Erklärung: https://msdn.microsoft.com/en-us/library/windows/desktop/dd607323%28v=vs.85%29.aspx

    Wie siehts es bei OpenGL aus? Haben die für ihre 24-Bit-Depth-Texture das gleiche Format?

    du kannst bei beiden festlegen wie das format sein soll, 24bit ist aber sehr gaengig, ja.

    Bei DirectX gibt es etwas, das nennen die DepthBias. Das kann man beim RasterizerState einstellen. MSDN-Erklärung: https://msdn.microsoft.com/en-us/library/windows/desktop/cc308048(v=vs.85).aspx

    Mein Verständnis dafür: Z-Werte werden alle um ein konstanten Wert verrückt, wenn man das macht. Stimmt das?

    Hat OpenGL das auch?

    ich glaube nicht, wenn ich mich recht erinnere nutzte man dafuer glpolygonoffset

    Ist das eine Sinnvolle Idee, um Shadow-Akne zu verhindern?

    ist gaengig, aber als erstes sollte natuerlich der depth buffer auf sich selbst keine artefakte produzieren, wie in deinem screenshots.

    Ihr müsst mir helfen^^

    alles hat seinen preis 😛

    Ihr seht ja selbst, wie ich hier übelst rumeier 😃

    mit c++ waere das einfacher 😉
    ach du machst doch immer gute fortschritte, alles braucht zeit, aber am ende wird es 🙂



  • rapso schrieb:

    kann es sein dass du immer eine andere projektionsmatrix nutzt?

    Die Matrix ist bei allen Grafik-Modien gleich. Ich habe nur eine Funtion, welche diese Matrix berechnet und für alle Medien verwendet wird. Ich habe das mit den Debugger geprüft.

    Mir ist aber noch eine andere Sache als Unterschied zwischen OpenGL und DirectX aufgefallen. Ich schreibe nun im Pixelshader nun direkt den Depth-Wert aus:

    DirectX

    PS_IN VS_ShadowmapCreation(VS_IN input)
    {
    	PS_IN output = (PS_IN)0;
    	output.pos = mul(float4(input.pos.xyz, 1.0), ShadowMatrix);
    	//output.pos = mul(float4(input.pos.xyz, 1.0), WorldViewProj);
    	output.tex = input.tex * TexturScaleFaktor;	
    
    	return output;
    }
    
    struct PS_OUTPUT
    {
        float4 Color: SV_Target0;
        float Depth: SV_Depth;
    };
    
    //float PS_ShadowmapCreation( PS_IN input ) : SV_Depth
    PS_OUTPUT PS_ShadowmapCreation( PS_IN input )
    {
    	PS_OUTPUT output = (PS_OUTPUT)0;
    
    	float4 objektColor = CurrentColor * (1 - UseTexture0) + GetTexelFromColorTexture(input.tex) * UseTexture0;	
    	if (BlendingBlackColor && (objektColor.x + objektColor.y + objektColor.z) < 0.1) discard;
    
    	output.Color = objektColor;
    	output.Depth = input.pos.z;// / input.pos.w;
    
    	return output;
    }
    

    OpenGL:

    out vec4 pos;
    
    void main() 
    {
       gl_Position =  ShadowMapMatrix[ShadowmappingTextureId] * vec4(in_position,1);
       pos = ShadowMapMatrix[ShadowmappingTextureId] * vec4(in_position,1);
       textcoord = in_textcoord * TexturScaleFaktor;
    }
    
    smooth in vec4 pos;
    
    void main()
    {
        vec4 objektColor = color * (1.0 - UseTexture0) + vec4(texture2D(Texture0, textcoord.xy)) * UseTexture0;	
    	if (BlendingWithBlackColor == 1 && (objektColor.x + objektColor.y + objektColor.z) < 0.1) discard;
    
    	out_frag_color = objektColor;
    
    	// Not really needed, OpenGL does it anyway
        //fragmentdepth = gl_FragCoord.z;				//Die Zeile kann auch auskommentiert werden. Sie schreibt nur in die Farbtextur. Die Tiefenpuffertextur kann über den Pixelshader nicht angesprochen werden (nur über Discard)
    
    	gl_FragDepth = pos.z / pos.w; //gl_FragCoord.z;
    }
    

    Bei OpenGL muss ich durch w teilen, damit die Depth-Werte mit DirectX übereinstimmen.

    MSDN sagt zu DirectX noch was https://msdn.microsoft.com/en-us/library/windows/desktop/bb509647%28v=vs.85%29.aspx

    Vertexshader-Output:

    POSITION[n] Position of a vertex in homogenous space. Compute position in screen-space by dividing (x,y,z) by w. Every vertex shader must write out a parameter with this semantic.

    Wenn ich es so mache, wie jetzt beschrieben, dann stimmen die Depth-Texturwerte zwischen OpenGL und DirectX nun überein. Es könnte aber auch sein, dass das Quatsch ist was ich hier mache.

    Vielleicht lautet die Formel, wie man in den Tiefenpuffer schreiben sollte auch so:

    OpenGL:
    gl_FragDepth = (pos.z / pos.w + 1) / 2;

    DirectX:
    output.Depth = (input.pos.z + 1) / 2;

    Der Grund warum ich das denke? Weil OpenGL nach den Clipping laut dieser Seite das noch mit Z macht:
    glDepthRange(n, f);
    Zw = (f - n) / 2 * zClip + (f + n) / 2

    Ich setze für n = 0 und für f = 1

    Ich erhalte:

    Z-Window = zClip / 2 + 0.5

    http://www.songho.ca/opengl/gl_transform.html

    Was meint ihr? Soll ich einfach nur das Z, was aus dem Clipping kommt und durch w geteilt wurde in den Tiefenpuffer schreiben, oder dann noch zustätzlich diese Umrechnung machen, wie sie songho beschreibt?

    Der Grund für die Umrechnung ist ja, damit man den 3D-Würfel, der von -1 bis +1 geht auf ein Würfel umrechnet, der von 0 bis 1 geht. Durch das Clipping liegt Z aber sowiso schon im Bereich von 0 bis 1. Wenn ich diese Formel dann anwende, dann liegt Z ja nur noch im Bereich 0.5 bis 1.



  • Ok ich habe die Lösung gefunden.

    OpenGL speichert seine Z-Werte mit folgender Formel:

    zClipping / w * 0.5 + 0.5

    DirectX speichert sie hiermit:

    zClipping / w

    Ich habe bei DirectX den ShadowCreate-Pixelshader nun so geschrieben:

    output.Depth = input.pos.z / 2 + 0.5f;

    Ich muss die z-Koordinate komischwerweise nicht durch w teilen. Sie ist vom Typ SV_POSITION.

    Beim Auslesen der Shadowtextur muss ich aber die input.shadowPos.z durch w teilen. Sie ist vom Typ POSITION2. Anscheinend verhält dich SV_POSITION anders als POSITION[n].

    Hier nun das Ergebnis:

    http://fs1.directupload.net/images/150420/5pvmps5r.jpg

    Vorher habe ich die Schatten über den Stencil-Puffer gemacht. Dort konnte der Drahttopf ganz vorne aber keine Schatten werfen, da die Löcher über Spezial-Blending gemacht werden. Nun geht das.

    Kommen wir nun zu Aufgabe 2:

    Die Textur beim Mario ist bei OpenGL 3.0 und DirectX falsch. Wenn ich aber mit OpenGL 1.0 ausgeben, dann sieht sie genau wie bei CPU aus. Was ist da also nun wieder los? Was ich nun gelernt habe ist, dass der Textur-Null-Punkt unterschiedlich ist. Aber das erklärt nicht, warum es falsch aussieht. Mein CPU-Shader hat den Textur-Nullpunkt links oben. So wie DirectX auch. Dieses kann aber auch nicht die Textur richtig anzeigen.

    Hat jemand Ideen, wie ich vorgehen könnte, um das Problem einzugrenzen?


Anmelden zum Antworten