Farbverlaufstext

8

Was ich eigentlich versuche zu erreichen: Ich möchte Text mit einer vertikalen Farbverlaufsfarbe zeichnen. Ich habe diese Lösung gefunden, aber sie passt nicht ganz zu mir, da sie in meinem Fall ein schwarzes Quadrat um die Verlaufsschrift hat - ich weiß nicht, wie ich sie loswerden soll, also habe ich eine einfache Frage (den irrelevanten Teil) gestellt Verstehe die Physik von Blending und Frame Buffer in opengl und libgdx besser

Was ich zu verstehen versuchte, ist für mein Ziel irrelevant: Ich habe eine Textur mit einem weißen Quadrat darauf, ich zeichne sie auf einen roten Hintergrund. Ich versuche, ein grünes Quadrat über das weiße zu zeichnen, das grüne Quadrat bedeckt teilweise das weiße und teilweise den roten Hintergrund (siehe Bild unten).

Meine Absicht ist: Der weiße Bereich, der sich hinter dem grünen Quadrat befindet, sollte in grüner Farbe gestrichen werden, aber der gesamte rote Hintergrund sollte nicht beeinträchtigt werden und unverändert bleiben (rot wie er ist).

Wie kann ich das machen?

package com.mygdx.game;

import com.badlogic.gdx.ApplicationAdapter;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer;

public class Game extends ApplicationAdapter {
    SpriteBatch batch;
    Texture img;
    private int height;
    private int width;
    private ShapeRenderer shapeRenderer;

    @Override
    public void create() {
        batch = new SpriteBatch();
        img = new Texture("white.png");
        width = Gdx.graphics.getWidth();
        height = Gdx.graphics.getHeight();
        shapeRenderer = new ShapeRenderer();
        shapeRenderer.setAutoShapeType(true);
    }

    @Override
    public void render() {
        Gdx.gl.glClearColor(1, 0, 0, 1);
        Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);

        batch.begin();
        batch.draw(img, width / 7, height / 4);
        batch.end();

        Gdx.gl.glEnable(GL20.GL_BLEND);
        Gdx.gl.glBlendFunc(GL20.GL_ONE, GL20.GL_SRC_COLOR);
        shapeRenderer.begin();
        shapeRenderer.set(ShapeRenderer.ShapeType.Filled);
        shapeRenderer.setColor(Color.GREEN);
        shapeRenderer.rect(width / 2 - 100, height / 4 - 50, 200, 200);
        shapeRenderer.end();
        Gdx.gl.glDisable(GL20.GL_BLEND);
    }

    @Override
    public void dispose() {
        batch.dispose();
        img.dispose();
    }
}

Idealerweise sollte das grüne Quadrat sowieso nicht transparent sein, sondern nur Weiß blockieren, wo es den weißen Bereich verbirgt.

Die Ausgabe, die ich bekomme: Bildschirmfoto

Update : Ich markiere die Antwort von @Xoppa als richtig, da sie meine ursprüngliche Frage mit folgendem Ergebnis löst:

Geben Sie hier die Bildbeschreibung ein

Exenza
quelle
3
Sie können die Farbinformationen nicht verwenden, um das Mischen auf diese Weise zu steuern. Der einzige Unterschied in den Pixeln im Puffer besteht im G- und B-Kanal, der 1 für das weiße Quadrat und 0 für den Hintergrund ist, aber das können Sie beim direkten Mischen nicht verwenden. Ein besserer Weg wäre, das grüne Rechteck in den Schablonenpuffer zu zeichnen und dann das weiße Quadrat zu rendern.
Bartek Banachewicz
Es wäre eine gute Idee, Ihr Beispiel / Ihre Frage zu aktualisieren, damit andere sehen können, was Sie wirklich brauchen
Luis Fernando Frontanilla
Ja, aktualisiert, danke
exenza
Können Sie eine neue Frage mit weiteren Details für Ihren aktualisierten Anwendungsfall posten? (zB: Schriftdetails, Verlaufsfarben / Richtung / usw.) Wie andere sagten, sind Sie höchstwahrscheinlich ein Shader dafür, und ich kann Ihnen möglicherweise dabei helfen.
Nicolas
Du hast Recht @Nicolas, ich hätte eine neue Frage mit dem direkten Anwendungsfall
posten sollen

Antworten:

6

Sie könnten tatsächlich eine Art Maske verwenden, um sie mit einem Quadrat zu mischen. Dazu können Sie den Text zunächst mit einem benutzerdefinierten Shader in den Schablonenpuffer rendern, der Fragmente mit einem Alpha-Wert unterhalb eines bestimmten Schwellenwerts verwirft. Danach können Sie das Quadrat mit der Schablonenfunktion rendern, um nur die vom Text "berührten" Fragmente zu beeinflussen. Beachten Sie, dass dies jedoch mehrere Renderaufrufe umfasst und daher auch Ihren Aufrufcode komplexer macht.

Sie sagen jedoch, dass Sie eigentlich nur Text mit Farbverlauf rendern möchten. Dafür benötigen Sie keinen so komplexen Ansatz und können den Verlauf einfach innerhalb desselben Renderaufrufs anwenden.

Wenn Sie Text zeichnen, rendern Sie tatsächlich viele kleine Quadrate, für jedes Zeichen im Text ein Quadrat. Auf jedes dieser Quadrate wird eine Texturregion angewendet, die das Zeichen auf einem transparenten Hintergrund enthält. Wenn Sie das Schriftbild öffnen (z. B. dies ist die Standardeinstellung ), wird dieses Quellbild angezeigt .

So wie Sie einem normalen Quadrat einen Farbverlauf zuweisen können, können Sie auch jedem einzelnen Quadrat, aus dem der Text besteht, einen Farbverlauf zuweisen. Dafür gibt es mehrere Möglichkeiten. Welche am besten passt, hängt vom Anwendungsfall ab. Wenn Sie beispielsweise einen horizontalen Farbverlauf oder mehrzeiligen Text benötigen, benötigen Sie einige zusätzliche Schritte. Da Sie dies nicht angegeben haben, gehe ich davon aus, dass Sie einen vertikalen Farbverlauf auf eine einzelne Textzeile anwenden möchten:

public class MyGdxGame extends ApplicationAdapter {
    public static class GradientFont extends BitmapFont {
        public static void applyGradient(float[] vertices, int vertexCount, float color1, float color2, float color3, float color4) {
            for (int index = 0; index < vertexCount; index += 20) {
                vertices[index + SpriteBatch.C1] = color1;
                vertices[index + SpriteBatch.C2] = color2;
                vertices[index + SpriteBatch.C3] = color3;
                vertices[index + SpriteBatch.C4] = color4;
            }
        }

        public GlyphLayout drawGradient(Batch batch, CharSequence str, float x, float y, Color topColor, Color bottomColor) {
            BitmapFontCache cache = getCache();
            float tc = topColor.toFloatBits();
            float bc = bottomColor.toFloatBits();
            cache.clear();
            GlyphLayout layout = cache.addText(str, x, y);
            for (int page = 0; page < cache.getFont().getRegions().size; page++) {
                applyGradient(cache.getVertices(page), cache.getVertexCount(page), bc, tc, tc, bc);
            }
            cache.draw(batch);
            return layout;
        }
    }

    SpriteBatch batch;
    GradientFont font;
    float topColor;
    float bottomColor;

    @Override
    public void create () {
        batch = new SpriteBatch();
        font = new GradientFont();
    }

    @Override
    public void render () {
        Gdx.gl.glClearColor(1, 0, 0, 1);
        Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
        batch.begin();
        font.drawGradient(batch, "Hello world", 0, 100, Color.GREEN, Color.BLUE);
        batch.end();
    }

    @Override
    public void dispose () {
        batch.dispose();
        font.dispose();
    }
}

Übrigens, um bessere Antworten zu erhalten, sollten Sie das eigentliche Problem angeben, das Sie lösen möchten, anstatt sich auf das zu konzentrieren, was Sie für die Lösung halten. Siehe auch: https://stackoverflow.com/help/asking .

Xoppa
quelle
Danke für das Teilen. Einige Anschlussfragen: 1. Nehmen wir zunächst an, dass die Schleife die sogenannte "Textur-Seite der Glyphen" durchläuft. Ich würde denken, es wäre die Anzahl der Zeichen in meiner Zeichenfolge, aber nimmt es tatsächlich die PNG-Textur der gesamten Schriftart an (deshalb nennen Sie den Iterator "Seite"?), Denn für mich führt diese Schleife immer nur solche aus
exenza
2. die zweite Schleife in applyGradient. Ich nehme an, hier haben Sie den Farbverlauf eingestellt (Sie haben Recht, ich wollte vertikal), aber ich bin mir nicht ganz sicher, wie es in Bezug auf die magische Zahl funktioniert index +=20(z. B. ist dieser Wert schriftspezifisch oder warum ist er 20?). und Konstanten SpriteBatch.CX(sind sie die Textur-Ecken?). Ist es Opengl Standard Vertices Färbung?
Exenza
3. Über Optimierung: Ist es in Ordnung, wenn ich das Material vom drawGradientKonstruktor der inneren Klasse verschiebe, damit es nicht bei jedem Rendering-Schritt aufgerufen wird (der Konstruktor würde irgendwo in der create()Methode als solche bezeichnet) und drawGradientnur in cache.draw(batch)und return layoutZeilen belassen?
Exenza
1
1) Dies ist die Anzahl der PNG-Dateien, die Ihre Schriftart verwendet. Jede PNG-Datei erfordert einen separaten Zeichenaufruf und entsprechende Scheitelpunkte. Dies hängt nicht von der Anzahl der Zeichen im Text selbst ab. 2) 4 Ecken x 5 Attribute pro Ecke (x, y, u, v, Farbe) = 20. Dies ist in Spritebatch fest codiert (und es ist Shader). 3) Eine Optimierung ist nicht erforderlich. Es könnte funktionieren, wenn Sie nichts anderes mit der Schriftart tun, aber nichts verhindert dies. Das ist also ein Fehler, der darauf wartet, passiert zu werden. Es gibt wahrscheinlich bessere Möglichkeiten, dies zu tun. Das wäre aber immer noch eine unnötige vorzeitige Optimierung.
Xoppa
4

Sie können das Mischen vortäuschen, indem Sie ein bisschen rechnen. Hier ist, was ich mir ausgedacht habe:

import com.badlogic.gdx.Game;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.math.Rectangle;

public class CalculatedMask extends Game {

    private SpriteBatch batch;          // The SpriteBatch to draw the white image
    private ShapeRenderer renderer;     // The ShapeRenderer to draw the green rectangle
    private Texture img;                // The texture of the image
    private Rectangle imgBounds;        // The bounds of the image
    private Rectangle squareBounds;     // The bounds of the square
    private float width;                // The width of the screen
    private float height;               // The height of the screen
    private float squareX;              // The x position of the green square
    private float squareY;              // The y position of the green square
    private float squareWidth;          // The width of the green square
    private float squareHeight;         // The height of the green square

    @Override
    public void create() {
        width = Gdx.graphics.getWidth();
        height = Gdx.graphics.getHeight();
        batch = new SpriteBatch();
        renderer = new ShapeRenderer();
        renderer.setAutoShapeType(true);

        img = new Texture("pixel.png");                 // A 1x1 white pixel png
        imgBounds = new Rectangle();                    // The white image bounds
        imgBounds.setPosition(width / 7f, height / 4f); // Position the white image bounds
        imgBounds.setSize(400f, 300f);                  // Scale the white image bounds
        calculateRectangle();
    }

    private void calculateRectangle() {
        // Here we define the green rectangle's original position and size
        squareBounds = new Rectangle();
        squareX = width / 2f - 300f;
        squareY = height / 4f - 50f;
        squareWidth = 200f;
        squareHeight = 200f;
        // Adjust green square x position
        squareBounds.x = MathUtils.clamp(squareX, imgBounds.x, imgBounds.x + imgBounds.width);
        // Adjust green square y position
        squareBounds.y = MathUtils.clamp(squareY, imgBounds.y, imgBounds.y + imgBounds.height);
        // Adjust green square width
        if (squareX < imgBounds.x) {
            squareBounds.width = Math.max(squareWidth + squareX - imgBounds.x, 0f);
        } else if (squareX + squareWidth > imgBounds.x + imgBounds.width) {
            squareBounds.width = Math.max(imgBounds.width - squareX + imgBounds.x, 0f);
        } else {
            squareBounds.width = squareWidth;
        }
        // Adjust green square height
        if (squareY < imgBounds.y) {
            squareBounds.height = Math.max(squareHeight + squareY - imgBounds.y, 0f);
        } else if (squareY + squareHeight > imgBounds.y + imgBounds.height) {
            squareBounds.height = Math.max(imgBounds.height - squareY + imgBounds.y, 0f);
        } else {
            squareBounds.height = squareHeight;
        }
    }

    @Override
    public void render() {
        // Clear previous frame
        Gdx.gl.glClearColor(1, 0, 0, 1);
        Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
        // Draw the white image
        batch.begin();
        batch.draw(img, imgBounds.x, imgBounds.y, imgBounds.width, imgBounds.height);
        batch.end();
        // Draw the green rectangle without affecting background
        renderer.begin();
        renderer.setColor(Color.GREEN);

        // Debug so we can see the real green rectangle
        renderer.set(ShapeRenderer.ShapeType.Line);
        renderer.rect(squareX, squareY, squareWidth, squareHeight);
        // Draw the modified green rectangle
        renderer.set(ShapeRenderer.ShapeType.Filled);
        renderer.rect(squareBounds.x, squareBounds.y, squareBounds.width, squareBounds.height);

        renderer.end();
    }
}

Und die Ergebnisse sind: Geben Sie hier die Bildbeschreibung ein

Und mit:

squareX = width / 2f + 100f;
squareY = height / 4f + 150f;

Geben Sie hier die Bildbeschreibung ein

Luis Fernando Frontanilla
quelle
Tolles Zeug, danke Luis für das Feedback. Meine Frage liefert ein ziemlich falsches Beispiel dafür, was ich tatsächlich versuche. Der weiße Bereich kann eine beliebige Form haben, z. B. ein Kreis oder sogar noch komplexer. Leider ist es nicht immer möglich, das Quadrat anzupassen
Exenza
Was sind die Kriterien in Ihrem tatsächlichen Anwendungsfall, für die Sie nicht möchten, dass es gezeichnet wird? Möchten Sie buchstäblich nur dort zeichnen, wo Pixel bereits reinweiß sind? Oder wo bereits bestimmte Sprites gezeichnet wurden? Etwas anderes?
Tenfour04
Ich fürchte, die einzige Möglichkeit, wie ich das tun kann, was Sie wollen, ist mit Shadern oder fortgeschrittenem Mischen. Versuchen Sie, die LibGDX-Maskierung zu untersuchen, da ich noch nie zuvor eine davon verwendet habe
Luis Fernando Frontanilla,
Ich habe das Update zur ursprünglichen Frage @ Tenfour04
exenza