Texto de color degradado

8

Lo que realmente intento lograr: me gustaría dibujar texto con un color vertical degradado. Encontré esta solución, pero no me queda del todo, ya que en mi caso tiene un cuadrado negro alrededor de la fuente de degradado; no sé cómo deshacerme de ella, así que comencé una pregunta simple (la parte irrelevante) para Comprender mejor la física de la mezcla y el búfer de trama en opengl y libgdx

Lo que estaba tratando de entender, irrelevante para mi objetivo: tengo una textura con un cuadrado blanco, lo dibujo sobre un fondo rojo. Estoy tratando de dibujar un cuadrado verde sobre el blanco, el cuadrado verde cubre parcialmente el blanco y parcialmente sobre el fondo rojo (vea la imagen a continuación).

Mi intención es: el área blanca, que está detrás del cuadrado verde, debe pintarse en color verde, pero todo el fondo rojo no debe verse afectado y permanecer sin cambios (rojo como está).

¿Cómo puedo hacer esto?

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();
    }
}

Idealmente, el cuadrado verde no debería ser transparente de todos modos, solo debería bloquear el blanco donde oculta el área blanca.

La salida que estoy obteniendo: captura de pantalla

Actualización : Marco la respuesta de @Xoppa como correcta, ya que resuelve mi pregunta original con el siguiente resultado:

ingrese la descripción de la imagen aquí

exenza
fuente
3
No puede usar la información de color para dirigir la mezcla de esa manera. La única diferencia en los píxeles en el búfer estará en el canal G y B, que será 1 para el cuadrado blanco y 0 para el fondo, pero eso no es algo que pueda utilizar para mezclar directamente. Una mejor manera sería dibujar el rectángulo verde en el búfer de la plantilla y luego renderizar el cuadrado blanco.
Bartek Banachewicz
Sería una buena idea actualizar su ejemplo / pregunta para que otros puedan ver lo que realmente necesita
Luis Fernando Frontanilla
Sí, actualizado, gracias
exenza
¿Puede publicar una nueva pregunta con más detalles para su caso de uso actualizado? (por ejemplo: detalles de fuente, degradado de colores / dirección / etc.) Como han dicho otros, lo más probable es que sea un sombreador para eso, y podría ayudarlo con eso.
Nicolas
Tienes razón @Nicolas, debería haber publicado una nueva pregunta con el caso de uso directo
exenza

Respuestas:

6

De hecho, podría usar algún tipo de máscara para mezclarla usando un cuadrado. Para eso, primero puede representar el texto en el búfer de la plantilla utilizando un sombreador personalizado que descarta los fragmentos con un valor alfa por debajo de un cierto umbral. Después de eso, puede renderizar el cuadrado utilizando la función de plantilla para afectar solo los fragmentos "tocados" por el texto. Tenga en cuenta que esto implica múltiples llamadas de renderizado y, por lo tanto, agrega complejidad a su código de llamada también.

Sin embargo, dices que en realidad solo quieres renderizar texto usando degradado. Para eso no necesita un enfoque tan complejo y simplemente puede aplicar el gradiente dentro de la misma llamada de renderizado.

Cuando dibuja texto, en realidad representa muchos cuadrados pequeños, para cada carácter en el texto un cuadrado. Cada uno de estos cuadrados tiene una región de textura aplicada que contiene el personaje en un fondo transparente. Si abre la imagen de la fuente (por ejemplo, esta es la predeterminada ), verá esta imagen de origen.

Al igual que puede aplicar un degradado a un cuadrado normal, también puede aplicar un degradado a cada uno de esos cuadrados individuales que conforman el texto. Hay múltiples formas de hacerlo. El mejor traje depende del caso de uso. Por ejemplo, si necesita un degradado horizontal o tiene texto de varias líneas, necesita algunos pasos adicionales. Como no especificó esto, voy a suponer que desea aplicar un degradado vertical en una sola línea de texto:

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();
    }
}

Por cierto, para obtener mejores respuestas, debe incluir el problema real que está tratando de resolver, en lugar de centrarse en lo que cree que es la solución. Ver también: https://stackoverflow.com/help/asking .

Xoppa
fuente
Gracias por compartir. Pocas preguntas de seguimiento: 1. Primero, para el bucle, suponga iterar a través de la llamada "página de textura de glifos"; Creo que sería el número de caracteres en mi cadena, pero en realidad toma la textura png de toda la fuente (¿por eso llama al iterador "página"?), Porque para mí este bucle siempre ejecuta solo unos
exenza
2. el segundo bucle applyGradient. Supongo que ahí es donde establece el gradiente (tiene razón, quería vertical), pero no estoy muy seguro de cómo funciona en relación con el número mágico index +=20(por ejemplo, ¿es este valor fuente específica o por qué es 20?) y constantes SpriteBatch.CX(¿son las esquinas de textura?). ¿Es el color estándar de vértices opengl?
exenza
3. acerca de la optimización: ¿está bien si muevo las cosas drawGradiental constructor de la clase interna, por lo que no se llama en cada paso de representación (el constructor se llamaría unos en algún lugar del create()método) y se deja drawGradientsolo cache.draw(batch)y return layoutlíneas?
exenza
1
1) Es la cantidad de archivos PNG que usa tu fuente. Cada archivo PNG requiere una llamada de sorteo separada y los vértices correspondientes. No depende de la cantidad de caracteres en el texto mismo. 2) 4 esquinas x 5 atributos por esquina (x, y, u, v, color) = 20. Esto está codificado en spritebatch (y es sombreador). 3) La optimización no es necesaria. Podría funcionar si no hace nada más con la fuente, pero nada lo impide. Entonces, eso es un error esperando a suceder. Probablemente hay mejores formas de hacerlo. Pero eso aún sería una optimización prematura innecesaria.
Xoppa
4

Puedes falsificar la mezcla haciendo algo de matemática. Esto es lo que se me ocurrió:

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();
    }
}

Y los resultados son: ingrese la descripción de la imagen aquí

Y con:

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

ingrese la descripción de la imagen aquí

Luis Fernando Frontanilla
fuente
Grandes cosas, gracias Luis por los comentarios. Mi pregunta proporciona un ejemplo bastante incorrecto de lo que realmente estoy tratando de hacer. El área blanca puede tener cualquier forma, digamos círculo o incluso más compleja, por lo que desafortunadamente no siempre es posible ajustar el cuadrado
exenza
¿Cuál es el criterio en su caso de uso real para el que no desea que se dibuje? ¿Literalmente solo desea dibujar donde los píxeles ya son de color blanco puro? ¿O dónde ya se han dibujado algunos sprites específicos? ¿Algo más?
Tenfour04
Me temo que las únicas formas en que sé hacer lo que quieres es con sombreadores o mezclas avanzadas, intenta investigar sobre el enmascaramiento de LibGDX ya que nunca usé ninguno de esos antes
Luis Fernando Frontanilla
Agregué la actualización a la pregunta original, @ Tenfour04
exenza