What I actually try to achieve: I'd like to draw text with a gradient vertical color. I found this solution, but it doesn't quite fit for me, as it has black square around the gradient font in my case - don't know how to get rid of it, so I started simple (the irrelevant part) question to understand better the physics of blending and frame buffer in opengl and libgdx
What I was trying to understand, irrelevant to my goal: I have a texture with a white square on it, I draw it on top of red background. I am trying to draw a green square on top of the white one, the green square partially covers the white one, and partially on top of the red background (see picture below).
My intention is: the white area, that is behind of the green square should be painted in green color, but all red background should not be affected and stayed unchanged (red as it is).
How can I do this?
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();
}
}
Ideally, the green square should not be transparent anyhow, just should block white where it hides the white area.
The output I'm getting:
Update: I mark @Xoppa 's answer as correct, as it solves my original question with the following result:
You could indeed use some kind of mask to blend it using a square. For that you can first render the text to the stencil buffer using a custom shader that discards fragments with an alpha value below a certain threshold. After that you can render the square using the stencil function to only affect the fragments "touched" by the text. Note that this does involve multiple render calls though and therefore adds complexity to your calling code as well.
However, you say that you actually just want to render text using gradient. For that you don't need such complex approach and can simply apply the gradient within the same render call.
When you draw text, you actually render many little squares, for each character in the text one square. Each of this square has a textureregion applied that contains the character on a transparent background. If you open the font image (e.g. this is the default), then you'll see this source image.
Just like you can apply a gradient to a normal square, you can also apply a gradient to each of those individual squares that make up the text. There are multiple ways to do that. Which best suits depends on the use-case. For example if you need a horizontal gradient or have multiline text, then you need some additional steps. Since you didn't specify this, I'm going to assume that you want to apply a vertical gradient on a single line of text:
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();
}
}
Btw, to get better answers you should include the actual problem you are trying to solve, instead of focusing on what you think is the solution. See also: https://stackoverflow.com/help/asking.
You can fake blending by doing some math here's what I came up with:
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();
}
}
And the results are:
And with:
squareX = width / 2f + 100f;
squareY = height / 4f + 150f;
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With