调整大小时使精灵重复 (libGDX)

Making a sprite repeat when resized (libGDX)

我正在制作一个带有背景纹理的游戏,该纹理存储为精灵,因为我需要调整它的大小。但是,当我调整它的大小时,它会改变图像的纵横比而不是重复它。我传入的用于创建 Sprite 的纹理将 wrap 设置为 Texture.TextureWrap.Repeat.

我现在的 class:

package com.lance.seajam;

import com.badlogic.gdx.ApplicationAdapter;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.Sprite;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.glutils.ShaderProgram;

public class Water extends ApplicationAdapter {
    private final Texture waterTexture;
    private final Texture noiseTexture;
    private SpriteBatch batch;
    private Sprite sprite;
    private ShaderProgram shaderProgram;

    private String vertexShaderString = Gdx.files.internal("shaders/water/mainvs.glsl").readString();
    private String fragmentShaderString = Gdx.files.internal("shaders/water/mainfs.glsl").readString();

    private float[] floatArrOf(float... A) {
        return A;
    }


    private void compileShader() {
        shaderProgram = new ShaderProgram(vertexShaderString, fragmentShaderString);
        if (!shaderProgram.isCompiled()) {
            System.out.println(shaderProgram.getLog());
        }
    }

    public Water(SpriteBatch batch, String imgDir) {
        this.batch = batch;
        waterTexture = new Texture(Gdx.files.internal(imgDir + "/water.png"));
        noiseTexture = new Texture(Gdx.files.internal(imgDir + "/noise.png"));

        noiseTexture.setWrap(Texture.TextureWrap.Repeat, Texture.TextureWrap.Repeat); // make the texture repeat
        waterTexture.setWrap(Texture.TextureWrap.Repeat, Texture.TextureWrap.Repeat); // make the texture repeat

        waterTexture.setFilter(Texture.TextureFilter.Linear, Texture.TextureFilter.Linear);
        noiseTexture.setFilter(Texture.TextureFilter.Linear, Texture.TextureFilter.Linear);

        sprite = new Sprite(waterTexture);
        sprite.setSize((float) Gdx.graphics.getWidth(), (float) Gdx.graphics.getHeight()); // setting sprite size to graphics size

        compileShader();
    }

    float time = 0f;
    public void Draw() {
        shaderProgram.setUniformf("u_noise_scale", 0.1f);
        shaderProgram.setUniform2fv("u_noise_scroll_velocity", floatArrOf(0.004f, 0.003f), 0, 2);
        shaderProgram.setUniformf("u_distortion", 0.04f);
        shaderProgram.setUniformf("u_time", time);

        noiseTexture.bind();

        batch.begin();
            time += Gdx.graphics.getDeltaTime(); // Gets how much seconds has passed

            Gdx.gl.glEnable(GL20.GL_BLEND);

            batch.setShader(shaderProgram);
            batch.draw(sprite, sprite.getX(), sprite.getY(), sprite.getWidth(), sprite.getHeight());

            Gdx.gl.glDisable(GL20.GL_BLEND);
        batch.end();
    }
}

有什么方法可以在调整大小时将其更改为重复?如果可以,怎么做?

永远不要使用 batch.draw(sprite, ...)。精灵应始终使用 sprite.draw(batch) 绘制。 libGDX 早期有一个不幸的设计决定,使 Sprite 成为 TextureRegion 的子 class,这使得调用 batch.draw(sprite, ...) 成为可能,但永远不应该这样做。 Sprite 已经定义了它的大小、比例、颜色等。当您通过调用 batch.draw() 将其视为 TextureRegion 时,所有这些信息都将被忽略,除非您还手动传递 Sprite 的那些相关属性。

在我看来,Sprite class 几乎不应该被使用,因为它将资产数据(TextureRegion)与模型数据(位置、大小、比例、旋转等)混为一谈。它仅适用于应该高度优化这些属性的情况,例如在粒子系统中(libGDX 它自己的 2D 粒子系统就是这样做的)。

目前您处于使用没有任何特定投影矩阵的共享 SpriteBatch 的危险区域,并且在 resize() 中没有做任何事情。这意味着这个 class 完全忽略了屏幕尺寸,并且在不同的设备上看起来完全不同。如果 DPI 不同,即使两部屏幕尺寸相同的手机看起来也会完全不同。

对于这个特定的着色器,它只是用一种效果填充屏幕,我认为使用单位矩阵并让着色器调整效果大小是合适的。您可以绘制纹理以覆盖标识中的 +/- 1 X 和 Y 方块 space,无论屏幕尺寸如何,它都会填满屏幕。

要像这样在着色器中重复纹理,您可以在将其传递给 draw 时使用大于 1 的 U2 和 V2 纹理坐标,或者您可以传递统一的 u_textureRepeat 用于顶点着色器乘以纹理坐标的 2 元素数组。第二种方法可能更容易正确。如果第二个噪声纹理需要缩放不同的次数,它还允许您使用不同的值。

您需要一些恒定值来表示横向或向下的重复次数,以便您可以按屏幕比例进行缩放。您可能还希望以相同的方式处理噪声标度,否则它会在不同宽高比屏幕上以不同方式拉伸。

您需要在设置制服之前绑定您的着色器。它现在可能工作正常,但是当其他着色器在您的应用程序中时,它会导致问题。

您需要专门将噪声纹理绑定到单元 1。您当前正在将其绑定到任意单元(无论与 glActiveTexture 交互的最后一件事做了什么),您不应该将其留给机会.

在使用 SpriteBatch 时,不要使用 glEnableglDisable 进行混合。 SpriteBatch 已在内部处理其混合。

把它们放在一起看起来像这样(我是凭记忆做的,没有测试所以请原谅语法错误):

public class Water extends ApplicationAdapter implements Disposable {
    private static final float TEXTURE_REPEATS_HORIZONTAL = 5f;
    private static final float NOISE_SPEED_HORIZONTAL = 0.004f;
    private static final Matrix4 IDT = new Matrix4();

    private final Texture waterTexture;
    private final Texture noiseTexture;
    private final SpriteBatch batch;
    private final ShaderProgram shaderProgram;

    private final String vertexShaderString = Gdx.files.internal("shaders/water/mainvs.glsl").readString();
    private final String fragmentShaderString = Gdx.files.internal("shaders/water/mainfs.glsl").readString();

    private void compileShader() {
        shaderProgram = new ShaderProgram(vertexShaderString, fragmentShaderString);
        if (!shaderProgram.isCompiled()) {
            System.out.println(shaderProgram.getLog());
        }
    }

    public Water(SpriteBatch batch, String imgDir) {
        this.batch = batch;
        waterTexture = new Texture(Gdx.files.internal(imgDir + "/water.png"));
        noiseTexture = new Texture(Gdx.files.internal(imgDir + "/noise.png"));

        noiseTexture.setWrap(Texture.TextureWrap.Repeat, Texture.TextureWrap.Repeat); // make the texture repeat
        waterTexture.setWrap(Texture.TextureWrap.Repeat, Texture.TextureWrap.Repeat); // make the texture repeat

        waterTexture.setFilter(Texture.TextureFilter.Linear, Texture.TextureFilter.Linear);
        noiseTexture.setFilter(Texture.TextureFilter.Linear, Texture.TextureFilter.Linear);

        compileShader();
    }

    @Override
    public void dispose() {
        waterTexture.dispose();
        noiseTexture.dispose();
        shaderProgram.dispose();
    }

    float time = 0f;
    public void Draw() {
        time += Gdx.graphics.getDeltaTime(); // Gets how much seconds has passed

        shaderProgram.bind();

        float screenRatio = ((float)Gdx.graphics.getWidth()) / Gdx.graphics.getHeight();
        shaderProgram.setUniform2f("u_texCoordScale", TEXTURE_REPEATS_HORIZONTAL, TEXTURE_REPEATS_HORIZONTAL / screenRatio);
        shaderProgram.setUniformf("u_noise_scale", 0.1f);
        shaderProgram.setUniform2f("u_noise_scroll_velocity", NOISE_SPEED_HORIZONTAL, NOISE_SPEED_HORIZONTAL / screenRatio);
        shaderProgram.setUniformf("u_distortion", 0.04f);
        shaderProgram.setUniformf("u_time", time);

        noiseTexture.bind(1);

        batch.begin();
            batch.setProjectionMatrix(IDT);
            batch.enableBlending();
            batch.setShader(shaderProgram);
            batch.draw(waterTexture, -1f, 1f, 2f, -2f); // screen-covering square for identity matrix
        batch.end();
        batch.setShader(null); // reset to default for other classes that use same batch
    }
}

注意我实现了 Disposable。由于此 class 实例化了一些 Disposable 对象,因此它也必须负责处理它们。并且 class 实例化这个的任何一个都必须负责处理它。否则你会发生内存泄漏。

另请注意,使用 shaderProgram.setUniformf 通常比 setUniformfv 更容易。当对各种不相关的参数使用单个数组时,您只需要后者。

在你的顶点着色器的某个地方,你应该有类似 v_texCoords = aTexCoord0; 的东西你应该为 uniform vec2 u_texCoordScale 添加 uniform 并在这条线上乘以它所以它是 v_texCoords = aTexCoord0 * u_texCoordScale;。如果您最终需要不同比例的噪声坐标,您可以在 class 中创建相关代码,用于计算不同的制服以乘以噪声的 tex 坐标。