Làm cách nào để di chuyển máy ảnh trong các khoảng thời gian pixel đầy đủ?


13

Về cơ bản, tôi muốn ngăn máy ảnh di chuyển trong các ảnh con, vì tôi nghĩ rằng điều này dẫn đến các họa tiết thay đổi rõ rệt kích thước của chúng nếu chỉ hơi quá. (Có thuật ngữ nào tốt hơn cho điều đó không?) Lưu ý rằng đây là một trò chơi nghệ thuật pixel mà tôi muốn có đồ họa pixel sắc nét. Đây là một gif cho thấy vấn đề:

nhập mô tả hình ảnh ở đây

Bây giờ những gì tôi đã thử là: Di chuyển máy ảnh, chiếu vị trí hiện tại (vì vậy đó là tọa độ màn hình) và sau đó làm tròn hoặc chuyển sang int. Sau đó chuyển đổi nó trở lại tọa độ thế giới và sử dụng nó làm vị trí máy ảnh mới. Theo tôi biết, điều này sẽ khóa máy ảnh với tọa độ màn hình thực tế, không phải phân số của chúng.

Tuy nhiên, vì một số lý do, ygiá trị của vị trí mới chỉ bùng nổ. Trong một vài giây, nó tăng lên một cái gì đó như 334756315000.

Đây là SSCCE (hoặc MCVE) dựa trên mã trong wiki LibGDX :

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

và đây là sựsc_map.jpgcácdungeon_guy.png

Tôi cũng muốn tìm hiểu về các cách đơn giản hơn và / hoặc tốt hơn để khắc phục vấn đề này.


"Có một thuật ngữ tốt hơn cho điều đó?" Răng cưa? Trong trường hợp đó, khử răng cưa nhiều lớp (MSAA) có thể là một giải pháp thay thế?
Yousef Amar

@Paraknight Xin lỗi, tôi đã quên đề cập rằng tôi đang làm việc trên một trò chơi pixel, nơi tôi cần đồ họa sắc nét để khử răng cưa sẽ không hoạt động (AFAIK). Tôi đã thêm thông tin đó vào câu hỏi. Cảm ơn mặc dù.
Joschua

Có lý do để làm việc ở độ phân giải cao hơn, nếu không thì làm việc ở độ phân giải 1 pixel và nâng cao kết quả cuối cùng?
Felsir

@Felsir Có, tôi di chuyển trong các pixel màn hình, để trò chơi cảm thấy mượt mà nhất có thể. Di chuyển toàn bộ pixel kết cấu trông rất bồn chồn.
Joschua

Câu trả lời:


21

Vấn đề của bạn không phải là di chuyển máy ảnh theo gia số pixel đầy đủ. Đó là tỷ lệ texel-pixel của bạn hơi không nguyên . Tôi sẽ mượn một số ví dụ từ câu hỏi tương tự này, tôi đã trả lời trên StackExchange .

Đây là hai bản sao của Mario - cả hai đều di chuyển trên màn hình với cùng tốc độ (bằng cách các họa tiết di chuyển sang phải trên thế giới hoặc máy ảnh di chuyển sang trái - kết thúc tương đương), nhưng chỉ có phần trên cùng hiển thị các tạo tác gợn sóng này và dưới cùng không:

Hai họa tiết Mario

Lý do là vì tôi đã tăng nhẹ tỷ lệ Mario hàng đầu lên 1 nhân tố là 1,01x - phần rất nhỏ đó có nghĩa là anh ta không còn xếp hàng với lưới pixel của màn hình.

Một bản dịch nhỏ sai lệch không phải là vấn đề - Bộ lọc kết cấu "Gần nhất" sẽ nhanh chóng chuyển sang texel gần nhất mà không cần chúng tôi làm gì đặc biệt.

Nhưng sự không phù hợp về quy mô có nghĩa là việc chụp nhanh này không phải luôn theo cùng một hướng. Tại một điểm, một pixel nhìn lên texel gần nhất sẽ chọn một chút ở bên phải, trong khi một pixel khác chọn một chút ở bên trái - và bây giờ một cột texels đã bị bỏ qua hoặc sao chép ở giữa, tạo ra một gợn sóng hoặc lung linh di chuyển qua sprite khi nó di chuyển trên màn hình.

Đây là một cái nhìn gần hơn. Tôi đã tạo ra một sprite nấm mờ di chuyển trơn tru, như thể chúng ta có thể kết xuất nó với độ phân giải pixel phụ không giới hạn. Sau đó, tôi đã phủ một lưới pixel bằng cách lấy mẫu lân cận gần nhất. Toàn bộ pixel thay đổi màu sắc để phù hợp với phần của sprite dưới điểm lấy mẫu (dấu chấm ở giữa).

Chia tỷ lệ 1: 1

Một sprite đúng tỷ lệ di chuyển không có gợn

Mặc dù sprite di chuyển mượt mà đến tọa độ pixel phụ, hình ảnh được hiển thị của nó vẫn chụp chính xác mỗi khi nó di chuyển một pixel đầy đủ. Chúng ta không cần phải làm gì đặc biệt để thực hiện công việc này.

Tỷ lệ 1: 1.0625

Sprite 16x16 hiển thị 17x17 trên màn hình, hiển thị các tạo tác

Trong ví dụ này, sprite nấm 16x16 texel đã được thu nhỏ lên tới 17x17 pixel màn hình, và do đó, việc chụp nhanh xảy ra khác nhau từ một phần của hình ảnh sang phần khác, tạo ra gợn sóng hoặc sóng kéo dài và đè bẹp nó khi nó di chuyển.

Vì vậy, mẹo là điều chỉnh kích thước / trường nhìn của máy ảnh để ánh xạ texels nguồn của tài sản của bạn thành số nguyên pixel màn hình. Nó không phải là 1: 1 - bất kỳ số nào cũng hoạt động tốt:

Chia tỷ lệ 1: 3

Phóng to theo tỷ lệ ba

Bạn sẽ cần phải làm điều này một chút khác nhau tùy thuộc vào độ phân giải mục tiêu của bạn - chỉ cần mở rộng để phù hợp với cửa sổ sẽ hầu như luôn dẫn đến một tỷ lệ phân đoạn. Một số lượng đệm ở các cạnh của màn hình có thể cần thiết cho một số độ phân giải nhất định.


1
Trước hết, cảm ơn rất nhiều! Đây là một hình dung tuyệt vời. Có một upvote cho điều đó một mình. Đáng buồn thay, tôi không thể tìm thấy bất cứ điều gì không phải là Integer trong mã được cung cấp. Kích thước của mapSprite100x100, The playerSpriteđược đặt thành kích thước 1x1 và chế độ xem được đặt thành kích thước 32x20. Ngay cả việc thay đổi mọi thứ thành bội số của 16 dường như không giải quyết được vấn đề. Có ý kiến ​​gì không?
Joschua

2
Hoàn toàn có thể có số nguyên ở mọi nơi và vẫn có tỷ lệ không nguyên. Lấy ví dụ về sprite 16 texel được hiển thị trên 17 pixel màn hình. Cả hai giá trị là số nguyên, nhưng tỷ lệ của chúng thì không. Tôi không biết nhiều về libgdx, nhưng trong Unity tôi sẽ xem tôi hiển thị bao nhiêu texels trên mỗi đơn vị thế giới (ví dụ: độ phân giải của sprite chia cho kích thước thế giới của sprite), có bao nhiêu đơn vị thế giới tôi đang hiển thị trong cửa sổ (sử dụng kích thước của máy ảnh) và có bao nhiêu pixel màn hình trên cửa sổ của tôi. Xâu chuỗi 3 giá trị đó, chúng ta có thể tính ra tỷ lệ texel-pixel cho kích thước cửa sổ hiển thị nhất định.
DMGregory

"và chế độ xem được đặt thành kích thước 32x20" - trừ khi "kích thước máy khách" (vùng hiển thị) của cửa sổ của bạn là bội số của số này, mà tôi đoán là không, 1 đơn vị trong chế độ xem của bạn sẽ không phải là số nguyên số pixel.
MaendingMonkey

Cảm ơn. Tôi đã thử lại. window sizelà 1000x1000 (pixel), WORLD_WIDTHx WORLD_HEIGHTlà 100x100, FillViewportlà 10 x 10, playerSpritelà 1x1. Đáng buồn thay, vấn đề vẫn còn tồn tại.
Joschua

Những chi tiết này sẽ hơi nhiều để cố gắng sắp xếp trong một luồng nhận xét. Bạn có muốn bắt đầu một phòng Trò chuyện Phát triển Trò chơi hoặc nhắn tin trực tiếp cho tôi trên Twitter @D_M_Gregory không?
DMGregory

1

đây là một danh sách kiểm tra nhỏ:

  1. Sắp xếp "vị trí camera hiện tại" với "vị trí camera ngắm". (như ndenarodev đã đề cập) Nếu ví dụ "vị trí camera hiện tại" là 1.1 - hãy tạo "vị trí camera ngắm" 1.0

Và chỉ thay đổi vị trí camera hiện tại nếu camera nên di chuyển.
Nếu vị trí máy ảnh nhắm là một cái gì đó khác với biến đổi.poseition - đặt biến đổi.poseition (của máy ảnh của bạn) thành "vị trí máy ảnh nhắm".

aim_camera_position = Mathf.Round(current_camera_position)

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

điều này sẽ giải quyết vấn đề của bạn. Nếu không: nói cho tôi biết. Tuy nhiên, bạn cũng nên xem xét làm những điều đó:

  1. đặt "trình chiếu camera" thành "ortographic" (đối với vấn đề y tăng bất thường của bạn) (từ giờ trở đi, bạn chỉ nên phóng to với "Trường nhìn")

  2. đặt xoay camera theo 90 ° - các bước (0 ° hoặc 90 ° hoặc 180 °)

  3. không tạo ra mipmaps nếu bạn không biết nếu bạn nên.

  4. sử dụng đủ kích thước cho các họa tiết của bạn và không sử dụng bộ lọc song tuyến hoặc bộ ba

  5. 5.

Nếu 1 đơn vị không phải là đơn vị pixel thì có thể phụ thuộc vào độ phân giải màn hình của nó. Bạn có quyền truy cập vào lĩnh vực xem? Tùy thuộc vào lĩnh vực xem của bạn: Tìm hiểu độ phân giải là 1 đơn vị.

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 

^ Và bây giờ nếu onePixel (chiều rộng) và onePixel (chiều cao) không phải là 1.0000f, chỉ cần di chuyển trong các đơn vị đó sang trái và phải bằng máy ảnh của bạn.


Chào! Cảm ơn câu trả lời của bạn. 1) Tôi đã lưu trữ vị trí máy ảnh thực tế và vị trí được làm tròn riêng biệt. 2) Tôi sử dụng LibGDX OrthographicCamerađể hy vọng sẽ hoạt động như vậy. (Bạn đang sử dụng Unity, phải không?) 3-5) Tôi đã thực hiện những điều đó trong trò chơi của mình.
Joschua

Được rồi - bạn cần tìm hiểu xem đơn vị 1 pixel là bao nhiêu. nó có thể là độ phân giải màn hình phụ thuộc. Tôi đã chỉnh sửa "5." trong bài trả lời của tôi. thử đi.
OC_RaizW

0

Tôi không quen thuộc với libgdx nhưng gặp vấn đề tương tự ở nơi khác. Tôi nghĩ vấn đề nằm ở chỗ bạn đang sử dụng lerp và có khả năng bị lỗi trong các giá trị dấu phẩy động. Tôi nghĩ rằng một giải pháp khả thi cho vấn đề của bạn là tạo đối tượng vị trí máy ảnh của riêng bạn. Sử dụng đối tượng này cho mã chuyển động của bạn và cập nhật nó cho phù hợp và sau đó đặt vị trí của máy ảnh thành giá trị mong muốn (số nguyên?) Gần nhất của đối tượng vị trí của bạn.


Trước hết, cảm ơn sự sáng suốt của bạn. Nó thực sự có thể có một cái gì đó để làm với lỗi dấu phẩy động. Tuy nhiên, vấn đề ( ytăng bất thường) cũng xuất hiện trước khi tôi sử dụng lerp. Tôi thực sự đã thêm nó vào ngày hôm qua để mọi người cũng có thể thấy vấn đề khác, trong đó kích thước của các họa tiết không thay đổi khi máy ảnh đang đến gần.
Joschua
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.