Bagaimana menghindari tekstur yang berdarah pada tekstur atlas?

46

Di gim saya ada medan mirip Minecraft yang terbuat dari kubus. Saya menghasilkan buffer vertex dari data voxel dan menggunakan atlas tekstur untuk tampilan blok yang berbeda:

tekstur atlas untuk medan berbasis voxel

Masalahnya adalah bahwa tekstur kubus jauh interpolasi dengan ubin yang berdekatan di atlas tekstur. Itu menghasilkan garis-garis warna yang salah di antara kubus (Anda mungkin perlu melihat tangkapan layar di bawah dalam ukuran penuh untuk melihat kekurangan grafis):

tangkapan layar medan dengan garis-garis berwarna salah

Untuk saat ini saya menggunakan pengaturan interpolasi ini tetapi saya mencoba setiap kombinasi dan bahkan GL_NEARESTtanpa memetakan tidak memberikan hasil yang lebih baik.

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

Saya juga mencoba menambahkan offset dalam koordinat tekstur untuk memilih area ubin yang sedikit lebih kecil tetapi karena efek yang tidak diinginkan tergantung pada jarak ke kamera ini tidak dapat menyelesaikan masalah sepenuhnya. Di kejauhan, garis-garis itu tetap terjadi.

Bagaimana saya bisa mengatasi pendarahan tekstur ini? Karena menggunakan atlas tekstur adalah teknik yang populer, mungkin ada pendekatan umum. Sayangnya, untuk beberapa alasan yang dijelaskan dalam komentar, saya tidak dapat mengubah tekstur atau array tekstur yang berbeda.

danijar
sumber
4
Masalahnya terletak pada pemetaan-pemetaan - koordinat tekstur Anda mungkin bekerja dengan baik untuk tingkat pemetaan terbesar, tetapi saat keadaan semakin jauh, ubin-ubin yang berbatasan menyatu. Anda dapat melawan ini dengan tidak menggunakan mipmaps (mungkin bukan ide yang baik), menumbuhkan zona pelindung Anda (area antara ubin), menumbuhkan zona penjaga secara dinamis dalam shader berdasarkan tingkat mip (rumit), melakukan sesuatu yang aneh saat membuat mipmaps (dapat Namun, saya tidak memikirkan apa pun) .. Saya sendiri belum pernah mengatasi masalah ini, tetapi ada beberapa hal yang harus dimulai dengan ... =)
Jari Komppa
Seperti yang saya katakan dengan sedih mematikan mipmapping tidak menyelesaikan masalah. Tanpa mipmaps itu akan interpolasi linier GL_LINEARyang memberikan hasil yang sama atau memilih piksel terdekat GL_NEARESTyang bagaimanapun juga menghasilkan garis-garis di antara blok. Opsi yang disebutkan terakhir berkurang tetapi tidak menghilangkan cacat piksel.
danijar
7
Salah satu solusinya adalah dengan menggunakan format yang telah menghasilkan mipmaps, maka Anda dapat membuat themipmaps diri Anda dan memperbaiki kesalahan. Solusi lain adalah dengan memaksa level mipmap tertentu dalam fragmentshader Anda saat Anda mencicipi tekstur. Solusi lain adalah mengisi mipmaps dengan mipmap pertama. Dengan kata lain, saat Anda membuat tekstur, Anda bisa menambahkan subimage ke gambar tersebut, yang berisi data yang sama.
Tordin
1
Saya memiliki masalah ini sebelumnya dan diselesaikan dengan menambahkan 1-2 pixel padding di atlas Anda di antara setiap tekstur yang berbeda.
Chuck D
1
@Kylotan. Masalahnya adalah tentang mengubah ukuran tekstur terlepas dari apakah itu dilakukan oleh mipmaps, interpolasi linier atau pengambilan piksel terdekat.
danijar

Jawaban:

34

Oke, saya pikir Anda memiliki dua masalah yang terjadi di sini.

Masalah pertama adalah dengan memetakan. Secara umum, Anda tidak ingin secara naif mencampur atlasing dengan pemetaan, karena kecuali semua subteks Anda berukuran tepat 1x1 piksel, Anda akan mengalami pendarahan tekstur. Menambahkan padding hanya akan memindahkan masalah ke level mip yang lebih rendah.

Sebagai aturan praktis, untuk 2D, yang biasanya Anda lakukan atlasing, Anda tidak mipmap; sedangkan untuk 3D, yang merupakan tempat Anda mipmap, Anda tidak atlas (sebenarnya, Anda menguliti sedemikian rupa sehingga perdarahan tidak akan menjadi masalah)

Namun, apa yang saya pikir sedang terjadi dengan program Anda saat ini adalah bahwa Anda mungkin tidak menggunakan koordinat tekstur yang benar, dan karena itu tekstur Anda berdarah.

Misalkan tekstur 8x8. Anda mungkin mendapatkan kuartal kiri atas dengan menggunakan koordinat uv dari (0,0) hingga (0,5, 0,5):

Pemetaan yang salah

Dan masalahnya adalah bahwa pada koordinat -0 0,5, sampler akan menginterpolasi fragmen tujuan menggunakan setengah dari texel kiri dan setengah dari texel kanan. Hal yang sama akan terjadi pada koordinat u 0, dan sama untuk koordinat v. Ini karena Anda menangani batas antara teks, dan bukan pusat.

Yang harus Anda lakukan adalah mengatasi pusat setiap texel. Dalam contoh ini, ini adalah koordinat yang ingin Anda gunakan:

Pemetaan yang benar

Dalam hal ini, Anda akan selalu mengambil sampel pusat dari setiap texel dan Anda tidak akan mendapatkan pendarahan ... Kecuali jika Anda menggunakan pemetaan, dalam hal ini Anda akan selalu mendapatkan pendarahan mulai dari tingkat mip di mana subtexture yang diskalakan tidak rapi. muat di dalam kotak pixel .

Untuk menggeneralisasi, jika Anda ingin mengakses texel tertentu, koordinat dihitung sebagai:

function get_texel_coords(x, y, tex_width, tex_height)
    u = (x + 0.5) / tex_width
    v = (y + 0.5) / tex_height
    return u, v
end

Ini disebut "koreksi setengah piksel". Ada penjelasan lebih rinci di sini jika Anda tertarik.

Piyama Panda
sumber
Penjelasan Anda terdengar sangat menjanjikan. Sayangnya karena saya harus kartu video saat ini, saya belum bisa mengimplementasikannya. Saya akan melakukannya jika kartu yang diperbaiki tiba. Dan saya akan menghitung peta jalan atlas sendiri tanpa menyisipkan batas ubin.
danijar
@sharethis Jika Anda benar-benar harus atlas, pastikan subteks Anda memiliki ukuran 2, sehingga Anda dapat membatasi pendarahan hingga level mip terendah. Jika Anda melakukannya, Anda tidak perlu membuat kerajinan tangan Anda sendiri.
Panda Pajama
Bahkan tekstur berukuran PoT dapat memiliki artefak berdarah, karena penyaringan bilinear dapat sampel melintasi batas-batas tersebut. (Setidaknya dalam implementasi yang saya lihat.) Adapun koreksi setengah-pixel, haruskah itu berlaku dalam kasus ini? Jika saya ingin memetakan tekstur batuannya misalnya, tentu itu akan selalu 0,0 sampai 0,25 sepanjang koordinat U dan V?
Kylotan
1
@Kylotan Jika ukuran tekstur Anda genap, dan semua subteksur memiliki ukuran genap dan terletak pada koordinat genap, pemfilteran bilinear saat memotong dua ukuran tekstur tidak akan bercampur antar subteksur. Generalisasi untuk kekuatan dua (jika Anda melihat artefak, Anda mungkin menggunakan bicubic, bukan bilinear filtering). Mengenai koreksi setengah-pixel, perlu, karena 0,25 bukan texel paling kanan, tetapi titik tengah antara kedua subtextures. Untuk memasukkannya ke dalam perspektif, bayangkan Anda adalah rasterizer: apa warna teksturnya (0,00, 0,25)?
Piyama Panda
1
@sharethis, lihat jawaban lain yang saya tulis ini yang mungkin dapat membantu Anda gamedev.stackexchange.com/questions/50023/…
Panda Pajama
19

Salah satu opsi yang akan jauh lebih mudah daripada mengutak-atik mipmaps dan menambahkan faktor fuzz koordinat tekstur adalah dengan menggunakan array tekstur. Array tekstur mirip dengan tekstur 3d, tetapi tanpa pemetaan di dimensi ke-3, sehingga ideal untuk atlas tekstur di mana "subteksur" memiliki ukuran yang sama.

http://www.opengl.org/wiki/Array_Texture

ccxvii
sumber
Jika tersedia, itu adalah solusi yang jauh lebih baik - Anda juga mendapatkan fitur lain, seperti membungkus tekstur, yang Anda lewatkan dengan atlas.
Jari Komppa
@JariKomppa. Saya tidak ingin menggunakan larik tekstur karena dengan begitu saya akan memiliki shader ekstra untuk medan. Karena mesin yang saya tulis harus sealami mungkin, saya tidak ingin membuat pengecualian ini. Apakah ada cara untuk membuat satu shader yang dapat menangani keduanya, array tekstur dan tekstur tunggal?
danijar
8
@sharethis Mengesampingkan solusi teknis yang sangat jelas karena Anda ingin menjadi "umum" adalah resep kegagalan. Jika Anda menulis permainan, jangan menulis mesin.
sam hocevar
@ SamHocevar. Saya menulis sebuah mesin dan proyek saya adalah tentang arsitektur spesifik, komponen sepenuhnya dipisahkan. Jadi komponen bentuk tidak harus merawat komponen terrain yang seharusnya hanya menyediakan buffer standar dan tekstur standar.
danijar
1
@sareth, tidak apa-apa. Saya entah bagaimana disesatkan oleh bagian "Dalam permainan saya" dari pertanyaan.
sam hocevar
6

Setelah banyak berjuang dengan masalah ini, saya akhirnya menemukan solusi.

Untuk menggunakan keduanya, atlas tekstur dan pemetaan, saya perlu melakukan downsampling sendiri, karena OpenGL jika tidak akan interpolasi atas batas-batas ubin di atlas. Selain itu saya perlu mengatur parameter tekstur yang tepat, karena interpolasi antara mipmaps juga akan menyebabkan pendarahan tekstur.

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

Dengan parameter ini, tidak ada perdarahan yang terjadi.

Ada satu hal yang harus Anda perhatikan ketika memberikan mipmaps khusus. Karena tidak masuk akal untuk mengecilkan atlas bahkan jika setiap ubin sudah ada 1x1, level mipmap maksimal harus diatur sesuai dengan apa yang Anda berikan.

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 7); // pick mipmap level 7 or lower

Terima kasih atas semua jawaban lain dan komentar yang sangat membantu! Ngomong-ngomong, saya masih belum tahu cara menggunakan penskalaan linear, tapi saya kira tidak ada cara.

danijar
sumber
senang mengetahui bahwa Anda telah menyelesaikannya. Anda harus menandai jawaban ini sebagai jawaban yang benar dan bukan jawaban saya.
Panda Pajama
mipmapping adalah teknik khusus untuk downsampling, yang tidak masuk akal untuk upsampling. Idenya adalah bahwa jika Anda akan menggambar segitiga kecil, akan butuh banyak waktu untuk mencicipi tekstur besar hanya untuk mendapatkan beberapa piksel dari itu, sehingga Anda melakukan pra-downsample dan menggunakan tingkat mip yang tepat ketika menggambar segitiga kecil. Untuk segitiga yang lebih besar, menggunakan sesuatu yang berbeda dari tingkat mip yang lebih besar tidak masuk akal, jadi itu adalah kesalahan untuk melakukan sesuatu sepertiglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_FILTER, GL_NEAREST_MIPMAP_LINEAR);
Panda Pajama
@PandaPajama. Anda benar, saya mengoreksi jawaban saya. Untuk mengatur GL_TEXTURE_MAX_FILTERuntuk GL_NEAREST_MIPMAP_LINEARmenyebabkan pendarahan bukan karena alasan pemetaan, tetapi untuk alasan interpolasi. Jadi dalam kasus ini sama dengan GL_LINEARkarena level mipmap terendah digunakan pula.
danijar
@danijar - Bisakah Anda menjelaskan secara lebih rinci bagaimana Anda melakukan downsampling sendiri, dan bagaimana Anda memberi tahu OpenGL di mana (dan kapan) untuk menggunakan atlas downsampled?
Tim Kane
1
@TimKane Pisahkan atlas ke dalam ubin. Untuk setiap level mipmap, kurangi sampel ubin secara bilinear, gabungkan, dan unggah ke GPU yang menentukan level mipmap sebagai parameter glTexImage2D(). GPU secara otomatis memilih level yang benar berdasarkan ukuran objek di layar.
danijar
4

Sepertinya masalah ini bisa disebabkan oleh MSAA. Lihat jawaban ini dan artikel yang ditautkan :

"ketika Anda mengaktifkan MSAA, maka mungkin shader dieksekusi untuk sampel yang ada di dalam area piksel, tetapi di luar area segitiga"

Solusinya adalah menggunakan sampling centroid. Jika Anda menghitung pembungkus koordinat tekstur dalam vertex shader, memindahkan logika itu ke shader fragmen juga bisa memperbaiki masalah.

msell
sumber
Seperti yang sudah saya tulis di komentar lain, saat ini saya tidak memiliki kartu video yang berfungsi sehingga saya tidak dapat memeriksa apakah itu masalah sebenarnya. Saya akan melakukannya sesegera mungkin.
danijar
Saya menonaktifkan antialiasing perangkat keras tetapi perdarahan masih tetap. Saya sudah melakukan tekstur mencari di shader fragmen.
danijar
1

Ini adalah topik lama, dan saya memiliki masalah yang sama dengan topik tersebut.

Saya memiliki koordinat tekstur saya dengan baik, tetapi dengan menempatkan posisi kamera (yang mengubah posisi elemen saya) seperti:

public static void tween(Camera cam, Direction dir, Vec2 buffer, Vec2 start, Vec2 end) {
    if(step > steps) {
        tweening = false;
        return;
    }

    final float alpha = step/steps;

    switch(dir) {
    case UP:
    case DOWN:
        buffer = new Vec2(start.getX(), Util.lerp(start.getY(), (float)end.getY(), alpha));
        cam.getPosition().set(buffer);
        break;
    case LEFT:
    case RIGHT:
        buffer = new Vec2(Util.lerp(start.getX(), end.getX(), alpha), start.getY());
        cam.getPosition().set(buffer);
        break;
    }

    step += 1;
}

Seluruh masalah adalah Util.lerp () mengembalikan float atau double. Ini buruk karena kamera menerjemahkan off-grid dan menyebabkan beberapa masalah aneh dengan potongan tekstur di mana> 0 di X dan> 0 di Y.

TLDR: jangan tween atau apapun menggunakan floats

Cody The Coder
sumber