การสร้างเอฟเฟ็กต์การสลับสีแบบย้อนยุคใน OpenGL


37

ฉันกำลังทำงานกับเกมที่คล้ายกับMegamanซึ่งฉันต้องเปลี่ยนสีของพิกเซลบางตัวในขณะใช้งานจริง สำหรับการอ้างอิง : ในMegamanเมื่อคุณเปลี่ยนอาวุธที่คุณเลือกจากนั้นจานสีของตัวละครหลักจะเปลี่ยนไปตามอาวุธที่เลือก ไม่ทั้งหมดของผีสางของการเปลี่ยนแปลงสีเพียงบางคนทำ

เอฟเฟกต์แบบนี้เป็นเรื่องปกติและค่อนข้างง่ายที่จะทำกับ NES เนื่องจากโปรแกรมเมอร์สามารถเข้าถึงพาเล็ตและการแมปโลจิคัลระหว่างพิกเซลและดัชนีพาเล็ต สำหรับฮาร์ดแวร์ที่ทันสมัยสิ่งนี้ค่อนข้างท้าทายกว่าเพราะแนวคิดของจานสีไม่เหมือนกัน

พื้นผิวทั้งหมดของฉันเป็นแบบ 32 บิตและไม่ใช้จานสี

มีสองวิธีที่ฉันรู้เพื่อให้ได้ผลตามที่ฉันต้องการ แต่ฉันอยากรู้ว่าถ้ามีวิธีที่ดีกว่าในการบรรลุผลนี้ได้อย่างง่ายดาย สองตัวเลือกที่ฉันรู้คือ:

  1. ใช้ shader และเขียน GLSL บางอย่างเพื่อดำเนินการ "palette swapping"
  2. หากไม่สามารถใช้เฉดสีได้ (เช่นเนื่องจากการ์ดกราฟิกไม่รองรับ) ก็เป็นไปได้ที่จะลอกแบบพื้นผิว "ดั้งเดิม" และสร้างรุ่นที่แตกต่างกันโดยมีการเปลี่ยนสีที่นำไปใช้ล่วงหน้า

เป็นการดีที่ฉันต้องการใช้ shader เนื่องจากดูเหมือนว่าตรงไปตรงมาและต้องการงานเพิ่มเติมเล็กน้อยเมื่อเทียบกับวิธีการทำซ้ำพื้นผิว ฉันกังวลว่าพื้นผิวที่ซ้ำซ้อนเพียงเพื่อเปลี่ยนสีในพวกเขากำลังสูญเสีย VRAM - ฉันไม่ควรกังวลเกี่ยวกับสิ่งนั้นหรือ

แก้ไข : ฉันลงเอยด้วยการใช้เทคนิคของคำตอบที่ยอมรับและนี่คือ shader ของฉันสำหรับการอ้างอิง

uniform sampler2D texture;
uniform sampler2D colorTable;
uniform float paletteIndex;

void main()
{
        vec2 pos = gl_TexCoord[0].xy;
        vec4 color = texture2D(texture, pos);
        vec2 index = vec2(color.r + paletteIndex, 0);
        vec4 indexedColor = texture2D(colorTable, index);
        gl_FragColor = indexedColor;      
}

พื้นผิวทั้งสองเป็น 32- บิตโดยใช้หนึ่งพื้นผิวเป็นตารางการค้นหาที่มีหลายจานซึ่งมีขนาดเท่ากันทั้งหมด (ในกรณีของฉัน 6 สี) ฉันใช้แชนเนลสีแดงของพิกเซลต้นทางเป็นดัชนีไปยังตารางสี สิ่งนี้ได้ผลเหมือนเป็นเสน่ห์ในการเปลี่ยนจานสีของ Megaman!

คำตอบ:


41

ฉันไม่ต้องกังวลกับการสูญเสีย VRAM สำหรับพื้นผิวของตัวละคร

สำหรับฉันโดยใช้ทางเลือกที่ 2 (มีพื้นผิวแตกต่างกันหรือมีการชดเชยแสง UV ที่แตกต่างกันหากเหมาะสม) เป็นวิธีที่จะไป: มีความยืดหยุ่นมากขึ้นขับเคลื่อนด้วยข้อมูลส่งผลกระทบต่อโค้ดน้อยลง


ถ้าคุณเริ่มสะสมตัวละครมากมายด้วยภาพเคลื่อนไหวสไปรต์จำนวนมากในหน่วยความจำบางทีคุณอาจเริ่มใช้สิ่งที่ OpenGL แนะนำโดยใช้จานสีแบบทำเอง:

พื้นผิว paletted

การสนับสนุนสำหรับส่วนขยายEXT_paletted_textureถูกทิ้งโดยผู้ขาย GL รายใหญ่ หากคุณต้องการพื้นผิวแบบ paletted บนฮาร์ดแวร์ใหม่คุณอาจใช้ shaders เพื่อให้ได้ผลลัพธ์นั้น

ตัวอย่าง Shader:

//Fragment shader
#version 110
uniform sampler2D ColorTable;     //256 x 1 pixels
uniform sampler2D MyIndexTexture;
varying vec2 TexCoord0;

void main()
{
  //What color do we want to index?
  vec4 myindex = texture2D(MyIndexTexture, TexCoord0);
  //Do a dependency texture read
  vec4 texel = texture2D(ColorTable, myindex.xy);
  gl_FragColor = texel;   //Output the color
}

นี่เป็นเพียงการสุ่มตัวอย่างในColorTable(จานสีใน RGBA8) โดยใช้MyIndexTexture(พื้นผิวสี่เหลี่ยมจัตุรัสขนาด 8 บิตในสีที่จัดทำดัชนี) เพียงทำซ้ำวิธีการทำงานของจานสไตล์ย้อนยุค


ตัวอย่างที่ยกมาข้างต้นใช้สองsampler2Dซึ่งมันสามารถใช้หนึ่งsampler1D+ หนึ่งsampler2Dจริง ฉันสงสัยว่านี่เป็นเพราะเหตุผลด้านความเข้ากันได้ ( ไม่มีพื้นผิวหนึ่งมิติใน OpenGL ES ) ... แต่ไม่เป็นไรสำหรับเดสก์ท็อป OpenGL สิ่งนี้สามารถทำให้ง่ายขึ้น:

uniform sampler1D Palette;             // A palette of 256 colors
uniform sampler2D IndexedColorTexture; // A texture using indexed color
varying vec2 TexCoord0;                // UVs

void main()
{
    // Pick up a color index
    vec4 index = texture2D(IndexedColorTexture, TexCoord0);
    // Retrieve the actual color from the palette
    vec4 texel = texture1D(Palette, index.x);
    gl_FragColor = texel;   //Output the color
}

Paletteเป็นพื้นผิวหนึ่งมิติของสี "ของจริง" (เช่นGL_RGBA8) และIndexedColorTextureเป็นพื้นผิวสองมิติของสีที่จัดทำดัชนี (โดยทั่วไปจะGL_R8ให้ดัชนี 256 ตัว) เพื่อสร้างผู้ที่มีหลาย การเขียน เครื่องมือและรูปแบบไฟล์ภาพออกมีที่สนับสนุน paletted ภาพมันควรจะเป็นไปได้ที่จะหาคนที่เหมาะกับความต้องการของคุณ


2
แน่นอนคำตอบที่ฉันจะได้รับเพียงรายละเอียดเพิ่มเติม จะ +2 ถ้าฉันทำได้
Ilmari Karonen

ฉันมีปัญหาบางอย่างในการทำให้สิ่งนี้ใช้งานได้สำหรับฉันส่วนใหญ่เป็นเพราะฉันไม่เข้าใจวิธีการกำหนดดัชนีสี - คุณสามารถขยายอีกหน่อยได้ไหม?
Zack The Human

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

ฉันขอโทษฉันไม่ชัดเจนในความคิดเห็นดั้งเดิมของฉัน ฉันคุ้นเคยกับสีที่มีการจัดทำดัชนีและวิธีการทำงาน แต่ฉันไม่ชัดเจนเกี่ยวกับวิธีการทำงานภายในบริบทของ shader หรือพื้นผิวแบบ 32 บิต (เนื่องจากไม่มีการทำดัชนีใช่ไหม)
Zack The Human

นี่คือสิ่งที่คุณมีจานสี 32 บิตหนึ่งสีที่มี 256 สีและหนึ่งพื้นผิวของดัชนี 8 บิต(จาก 0 ถึง 255) ดูการแก้ไขของฉัน;)
Laurent Couvidou

10

มี 2 ​​วิธีที่ฉันคิดได้

วิธี # 1: GL_MODULATE

คุณสามารถทำให้เทพดาในเฉดสีเทาและGL_MODULATEพื้นผิวเมื่อมันถูกวาดด้วยสีทึบเดียว

ตัวอย่างเช่น,

ป้อนคำอธิบายรูปภาพที่นี่

ป้อนคำอธิบายรูปภาพที่นี่

สิ่งนี้ไม่ได้ช่วยให้คุณมีความยืดหยุ่นมากนักโดยทั่วไปคุณสามารถเบาและเข้มกว่าในที่ร่มเดียวกัน

วิธี # 2: ifงบ (ไม่ดีสำหรับประสิทธิภาพ)

อีกสิ่งที่คุณสามารถทำได้คือปรับสีพื้นผิวของคุณด้วยค่าคีย์ที่เกี่ยวข้องกับ R, G และ B ดังนั้นตัวอย่างเช่นสีแรกคือ (1,0,0) สีที่สองคือ (0,1,0), อันดับที่ 3 สีคือ (0,0,1)

คุณประกาศเครื่องแบบสองสามอย่าง

uniform float4 color1 ;
uniform float4 color2 ;
uniform float4 color3 ;

จากนั้นใน shader สีของพิกเซลสุดท้ายเป็นสิ่งที่ต้องการ

float4 pixelColor = texel.r*color1 + texel.g*color2 + texel.b*color3 ;

color1, color2, color3มีเครื่องแบบเพื่อให้พวกเขาสามารถตั้งค่าก่อนที่จะวิ่ง Shader (ขึ้นอยู่กับสิ่งที่ "ชุด" megaman อยู่ใน) จากนั้น Shader ก็ไม่เหลือ

คุณสามารถใช้ifข้อความสั่งถ้าคุณต้องการสีเพิ่มเติม แต่คุณจะต้องตรวจสอบความเท่าเทียมกันทำงานอย่างถูกต้อง (พื้นผิวมักจะอยู่R8G8B8ระหว่าง 0 ถึง 255 ในไฟล์พื้นผิว แต่ใน shader คุณมี rgba ลอยอยู่)

วิธี # 3: เพียงเก็บภาพสีต่าง ๆ ซ้ำซ้อนในแผนที่สไปรต์

ป้อนคำอธิบายรูปภาพที่นี่

นี่อาจเป็นวิธีที่ง่ายที่สุดถ้าคุณไม่พยายามประหยัดหน่วยความจำอย่างบ้าคลั่ง


2
# 1 สามารถเรียบร้อย แต่ # 2 และ # 3 เป็นคำแนะนำที่ไม่ดีอย่างน่ากลัว GPU มีความสามารถในการจัดทำดัชนีจากแผนที่ (ซึ่งมีจานสีเป็นพื้น) และทำได้ดีกว่าคำแนะนำเหล่านั้นทั้งคู่
Skrylar

6

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


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

ใช่คุณจะต้องมี x pass ต่อ sprite ซึ่งสามารถเพิ่มความซับซ้อนได้บ้าง เมื่อพิจารณาถึงฮาร์ดแวร์ในปัจจุบันมักจะรองรับพิกเซลพื้นฐานอย่างน้อย (แม้แต่เน็ตบุ๊ก GPU) ฉันจะไปกับสิ่งเหล่านี้แทน มันอาจเป็นระเบียบถ้าคุณต้องการแต้มสีเฉพาะเช่นแถบสุขภาพหรือไอคอน
มาริโอ

3

"สีของสไปรท์ไม่ได้เปลี่ยนไปทั้งหมดมีเพียงบางสีเท่านั้น"

เกมเก่าทำเทคนิคจานสีเพราะนั่นคือทั้งหมดที่พวกเขามี วันนี้คุณสามารถใช้พื้นผิวหลาย ๆ แบบและรวมเข้าด้วยกันได้หลายวิธี

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

ในภาษา Lader:

vec3 baseColor = texture (BaseSampler, att_TexCoord) .rgb;
จำนวนลอย = พื้นผิว (MaskSampler, att_TexCoord) .r;
vec3 color = mix (baseColor, baseColor * ModulationColor, จำนวนเงิน);

หรือคุณสามารถใช้การทำพื้นผิวแบบหลายฟังก์ชั่นแบบคงที่ด้วยโหมด combiner แบบต่างๆ

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

อีกวิธีที่ง่ายกว่าคือการวาดเทพดาโดยใช้พื้นผิวแยกต่างหากสำหรับแต่ละองค์ประกอบ หนึ่งพื้นผิวสำหรับผมหนึ่งชิ้นสำหรับเกราะหนึ่งชิ้นสำหรับรองเท้าบู๊ต ฯลฯ แต่ละสีจะย้อมสีแยกกัน


2

ก่อนอื่นโดยทั่วไปเรียกว่าการขี่จักรยาน (การขี่จักรยานบนพาเลท) ดังนั้นอาจช่วย Google-fu ของคุณ

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

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

นอกจากนี้คุณยังจะต้องออกกำลังด้วยหากคุณกำลังใช้พาเลท 'ทั่วโลก' เช่นเดียวกับวิธีการทำงานของฮาร์ดแวร์เก่าหน่วยความจำน้อยกว่าสำหรับพาเลทเนื่องจากคุณต้องการเพียงหนึ่ง แต่ยากที่จะสร้างงานศิลปะของคุณเพราะคุณต้องซิงโครไนซ์พาเลทกับภาพทั้งหมด) หรือให้แต่ละภาพเป็นพาเลทของตัวเอง นั่นจะทำให้คุณมีความยืดหยุ่นมากขึ้น แต่ใช้หน่วยความจำมากขึ้น แน่นอนคุณสามารถทำได้ทั้งสองอย่างและคุณสามารถใช้พาเลทสำหรับสิ่งที่คุณขี่จักรยานสีเท่านั้น ลองดูว่าคุณไม่ได้ จำกัด สีแค่ไหนเกมเก่าจะใช้ 256 สีเช่นกัน หากคุณใช้พื้นผิวคุณจะได้รับจำนวนพิกเซลดังนั้น 256 * 256 จะให้คุณ

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

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

มิฉะนั้นคุณสามารถปลอมใน shader ได้ถ้า color == การสลับสีให้สลับ นั่นจะช้าและใช้งานได้เฉพาะกับการเปลี่ยนสีในจำนวนที่ จำกัด แต่ไม่ควรกังวลกับฮาร์ดแวร์ใด ๆ ในปัจจุบันโดยเฉพาะอย่างยิ่งถ้าคุณเพิ่งจัดการกับกราฟิก 2D และคุณสามารถทำเครื่องหมายว่า Sprite ที่มีการสลับสีและใช้ shader อื่น ๆ

ไม่เช่นนั้นจะปลอมพวกเขาออกมาสร้างพื้นผิวที่เคลื่อนไหวได้สำหรับแต่ละรัฐ

นี่คือตัวอย่างที่ดีของการขี่จักรยานพาเลททำใน HTML5


0

ฉันเชื่อว่า ifs เร็วพอสำหรับการแบ่งพื้นที่สี [0 ... 1] ให้เป็นสอง:

if (color.r > 0.5) {
   color.r = (color.r-0.5) * uniform_r + uniform_base_r;
} else {
  color.r *=2.0;
} // this could be vectorised for all colors

ด้วยวิธีนี้ค่าสีระหว่าง 0 ถึง 0.5 จะถูกสงวนไว้สำหรับค่าสีปกติ และ 0.5 - 1.0 ถูกสงวนไว้สำหรับค่า "มอดูเลต" ที่ 0.5..1.0 แมปกับช่วงใด ๆ ที่เลือกโดยผู้ใช้

ความละเอียดของสีจะถูกตัดทอนจาก 256 ค่าต่อพิกเซลเป็น 128 ค่า

อาจเป็นไปได้ว่าสามารถใช้ฟังก์ชัน builtin เช่น max (color-0.5f, 0.0f) เพื่อลบ ifs อย่างสมบูรณ์ (แลกเปลี่ยนให้คูณด้วยศูนย์หรือหนึ่ง ... )

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