मैं पूर्ण पिक्सेल अंतराल में कैमरा कैसे स्थानांतरित करूं?


13

मूल रूप से मैं कैमरे को उप-पिक्सल्स में जाने से रोकना चाहता हूं, जैसा कि मुझे लगता है कि यह दृश्यमान रूप से उनके आयामों को बदलने का नेतृत्व करता है यदि बस इतना थोड़ा। (क्या इसके लिए कोई बेहतर शब्द है?) ध्यान दें कि यह एक पिक्सेल-आर्ट गेम है जहाँ मैं क्रिस्प पिक्सेल ग्राफिक्स बनाना चाहता हूँ। यहाँ एक gif है जो समस्या दिखाता है:

यहाँ छवि विवरण दर्ज करें

अब मैंने जो कोशिश की वह थी: कैमरे को स्थानांतरित करना, वर्तमान स्थिति (ताकि यह स्क्रीन निर्देशांक है) को प्रोजेक्ट करें और फिर इंट को राउंड या कास्ट करें। उसके बाद इसे वापस दुनिया के निर्देशांक में परिवर्तित करें और नए कैमरे की स्थिति के रूप में उपयोग करें। जहां तक ​​मुझे पता है, यह कैमरे को वास्तविक स्क्रीन निर्देशांक पर लॉक करना चाहिए, न कि अंशों को।

किसी कारण के लिए, हालांकि, yनई स्थिति का मूल्य बस विस्फोट हो जाता है। कुछ ही सेकंड में यह कुछ बढ़ जाता है 334756315000

यहाँ एक 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) एक वैकल्पिक समाधान हो सकता है?
Yousef Amar

@Paraknight क्षमा करें, मैं यह उल्लेख करना भूल गया कि मैं एक पिक्सेलड गेम पर काम कर रहा हूं, जहां मुझे कुरकुरा ग्राफिक्स की आवश्यकता है ताकि एंटी-अलियासिंग काम न करे (AFAIK)। मैंने उस जानकारी को प्रश्न में जोड़ दिया। हालांकि धन्यवाद।
जोशुआ 17

क्या उच्च रिज़ॉल्यूशन पर काम करने का कोई कारण है, अन्यथा 1 पिक्सेल रिज़ॉल्यूशन पर काम करें और अंतिम परिणाम को बढ़ाएँ?
फेलसिर

@Felsir हां, मैं स्क्रीन पिक्सल में स्थानांतरित करता हूं, ताकि खेल जितना संभव हो उतना सहज महसूस हो। संपूर्ण बनावट पिक्सेल को हिलाने पर बहुत जलन होती है।
जोशुआ 8

जवाबों:


22

आपकी समस्या पूर्ण-पिक्सेल वेतन वृद्धि में कैमरा नहीं बढ़ रहा है। यह है कि आपका टेक्सेल-टू-पिक्सेल अनुपात थोड़ा गैर-पूर्णांक हैमैं इसी तरह के सवाल से कुछ उदाहरण उधार लूंगा जिसका जवाब मैंने StackExchange पर दिया था

यहां मारियो की दो प्रतियां हैं - दोनों एक ही दर पर स्क्रीन के पार जा रहे हैं (या तो दुनिया में दाहिने तरफ घूमते हुए, या कैमरे को बाएं घुमाते हुए - यह समतुल्य समाप्त होता है), लेकिन केवल शीर्ष इन तरंग कलाकृतियों को दिखाता है और नीचे एक नहीं है:

दो मारियो स्प्राइट

इसका कारण यह है कि मैंने 1.01x के 1 कारक द्वारा शीर्ष मारियो को थोड़ा बढ़ाया है - अतिरिक्त छोटे अंश का मतलब है कि वह अब स्क्रीन के पिक्सेल ग्रिड के साथ नहीं है।

एक छोटे से अनुवाद संबंधी मिसलिग्न्मेंट में कोई समस्या नहीं है - "निकटतम" बनावट फ़िल्टरिंग कुछ विशेष कार्य किए बिना, वैसे भी निकटतम टेक्सल पर स्नैप करेगा।

लेकिन एक पैमाने पर बेमेल का मतलब है कि यह तड़क हमेशा एक ही दिशा में नहीं है। एक जगह पर एक पिक्सेल जो सबसे नज़दीकी टेक्सेल को देखता है, एक को थोड़ा दाईं ओर ले जाएगा, जबकि दूसरा पिक्सेल बाईं ओर एक को थोड़ा सा उठाएगा - और अब टेक्सल्स का एक कॉलम छोड़ दिया गया है या इसके बीच में डुप्लिकेट किया गया है, जिससे एक तरंग या झिलमिलाता है स्प्राइट के ऊपर से चलता है क्योंकि यह स्क्रीन के पार जाता है।

यहाँ एक करीब देखो। मैंने एनिमेटेड ट्रांसलूसेंट मशरूम स्प्राइट को आसानी से घुमाया है, हालांकि हम इसे असीमित उप-पिक्सेल रिज़ॉल्यूशन के साथ प्रस्तुत कर सकते हैं। फिर मैंने निकटतम-पड़ोसी नमूने का उपयोग करके एक पिक्सेल ग्रिड को ओवरलैड किया है। नमूना बिंदु (केंद्र में डॉट) के तहत स्प्राइट के हिस्से से मिलान करने के लिए पूरा पिक्सेल रंग बदलता है।

1: 1 स्केलिंग

एक ठीक से फैला हुआ स्प्राइट बिना किसी लहर के साथ घूम रहा है

भले ही स्प्राइट आसानी से उप-पिक्सेल निर्देशांक में चला जाता है, लेकिन इसकी रेंडर की गई छवि अभी भी सही ढंग से स्नैक्स करती है, हर बार जब यह पूर्ण पिक्सेल की यात्रा करती है। इस काम को करने के लिए हमें कुछ खास करने की जरूरत नहीं है।

1: 1.0625 स्केलिंग

16x16 स्प्राइट ने कलाकृतियों को दिखाते हुए 17x17 को स्क्रीन पर प्रस्तुत किया

इस उदाहरण में, 16x16 टेक्सल मशरूम स्प्राइट को 17x17 स्क्रीन पिक्सल्स तक स्केल किया गया है, और इसलिए स्नैप छवि के एक हिस्से से दूसरे हिस्से में अलग-अलग होता है, जिससे लहर या स्क्वैश फैलता है और स्क्वैश होता है।

तो, चाल अपने कैमरे के आकार / देखने के क्षेत्र को समायोजित करने के लिए है ताकि आपकी संपत्ति का स्रोत स्क्रीन पिक्सल के पूर्णांक संख्या में मैप हो। यह 1: 1 होना जरूरी नहीं है - कोई भी पूरी संख्या बढ़िया काम करती है:

1: 3 स्केलिंग

ट्रिपल स्केल पर ज़ूम इन करें

आपको अपने लक्ष्य रिज़ॉल्यूशन के आधार पर इसे थोड़ा अलग ढंग से करने की आवश्यकता होगी - बस खिड़की को फिट करने के लिए स्केलिंग करना लगभग हमेशा एक भिन्नात्मक पैमाने का परिणाम होगा। कुछ प्रस्तावों के लिए स्क्रीन के किनारों पर कुछ पैडिंग की आवश्यकता हो सकती है।


1
सबसे पहले, बहुत बहुत धन्यवाद! यह एक महान दृश्य है। उस अकेले के लिए एक उत्थान है। हालांकि, अफसोस की बात है कि मुझे दिए गए कोड में कुछ भी नहीं मिला। का आकार mapSprite100x100 है, यह playerSprite1x1 के आकार पर सेट है और व्यूपोर्ट 32x20 के आकार पर सेट है। यहां तक ​​कि 16 के गुणकों में सब कुछ बदलने से समस्या हल नहीं होती है। कोई विचार?
जोसुआ

2
पूर्णांक हर जगह होना संभव है और अभी भी एक गैर-पूर्णांक अनुपात प्राप्त होता है। 17 स्क्रीन पिक्सल पर प्रदर्शित किए जा रहे 16 टेक्सल स्प्राइट का उदाहरण लें। दोनों मान पूर्णांक हैं, लेकिन उनका अनुपात नहीं है। मुझे libgdx के बारे में ज्यादा जानकारी नहीं है, लेकिन यूनिटी में मैं देखूंगा कि मैं प्रति यूनिट कितने टेक्सल्स प्रदर्शित कर रहा हूं (उदाहरण के लिए स्प्राइट के विश्व आकार से विभाजित स्प्राइट का रिज़ॉल्यूशन), मैं कितनी विश्व इकाइयों को प्रदर्शित कर रहा हूं विंडो (कैमरे के आकार का उपयोग करके), और मेरी स्क्रीन पर कितने स्क्रीन पिक्सेल हैं। उन 3 मूल्यों को परिभाषित करते हुए, हम किसी दिए गए डिस्प्ले विंडो के आकार के लिए टेक्सेल-टू-पिक्सेल अनुपात का काम कर सकते हैं।
DMGregory

"और व्यूपोर्ट 32x20 के आकार पर सेट है" - जब तक कि आपकी विंडो का "क्लाइंट आकार" (रेंडर करने योग्य क्षेत्र) इस का एक पूर्णांक एकाधिक नहीं है, जो मुझे लगता है कि यह नहीं है, आपके व्यूपोर्ट में 1 इकाई कुछ गैर-पूर्णांक होगी पिक्सेल की संख्या।
MaulingMonkey 16

धन्यवाद। मैंने फिर से कोशिश की है। window size1000x1000 (पिक्सेल) है, WORLD_WIDTHx WORLD_HEIGHT100x100 है, FillViewport10x10 है, playerSprite1x1 है। अफसोस की बात है, समस्या अभी भी बनी हुई है।
जोसुआ

कमेंट थ्रेड में छाँटने की कोशिश करने के लिए ये विवरण थोड़ा बहुत होने जा रहे हैं। क्या आप गेम डेवलपमेंट चैट रूम शुरू करना चाहते हैं, या ट्विटर @D_M_Gregory पर मुझे सीधे संदेश भेज सकते हैं?
DMGregory

1

यहाँ एक छोटी सूची है:

  1. "लक्ष्य कैमरा स्थिति" के साथ "वर्तमान कैमरा स्थिति" को अलग करें। (जैसे ndenarodev का उल्लेख किया गया है) यदि उदाहरण के लिए "वर्तमान कैमरा स्थिति" 1.1 है - "लक्ष्य कैमरा स्थिति" 1.0 बनाएं

और केवल वर्तमान कैमरा स्थिति को बदल दें यदि कैमरा हिलना चाहिए।
यदि लक्ष्य कैमरा की स्थिति ट्रांसफ़ॉर्मेशन की तुलना में कुछ और है तो - "ट्रांस कैमरा स्थिति" सेट करें।

aim_camera_position = Mathf.Round(current_camera_position)

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

इससे आपकी समस्या का समाधान हो जाना चाहिए। यदि नहीं: मुझे बताएं हालाँकि आपको उन चीजों को करने पर भी विचार करना चाहिए:

  1. "कैमरा प्रोजेक्शन" को "ऑर्टोग्राफ़िक" (आपकी असामान्य रूप से बढ़ती वाई-समस्या के लिए) सेट करें (अब से, आपको केवल "फ़ील्ड ऑफ़ व्यू" के साथ ज़ूम करना चाहिए)

  2. कैमरा रोटेशन को 90 ° में सेट करें - चरण (0 ° या 90 ° या 180 °)

  3. यदि आप नहीं जानते कि अगर आपको पता होना चाहिए, तो mipmaps उत्पन्न न करें।

  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 

^ और अब अगर वनपिक्सल (चौड़ाई) और वनपिक्सल (ऊंचाई) 1.0000 एफ नहीं हैं, तो बस उन यूनिटों को अपने कैमरे से बाएं और दाएं घुमाएं।


अरे! आपके उत्तर के लिए धन्यवाद। 1) मैं पहले से ही वास्तविक कैमरा पोजीशन और राउंडेड को अलग से स्टोर करता हूँ। 2) मैं LibGDX का उपयोग करता हूं OrthographicCameraताकि उम्मीद है कि इस तरह काम करना चाहिए। (आप एकता का उपयोग कर रहे हैं, ठीक है?) 3-5) मैं पहले से ही अपने खेल में ऐसा करता हूं।
जोसुआ

ठीक है, फिर आपको यह पता लगाना होगा कि 1 पिक्सेल कितनी इकाइयाँ हैं। यह स्क्रीन रिज़ॉल्यूशन पर निर्भर हो सकता है। मैंने "5." का संपादन किया मेरे उत्तर पोस्ट में। इसे इस्तेमाल करे।
OC_RaizW 10

0

मैं libgdx से परिचित नहीं हूं, लेकिन अन्य जगहों पर भी ऐसी ही समस्याएं हैं। मुझे लगता है कि समस्या इस तथ्य में निहित है कि आप अस्थायी बिंदु मानों में lerp और संभावित त्रुटि का उपयोग कर रहे हैं। मुझे लगता है कि आपकी समस्या का एक संभावित समाधान अपनी खुद की कैमरा लोकेशन ऑब्जेक्ट बनाना है। अपने आंदोलन कोड के लिए इस ऑब्जेक्ट का उपयोग करें और तदनुसार अपडेट करें और फिर अपने स्थान ऑब्जेक्ट के निकटतम वांछित (पूर्णांक?) मान पर कैमरे की स्थिति निर्धारित करें।


सबसे पहले, आपकी अंतर्दृष्टि के लिए धन्यवाद। यह वास्तव में फ्लोटिंग पॉइंट त्रुटियों के साथ कुछ कर सकता है। हालाँकि समस्या ( yअसामान्य रूप से बढ़ने की) भी मेरे द्वारा उपयोग किए जाने से पहले मौजूद थी lerp। मैंने वास्तव में इसे कल जोड़ा ताकि लोग दूसरी समस्या को भी देख सकें, जहां कैमरा आ रहा है, स्प्राइट्स का आकार स्थिर नहीं रहता है।
जोसुआ
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.