Tạo hiệu ứng hoán đổi bảng màu theo phong cách retro trong OpenGL


37

Tôi đang làm việc trên một trò chơi giống Megaman , nơi tôi cần thay đổi màu của một số pixel nhất định khi chạy. Để tham khảo : trong Megaman khi bạn thay đổi vũ khí đã chọn thì bảng màu của nhân vật chính sẽ thay đổi để phản ánh vũ khí đã chọn. Không phải tất cả các màu của sprite thay đổi, chỉ một số màu nhất định làm .

Loại hiệu ứng này khá phổ biến và khá dễ thực hiện trên NES vì lập trình viên có quyền truy cập vào bảng màu và ánh xạ logic giữa các pixel và chỉ số bảng màu. Tuy nhiên, trên phần cứng hiện đại, điều này khó khăn hơn một chút vì khái niệm bảng màu không giống nhau.

Tất cả các kết cấu của tôi là 32 bit và không sử dụng bảng màu.

Có hai cách tôi biết để đạt được hiệu quả tôi muốn, nhưng tôi tò mò liệu có cách nào tốt hơn để đạt được hiệu ứng này một cách dễ dàng không. Hai tùy chọn tôi biết là:

  1. Sử dụng một shader và viết một số GLSL để thực hiện hành vi "tráo đổi bảng màu".
  2. Nếu các shader không có sẵn (giả sử, vì card đồ họa không hỗ trợ chúng) thì có thể sao chép họa tiết "gốc" và tạo các phiên bản khác nhau với các thay đổi màu được áp dụng trước.

Lý tưởng nhất là tôi muốn sử dụng một shader vì nó có vẻ đơn giản và đòi hỏi ít công việc bổ sung trái ngược với phương pháp kết cấu trùng lặp. Tôi lo lắng rằng việc sao chép họa tiết chỉ để thay đổi màu sắc trong chúng là lãng phí VRAM - tôi có nên lo lắng về điều đó không?

Chỉnh sửa : Tôi đã kết thúc bằng cách sử dụng kỹ thuật của câu trả lời được chấp nhận và đây là shader của tôi để tham khảo.

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

Cả hai kết cấu là 32 bit, một kết cấu được sử dụng làm bảng tra cứu có chứa một số bảng màu có cùng kích thước (trong trường hợp của tôi là 6 màu). Tôi sử dụng kênh màu đỏ của pixel nguồn làm chỉ mục cho bảng màu. Điều này hoạt động như một cơ duyên để đạt được hoán đổi bảng màu giống Megaman!

Câu trả lời:


41

Tôi sẽ không lo lắng về việc lãng phí VRAM cho một vài họa tiết nhân vật.

Đối với tôi, sử dụng tùy chọn của bạn 2. (với kết cấu khác nhau hoặc độ lệch UV khác nhau nếu phù hợp) là cách tốt nhất: linh hoạt hơn, dựa trên dữ liệu, ít ảnh hưởng đến mã hơn, ít lỗi hơn, ít lo lắng hơn.


Điều này đặt sang một bên, nếu bạn bắt đầu tích lũy hàng tấn ký tự với hàng tấn hoạt hình sprite trong bộ nhớ, có lẽ bạn có thể bắt đầu sử dụng những gì được đề xuất bởi OpenGL , bảng màu tự làm:

Hoạ tiết

Hỗ trợ cho tiện ích mở rộng EXT_palettedunderure đã bị các nhà cung cấp GL lớn bỏ. Nếu bạn thực sự cần các kết cấu được chỉnh sửa trên phần cứng mới, bạn có thể sử dụng các shader để đạt được hiệu ứng đó.

Ví dụ về 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
}

Điều này chỉ đơn giản là lấy mẫu trong ColorTable(một bảng màu trong RGBA8), bằng cách sử dụng MyIndexTexture(một kết cấu vuông 8 bit trong các màu được lập chỉ mục). Chỉ cần tái tạo cách hoạt động của bảng màu theo phong cách retro.


Ví dụ được trích dẫn ở trên sử dụng hai sampler2D, trong đó nó thực sự có thể sử dụng một sampler1D+ một sampler2D. Tôi nghi ngờ đây là vì lý do tương thích ( không có kết cấu một chiều trong OpenGL ES ) ... Nhưng đừng bận tâm, đối với OpenGL trên máy tính để bàn, điều này có thể được đơn giản hóa thành:

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
}

Palettelà kết cấu một chiều của các màu "thực" (ví dụ GL_RGBA8) và IndexedColorTexturelà kết cấu hai chiều của các màu được lập chỉ mục (thông thường GL_R8, cung cấp cho bạn 256 chỉ số). Để tạo ra chúng, có một số công cụ tác giảđịnh dạng tệp hình ảnh ngoài đó hỗ trợ các hình ảnh được chỉnh sửa, bạn nên tìm một công cụ phù hợp với nhu cầu của mình.


2
Chính xác câu trả lời tôi sẽ đưa ra, chỉ chi tiết hơn. Sẽ +2 nếu tôi có thể.
Ilmari Karonen

Tôi đang gặp một số vấn đề khiến nó hoạt động với tôi, chủ yếu là vì tôi không hiểu làm thế nào chỉ số màu được xác định - bạn có thể mở rộng thêm một chút nữa không?
Zack The Human

Bạn có lẽ nên đọc lên các màu được lập chỉ mục . Kết cấu của bạn trở thành một mảng 2D của các chỉ mục, đánh dấu các chỉ số tương ứng với màu sắc trong bảng màu (thường là kết cấu một chiều). Tôi sẽ chỉnh sửa câu trả lời của mình để đơn giản hóa ví dụ do trang web OpenGL đưa ra.
Laurent Couvidou

Tôi xin lỗi, tôi đã không rõ ràng trong nhận xét ban đầu của tôi. Tôi quen thuộc với các màu được lập chỉ mục và cách chúng hoạt động, nhưng tôi không rõ về cách chúng hoạt động trong bối cảnh của một shader hoặc kết cấu 32 bit (vì chúng không được lập chỉ mục - phải không?).
Zack The Human

Chà, điều đáng nói là ở đây bạn có một bảng màu 32 bit chứa 256 màu và một kết cấu của các chỉ số 8 bit (đi từ 0 đến 255). Xem bản chỉnh sửa của tôi;)
Laurent Couvidou

10

Có 2 cách tôi có thể nghĩ để làm điều này.

Cách 1: GL_MODULATE

Bạn có thể làm cho sprite có màu xám và GL_MODULATEkết cấu khi nó được vẽ bằng một màu đơn.

Ví dụ,

nhập mô tả hình ảnh ở đây

nhập mô tả hình ảnh ở đây

Điều này không cho phép bạn linh hoạt nhiều, tuy nhiên, về cơ bản, bạn chỉ có thể đi sáng hơn và tối hơn cùng một sắc thái.

Cách # 2: iftuyên bố (không tốt cho hiệu suất)

Một điều khác bạn có thể làm là tô màu họa tiết của bạn với một số giá trị chính liên quan đến R, G và B. SO, ví dụ, màu đầu tiên là (1,0,0), màu thứ 2 là (0,1,0), màu thứ 3 màu là (0,0,1).

Bạn tuyên bố một vài bộ đồng phục như

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

Sau đó, trong shader, màu pixel cuối cùng là một cái gì đó như

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

color1, color2, color3, Là đồng phục để họ có thể được thiết lập trước khi chạy shader (tùy thuộc vào những gì "phù hợp với" megaman là in), sau đó đổ bóng chỉ đơn giản là hiện phần còn lại.

Bạn cũng có thể sử dụng các ifcâu lệnh nếu bạn cần nhiều màu sắc hơn, nhưng bạn sẽ phải kiểm tra đẳng thức hoạt động chính xác (kết cấu thường R8G8B8nằm trong khoảng từ 0 đến 255 mỗi câu trong tệp kết cấu, nhưng trong shader, bạn có rgba float)

Cách # 3: Chỉ cần lưu trữ dự phòng các hình ảnh có màu khác nhau trong bản đồ sprite

nhập mô tả hình ảnh ở đây

Đây có thể là cách dễ nhất, nếu bạn không cố gắng bảo tồn bộ nhớ


2
# 1 có thể gọn gàng, nhưng # 2 và # 3 là những lời khuyên tồi tệ khủng khiếp. GPU có khả năng lập chỉ mục từ các bản đồ (trong đó về cơ bản là một bảng màu) và thực hiện tốt hơn cả hai đề xuất đó.
Skrylar

6

Tôi sẽ sử dụng shader. Nếu bạn không muốn sử dụng chúng (hoặc không thể), tôi sẽ sử dụng một họa tiết (hoặc một phần của họa tiết) cho mỗi màu và chỉ sử dụng màu trắng sạch. Điều này sẽ cho phép bạn tô màu cho kết cấu (ví dụ như thông qua glColor()) mà bạn không phải lo lắng về việc tạo thêm họa tiết cho màu sắc. Bạn thậm chí có thể trao đổi màu sắc trên đường chạy mà không cần thêm kết cấu.


Đây là một ý tưởng khá tốt. Tôi thực sự đã nghĩ về điều này một thời gian trước nhưng nó không xuất hiện khi tôi đăng câu hỏi này. Có một số phức tạp thêm với điều này vì bất kỳ sprite nào sử dụng loại kết cấu hỗn hợp này sẽ cần một logic vẽ phức tạp hơn. Cảm ơn lời đề nghị tuyệt vời này!
Zack The Human

Có, bạn cũng sẽ cần x lượt cho mỗi sprite, có thể thêm một số phức tạp. Xem xét phần cứng ngày nay thường hỗ trợ ít nhất các trình tạo pixel cơ bản (thậm chí cả GPU netbook), thay vào đó tôi sẽ sử dụng các phần mềm này. Nó có thể gọn gàng nếu bạn chỉ muốn pha màu những thứ cụ thể, ví dụ như thanh sức khỏe hoặc biểu tượng.
Mario

3

"Không phải tất cả các màu của sprite thay đổi, chỉ có một số màu nhất định làm."

Các trò chơi cũ đã thực hiện các thủ thuật bảng màu vì đó là tất cả những gì họ có. Ngày nay, bạn có thể sử dụng nhiều kết cấu và kết hợp chúng theo nhiều cách khác nhau.

Bạn có thể tạo một kết cấu mặt nạ thang độ xám riêng mà bạn sử dụng để quyết định pixel nào sẽ thay đổi màu. Các vùng màu trắng trong kết cấu nhận được sự thay đổi màu sắc đầy đủ và các vùng màu đen không thay đổi.

Trong languague shader:

vec3 baseColor = texture (BaseSampler, att_TexCoord) .rgb;
số lượng float = texture (MaskSampler, att_TexCoord) .r;
vec3 color = mix (baseColor, baseColor * ModulationColor, lượng);

Hoặc bạn có thể sử dụng đa kết cấu chức năng cố định với các chế độ kết hợp kết cấu khác nhau.

Rõ ràng có nhiều cách khác nhau bạn có thể đi về điều này; bạn có thể đặt mặt nạ cho các màu khác nhau trong mỗi kênh RGB hoặc điều chỉnh màu bằng cách chuyển màu trong không gian màu HSV.

Một tùy chọn khác, có lẽ đơn giản hơn, chỉ đơn giản là vẽ sprite bằng cách sử dụng một kết cấu riêng cho từng thành phần. Một kết cấu cho tóc, một cho áo giáp, một cho ủng, v.v ... Mỗi một màu có thể pha màu riêng biệt.


2

Đầu tiên, nó thường được gọi là đi xe đạp (đi xe đạp pallet), do đó có thể giúp Google-fu của bạn.

Điều này sẽ phụ thuộc vào chính xác những hiệu ứng bạn muốn tạo ra và nếu bạn đang xử lý nội dung 2D tĩnh hoặc nội dung 3D động (nghĩa là bạn chỉ muốn xử lý các họa tiết / kết cấu hoặc hiển thị toàn bộ cảnh 3D sau đó hoán đổi vị trí đó). Ngoài ra nếu bạn đang giới hạn bản thân trong một số màu hoặc sử dụng màu đầy đủ.

Một cách tiếp cận đầy đủ hơn, sử dụng các shader, sẽ thực sự tạo ra các hình ảnh được lập chỉ mục màu với kết cấu pallet. Tạo một kết cấu nhưng thay vì có màu sắc, có một màu đại diện cho một chỉ mục để tra cứu sau đó có một kết cấu khác của màu sắc đó là tấm. Ví dụ: màu đỏ có thể là kết cấu t và màu xanh lá cây có thể là tọa độ s. Sử dụng một shader để thực hiện tra cứu. Và Animate / Sửa đổi màu sắc của kết cấu pallet đó. Điều này sẽ khá gần với hiệu ứng retro nhưng chỉ hoạt động đối với nội dung 2D tĩnh như họa tiết hoặc kết cấu. Nếu bạn làm đồ họa retro chính hãng thì bạn có thể tạo ra các họa tiết màu được lập chỉ mục thực tế và viết một cái gì đó để nhập chúng và các palet.

Bạn cũng sẽ muốn làm việc nếu bạn sẽ sử dụng pallet 'toàn cầu'. Giống như phần cứng cũ sẽ hoạt động như thế nào, ít bộ nhớ hơn cho các pallet vì bạn chỉ cần một nhưng khó hơn để tạo ra tác phẩm nghệ thuật của mình vì bạn phải đồng bộ hóa pallet trên tất cả các hình ảnh) hoặc cung cấp cho mỗi hình ảnh đó là pallet riêng. Điều đó sẽ giúp bạn linh hoạt hơn nhưng sử dụng nhiều bộ nhớ hơn. Tất nhiên bạn có thể làm cả hai, bạn cũng chỉ có thể sử dụng pallet cho những thứ bạn đang đạp xe màu. Ngoài ra, hãy tìm ra mức độ bạn sẽ không giới hạn màu sắc của mình, các trò chơi cũ sẽ sử dụng 256 màu chẳng hạn. Nếu bạn đang sử dụng họa tiết thì bạn sẽ nhận được số pixel nên 256 * 256 sẽ cung cấp cho bạn

Nếu bạn chỉ muốn một hiệu ứng giả rẻ tiền, chẳng hạn như nước chảy, bạn có thể tạo kết cấu thứ 2 chứa mặt nạ thang độ xám khu vực bạn muốn sửa đổi, sau đó sử dụng họa tiết được chỉnh sửa màu sắc / độ bão hòa / độ sáng theo số lượng trong kết cấu mặt nạ màu xám. Bạn thậm chí có thể lười hơn và chỉ cần thay đổi màu sắc / độ sáng / độ bão hòa của bất cứ thứ gì trong một phạm vi màu.

Nếu bạn cần nó ở chế độ 3D đầy đủ, bạn có thể thử tạo hình ảnh được lập chỉ mục một cách nhanh chóng nhưng nó sẽ chậm vì bạn sẽ phải tra cứu pallet, bạn cũng có thể bị giới hạn trong phạm vi màu.

Nếu không, bạn chỉ có thể giả mạo nó trong shader, nếu màu == đổi màu, hoán đổi nó. Điều đó sẽ chậm và sẽ chỉ hoạt động với một số lượng màu hoán đổi hạn chế nhưng không nên nhấn mạnh bất kỳ phần cứng nào hiện nay, đặc biệt nếu bạn chỉ xử lý đồ họa 2D và bạn luôn có thể đánh dấu các sprite có hoán đổi màu và sử dụng một shader khác.

Nếu không thực sự giả chúng, tạo ra một loạt các kết cấu hoạt hình cho mỗi tiểu bang.

Dưới đây là một bản demo tuyệt vời về việc đạp xe pallet được thực hiện trong HTML5 .


0

Tôi tin rằng ifs đủ nhanh để không gian màu [0 ... 1] được chia thành hai:

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

Theo cách này, các giá trị màu từ 0 đến 0,5 được dành riêng cho các giá trị màu bình thường; và 0,5 - 1,0 được dành riêng cho các giá trị "đã điều chế", trong đó ánh xạ 0,5..1.0 đến bất kỳ phạm vi nào được người dùng chọn.

Chỉ có độ phân giải màu được cắt ngắn từ 256 giá trị trên mỗi pixel thành 128 giá trị.

Có lẽ người ta có thể sử dụng các hàm dựng sẵn như max (color-0.5f, 0.0f) để loại bỏ hoàn toàn ifs (giao dịch chúng thành phép nhân bằng 0 hoặc một ...).

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.