카메라를 전체 픽셀 간격으로 어떻게 이동합니까?


13

기본적으로 스프라이트가 치수를 약간만 변경해도 스프라이트가 눈에 띄게 변경되기 때문에 서브 픽셀에서 카메라가 움직이지 않게하고 싶습니다. (더 나은 용어가 있습니까?)이 게임은 선명한 픽셀 화 된 그래픽을 원하는 픽셀 아트 게임입니다. 다음은 문제를 보여주는 GIF입니다.

여기에 이미지 설명을 입력하십시오

이제 시도한 것은 다음과 같습니다. 카메라를 움직여서 현재 위치를 투사하고 (화면 좌표) 투사하거나 int로 캐스트하십시오. 그런 다음 다시 월드 좌표로 변환하여 새 카메라 위치로 사용하십시오. 내가 아는 한, 이것은 카메라를 실제 화면 좌표에 고정시켜야하며 그 일부는 아닙니다.

그러나 어떤 이유로 y새로운 지위 의 가치는 폭발적으로 증가합니다. 몇 초 만에 다음과 같이 증가합니다 334756315000.

다음은 LibGDX 위키코드를 기반으로 한 SSCCE (또는 MCVE) 입니다 .

import com.badlogic.gdx.ApplicationListener;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input;
import com.badlogic.gdx.backends.lwjgl3.Lwjgl3Application;
import com.badlogic.gdx.backends.lwjgl3.Lwjgl3ApplicationConfiguration;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.Sprite;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.math.Vector3;
import com.badlogic.gdx.utils.viewport.ExtendViewport;
import com.badlogic.gdx.utils.viewport.Viewport;


public class PixelMoveCameraTest implements ApplicationListener {

    static final int WORLD_WIDTH = 100;
    static final int WORLD_HEIGHT = 100;

    private OrthographicCamera cam;
    private SpriteBatch batch;

    private Sprite mapSprite;
    private float rotationSpeed;

    private Viewport viewport;
    private Sprite playerSprite;
    private Vector3 newCamPosition;

    @Override
    public void create() {
        rotationSpeed = 0.5f;

        playerSprite = new Sprite(new Texture("/path/to/dungeon_guy.png"));
        playerSprite.setSize(1f, 1f);

        mapSprite = new Sprite(new Texture("/path/to/sc_map.jpg"));
        mapSprite.setPosition(0, 0);
        mapSprite.setSize(WORLD_WIDTH, WORLD_HEIGHT);

        float w = Gdx.graphics.getWidth();
        float h = Gdx.graphics.getHeight();

        // Constructs a new OrthographicCamera, using the given viewport width and height
        // Height is multiplied by aspect ratio.
        cam = new OrthographicCamera();

        cam.position.set(0, 0, 0);
        cam.update();
        newCamPosition = cam.position.cpy();

        viewport = new ExtendViewport(32, 20, cam);
        batch = new SpriteBatch();
    }


    @Override
    public void render() {
        handleInput();
        cam.update();
        batch.setProjectionMatrix(cam.combined);

        Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);

        batch.begin();
        mapSprite.draw(batch);
        playerSprite.draw(batch);
        batch.end();
    }

    private static float MOVEMENT_SPEED = 0.2f;

    private void handleInput() {
        if (Gdx.input.isKeyPressed(Input.Keys.A)) {
            cam.zoom += 0.02;
        }
        if (Gdx.input.isKeyPressed(Input.Keys.Q)) {
            cam.zoom -= 0.02;
        }
        if (Gdx.input.isKeyPressed(Input.Keys.LEFT)) {
            newCamPosition.add(-MOVEMENT_SPEED, 0, 0);
        }
        if (Gdx.input.isKeyPressed(Input.Keys.RIGHT)) {
            newCamPosition.add(MOVEMENT_SPEED, 0, 0);
        }
        if (Gdx.input.isKeyPressed(Input.Keys.DOWN)) {
            newCamPosition.add(0, -MOVEMENT_SPEED, 0);
        }
        if (Gdx.input.isKeyPressed(Input.Keys.UP)) {
            newCamPosition.add(0, MOVEMENT_SPEED, 0);
        }
        if (Gdx.input.isKeyPressed(Input.Keys.W)) {
            cam.rotate(-rotationSpeed, 0, 0, 1);
        }
        if (Gdx.input.isKeyPressed(Input.Keys.E)) {
            cam.rotate(rotationSpeed, 0, 0, 1);
        }

        cam.zoom = MathUtils.clamp(cam.zoom, 0.1f, 100 / cam.viewportWidth);

        float effectiveViewportWidth = cam.viewportWidth * cam.zoom;
        float effectiveViewportHeight = cam.viewportHeight * cam.zoom;

        cam.position.lerp(newCamPosition, 0.02f);
        cam.position.x = MathUtils.clamp(cam.position.x,
                effectiveViewportWidth / 2f, 100 - effectiveViewportWidth / 2f);
        cam.position.y = MathUtils.clamp(cam.position.y,
                effectiveViewportHeight / 2f, 100 - effectiveViewportHeight / 2f);


        // if this is false, the "bug" (y increasing a lot) doesn't appear
        if (true) {
            Vector3 v = viewport.project(cam.position.cpy());
            System.out.println(v);
            v = viewport.unproject(new Vector3((int) v.x, (int) v.y, v.z));
            cam.position.set(v);
        }
        playerSprite.setPosition(newCamPosition.x, newCamPosition.y);
    }

    @Override
    public void resize(int width, int height) {
        viewport.update(width, height);
    }

    @Override
    public void resume() {
    }

    @Override
    public void dispose() {
        mapSprite.getTexture().dispose();
        batch.dispose();
    }

    @Override
    public void pause() {
    }

    public static void main(String[] args) {
        new Lwjgl3Application(new PixelMoveCameraTest(), new Lwjgl3ApplicationConfiguration());
    }
}

그리고 여기 및 sc_map.jpgdungeon_guy.png

또한이 문제를 해결하는 더 간단하고 좋은 방법에 대해 배우고 싶습니다.


"그에 대한 더 나은 용어가 있습니까?" 앨리어싱? 이 경우 MSAA (Multisample anti-aliasing)가 대안 솔루션이 될 수 있습니까?
Yousef Amar

@Paraknight 죄송합니다. 앤티 앨리어싱 (AFAIK)이 작동하지 않는 선명한 그래픽이 필요한 픽셀 화 된 게임을하고 있다는 점을 언급하지 않았습니다. 그 정보를 질문에 추가했습니다. 그래도 고마워.
Joschua

더 높은 해상도로 작업해야하는 이유가 있습니까? 그렇지 않으면 1 픽셀 해상도로 작업하여 최종 결과를 업 스케일합니까?
Felsir

@Felsir 예, 게임이 가능한 한 부드럽게 느껴지도록 화면 픽셀로 이동합니다. 전체 텍스처 픽셀을 움직이는 것은 매우 불안해 보입니다.
Joschua

답변:


22

문제는 카메라를 전체 픽셀 단위로 움직이지 않는 것입니다. 텍셀 대 픽셀 비율이 약간 정수가 아닙니다 . StackExchange에서 대답 한이 비슷한 질문에서 몇 가지 예를 빌릴 것 입니다.

여기 마리오의 두 사본이 있습니다. 둘 다 화면에서 같은 속도로 움직입니다 (전 세계에서 오른쪽으로 움직이는 스프라이트 또는 왼쪽으로 움직이는 카메라에 의해 동일하게 끝남). 아래 하나는하지 않습니다 :

마리오 스프라이트 2 개

그 이유는 상단 마리오를 1 배 1.01 배로 약간 조정했기 때문입니다. 즉, 아주 작은 분수는 더 이상 화면의 픽셀 그리드와 정렬되지 않습니다.

작은 번역 오정렬은 문제가되지 않습니다. "가장 가까운"텍스처 필터링은 특별한 작업을 수행하지 않고도 가장 가까운 텍셀에 스냅됩니다.

그러나 스케일 불일치는이 스냅이 항상 같은 방향으로 진행되는 것은 아닙니다. 한 지점에서 가장 가까운 텍셀을 찾는 픽셀은 약간 오른쪽에서 하나를 선택하고 다른 픽셀은 약간 왼쪽에서 하나를 선택합니다. 이제 텍셀 열이 생략되거나 중간에 복제되어 잔물결 또는 쉬머가 발생합니다. 스프라이트가 화면을 가로 질러 이동할 때 스프라이트 위로 이동합니다.

자세히 살펴 보겠습니다. 반투명 버섯 스프라이트가 매끄럽게 움직이는 애니메이션을 만들었습니다. 마치 서브 픽셀 해상도를 무제한으로 렌더링 할 수 있습니다. 그런 다음 가장 가까운 이웃 샘플링을 사용하여 픽셀 격자를 오버레이했습니다. 전체 픽셀의 색상은 샘플링 지점 (중앙의 점) 아래 스프라이트 부분과 일치하도록 색상이 변경됩니다.

1 : 1 스케일링

잔물결이없는 적절한 스케일의 스프라이트

스프라이트가 하위 픽셀 좌표로 부드럽게 이동하더라도 렌더링 된 이미지는 전체 픽셀을 이동할 때마다 올바르게 스냅됩니다. 이 작업을 수행하기 위해 특별한 작업을 수행 할 필요는 없습니다.

1 : 1.0625 스케일링

화면에 17x17로 렌더링 된 16x16 스프라이트, 아티팩트 표시

이 예에서 16x16 텍셀 버섯 스프라이트는 최대 17x17 화면 픽셀로 크기가 조정되었으므로 이미지의 한 부분에서 다른 부분으로 스냅이 다르게 발생하여 리플이나 웨이브가 움직일 때 늘어나거나 찌그러집니다.

따라서, 에셋의 소스 텍셀이 정수의 화면 픽셀에 매핑되도록 카메라 크기 / 시야를 조정하는 것이 요령입니다. 1 : 1 일 필요는 없습니다. 모든 정수가 훌륭하게 작동합니다.

1 : 3 스케일링

트리플 스케일로 확대

대상 해상도에 따라 약간 다르게해야합니다. 창에 맞게 크기를 조정하면 거의 항상 분수가됩니다. 특정 해상도에 따라 화면 가장자리에 약간의 패딩이 필요할 수 있습니다.


1
우선, 정말 고마워요! 이것은 훌륭한 시각화입니다. 그것만으로 찬성 투표하십시오. 슬프게도 제공된 코드에서 정수가 아닌 것을 찾을 수 없습니다. 의 크기 mapSprite는 100x100이고, The playerSprite의 크기는 1x1이고 뷰포트의 크기는 32x20입니다. 모든 것을 16의 배수로 변경해도 문제가 해결되지 않는 것 같습니다. 어떤 아이디어?
Joschua

2
모든 곳에서 정수를 사용하고 정수가 아닌 비율을 얻는 것이 전적으로 가능합니다. 17 개의 화면 픽셀에 16 개의 텍셀 스프라이트가 표시되는 예를 보자. 두 값 모두 정수이지만 비율은 다릅니다. 나는 libgdx에 대해 많이 알지 못하지만 Unity에서는 월드 단위 당 몇 개의 텍셀을 표시합니까 (예 : 스프라이트의 해상도를 월드 크기의 스프라이트로 나눈 값), 내가 표시하는 월드 단위의 수를 보았습니다 창 (카메라 크기 사용) 및 내 창에 몇 개의 화면 픽셀이 있는지. 이 3 개의 값을 연결하여 주어진 디스플레이 창 크기에 대한 텍셀 대 픽셀 비율을 계산할 수 있습니다.
DMGregory

"뷰포트의 크기는 32x20으로 설정"-창의 "클라이언트 크기"(렌더링 가능 영역)가 정수의 배수가 아닌 한, 그렇지 않다면, 뷰포트의 1 단위는 정수가 아닌 것입니다 픽셀 수
MaulingMonkey

감사. 다시 시도했습니다. window size1000x1000 (픽셀), WORLD_WIDTHx WORLD_HEIGHT는 100x100, FillViewport10x10, playerSprite1x1입니다. 슬프게도 문제는 여전히 지속됩니다.
Joschua

이러한 세부 사항은 주석 스레드에서 정렬하려고 시도하는 데 약간 도움이 될 것입니다. 게임 개발 대화방 을 시작 하시겠습니까 , 아니면 트위터 @D_M_Gregory로 메시지를 보내시겠습니까?
DMGregory

1

여기에 작은 점검표 :

  1. "현재 카메라 위치"를 "카메라 위치 목표"로 분리하십시오. 예를 들어 "현재 카메라 위치"가 1.1 인 경우 "목표 카메라 위치"를 1.0으로 설정하십시오.

카메라가 움직여야하는 경우에만 현재 카메라 위치를 변경하십시오.
조준 카메라 위치가 transform.position 이외의 위치 인 경우-카메라의 transform.position을 "aim camera position"으로 설정하십시오.

aim_camera_position = Mathf.Round(current_camera_position)

if (transform.position != aim_camera_position)
{
  transform.position = aim_camera_position;
}

문제를 해결해야합니다. 그렇지 않은 경우 : 말해주십시오. 그러나 당신도 그 일을하는 것을 고려해야합니다 :

  1. "카메라 프로젝션"을 "인쇄 학적"으로 설정하십시오 (이상적으로 증가하는 y 문제의 경우). 이제부터는 "시야"로 확대해야합니다.

  2. 카메라 회전을 90 °-단계 (0 ° 또는 90 ° 또는 180 °)로 설정

  3. 당신이 해야하는지 모르겠다면 밉맵을 생성하지 마십시오.

  4. 스프라이트에 충분한 크기를 사용하고 이중 선형 또는 삼선 형 필터를 사용하지 마십시오

  5. 5.

1 단위가 픽셀 단위가 아닌 경우 화면 해상도에 따라 달라질 수 있습니다. 시야에 액세스 할 수 있습니까? 시야에 따라 : 1 단위의 해상도가 얼마인지 알아보십시오.

float tan2Fov = tan(radians( Field of view / 2)); 
float xScrWidth = _CamNear*tan2Fov*_CamAspect * 2; 
float xScrHeight = _CamNear*tan2Fov * 2; 
float onePixel(width) = xWidth / screenResolutionX 
float onePixel(height) = yHeight / screenResolutionY 

^ 이제 onePixel (width) 및 onePixel (height)이 1.0000f가 아닌 경우 카메라를 사용하여 해당 단위를 좌우로 이동하십시오.


야! 답변 주셔서 감사합니다. 1) 이미 실제 카메라 위치와 둥근 카메라를 별도로 저장합니다. 2) 나는 LibGDX를 사용 OrthographicCamera하므로 희망적으로 그렇게 작동합니다. (당신은 Unity를 사용하고 있습니까?) 3-5) 나는 이미 내 게임에서 그 일을하고 있습니다.
Joschua

좋아, 그럼 당신은 1 픽셀 단위를 알아 내야합니다. 화면 해상도에 따라 달라질 수 있습니다. "5"를 편집했습니다. 내 답변 게시물에. 이 시도.
OC_RaizW

0

나는 libgdx에 익숙하지 않지만 다른 곳에서 비슷한 문제가 있습니다. 문제는 lerp를 사용하고 부동 소수점 값에서 잠재적으로 오류가 있다는 사실에 있다고 생각합니다. 귀하의 문제에 대한 가능한 해결책은 자신의 카메라 위치 객체를 만드는 것입니다. 이동 코드에이 객체를 사용하고 그에 따라 업데이트 한 다음 카메라의 위치를 ​​원하는 위치 객체의 가장 가까운 (정수?) 값으로 설정하십시오.


우선 통찰력을 가져 주셔서 감사합니다. 실제로 부동 소수점 오류와 관련이있을 수 있습니다. 그러나을 y사용하기 전에 (비정상적 으로 증가 하는) 문제 도있었습니다 lerp. 나는 어제 실제로 그것을 추가 하여 사람들이 카메라에 접근 할 때 스프라이트의 크기가 일정하게 유지되지 않는 다른 문제를 볼 수있었습니다.
Joschua
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.