简体   繁体   English

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

[英]Making a sprite repeat when resized (libGDX)

I'm making a game with a background texture which is stored as a sprite because I need to resize it.我正在制作一个带有背景纹理的游戏,该纹理存储为精灵,因为我需要调整它的大小。 However, when I resize it, it changes the aspect ratio of the image instead of repeating it.但是,当我调整它的大小时,它会改变图像的纵横比而不是重复它。 The texture I passed in to create the Sprite has the wrap set to Texture.TextureWrap.Repeat.我为创建 Sprite 而传入的纹理将包裹设置为 Texture.TextureWrap.Repeat。

My current class:我目前的 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();
    }
}

Is there any way that I can change it to repeat when resizing?有什么方法可以在调整大小时将其更改为重复? If so, how?如果是这样,怎么做?

Never use batch.draw(sprite, ...) .永远不要使用batch.draw(sprite, ...) Sprites should always be drawn using sprite.draw(batch) .应始终使用sprite.draw(batch)绘制精灵。 There was an unfortunate design decision in the early days of libGDX to make Sprite a subclass of TextureRegion, which makes it possible to call batch.draw(sprite, ...) , but this should never be done.在 libGDX 的早期有一个不幸的设计决定,使 Sprite 成为 TextureRegion 的子类,这使得调用batch.draw(sprite, ...)成为可能,但绝不应该这样做。 Sprite already defines its size, scale, color, etc. When you treat it like a TextureRegion by calling batch.draw() , all that information is ignored unless you manually also pass those associated properties of the Sprite. Sprite 已经定义了它的大小、比例、颜色等。当您通过调用batch.draw()将其视为 TextureRegion 时,所有这些信息都将被忽略,除非您还手动传递 Sprite 的这些相关属性。

In my opinion, the Sprite class should almost never be used at all, because it conflates asset data (a TextureRegion) with model data (position, size, scale, rotation, etc.).在我看来,Sprite class 几乎不应该被使用,因为它将资产数据(纹理区域)与 model 数据(位置、大小、比例、旋转等)混为一谈。 It is only appropriate for situations where these attributes should be highly optimized, like in a particle system (which is how libGDX does it's own 2D particle system).它仅适用于需要高度优化这些属性的情况,例如在粒子系统中(这就是 libGDX 自己的 2D 粒子系统的方式)。

Currently you are in the danger zone of using a shared SpriteBatch without any specific projection matrix, and are not doing anything in resize() .目前,您处于使用没有任何特定投影矩阵的共享 SpriteBatch 的危险区域,并且在resize()中没有做任何事情。 This means this class completely ignores screen dimensions and it will look completely different on different devices.这意味着这个 class 完全忽略了屏幕尺寸,在不同的设备上看起来会完全不同。 Even two phones with the same size screen could look completely different if they have different DPI.如果 DPI 不同,即使是相同尺寸屏幕的两部手机看起来也会完全不同。

For this particular shader, where it just fills the screen with an effect, I think it's appropriate to use an identity matrix and let the shader size the effect.对于这个特定的着色器,它只是用一种效果填充屏幕,我认为使用单位矩阵并让着色器调整效果的大小是合适的。 You can draw your texture to cover the +/- 1 X and Y square in identity space and it will fill the screen regardless of screen dimensions.您可以绘制纹理以覆盖身份空间中的 +/- 1 X 和 Y 正方形,并且无论屏幕尺寸如何,它都会填满屏幕。

To make the texture repeat in a shader like this, you can either use U2 and V2 texture coordinates that are bigger than 1 when passing it to draw , or you can pass a uniform u_textureRepeat 2-element array for the vertex shader to multiply by the texture coordinates.要使纹理在这样的着色器中重复,您可以在将它传递给draw时使用大于 1 的 U2 和 V2 纹理坐标,或者您可以为顶点着色器传递一个统一的u_textureRepeat 2 元素数组以乘以纹理坐标。 The second way is probably easier to get right.第二种方法可能更容易正确。 It also allows you to use a different value for the second noise texture if it needs to be scaled a different number of times.如果需要缩放不同的次数,它还允许您对第二个噪波纹理使用不同的值。

You'll want some constant value for the number of repeats across or down so you can scale by screen ratio.您需要一些恒定的重复次数值,以便您可以按屏幕比例进行缩放。 You probably also want your noise scale to be handled the same way, or it will be stretched differently on different aspect ratio screens.您可能还希望以相同的方式处理您的噪声标度,或者它会在不同的纵横比屏幕上以不同的方式拉伸。

You need to bind your shader before setting its uniforms.您需要在设置其制服之前绑定您的着色器。 It might be working now, but when other shaders are in your app, it will cause problems.它现在可能正在工作,但是当您的应用程序中有其他着色器时,它会导致问题。

You need to specifically bind your noise texture to unit 1. You are currently binding it to an arbitrary unit (whatever the last thing that interacted with glActiveTexture did), and you shouldn't leave that to chance.您需要将噪声纹理专门绑定到单元 1。您当前正在将其绑定到任意单元(无论与glActiveTexture交互的最后一件事是什么),您不应该把它留给机会。

Don't use glEnable and glDisable for blending when using SpriteBatch.使用 SpriteBatch 时不要使用glEnableglDisable进行混合。 SpriteBatch already internally handles its blending. SpriteBatch 已经在内部处理了它的混合。

Pulling it all together looks something like this (I'm doing this from memory without testing so please excuse syntax errors):把它们放在一起看起来像这样(我是从 memory 开始的,没有经过测试,所以请原谅语法错误):

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
    }
}

Notice I implement Disposable.注意我实现了 Disposable。 Since this class instantiates some Disposable objects, it must be responsible also for disposing them.由于这个 class 实例化了一些 Disposable 对象,它还必须负责处理它们。 And whichever class instantiates this one must be responsible for disposing of it.并且无论哪个 class 实例化这个必须负责处理它。 Otherwise you will have memory leaks.否则您将有 memory 泄漏。

Also note it's usually easier to use shaderProgram.setUniformf than setUniformfv .另请注意,使用shaderProgram.setUniformf通常比使用setUniformfv更容易。 You only need the latter when using a single array for various unrelated parameters.当为各种不相关的参数使用单个数组时,您只需要后者。

Somewhere in your vertex shader, you should have something like v_texCoords = aTexCoord0;在你的顶点着色器的某个地方,你应该有类似v_texCoords = aTexCoord0;的东西。 You should add the uniform for uniform vec2 u_texCoordScale and on this line multiply it so it is v_texCoords = aTexCoord0 * u_texCoordScale;您应该为uniform vec2 u_texCoordScale添加统一,并在此行上将其相乘,使其为v_texCoords = aTexCoord0 * u_texCoordScale; . . If you end up needing a different scale for noise coordinates, you can create associated code in your class for calculating a different uniform for multiplying by the noise's tex coords.如果您最终需要不同的噪声坐标比例,您可以在 class 中创建相关代码,以计算不同的均匀值以乘以噪声的纹理坐标。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM