2D Schatten (mal wieder) + Stencil Buffer



  • Hallo.

    Ich fummel schon knapp 2 Wochen mit 2D Schatten mittels Stencil Buffer rum, aber habe irgendwie immense Logikprobleme und bekomme es kaum gebacken. Nach langem raetseln habe ich es mittlerweile hinbekommen, das Schatten, Licht und Geometrie korrekt in den Stencilbuffer geschrieben werden:

    (P.S.: Am Boden des Codes schreibe ich nen kurzen Ablaufplan, was ich da mache und wieso.)

    public void display()
    	{
    		GL11.glColorMask(false, false, false, false);
    		GL11.glEnable(GL11.GL_STENCIL_TEST);
    		GL11.glDisable(GL11.GL_DEPTH_TEST);
    
    		GL11.glStencilFunc(GL11.GL_ALWAYS, 1, 1);
    		GL11.glStencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_REPLACE);
    		for(int i=0;i<lights.size();++i)
    			lights.get(i).render();
    
    		GL11.glStencilFunc(GL11.GL_EQUAL,1,1);
    		GL11.glStencilOp(GL11.GL_ZERO, GL11.GL_ZERO, GL11.GL_INCR);
    		for(int i=0;i<drawables.size();++i)
    		{
    			drawables.get(i).render();
    		}
    
    		GL11.glStencilFunc(GL11.GL_EQUAL, 1, 1);
    		GL11.glStencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_ZERO);
    
    		Vector<Shape> shadows = calculateShadows();
    		for(int i=0;i<shadows.size(); ++i)
    		{
    			shadows.get(i).render();
    		}
    
    		GL11.glColorMask(true,true,true,true);
    		GL11.glStencilFunc(GL11.GL_LEQUAL, 1, 3);
    		GL11.glStencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_KEEP);
    		GL11.glDisable(GL11.GL_DEPTH_TEST);
    
    		for(int i=0;i<backgrounds.size();++i)
    		{
    			backgrounds.get(i).render();
    		}
    		for(int i=0;i<drawables.size();++i)
    		{
    			drawables.get(i).render();
    		}
    
    		GL11.glDisable(GL11.GL_STENCIL_TEST);
    		drawables.clear();
    	}
    

    Was ich hier mache und wieso:
    - Es sei zunaechst erwaehnt, dass ich etwas rumtricksen muss, weil meine ausgerechneten Schattenformen manchmal mit den Drawables, von denen sie gecastet werden, ueberlappen. Deswegen muss ich guggen wo im Stencil Buffer Schatten und Geometrie ueberlappt und nur da die Region zu Nullen machen wo grade kein Licht draufscheint.

    Dann:
    - An die Position der Lichter Einsen in den Stencil Buffer schreiben
    - Geometrie in den Stencil Buffer schreiben, mit folgenden Bedingungen:
    * Wenn schon ne 1 an der Stelle des momentan getesteten Pixels steht, dann zaehl das Gebit hoch (auf 2)
    * Ansonsten: Gebiet im Stencil Buffer auf 0 setzen.
    - Schatten in den Stencil Buffer schreiben: Wenn Stelle im Puffer 1 ist ( Das heisst: Da leuchtet zwar Licht drauf aber da ist keine Geometrie), dann Schatten drueber malen, ansonsten Werte einfach in Ruhe lassen.

    - Mit glColorMask wieder alle Farben durchlassen und Rest normal rendern. Backgrounds und normale Drawables hab ich getrennt, weil ich von Backgrounds keine Schatten berechnen will.

    Lichtfarbe wird hier noch ignoriert, das sollte im Nachhinein noch relativ einfach hinzufuegbar sein, jetzt moechte ich erstmal den letzten laestigen Bug beseitigen:

    Bei einem Licht funktioniert das ganze richtig toll wie gewollt. Bei mehreren Lichtern malen die Schatten eines Lichtes aber in den Lichtkreis des anderen, was ich aber nicht moechte. Das heisst, dann wird am Ende ein Teil der Geometrie nicht auf dem Bildschirm erscheinen, obwohl das Licht die Gegend erleuchtet, eben weil eins der Lichter einen Schatten in den Lichtbereich eines anderen Lichtes castet und dort somit lauter 0'en im Stencil Buffer stehen.

    Ich weiss schon, woran das liegt: Da beim malen der Schatten in den Stencilbuffer nur auf "1" ueberprueft wird, malt er natuerlich einfach ueber ein Licht drueber und setzt da alles auf 0en. Aber ich komme beim besten Willen auch nach langem Gruebeln nicht drauf wie ich es loesen kann.

    Deswegen bitte ich euch aus Verzweiflung: Helft mir beim buxfixen! Wie wuerdet ihr es angehen? Was wuerdet ihr konkret aendern?



  • Ich bin ein wenig weiter. Ein Bug existiert noch, bei dem ihr mir vielleicht helfen könnt:

    public void display() 
        { 
            GL11.glColorMask(false, false, false, false); 
            GL11.glEnable(GL11.GL_STENCIL_TEST); 
            GL11.glDisable(GL11.GL_DEPTH_TEST); 
    
            Vector<Shape> shadows = new Vector<Shape>();
            GL11.glStencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_REPLACE);
            for(int i=2;i<lights.size()+2;++i)
            {
     	        for(int d = 0; d<drawables.size();++d)
     	        {
     	        	shadows.add(lights.get(i-2).calculateShadow(drawables.get(d)));
     	        }
     	        GL11.glStencilFunc(GL11.GL_ALWAYS, i, i);
     	        for(int s = 0; s<shadows.size();++s)
     	        {
     	        	shadows.get(s).render();
     	        }
     	        shadows.clear();
            }
    
            GL11.glStencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_ZERO);
            for(int i=2;i<lights.size()+2;++i)
            {
            	GL11.glStencilFunc(GL11.GL_NOTEQUAL, i, 255);
            	lights.get(i-2).render();
            }
    
            GL11.glStencilFunc(GL11.GL_ALWAYS, 125, 255); 
            GL11.glStencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_REPLACE); 
    
            for(int i=0;i<drawables.size();++i)
            {
            	drawables.get(i).render();
            }
    
            GL11.glStencilFunc(GL11.GL_EQUAL, 125, 255); 
            GL11.glStencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_ZERO); 
            for(int i=0;i<lights.size();++i)
            {
            	lights.get(i).render();
            }
    
            GL11.glColorMask(true,true,true,true); 
            GL11.glStencilFunc(GL11.GL_EQUAL, 0, 255); 
            GL11.glStencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_KEEP); 
            GL11.glDisable(GL11.GL_DEPTH_TEST); 
    
            for(int i=0;i<backgrounds.size();++i) 
            { 
                backgrounds.get(i).render(); 
            } 
            for(int i=0;i<drawables.size();++i) 
            { 
                drawables.get(i).render(); 
            } 
    
            GL11.glDisable(GL11.GL_STENCIL_TEST); 
            drawables.clear(); 
        }
    

    Die Effekte sind jetzt wie gewollt: Schatten malen nicht in fremde Lichter rein, Lichter erleuchten den korrekten Bereich etc.

    Was ich mache:
    -Stencilbuffer wird bei jedem glClear auf "1" gecleart (glClearStencil(1)) (gecleared wird übrigens außerhalb der Funktion).
    -der Wert 0 symbolisiert "hier hin wird gezeichnet". Demnach wird auch das "farbige" Zeichnen so konditioniert:

    GL11.glColorMask(true,true,true,true); 
            GL11.glStencilFunc(GL11.GL_EQUAL, 0, 255); 
            GL11.glStencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_KEEP); 
            GL11.glDisable(GL11.GL_DEPTH_TEST); 
    
            for(int i=0;i<backgrounds.size();++i) 
            { 
                backgrounds.get(i).render(); 
            } 
            for(int i=0;i<drawables.size();++i) 
            { 
                drawables.get(i).render(); 
            }
    

    - ich berechne und zeichne die Schatten mit einer "ID" in den Stencilbuffer.
    - Lichter einzeln durchlaufen: Wenn an Stell im Stencilbuffer, an das das i-te Licht hinmalen will, bereits der Wert "i" steht (die ID), dann dort nix ändern, wenn irgendein anderer Wert dort steht, den Wert auf 0 setzen.

    Das ganze ist dieser Code-Teil:

    GL11.glColorMask(false, false, false, false); 
            GL11.glEnable(GL11.GL_STENCIL_TEST); 
            GL11.glDisable(GL11.GL_DEPTH_TEST); 
    
            Vector<Shape> shadows = new Vector<Shape>();
            GL11.glStencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_REPLACE);
            for(int i=2;i<lights.size()+2;++i)
            {
     	        for(int d = 0; d<drawables.size();++d)
     	        {
     	        	shadows.add(lights.get(i-2).calculateShadow(drawables.get(d)));
     	        }
     	        GL11.glStencilFunc(GL11.GL_ALWAYS, i, i);
     	        for(int s = 0; s<shadows.size();++s)
     	        {
     	        	shadows.get(s).render();
     	        }
     	        shadows.clear();
            }
    
            GL11.glStencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_ZERO);
            for(int i=2;i<lights.size()+2;++i)
            {
            	GL11.glStencilFunc(GL11.GL_NOTEQUAL, i, 255);
            }
    

    Da die berechneten Schattenflächen gezwungenermaßen aber manchmal mit den Drawables überlappen, von denen sie geworfen werden, muss jetzt noch ein letzter Schritt her:

    - Zeichne alle "Drawables" (in meinem Testprogramm einfach Sprites) mit einem willkürlichen Wert in den Stencilbuffer (Bei mir 125. Stencil Buffer ist übrigens 8 bit lang).

    - Gehe nochmals durch alle Lichter und zeichne an die Stellen, an denen im Stencil Buffer eine 125 steht, eine 0 rein.

    Das ist folgender Code-Teil:

    GL11.glStencilFunc(GL11.GL_ALWAYS, 125, 255); 
            GL11.glStencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_REPLACE); 
    
            for(int i=0;i<drawables.size();++i)
            {
            	drawables.get(i).render();
            }
    
            GL11.glStencilFunc(GL11.GL_EQUAL, 125, 255); 
            GL11.glStencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_ZERO); 
            for(int i=0;i<lights.size();++i)
            {
            	lights.get(i).render();
            }
    

    Das funktioniert aber aus irgendnem Grund nicht. Und ich hab keine Ahnung wieso. Wenn ich den Code wie oben so ausführe, sieht das so aus:

    http://i.imgur.com/OVAtt.jpg

    Dieses Rote Ding, was da rausguggt, ist einfach eins der beiden Sprites, das gezeichnet wird. Die Schwarzen Flächen darüber sind die (korrekt) berechneten Schattenformen, die eben wie schon gesagt teilweise mit den Formen überlappen, weswegen ich sie in dem letzten Schritt rausfiltern muss.

    Doch das rausfiltern sollte so eigentlich definitiv funktioniert, oder nicht? Zunächst alle Drawables in Buffer malen mit glStencilFunc(GL_ALWAYS,125,125). (und GL_REPLACE in glStencilOp). Nun sollte an den Stellen der Sprites im Stencilbuffer eine 125 stehen.

    Das tut es übrigens auch! Wenn ich den Code bei dem ich zum zweiten mal alle Lichter durchlaufe rauskommentiere, werden die Regionen wo die Sprites sind nicht gezeichnet (weil da eben 125 != 0 steht und somit Stencil Test beim zeichnen fehlschlägt.)

    Demnach sollte der Fehler also irgendwie beim letzten Schritt (dem zweiten Durchlaufen aller Lichter) liegen:

    GL11.glStencilFunc(GL11.GL_EQUAL, 125, 255); 
            GL11.glStencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_ZERO); 
            for(int i=0;i<lights.size();++i)
            {
            	lights.get(i).render();
            }
    

    Doch was soll da denn falsch sein? GL_EQUAL lässt den Test erfolgreich sein, wenn ref&mask == stencilvalue&mask , sprich hier: wenn 125&255 == wertimstencil & 255 , also konkret wenn wertimstencil == 125, was ja bei der Stelle der Sprites so sein sollte.

    Und wenn der Test nicht scheitert, wird an die Stelle im Buffer 0 geschrieben (mit GL_ZERO).

    Was ist hier falsch?


Anmelden zum Antworten