Bagaimana Anda memindahkan sprite dalam peningkatan sub-pixel?

11

Piksel hidup atau mati. Jumlah minimum yang dapat Anda pindahkan sprite adalah satu piksel. Jadi bagaimana Anda membuat sprite bergerak lebih lambat dari 1 pixel per frame?

Cara saya melakukannya adalah menambahkan kecepatan ke variabel dan menguji apakah telah mencapai 1 (atau -1). Jika ya, maka saya akan memindahkan sprite dan mereset variabel ke 0, seperti:

update(dt):
    temp_dx += speed * dt
    temp_dy += speed * dt

    if (temp_dx > 1)
        move sprite
        reset temp_dx to 0
    if (tempy_dy > 1)
        move sprite
        reset temp_dy to 0

Saya tidak menyukai pendekatan ini karena rasanya konyol dan gerakan sprite terlihat sangat tersentak-sentak. Jadi dengan cara apa Anda menerapkan gerakan sub-pixel?

bzzr
sumber
Satu-satunya hal yang dapat Anda lakukan adalah penyaringan bilinear.
AturSams
Jika Anda bergerak kurang dari 1 px per frame, bagaimana bisa terlihat tersentak-sentak?

Jawaban:

17

Ada sejumlah opsi:

  1. Lakukan seperti yang Anda lakukan. Anda sudah mengatakan itu tidak terlihat mulus. Ada beberapa kekurangan dengan metode Anda saat ini. Untuk x, Anda dapat menggunakan yang berikut ini:

    tempx += speed * dt
    
    while (tempx > 0.5)
      move sprite to x+1
      tempx -= 1
    while (tempx < -0.5)
      move sprite to x-1
      tempx += 1
    

    ini seharusnya lebih baik. Saya telah mengganti statemen if untuk menggunakan 0,5, seperti ketika Anda lulus 0,5 Anda lebih dekat ke nilai berikutnya daripada sebelumnya. Saya telah menggunakan loop sementara untuk memungkinkan perpindahan lebih dari 1 piksel per langkah waktu (ini bukan cara terbaik untuk melakukannya, tetapi ia membuat kode yang ringkas dan mudah dibaca). Untuk objek yang bergerak lambat masih akan gelisah, karena kami belum membahas masalah mendasar penyelarasan piksel.

  2. Miliki banyak gambar untuk sprite Anda dan gunakan yang berbeda tergantung pada offset subpixel. Untuk melakukan penghalusan subpiksel hanya dalam x, misalnya, Anda dapat membuat grafik untuk sprite Anda x+0.5, dan jika posisinya berada di antara x+0.25dan x+0.75, gunakan sprite ini sebagai ganti dari aslinya. Jika Anda ingin pemosisian yang lebih halus, cukup buat lebih banyak grafis subpixel. Jika Anda melakukan ini xdan yjumlah rendering Anda dapat dengan cepat bertambah, karena jumlah yang diukur dengan kuadrat dari jumlah interval: spasi 0.5membutuhkan 4 rendering, 0.25akan membutuhkan 16.

  3. Sampel luar biasa. Ini adalah cara malas (dan berpotensi sangat mahal) untuk membuat gambar dengan resolusi subpixel. Pada dasarnya, gandakan (atau lebih) resolusi tempat Anda merender adegan, lalu turunkan saat runtime. Saya akan merekomendasikan perawatan di sini, karena kinerja dapat turun dengan cepat. Cara yang kurang agresif untuk melakukan ini adalah dengan mengganti sprite Anda, dan menurunkannya saat runtime.

  4. Seperti yang disarankan oleh Zehelvion , mungkin platform yang Anda gunakan sudah mendukung ini. Jika Anda diizinkan menentukan koordinat x dan y bukan bilangan bulat, mungkin ada opsi untuk mengubah pemfilteran tekstur. Tetangga terdekat sering merupakan default, dan ini akan menyebabkan gerakan "tersentak". Penyaringan lainnya (linier / kubik) akan menghasilkan efek yang jauh lebih halus.

Pilihan antara 2. 3. dan 4. tergantung pada bagaimana grafik Anda diimplementasikan. Gaya gambar bitmap sesuai dengan pra-render jauh lebih baik, sedangkan gaya vektor mungkin cocok dengan supersampling sprite. Jika sistem Anda mendukungnya, 4. mungkin cara yang tepat.

Jez
sumber
Mengapa mengganti ambang batas dengan 0,5 dalam opt 1? Saya juga tidak mengerti mengapa Anda memindahkan pernyataan ke loop sementara. Fungsi pembaruan dipanggil sekali per centang.
bzzr
1
Pertanyaan bagus - Saya sudah mengklarifikasi jawabannya berdasarkan komentar Anda.
Jez
Supersampling mungkin "malas", tetapi dalam banyak kasus biaya kinerja 2x atau bahkan 4x oversampling tidak akan menjadi masalah khusus, terutama jika memungkinkan aspek rendering lainnya disederhanakan.
supercat
Dalam game beranggaran besar, saya biasanya melihat 3 sebagai opsi yang tercantum dalam pengaturan grafis sebagai anti-aliasing.
Kevin
1
@ Kevin: Anda mungkin sebenarnya tidak. Sebagian besar game menggunakan multisampling , yang agak berbeda. Varian lain juga biasa digunakan, misalnya dengan cakupan yang bervariasi serta sampel kedalaman. Supersampling hampir tidak pernah dilakukan karena biaya eksekusi shader fragmen.
Imallett
14

Salah satu cara di mana banyak game skool lama memecahkan (atau menyembunyikan) masalah ini adalah dengan menghidupkan sprite.

Yaitu, jika sprite Anda akan bergerak kurang dari satu pixel per frame (atau, terutama, jika rasio pixel / frame akan menjadi sesuatu yang aneh seperti 2 pixel dalam 3 frame), Anda dapat menyembunyikan jerkiness dengan membuat n frame animasi loop itu, di atas n frame itu, akhirnya menggerakkan sprite oleh beberapa k < n piksel.

Intinya adalah, selama sprite selalu bergerak dengan cara tertentu pada setiap frame, tidak akan pernah ada frame tunggal di mana seluruh sprite tiba-tiba "menyentak" ke depan.


Saya tidak dapat menemukan sprite yang sebenarnya dari video game lama untuk menggambarkan ini (walaupun saya pikir misalnya beberapa animasi penggalian dari Lemmings seperti itu), tetapi ternyata pola "glider" dari Conway's Game of Life membuat ilustrasi yang sangat bagus:

masukkan deskripsi gambar di sini
Animasi oleh Kieff / Wikimedia Commons , digunakan di bawah lisensi CC-By-SA 3.0 .

Di sini, blok kecil piksel hitam yang merangkak merangkak ke bawah dan kanan adalah glider. Jika Anda perhatikan dengan seksama, Anda akan melihat bahwa mereka membutuhkan empat frame animasi untuk merayapi satu piksel secara diagonal, tetapi karena mereka bergerak dengan cara tertentu pada masing-masing frame tersebut, gerakannya tidak terlihat seperti tersentak-sentak (well, setidaknya tidak lagi) tersentak dari apa pun yang terlihat pada frame rate itu, sih).

Ilmari Karonen
sumber
2
Ini adalah pendekatan yang sangat baik jika kecepatannya tetap, atau jika kurang dari 1/2 pixel per frame, atau jika animasinya cukup "besar" dan cukup cepat untuk mengaburkan variasi dalam gerakan.
supercat
Ini adalah pendekatan yang baik, meskipun jika kamera harus mengikuti sprite Anda masih akan mengalami masalah karena itu tidak akan bergerak dengan lancar.
Elden Abob
6

Posisi sprite harus disimpan sebagai kuantitas titik apung, dan dibulatkan menjadi bilangan bulat tepat sebelum tampilan.

Anda juga dapat mempertahankan sprite pada resolusi super dan mengurangi sprite sebelum ditampilkan. Jika Anda mempertahankan sprite pada resolusi tampilan 3x, Anda akan memiliki 9 sprite aktual yang berbeda tergantung pada posisi subpixel dari sprite.

ddyer
sumber
3

Satu-satunya solusi nyata di sini adalah menggunakan pemfilteran bilinear. Idenya adalah membiarkan GPU menghitung nilai setiap piksel berdasarkan empat piksel sprite yang tumpang tindih dengannya. Ini adalah teknik umum dan efektif. Anda hanya perlu menempatkan sprite pada 2d-plain (papan reklame) sebagai tekstur; kemudian gunakan GPU untuk membuat dataran ini. Ini bekerja dengan baik tetapi berharap untuk mendapatkan hasil yang agak buram dan kehilangan tampilan 8-bit atau 16-bit jika Anda membidiknya.

pro:

Sudah diimplementasikan, sangat cepat, solusi berbasis perangkat keras.

kontra:

Kehilangan kesetiaan 8-bit / 16-bit.

AturSams
sumber
1

Floating point adalah cara normal untuk melakukan ini, terutama jika Anda pada akhirnya merender ke target GL di mana perangkat keras cukup senang dengan poligon berbayang dengan koordinat float.

Namun ada cara lain untuk melakukan apa yang Anda lakukan saat ini tetapi sedikit kurang tersentak: titik tetap. Nilai posisi 32 bit dapat mewakili nilai 0 hingga 4.294.967.295 meskipun layar Anda hampir pasti memiliki kurang dari 4k piksel di sepanjang sumbu mana pun. Jadi letakkan "titik biner" tetap (dengan analogi dengan titik desimal) di tengah dan Anda dapat mewakili posisi piksel dari 0-65536 dengan 16 bit resolusi subpixel lainnya. Anda kemudian dapat menambahkan angka seperti biasa, Anda hanya harus ingat untuk mengkonversi dengan menggeser 16 bit setiap kali mengkonversi ke ruang layar atau ketika Anda telah mengalikan dua angka secara bersamaan.

(Saya menulis mesin 3D dalam titik tetap 16-bit pada hari ketika saya memiliki Intel 386 tanpa unit floating point. Ia mencapai kecepatan menyilaukan 200 poligon berbayang Phong per frame.)

pjc50
sumber
1

Selain jawaban lain di sini, Anda dapat menggunakan dithering sampai batas tertentu. Dithering adalah di mana tepi piksel objek lebih terang / lebih gelap dalam warna untuk mencocokkan latar belakang membuat tepi lebih lembut. Misalnya, Anda memiliki persegi 4 piksel yang akan saya aproksimasi dengan:

OO
OO

Jika Anda memindahkan 1/2 piksel ini ke kanan, tidak ada yang benar-benar bergerak. Tetapi dengan sedikit ragu Anda bisa membuat ilusi gerakan sedikit. Pertimbangkan O sebagai hitam dan o sebagai abu-abu, jadi Anda mungkin melakukannya:

oOo
oOo

Dalam satu bingkai dan

 OO
 OO

Selanjutnya. "Kotak" sebenarnya dengan tepi abu-abu sebenarnya akan mengambil 3 piksel, tetapi karena mereka abu-abu lebih terang daripada hitam mereka tampak lebih kecil. Ini bisa sulit untuk dikodekan, namun, dan saya tidak benar-benar menyadari apa pun yang melakukan ini sekarang. Tentang waktu mereka mulai menggunakan dithering untuk resolusi hal-hal dengan cepat menjadi lebih baik dan tidak ada banyak kebutuhan kecuali dalam hal-hal seperti manipulasi gambar.

Serpardum
sumber