调整大小时使精灵重复 (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 时,不要使用 glEnable
和 glDisable
进行混合。 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 坐标。
我正在制作一个带有背景纹理的游戏,该纹理存储为精灵,因为我需要调整它的大小。但是,当我调整它的大小时,它会改变图像的纵横比而不是重复它。我传入的用于创建 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 时,不要使用 glEnable
和 glDisable
进行混合。 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 坐标。