Sự khác biệt và mối quan hệ giữa glActiveTexture và glBindTexture


137

Từ những gì tôi thu thập được, glActiveTexturethiết lập "đơn vị kết cấu" đang hoạt động. Mỗi đơn vị kết cấu có thể có nhiều mục tiêu kết cấu (thường là GL_TEXTURE_1D, 2D, 3D hoặc CUBE_MAP).

Nếu tôi hiểu chính xác, bạn phải gọi glActiveTextuređể đặt đơn vị kết cấu trước (khởi tạo thành GL_TEXTURE0), và sau đó bạn liên kết (một hoặc nhiều) "mục tiêu kết cấu" với đơn vị kết cấu đó?

Số lượng các đơn vị kết cấu có sẵn là phụ thuộc hệ thống. Tôi thấy enum cho tới 32 trong thư viện của tôi. Tôi đoán điều này về cơ bản có nghĩa là tôi có thể có giới hạn GPU thấp hơn (mà tôi nghĩ là168) và 32 kết cấu trong bộ nhớ GPU bất cứ lúc nào? Tôi đoán có một giới hạn bổ sung mà tôi không vượt quá bộ nhớ tối đa của GPU (được cho là 1 GB).

Tôi có hiểu chính xác mối quan hệ giữa mục tiêu kết cấu và đơn vị kết cấu không? Giả sử tôi cho phép 16 đơn vị và 4 mục tiêu mỗi mục tiêu, điều đó có nghĩa là có chỗ cho 16 * 4 = 64 mục tiêu hay nó không hoạt động như vậy?

Tiếp theo bạn thường muốn tải một kết cấu. Bạn có thể làm điều này thông qua glTexImage2D. Đối số đầu tiên trong đó là một mục tiêu kết cấu. Nếu điều này hoạt động như thếglBufferData , thì về cơ bản chúng ta liên kết "tên xử lý" / "tên kết cấu" với mục tiêu kết cấu, sau đó tải dữ liệu kết cấu vào mục tiêu đó và do đó gián tiếp liên kết nó với xử lý đó.

Thế còn glTexParameter? Chúng ta phải liên kết một mục tiêu kết cấu, và sau đó chọn lại mục tiêu đó làm đối số đầu tiên? Hoặc mục tiêu kết cấu không cần phải bị ràng buộc miễn là chúng ta có đơn vị kết cấu hoạt động chính xác?

glGenerateMipmap hoạt động trên một mục tiêu quá ... mục tiêu đó vẫn phải được ràng buộc với tên kết cấu để nó thành công?

Sau đó, khi chúng ta muốn vẽ đối tượng của mình bằng một kết cấu trên nó, chúng ta có phải chọn cả một đơn vị kết cấu hoạt động, và sau đó là một mục tiêu kết cấu không? Hoặc chúng ta chọn một đơn vị kết cấu, và sau đó chúng ta có thể lấy dữ liệu từ bất kỳ mục tiêu nào trong số 4 mục tiêu được liên kết với đơn vị đó? Đây là phần thực sự làm tôi bối rối.

Câu trả lời:


259

Tất cả về các đối tượng OpenGL

Mô hình chuẩn cho các đối tượng OpenGL như sau.

Đối tượng có nhà nước. Hãy nghĩ về họ như một struct. Vì vậy, bạn có thể có một đối tượng được xác định như thế này:

struct Object
{
    int count;
    float opacity;
    char *name;
};

Đối tượng có các giá trị nhất định được lưu trữ trong nó và nó có trạng thái . Các đối tượng OpenGL cũng có trạng thái.

Thay đổi trạng thái

Trong C / C ++, nếu bạn có một thể hiện của loại Object, bạn sẽ thay đổi trạng thái của nó như sau: obj.count = 5;Bạn sẽ trực tiếp tham chiếu một thể hiện của đối tượng, lấy phần trạng thái cụ thể mà bạn muốn thay đổi và chuyển một giá trị vào nó.

Trong OpenGL, bạn không làm điều này.

Vì lý do di sản tốt hơn không giải thích được, để thay đổi trạng thái của một đối tượng OpenGL, trước tiên bạn phải liên kết nó với bối cảnh. Điều này được thực hiện với một số từ glBind*cuộc gọi.

C / C ++ tương đương với điều này như sau:

Object *g_objs[MAX_LOCATIONS] = {NULL};    
void BindObject(int loc, Object *obj)
{
  g_objs[loc] = obj;
}

Hoạ tiết rất thú vị; họ đại diện cho một trường hợp đặc biệt của ràng buộc. Nhiều glBind*cuộc gọi có tham số "đích". Điều này thể hiện các vị trí khác nhau trong bối cảnh OpenGL nơi các đối tượng thuộc loại đó có thể bị ràng buộc. Ví dụ: bạn có thể liên kết một đối tượng bộ đệm khung để đọc ( GL_READ_FRAMEBUFFER) hoặc để viết ( GL_DRAW_FRAMEBUFFER). Điều này ảnh hưởng đến cách OpenGL sử dụng bộ đệm. Đây là những gì các loctham số ở trên đại diện.

Hoạ tiết rất đặc biệt vì khi bạn lần đầu tiên liên kết chúng với mục tiêu, chúng sẽ nhận được thông tin đặc biệt. Khi bạn lần đầu tiên liên kết một kết cấu như một GL_TEXTURE_2D, bạn thực sự đang thiết lập trạng thái đặc biệt trong kết cấu. Bạn đang nói rằng kết cấu này là một kết cấu 2D. Và nó sẽ luôn là một kết cấu 2D; trạng thái này không thể thay đổi bao giờ . Nếu bạn có một kết cấu được ràng buộc đầu tiên là a GL_TEXTURE_2D, bạn phải luôn liên kết nó như một GL_TEXTURE_2D; cố gắng ràng buộc nó GL_TEXTURE_1Dsẽ làm phát sinh lỗi (trong thời gian chạy).

Một khi đối tượng bị ràng buộc, trạng thái của nó có thể được thay đổi. Điều này được thực hiện thông qua các chức năng chung cụ thể cho đối tượng đó. Họ cũng có một vị trí đại diện cho đối tượng để sửa đổi.

Trong C / C ++, điều này trông giống như:

void ObjectParameteri(int loc, ObjectParameters eParam, int value)
{
  if(g_objs[loc] == NULL)
    return;

  switch(eParam)
  {
    case OBJECT_COUNT:
      g_objs[loc]->count = value;
      break;
    case OBJECT_OPACITY:
      g_objs[loc]->opacity = (float)value;
      break;
    default:
      //INVALID_ENUM error
      break;
  }
}

Lưu ý cách hàm này đặt bất cứ điều gì xảy ra trong locgiá trị hiện tại bị ràng buộc .

Đối với các đối tượng kết cấu, các chức năng thay đổi trạng thái kết cấu chính là glTexParameter. Các chức năng khác chỉ thay đổi trạng thái kết cấu là các glTexImagechức năng và các biến thể của chúng ( glCompressedTexImage,, glCopyTexImagegần đây glTexStorage). Các SubImagephiên bản khác nhau thay đổi nội dung của kết cấu, nhưng về mặt kỹ thuật chúng không thay đổi trạng thái của nó . Các Imagechức năng phân bổ lưu trữ kết cấu và đặt định dạng của kết cấu; các SubImagechức năng chỉ sao chép các pixel xung quanh. Đó không được coi là trạng thái của kết cấu.

Cho phép tôi lặp lại: đây là các chức năng duy nhất sửa đổi trạng thái kết cấu. glTexEnvSửa đổi trạng thái môi trường; nó không ảnh hưởng đến bất cứ thứ gì được lưu trữ trong các đối tượng kết cấu.

Kết cấu hoạt động

Tình hình cho kết cấu phức tạp hơn, một lần nữa vì lý do di sản tốt nhất không được tiết lộ. Đây là nơi glActiveTextuređến.

Đối với kết cấu, không có chỉ tiêu ( GL_TEXTURE_1D, GL_TEXTURE_CUBE_MAP, vv). Ngoài ra còn có các đơn vị kết cấu . Xét về ví dụ C / C ++ của chúng tôi, những gì chúng tôi có là:

Object *g_objs[MAX_OBJECTS][MAX_LOCATIONS] = {NULL};
int g_currObject = 0;

void BindObject(int loc, Object *obj)
{
  g_objs[g_currObject][loc] = obj;
}

void ActiveObject(int currObject)
{
  g_currObject = currObject;
}

Bây giờ, chúng ta không chỉ có một danh sách 2D Object, mà chúng ta còn có khái niệm về một đối tượng hiện tại. Chúng ta có một chức năng để thiết lập đối tượng hiện tại, chúng ta có khái niệm về số lượng đối tượng hiện tại tối đa và tất cả các chức năng thao tác đối tượng của chúng ta được điều chỉnh để chọn từ đối tượng hiện tại.

Khi bạn thay đổi đối tượng hiện đang hoạt động, bạn thay đổi toàn bộ nhóm vị trí đích. Vì vậy, bạn có thể liên kết một cái gì đó đi vào đối tượng hiện tại 0, chuyển sang đối tượng hiện tại 4 và sẽ sửa đổi một đối tượng hoàn toàn khác.

Sự tương đồng với các đối tượng kết cấu là hoàn hảo ... gần như.

Xem, glActiveTexturekhông lấy một số nguyên; phải mất một điều tra viên . Mà trong lý thuyết có nghĩa là nó có thể lấy bất cứ thứ gì từ GL_TEXTURE0đến GL_TEXTURE31. Nhưng có một điều bạn phải hiểu:

ĐIỀU NÀY LÀ SAI!

Phạm vi thực tế glActiveTexturecó thể mất được chi phối bởi GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS. Đó là số lượng đa điểm đồng thời tối đa mà việc triển khai cho phép. Chúng được chia thành các nhóm khác nhau cho các giai đoạn đổ bóng khác nhau. Ví dụ, trên phần cứng lớp GL 3.x, bạn nhận được 16 kết cấu đổ bóng đỉnh, 16 kết cấu đổ bóng mảnh và 16 kết cấu đổ bóng hình học. Do đó, GL_MAX_COMBINED_TEXTURE_IMAGE_UNITSsẽ là 48.

Nhưng không có 48 điều tra viên. Đó là lý do tại sao glActiveTexturekhông thực sự mất các điều tra viên. Các đúng cách để gọi glActiveTexturelà như sau:

glActiveTexture(GL_TEXTURE0 + i);

trong đó imột số từ 0 đến GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS.

Kết xuất

Vì vậy, tất cả những điều này có liên quan gì đến kết xuất?

Khi sử dụng các shader, bạn đặt đồng phục lấy mẫu của mình thành một đơn vị hình ảnh kết cấu ( glUniform1i(samplerLoc, i), đơn vị hình ảnh ở đâu i). Điều đó thể hiện số lượng bạn đã sử dụng với glActiveTexture. Người lấy mẫu sẽ chọn mục tiêu dựa trên loại người lấy mẫu. Vì vậy, một sampler2Dsẽ chọn từ GL_TEXTURE_2Dmục tiêu. Đây là một lý do tại sao các mẫu lấy mẫu có các loại khác nhau.

Bây giờ điều này nghe có vẻ đáng ngờ giống như bạn có thể có hai bộ lấy mẫu GLSL, với các loại khác nhau sử dụng cùng một đơn vị hình ảnh kết cấu. Nhưng bạn không thể; OpenGL cấm điều này và sẽ gây ra lỗi khi bạn cố kết xuất.


12
Ồ Một câu trả lời tuyệt vời khác - cảm ơn Nicol! Tôi đặc biệt thích đoạn đó về kết cấu 2D luôn là kết cấu 2D. Bây giờ tôi đang xây dựng một trình bao bọc xung quanh một số trong những điều này, và tôi không chắc liệu tôi có nên để nó mở để thay đổi hay không. Và phần về GL_TEXTURE0 + i- tôi có ý kiểm tra các giá trị enum để xem liệu điều đó có hợp lệ hay không. Và đoạn cuối - không biết điều đó có hợp pháp hay không. Thông minh! Tôi đánh dấu tất cả các câu trả lời của bạn để tôi có thể tham khảo lại.
mở

6
@Nicol Bolas: Điều này thực sự được giải thích. Bạn nên sao chép một số điều này vào chương kết cấu trong cuốn sách opengl trực tuyến của bạn. Tôi nghĩ rằng điều này rõ ràng hơn nhiều và sẽ khen chương này tốt.
WesDec

3
@Nicol Bolas Tôi mới bắt đầu học OpenGL và câu trả lời này đã giúp tôi rất nhiều. Cảm ơn bạn!
nội tuyến

2
Xin chào, chỉ muốn chỉ ra lỗi đánh máy nhỏ của bạn: đó là GL_DRAW_FRAMEBUFFER chứ không phải GL_WRITE_FRAMEBUFFER
Defd

3
@Nicol: Wow, định nghĩa tốt nhất mà tôi có về điều này trước đây là từ các hướng dẫn tổng hợp của bạn, bây giờ bạn đã vượt qua cả nguồn tuyệt vời đó. Thankyou
Baggers

20

Tôi sẽ thử! Tất cả điều này không quá phức tạp, chỉ là một câu hỏi về các điều khoản, hy vọng tôi sẽ làm cho mình rõ ràng.


Bạn có thể tạo khoảng nhiều đối tượng Texture khi có bộ nhớ khả dụng trong hệ thống của bạn. Các đối tượng này giữ dữ liệu thực tế (texels) của kết cấu của bạn, cùng với các tham số, được cung cấp bởi glTexParameter (xem Câu hỏi thường gặp ).

Khi được tạo ra, bạn phải gán một Texture Target để một đối tượng kết cấu, đại diện cho các loại hình kết cấu ( GL_TEXTURE_2D, GL_TEXTURE_3D, GL_TEXTURE_CUBE, ...).

Hai mục này, đối tượng kết cấumục tiêu kết cấu đại diện cho dữ liệu kết cấu. Chúng tôi sẽ quay lại với họ sau.

Đơn vị kết cấu

Bây giờ, OpenGL cung cấp một loạt các đơn vị kết cấu , có thể được sử dụng đồng thời trong khi vẽ. Kích thước của mảng phụ thuộc vào hệ thống OpenGL, của bạn có 8.

Bạn có thể liên kết một đối tượng kết cấu với một đơn vị kết cấu để sử dụng kết cấu đã cho trong khi vẽ.

Trong một thế giới đơn giản và dễ dàng, để vẽ với một kết cấu nhất định, bạn sẽ liên kết một đối tượng kết cấu với đơn vị kết cấu và bạn sẽ làm (mã giả):

glTextureUnit[0] = textureObject

Vì GL là một máy trạng thái, nó, than ôi, không hoạt động theo cách này. Giả sử rằng chúng tôi textureObjectcó dữ liệu cho GL_TEXTURE_2Dmục tiêu kết cấu, chúng tôi sẽ biểu thị nhiệm vụ trước đó là:

glActiveTexture(GL_TEXTURE0);                   // select slot 0 of the texture units array
glBindTexture(GL_TEXTURE_2D, textureObject);    // do the binding

Lưu ý rằng GL_TEXTURE_2Dthực sự phụ thuộc vào loại kết cấu bạn muốn liên kết.

Đối tượng kết cấu

Trong mã giả, để đặt dữ liệu kết cấu hoặc tham số kết cấu, bạn sẽ làm ví dụ:

setTexData(textureObject, ...)
setTexParameter(textureObject, TEXTURE_MIN_FILTER, LINEAR)

OpenGL không thể trực tiếp thao tác với các đối tượng kết cấu, để cập nhật / thiết lập nội dung của chúng hoặc thay đổi các tham số của chúng, trước tiên bạn phải liên kết chúng với đơn vị kết cấu hoạt động (bất kể đó là gì). Mã tương đương trở thành:

glBindTexture(GL_TEXTURE_2D, textureObject)       // this 'installs' textureObject in texture unit
glTexImage2D(GL_TEXTURE_2D, ...)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)

Shader

Shader có quyền truy cập vào tất cả các đơn vị kết cấu, họ không quan tâm đến kết cấu hoạt động.

Đồng phục mẫu là intcác giá trị đại diện cho chỉ số của đơn vị kết cấu sẽ sử dụng cho bộ lấy mẫu ( chứ không phải đối tượng kết cấu sẽ sử dụng).

Vì vậy, bạn phải liên kết các đối tượng kết cấu của bạn với các đơn vị bạn muốn sử dụng.

Loại bộ lấy mẫu sẽ khớp với mục tiêu kết cấu được sử dụng trong đơn vị kết cấu: Sampler2Dfor GL_TEXTURE_2D, v.v.


Một điều tôi không hiểu. Giả sử tôi có một số kết cấu và nó được sử dụng trong nhiều shader trên các đơn vị kết cấu khác nhau. Giả sử tôi muốn thay đổi lọc kết cấu trong thời gian chạy. Tôi nên sử dụng đơn vị kết cấu nào? Tôi có thể thay đổi trạng thái kết cấu trên Đơn vị 0 và sau đó sử dụng kết cấu đó trên các đơn vị khác nhau không?
Majakthecoder

@majakthecoder Trong câu trả lời của tôi, tôi coi bộ lọc là một thuộc tính của đối tượng kết cấu - có nghĩa là bạn không thể thay đổi cụ thể trong một đơn vị kết cấu. Tùy thuộc vào hương vị của OpenGL mà bạn nhắm mục tiêu, bạn có thể lấy mẫu các đối tượng để giải quyết vấn đề này ( opengl.org/wiki/Sampler_Object ), nếu không, bạn có thể phải nhân đôi đối tượng kết cấu, để có nhiều bộ lọc đồng thời.
rotoglup

12

Hãy tưởng tượng GPU giống như một số nhà máy xử lý sơn.

Có một số xe tăng, cung cấp thuốc nhuộm cho một số máy vẽ. Trong máy vẽ, thuốc nhuộm sau đó được áp dụng cho đối tượng. Những chiếc xe tăng là đơn vị kết cấu

Những bể chứa có thể được trang bị các loại thuốc nhuộm khác nhau. Mỗi loại thuốc nhuộm đòi hỏi một số loại dung môi khác. "Dung môi" là mục tiêu kết cấu . Để thuận tiện, mỗi bể được kết nối với một số nguồn cung cấp dung môi, nhưng chỉ có thể sử dụng một loại dung môi tại một thời điểm trong mỗi bể. Vì vậy, có một van / switch TEXTURE_CUBE_MAP, TEXTURE_3D, TEXTURE_2D, TEXTURE_1D. Bạn có thể đổ tất cả các loại thuốc nhuộm vào bể cùng một lúc, nhưng vì chỉ có một loại dung môi đi vào, nó sẽ "pha loãng" chỉ loại thuốc nhuộm phù hợp. Vì vậy, bạn có thể có từng loại kết cấu bị ràng buộc, nhưng liên kết với dung môi "quan trọng nhất" sẽ thực sự đi vào bể và trộn với loại thuốc nhuộm mà nó thuộc về.

Và sau đó có thuốc nhuộm, xuất phát từ một nhà kho và được đổ đầy vào bể bằng cách "ràng buộc" nó. Đó là kết cấu của bạn.


2
Một loại tương tự kỳ lạ ... Tôi không chắc nó thực sự xóa sạch mọi thứ. Đặc biệt là phần về "pha loãng" và "dung môi quan trọng nhất". Bạn đang nói nếu tôi liên kết cả kết cấu 2d và kết cấu 3d, tôi chỉ có thể sử dụng một trong số chúng, hoặc cái gì? Cái nào sẽ được coi là quan trọng nhất?
mở

2
@Mark: Chà, tôi đã cố gắng nói theo cách của một họa sĩ làm việc với thuốc nhuộm theo nghĩa đen (nói dựa trên dầu và nước). Dù sao, có nếu bạn liên kết và kích hoạt nhiều mục tiêu kết cấu có quyền ưu tiên: CUBE_MAP> 3D> TEXTURE_ARRAY> 2D> 1D.
datenwolf

1
Khéo léo! Tôi không biết về quyền ưu tiên. Bây giờ có ý nghĩa hơn khi tôi chỉ biết một mục tiêu kết cấu có thể được sử dụng cho mỗi đơn vị kết cấu.
mở

1
@ legends2k: Chà, giờ thì thật thú vị. Chúng ta đang nói về cốt lõi hoặc hồ sơ tương thích. Do chúng tôi giả định lý tưởng, hoặc trình điều khiển lỗi. Về lý thuyết, loại đồng phục chọn mục tiêu của đơn vị kết cấu sẽ chọn. Trong thực tế điều này xảy ra trong hồ sơ cốt lõi. Trong cấu hình tương thích, mong muốn một số trình điều khiển lỗi hiển thị cho bạn kết cấu mặc định toàn màu trắng nếu mục tiêu trước của đơn vị kết cấu không khớp với loại của bộ lấy mẫu.
datenwolf

1
@ legends2k: Ngoài ra, hãy suy nghĩ về những gì sẽ xảy ra nếu có kết cấu 2D và 3D được liên kết với cùng một đơn vị và bạn có đồng phục lấy mẫu 2D và 3D, mà bạn liên kết với cùng một đơn vị? Bạn có thể kích hoạt tất cả các loại lỗi trình điều khiển kỳ lạ với điều này. Trong thực tế suy nghĩ trong mô hình ưu tiên chức năng cố định cũ giữ cho tâm trí của bạn lành mạnh và chương trình của bạn hoạt động, bởi vì đó là cách hầu hết các trình điều khiển sẽ hành xử theo cách có thể dự đoán được.
datenwolf

2

Nếu trong shader của bạn, bạn cần tra cứu từ 2 kết cấu:

uniform sampler2D tex1;  
uniform sampler2D tex2;  

cần phải chỉ ra cho tex1 và tex2 nguồn của chúng như sau:

tex1 = gl.createTexture();  
gl.activeTexture(gl.TEXTURE3);  
gl.bindTexture(gl.TEXTURE_2D, tex1); 
gl.texParameteri(gl.TEXTURE_2D, ...);  
....


tex2 = gl.createTexture();  
gl.activeTexture(gl.TEXTURE7);  
gl.bindTexture(gl.TEXTURE_2D, tex2); 
gl.texParameteri(gl.TEXTURE_2D, ...);  
....  
var tex1Loc  = gl.getUniformLocation(your_shader,"tex1");  
var tex2Loc  = gl.getUniformLocation(your_shader,"tex2");

trong vòng lặp render:

gl.uniform1i(tex1Loc, 3);  
gl.uniform1i(tex2Loc, 7);  
// but you can dynamically change these values

Với gl_bindtexture, không thể làm điều đó. Mặt khác, có thể sử dụng một liên kết trong vòng lặp kết xuất, là trường hợp bạn cung cấp một kết cấu có nội dung trong luồng (video, webcam):

gl.bindTexture(gl.TEXTURE_2D, tex1);  
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, video);  
// in the render loop
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.