【问题标题】:Making a sprite repeat when resized (libGDX)调整大小时使精灵重复(libGDX)
【发布时间】:2021-11-22 12:26:44
【问题描述】:

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

我现在的班级:

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

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

【问题讨论】:

    标签: java libgdx


    【解决方案1】:

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

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

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

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

    要像这样在着色器中重复纹理,您可以在将其传递给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。由于此类实例化了一些 Disposable 对象,因此它还必须负责处置它们。无论哪个类实例化这个类,都必须负责处理它。否则会出现内存泄漏。

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

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

    【讨论】:

    • 我猜u_noise_scaleu_distortion 应该是vec2s,而不是具有相同屏幕比例缩放的浮点数,如果你想让它看起来不失真不同纵横比的屏幕。
    • 感谢您提供的额外信息!使用 sprite.draw(batch) 后,精灵现在可以工作了。我也忘了添加处置。
    猜你喜欢
    • 1970-01-01
    • 2014-04-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-19
    • 2015-07-21
    相关资源
    最近更新 更多