วิธีการหลีกเลี่ยงการตกเลือดในพื้นผิวแผนที่?


46

ในเกมของฉันมีภูมิประเทศที่เหมือน Minecraft ทำจากก้อน ฉันสร้างบัฟเฟอร์จุดสุดยอดจากข้อมูล voxel และใช้แผนที่พื้นผิวสำหรับลักษณะของบล็อกที่แตกต่างกัน:

พื้นผิวแผนที่สำหรับภูมิประเทศตาม voxel

ปัญหาคือพื้นผิวของลูกบาศก์ที่อยู่ห่างไกลสอดแทรกด้วยกระเบื้องที่อยู่ติดกันในแผนที่พื้นผิว ผลลัพธ์ในบรรทัดของสีที่ไม่ถูกต้องระหว่างคิวบ์ (คุณอาจต้องดูภาพหน้าจอด้านล่างในขนาดเต็มเพื่อดูข้อบกพร่องกราฟิก):

สกรีนช็อตของภูมิประเทศที่มีแถบสีผิด

ตอนนี้ฉันใช้การตั้งค่าการแก้ไขเหล่านี้ แต่ฉันลองทุกชุดรวมกันและแม้GL_NEARESTไม่มีการแมปแมปก็ไม่ได้ผลลัพธ์ที่ดี

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

ฉันได้ลองเพิ่มออฟเซ็ตในพิกัดพื้นผิวเพื่อเลือกพื้นที่เล็ก ๆ ของกระเบื้อง แต่เนื่องจากเอฟเฟกต์ที่ไม่ต้องการขึ้นอยู่กับระยะห่างจากกล้องจึงไม่สามารถแก้ปัญหาได้อย่างสมบูรณ์ ในระยะทางไกล

ฉันจะแก้ไขการตกเลือดนี้ได้อย่างไร เนื่องจากการใช้แผนที่พื้นผิวเป็นเทคนิคที่นิยมอาจมีวิธีการทั่วไป น่าเศร้าด้วยเหตุผลบางอย่างที่อธิบายในความคิดเห็นฉันไม่สามารถเปลี่ยนเป็นพื้นผิวหรืออาร์เรย์พื้นผิวที่แตกต่างกันได้


4
ปัญหาอยู่กับการทำแผนที่ - พิกัดพื้นผิวของคุณอาจทำงานได้ดีสำหรับระดับ mip ที่ใหญ่ที่สุด แต่เมื่อสิ่งต่าง ๆ อยู่ไกลออกไปกระเบื้องที่มีพรมแดนติดกันจะรวมกัน คุณสามารถต่อสู้กับสิ่งนี้ได้โดยไม่ใช้ mipmaps (อาจไม่ใช่ความคิดที่ดี), เพิ่มพื้นที่ป้องกันของคุณ (พื้นที่ระหว่างแผ่นกระเบื้อง), ขยายเขตยามแบบไดนามิกใน shader ตามระดับ mip (หากิน) ทำสิ่งแปลก ๆ ในขณะที่สร้าง mipmaps ไม่ต้องคิดอะไร)) ฉันไม่เคยจัดการปัญหานี้ด้วยตัวเอง แต่มีสิ่งที่ต้องเริ่มต้นด้วย .. =)
Jari Komppa

อย่างที่ฉันบอกว่าการปิดการทำแผนที่ด้วยความเศร้าไม่ได้ช่วยแก้ปัญหา หากไม่มี mipmaps มันจะทำการสอดแทรกเชิงเส้นGL_LINEARซึ่งให้ผลลัพธ์ที่คล้ายกันหรือเลือกพิกเซลที่ใกล้ที่สุดGL_NEARESTซึ่งจะส่งผลให้เกิดแถบระหว่างบล็อก ตัวเลือกที่กล่าวถึงล่าสุดจะลดลง แต่ไม่กำจัดข้อบกพร่องของพิกเซล
danijar

7
ทางออกหนึ่งคือการใช้รูปแบบที่สร้าง mipmaps จากนั้นคุณสามารถสร้าง mipmaps เหล่านั้นด้วยตนเองและแก้ไขข้อผิดพลาด อีกวิธีหนึ่งคือการบังคับใช้ระดับ mipmap เฉพาะในแฟรกเมนต์ส่วนหัวของคุณเมื่อคุณสุ่มตัวอย่างพื้นผิว อีกวิธีคือเติม mipmaps ด้วย mipmap แรก กล่าวอีกนัยหนึ่งเมื่อคุณสร้างพื้นผิวของคุณคุณสามารถเพิ่มภาพย่อยไปยังภาพนั้นโดยมีข้อมูลเดียวกัน
Tordin

1
ฉันเคยมีปัญหานี้มาก่อนและได้รับการแก้ไขโดยเพิ่มการเติมความหนา 1-2 พิกเซลในสมุดบันทึกของคุณระหว่างแต่ละพื้นผิวที่แตกต่างกัน
Chuck D

1
@Kylotan ปัญหาคือเกี่ยวกับการปรับขนาดพื้นผิวโดยไม่คำนึงถึงว่ามันทำโดย mipmaps การแก้ไขเชิงเส้นหรือการเลือกพิกเซลที่ใกล้ที่สุด
danijar

คำตอบ:


34

โอเคฉันคิดว่าคุณมีปัญหาสองอย่างเกิดขึ้นที่นี่

ปัญหาแรกคือการทำแผนที่ โดยทั่วไปแล้วคุณไม่ต้องการผสมอย่างไร้เดียงสากับการทำแผนที่เพราะถ้าสิ่งที่คุณมีอยู่นั้นมีขนาด 1x1 พิกเซลคุณจะมีเลือดออก การเพิ่มการเติมจะทำให้ปัญหาเกิดขึ้นในระดับ mip ที่ต่ำกว่า

ในฐานะที่เป็นกฎง่ายๆสำหรับ 2D ซึ่งเป็นตำแหน่งที่คุณมักจะทำการเติมคุณจะไม่ทำแผนที่ ในขณะที่สำหรับ 3D ซึ่งเป็นที่ที่คุณทำแผนที่คุณไม่ได้ทำแผนที่ (จริงๆแล้วผิวของคุณในลักษณะที่เลือดออกจะไม่มีปัญหา)

อย่างไรก็ตามสิ่งที่ฉันคิดว่าเกิดขึ้นกับโปรแกรมของคุณในตอนนี้คือคุณอาจไม่ได้ใช้พิกัดพื้นผิวที่ถูกต้องและเนื่องจากพื้นผิวของคุณมีเลือดออก

สมมุติว่าพื้นผิว 8x8 คุณอาจได้รับไตรมาสบนซ้ายโดยใช้พิกัดยูวีของ (0,0) ถึง (0.5, 0.5):

การทำแผนที่ไม่ถูกต้อง

และปัญหาก็คือที่พิกัด 0.5 ของคุณตัวอย่างจะแก้ไขชิ้นส่วนปลายทางโดยใช้ครึ่งหนึ่งของ Texel ด้านซ้ายและครึ่งหนึ่งของ Texel ที่ถูกต้อง สิ่งนี้จะเกิดขึ้นที่ u-Coordinate 0 และเหมือนกันสำหรับ v-พิกัด นี่เป็นเพราะคุณกำลังพูดถึงเส้นขอบระหว่างข้อความและไม่ใช่ศูนย์กลาง

สิ่งที่คุณควรทำคือจัดการกับศูนย์กลางของแต่ละ texel ในตัวอย่างนี้พิกัดเหล่านี้คือสิ่งที่คุณต้องการใช้:

การทำแผนที่ที่ถูกต้อง

ในกรณีนี้คุณจะสุ่มตัวอย่างศูนย์กลางของแต่ละ texel และคุณไม่ควรมีเลือดออก ... เว้นแต่คุณจะใช้ mipmapping ซึ่งในกรณีนี้คุณจะมีเลือดออกเริ่มต้นที่ระดับ mip โดยที่ส่วนย่อยของการปรับขนาดไม่ได้เป็นระเบียบ พอดีภายในตารางพิกเซล

หากต้องการพูดคุยทั่วไปหากคุณต้องการเข้าถึง texel ที่เฉพาะเจาะจงพิกัดจะถูกคำนวณเป็น:

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

สิ่งนี้เรียกว่า "การแก้ไขครึ่งพิกเซล" มีคำอธิบายโดยละเอียดเพิ่มเติมที่นี่หากคุณสนใจ


คำอธิบายของคุณฟังดูมีแนวโน้มมาก น่าเศร้าที่ฉันต้องใช้การ์ดวิดีโอในขณะนี้ฉันยังไม่สามารถใช้งานได้ ฉันจะทำเช่นนั้นหากบัตรที่ได้รับการซ่อมแซมมาถึงแล้ว และฉันจะคำนวณ mipmaps ของแผนที่ด้วยตัวเองโดยไม่ต้องแก้ไขขอบเขตของแผ่นกระเบื้องในตอนนั้น
danijar

@sharethis หากคุณต้องมีแผนที่อย่างแน่นอนให้แน่ใจว่าสิ่งที่แนบมาของคุณมีขนาดเป็น 2, เพื่อให้คุณสามารถ จำกัด เลือดออกในระดับ mip ที่ต่ำที่สุด ถ้าคุณทำเช่นนั้นคุณไม่จำเป็นต้องสร้าง mipmaps ของคุณเอง
แพนด้า Pajama

แม้แต่พื้นผิวที่มีขนาด PoT ก็สามารถแยกสิ่งประดิษฐ์ออกมาได้เนื่องจากการกรอง bilinear สามารถสุ่มตัวอย่างข้ามขอบเขตเหล่านั้นได้ (อย่างน้อยในการใช้งานที่ฉันได้เห็น) สำหรับการแก้ไขครึ่งพิกเซลนั้นควรใช้ในกรณีนี้หรือไม่? ถ้าฉันต้องการแมปพื้นผิวหินของเขาเช่นนั้นแน่นอนว่าจะเป็น 0.0 ถึง 0.25 ตามพิกัด U และ V เสมอ
Kylotan

1
@Kylotan หากขนาดพื้นผิวของคุณเท่ากันและพื้นผิวย่อยทั้งหมดมีขนาดเท่ากันและอยู่ที่พิกัดเท่ากันการกรองแบบสองทางเมื่อแบ่งครึ่งขนาดพื้นผิวจะไม่รวมกันระหว่างพื้นผิวย่อย พูดคุยกับพลังของสอง (ถ้าคุณเห็นสิ่งประดิษฐ์คุณอาจใช้ bicubic ไม่ใช่การกรองแบบสองทาง) สำหรับการแก้ไขครึ่งพิกเซลนั้นมีความจำเป็นเพราะ 0.25 ไม่ใช่เท็กเซลที่ถูกต้องที่สุด แต่เป็นจุดกึ่งกลางระหว่างทั้งสองพื้นผิว ลองจินตนาการว่าคุณเป็นแรสเตอร์เซอร์: สีของพื้นผิวที่ (0.00, 0.25) คืออะไร?
แพนด้า Pajama

1
@sharethis ลองดูคำตอบอื่น ๆ ที่ฉันเขียนซึ่งอาจช่วยคุณได้gamedev.stackexchange.com/questions/50023/…
ชุดนอนแพนด้า

19

ทางเลือกหนึ่งที่จะง่ายกว่าการเล่น mipmaps และการเพิ่มค่าพิกัดของ fuzz ให้กับพื้นผิวคือการใช้อาร์เรย์ของวัตถุ Array Textures Array Textures Array Texture Array Textures Array Textures Array Textures Array Textures Array Textures Array Textures Array Textures Array Textures Array Textures Array Textures Array Textures Array Textures Array Array Textures Array

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


หากมีให้บริการพวกเขาจะเป็นทางออกที่ดีกว่ามาก - คุณได้รับคุณสมบัติอื่น ๆ เช่นพื้นผิวห่อหุ้มที่คุณพลาดด้วยแผนที่
Jari Komppa

@JariKomppa ฉันไม่ต้องการใช้อาร์เรย์พื้นผิวเพราะฉันจะเพิ่มเฉดสีให้กับภูมิประเทศ เนื่องจากเครื่องยนต์ที่ฉันเขียนควรเป็นทั่วไปที่สุดฉันไม่ต้องการทำข้อยกเว้นนี้ มีวิธีการสร้าง shader เดียวที่สามารถจัดการทั้งสองอาร์เรย์พื้นผิวและพื้นผิวเดียวได้หรือไม่?
danijar

8
@sharethis การยกเลิกการแก้ปัญหาด้านเทคนิคที่ยอดเยี่ยมอย่างไม่น่าสงสัยเพราะคุณต้องการที่จะ "ทั่วไป" เป็นสูตรสำหรับความล้มเหลว ถ้าคุณเขียนเกมอย่าเขียนเอ็นจิน
sam hocevar

@SamHocevar ฉันกำลังเขียนเครื่องยนต์และโครงการของฉันเกี่ยวกับสถาปัตยกรรมที่เฉพาะเจาะจงส่วนประกอบต่าง ๆ ถูกแยกออกอย่างสมบูรณ์ ดังนั้นองค์ประกอบของแบบฟอร์มไม่ควรดูแลส่วนประกอบภูมิประเทศที่ควรให้บัฟเฟอร์มาตรฐานและพื้นผิวมาตรฐานเท่านั้น
danijar

1
@sharethis ตกลง ฉันถูกเข้าใจผิดโดยส่วน "ในเกมของฉัน" ของคำถาม
sam hocevar

6

หลังจากที่ต้องดิ้นรนอย่างมากกับปัญหานี้ในที่สุดฉันก็พบวิธีแก้ปัญหา

ในการใช้ทั้งแผนที่พื้นผิวและการทำแผนที่ฉันจำเป็นต้องทำการสุ่มตัวอย่างด้วยตนเองเพราะ OpenGL จะทำการสอดแทรกข้ามขอบเขตของแผ่นกระเบื้องในแผนที่ ยิ่งไปกว่านั้นฉันจำเป็นต้องตั้งค่าพารามิเตอร์พื้นผิวที่เหมาะสมเพราะการสอดแทรกระหว่าง mipmaps จะทำให้มีเลือดออกที่พื้นผิวเช่นกัน

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

ด้วยพารามิเตอร์เหล่านี้ไม่มีเลือดออกเกิดขึ้น

มีสิ่งหนึ่งที่คุณต้องแจ้งให้ทราบเมื่อจัดทำ mipmaps ที่กำหนดเองคือ เนื่องจากไม่เหมาะสมที่จะลดขนาดแผนที่แม้ว่าแต่ละไทล์จะมีอยู่แล้ว1x1ระดับ mipmap สูงสุดจึงต้องตั้งค่าตามที่คุณให้ไว้

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

ขอบคุณสำหรับคำตอบอื่น ๆ และความคิดเห็นที่เป็นประโยชน์มาก! โดยวิธีการที่ฉันยังไม่ทราบวิธีการใช้การขยายเชิงเส้น แต่ฉันเดาว่าไม่มีทาง


ดีที่รู้ว่าคุณได้รับการแก้ไขแล้ว คุณควรทำเครื่องหมายคำตอบนี้ว่าถูกต้องแทนที่จะเป็นของฉัน
ชุดนอนแพนด้า

การทำแผนที่เป็นเทคนิคเฉพาะสำหรับการลดขนาดตัวอย่างซึ่งไม่สมเหตุสมผลสำหรับการยกตัวอย่าง แนวคิดก็คือถ้าคุณจะวาดสามเหลี่ยมเล็ก ๆ มันต้องใช้เวลานานมากในการสุ่มตัวอย่างพื้นผิวขนาดใหญ่เพื่อให้พิกเซลออกมาไม่กี่พิกเซลดังนั้นคุณจึงลองตัวอย่างก่อนและใช้ระดับ mip ที่เหมาะสมเมื่อ การวาดรูปสามเหลี่ยมขนาดเล็ก สำหรับรูปสามเหลี่ยมขนาดใหญ่การใช้อะไรที่แตกต่างจากระดับ mip ที่ใหญ่ขึ้นก็ไม่สมเหตุสมผลดังนั้นจึงเป็นข้อผิดพลาดที่ต้องทำเช่นนี้glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_FILTER, GL_NEAREST_MIPMAP_LINEAR);
Panda Pajama

@PandaPajama คุณพูดถูกฉันตอบถูกแล้ว ในการตั้งค่าGL_TEXTURE_MAX_FILTERเพื่อGL_NEAREST_MIPMAP_LINEARสาเหตุที่เลือดออกไม่ได้ด้วยเหตุผลของ Mipmapping แต่สำหรับเหตุผลของการแก้ไข ดังนั้นในกรณีนี้มันจึงเทียบเท่ากับGL_LINEARเนื่องจากมีการใช้ระดับ mipmap ต่ำที่สุด
danijar

@danijar - คุณสามารถอธิบายรายละเอียดเพิ่มเติมเกี่ยวกับวิธีดำเนินการ downsampling ด้วยตัวคุณเองได้อย่างไรและคุณจะบอก OpenGL ได้อย่างไรว่าที่ไหน (และเมื่อใด) ที่จะใช้ atlas ที่ลดขนาดตัวอย่าง?
Tim Kane

1
@TimKane แยกแผนที่เป็นกระเบื้อง สำหรับแต่ละระดับมิพแมพ, downsample กระเบื้อง bilinearly รวมพวกเขาและอัปโหลดที่กับ GPU glTexImage2D()ระบุระดับมิพแมพเป็นพารามิเตอร์ที่จะ GPU จะเลือกระดับที่ถูกต้องโดยอัตโนมัติตามขนาดของวัตถุบนหน้าจอ
danijar

4

ดูเหมือนว่าปัญหาอาจเกิดจาก MSAA ดูคำตอบนี้และบทความที่เชื่อมโยง :

"เมื่อคุณเปิด MSAA มันจะเป็นไปได้ที่ shader จะถูกเรียกใช้งานสำหรับตัวอย่างที่อยู่ในพื้นที่พิกเซล แต่อยู่นอกพื้นที่สามเหลี่ยม"

การแก้ปัญหาคือการใช้การสุ่มตัวอย่าง centroid หากคุณกำลังคำนวณการตัดพิกัดของพื้นผิวใน Shader ที่ยอดการย้ายตรรกะนั้นไปยังส่วนที่ Shader สามารถแก้ไขปัญหาได้


ในขณะที่ฉันเขียนไว้ในความคิดเห็นอื่นฉันไม่มีการ์ดแสดงผลที่ใช้งานได้ในขณะนี้ดังนั้นฉันไม่สามารถตรวจสอบว่าเป็นปัญหาจริงหรือไม่ ฉันจะทำทันทีที่ฉันสามารถ
danijar

ฉันปิดใช้งานการลดรอยหยักของฮาร์ดแวร์ แต่เลือดยังคงอยู่ ฉันทำพื้นผิวที่ดูในส่วนที่แตกออก
danijar

1

นี่เป็นหัวข้อเก่าและฉันมีปัญหาคล้ายกับหัวข้อ

ฉันมีพิกัดพื้นผิวของฉันได้ดี แต่โดย lerping ตำแหน่งกล้อง (ซึ่งเปลี่ยนตำแหน่งองค์ประกอบของฉัน) ดังนี้:

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

ปัญหาทั้งหมดคือ Util.lerp () คืนค่า float หรือ double สิ่งนี้ไม่ดีเพราะกล้องแปลกริดและทำให้เกิดปัญหาแปลก ๆ กับตัวอย่างเนื้อสัมผัสซึ่ง> 0 ใน X และ> 0 ใน Y

TLDR: อย่าทวีตหรืออะไรก็ตามที่ใช้ลอย

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.