您好,登錄后才能下訂單哦!
周一到周五,每天一篇,北京時間早上7點準時更新~
What You’ll Learn in This Chapter(你將會在本章學到啥)
How to create buffers and textures that you can use to store data that your program can access
How to get OpenGL to supply the values of your vertex attributes automatically
How to access textures and buffers from your shaders
如何創建緩沖區和紋理貼圖
如何讓OpenGL自動設置你的頂點的數據
如何從shader中訪問緩沖區和紋理
In the examples you’ve seen so far, either we have used hard-coded data directly in our shaders, or we have passed values to shaders one at a time. While sufficient to demonstrate the configuration of the OpenGL pipeline, this is hardly representative of modern graphics programming. Recent graphics processors are designed as streaming processors that consume and produce huge amounts of data. Passing a few values to OpenGL at a time is extremely inefficient. To allow data to be stored and accessed by OpenGL, we include two main forms of data storage—buffers and textures. In this chapter, we first introduce buffers, which are linear blocks of untyped data and can be seen as generic memory allocations. Next, we introduce textures, which are normally used to store multidimensional data, such as images or other data types
到現在為止,我們展示的那些硬編碼的數據也好,從C語言里向shader中傳數據也好,盡管說明了OpenGL的玩法,但是它實際上僅僅是教學需要。 我們接下來要學習的才是高效的使用OpenGL的方式。在OpenGL中主要有兩種形式的數據:存儲一般數據用的緩沖區和紋理緩沖區。在本章節中,我們 首先來看看存儲一般數據的緩沖區,它是線性的無類型的數據塊。緊接著我們來介紹紋理,你可以在里面存儲多維的數據比如圖片或者什么的。
Buffers(緩沖區)
In OpenGL, buffers are linear allocations of memory that can be used for a number of purposes. They are represented by names, which are essentially opaque handles that OpenGL uses to identify them. Before you can start using buffers, you have to ask OpenGL to reserve some names for you and then use them to allocate memory and put data into that memory. The memory allocated for a buffer object is called its data store. The data store of the buffer is where OpenGL stores its data. You can put data into the buffer using OpenGL commands, or you can map the buffer object, which means that you can get a pointer that your application can use to write directly into (or read directly out of) the buffer
在OpenGL中,緩沖區是線性存儲的內存塊,它們有各自的名字,這個名字基本上就是一個OpenGL用于區分它們的標記。在你開始使用緩沖區之前, 你需要讓OpenGL給你分配一些名字,這樣一來你就可以為這些名字對應的緩沖區分配內存并操作它們。你可以通過OpenGL的API給這些緩沖區里塞數據, 你也可以直接獲取到這些緩沖區的地址,然后往里面寫數據或者從里面讀數據。
Once you have the name of a buffer, you can attach it to the OpenGL context by binding it to a buffer binding point. Binding points are sometimes referred to as targets; these terms may be used interchangeably. There are a large number of buffer binding points in OpenGL and each has a different use, although the buffers you bind to them are the same. For example, you can use the contents of a buffer to automatically supply the inputs of a vertex shader, to store the values of variables that will be used by your shaders, or as a place for shaders to store the data they produce. You can even use the same buffer for multiple purposes at the same time
當你有了緩沖區對象了之后,你就可以將它綁定到當前的OpenGL上下文。這些綁定節點有時候我們也叫它目標。OpenGL里面有很多綁定節點,盡管你綁定上去的緩沖區都一樣,但每種節點有不同的用處。 比如說,你可以用緩沖區去提供shader的頂點數據,或者是用緩沖區去存儲shader產生的輸出數據。你甚至可以在同一時間將同一個緩沖區用于多個目的。
Creating Buffers and Allocating Memory(創建緩沖區并分配內存)
Before you can ask OpenGL to allocate memory, you need to create a buffer object to represent that allocation. Like most objects in OpenGL, buffer objects are represented by a GLuint variable, which is generally called its name. One or more buffer objects can be created using the glCreateBuffers() function, whose prototype is
在你為緩沖區對象分配內存前,你需要先創建這樣一個緩沖區。跟大部分的OpenGL里的對象一樣,緩沖區物體的標記是一個GLuint類型的,這就是它的名字。 你可以使用glCreateBuffers一次性去創建1個或者多個緩沖區對象
void glCreateBuffers(GLsizei n, GLuint* buffers);
The first parameter to glCreateBuffers(), n, is the number of buffer objects to create. The second parameter, buffers, is the address of the variable or variables that will be used to store the names of the buffer objects. If you need to create only one buffer object, set n to 1 and set buffers to the address of a single GLuint variable. If you need to create more than one buffer at a time, simply set n to that number and point buffers to the beginning of an array of at least n GLuint variables. OpenGL will just trust that the array is big enough and will write that many buffer names to the pointer that you specify
第一個參數是你需要創建多少個緩沖區對象,第二個參數是用于存儲緩沖區對象的地址。需要注意的是,第二個參數需要有足夠多的空間去存儲對象的名字,比如你如果要10個緩沖區對象, 那么第二個參數指向的地址必須要至少可以放10個GLuint。
Each of the names you get back from glCreateBuffers() represents a single buffer object. You can bind the buffer objects to the current OpenGL context by calling glBindBuffer(), the prototype of which is
創建好了緩沖區對象之后,你就可以使用glBindBuffer去將某個緩沖區對象綁定到當前的OpenGL的上下文了
void glBindBuffer(GLenum target, GLuint buffer);
Before you can actually use the buffer objects, you need to allocate their data stores, which is another term for the memory represented by the buffer object. The functions that are used to allocate memory using a buffer object are glBufferStorage() and glNamedBufferStorage(). Their prototypes are
完成了上面的操作后,你就可以為你的緩沖區對象分配內存了,使用glBufferStoerage和glNamedBufferStorage來分配
void glBufferStorage(GLenum target,
GLsizeiptr size,
const void data,
GLbitfield flags);
void glNamedBufferStorage(GLuint buffer,
GLsizeiptr size,
const void data,
GLbtifield flags);
The first function affects the buffer object bound to the binding point specified by target; the second function directly affects the buffer specified by buffer. The remainder of the parameters serve the same purpose in both functions. The size parameter specifies how big the storage region is to be, in bytes. The data parameteris used to pass a pointer to any data that you want to initialize the buffer with. If this is NULL, then the storage associated with the buffer object will at first be uninitialized. The final parameter, flags, is used to tell OpenGL how you’re planning to use the buffer object.
第一個函數會影響綁定在target節點上的緩沖區對象。第二個函數直接影響buffer指定的那個緩沖區對象。倆函數剩余的參數的含義是一樣的。 size表示的是緩沖區對象的內存有多少字節,data表示的數據,如果data是空,則緩沖區僅分配內存,不會寫入數據,如果data有數據,則會把data上的數據拷貝到緩沖區對象里去。 最后的一個參數是一個標志位,用來告訴OpenGL,我們的程序后面會如何去使用這個緩沖區對象。
Once storage has been allocated for a buffer object using either glBufferStorage() or glNamedBufferStorage(), it cannot be reallocated or respecified, but is considered immutable. To be clear, the contents of the buffer object’s data store can be changed, but its size or usage flags may not. If you need to resize a buffer, you need to delete it, create a new one, and set up new storage for that
當你為緩沖區對象分配好了內存后,你可以改變緩沖區對象里的內容,但是你不可以改變它的大小,如果你非得這么做,你只能先刪除當前的緩沖區對象,然后重新創建一個新的。
The most interesting parameter to these two functions is the flags parameter. This should give OpenGL enough information to allocate memory suitable for your intended purpose and allow it to make an informed decision about the storage requirements of the buffer. flags is a GLbitfield type, which means that it’s a combination of one or more bits. The flags that you can set are shown in Table 5.1
最搞笑的參數是最后的那個參數,那個參數會告訴OpenGL你將如何使用這些緩沖區對象,這樣OpenGL才能將這些緩沖區分配到合適的內存上,最后的參數可以是表5.1中的多個標志位采用位運算來進行組合
The flags listed in Table 5.1 may seem a little terse and probably deserve more explanation. In particular, the absence of certain flags can mean something to OpenGL, some flags may be used only in combination with others, and the specification of these flags can have an effect on what you’re allowed to do with the buffer later. We’ll provide a brief explanation of each of these flags here and then dive deeper into some of their meanings as we cover further functionality
表5.1里的這些標志位可能需要更多的說明才行,特別是,某些標志對OpenGL有著特殊的意義,有的標志只能與特定的標志進行結合使用。我們先來進行一些簡短的說明, 后面我們用到那些功能的時候再進行深入講解
First, the GL_DYNAMIC_STORAGE_BIT flag is used to tell OpenGL that you mean to update the contents of the buffer directly—perhaps once for every time that you use thedata. If this flag is not set, OpenGL will assume that you’re not likely to need to change the contents of the buffer and might put the data somewhere that is less accessible. If you don’t set this bit, you won’t be able to use commands like glBufferSubData() to update the buffer content, although you will be able to write into it directly from the GPU using other OpenGL commands
首先是GL_DYNAMIC_STORAGE_BIT,這個標記告訴OpenGL,你需要頻繁的去更新緩沖區對象,如果這個標記沒有被設置的話,OpenGL為你的緩沖區對象分配的內存可以在遙遠的山區, 你訪問這樣的緩沖區可能會造成內分泌失調等,甚至大小便失禁。如果你不設置這個東西,你可能無法使用glBufferSubData去更新緩沖區的數據,盡管你可以通過其他的OpenGL指令直接從GPU里往里面寫數據
The mapping flags GL_MAP_READ_BIT, GL_MAP_WRITE_BIT, GL_MAP_PERSISTENT_BIT, and GL_MAP_COHERENT_BIT tell OpenGL if and how you’re planning to map the buffer’s data store. Mapping is the process of getting a pointer that you can use from your application that represents the underlying data store of the buffer. For example, you may map the buffer for read or write access only if you specify the GL_MAP_READ_BIT or GL_MAP_WRITE_BIT flags, respectively. Of course, you can specify both if you wish to map the buffer for both reading and writing. If you specify GL_MAP_PERSISTENT_BIT, then this flag tells OpenGL that you wish to map the buffer and then leave it mapped while you call other drawing comands. If you don’t set this bit, then OpenGL requires that you don’t have the buffers mapped while you’re using it from drawing commands. Supporting peristent maps might come at the expense of some performance, so it’s best not to set this bit unless you really need to. The final bit, GL_MAP_COHERENT_BIT, goes further and tells OpenGL that you want to be able to share data quite tightly with the GPU. If you don’t set this bit, you need to tell OpenGL when you’ve written data into the buffer, even if you don’t unmap it.
GL_MAP_READ_BIT、GL_MAP_WRITE_BIT、GL_MAP_PERSISTENT_BIT和GL_MAP_COHERENT_BIT告訴OpenGL你將如何操作緩沖區的數據。 比如只有你設置了GL_MAP_READ_BIT或者GL_MAP_WRITE_BIT的時候,你才可以通過mapping的方式去讀或者寫數據。你可以對緩沖區對象同時使用讀和寫兩種標志。 GL_MAP_PERSISTENT_BIT標志告訴OpenGL,你在調用繪制指令的時候,不會取消mapping狀態,如果你沒有使用這個標志位,那么你在繪圖的時候,必須要取消緩沖區對象的mapping狀態。 GL_MAP_PERSISTENT_BIT這個標志位實際上會帶來很大的系統負擔, 所以最好還是不要用的比較好哦,除非你真的需要。GL_MAP_COHERENT_BIT這個標志告訴OpenGL你希望能夠 與GPU共享緊湊的數據,如果你不去設置這個標志位,即使你不去unmap,你也需要告訴OpenGL,你啥時候把數據寫入緩沖區了。
// The type used for names in OpenGL is GLuint
GLuint buffer;
// Create a buffer
glCreateBuffers(1, &buffer);
// Specify the data store parameters for the buffer
glNamedBufferStorage(
buffer, // Name of the buffer
1024 * 1024, // 1 MiB of space
NULL, // No initial data
GL_MAP_WRITE_BIT); // Allow map for writing
// Now bind it to the context using the GL_ARRAY_BUFFER binding point
glBindBuffer(GL_ARRAY_BUFFER, buffer);
Listing 5.1: Creating and initializing a buffer
清單5.1:創建和初始化一個緩沖區對象
After the code in Listing 5.1 has executed, buffer contains the name of a buffer object that has been initialized to represent one megabyte of storage for whatever data we choose. Using the GL_ARRAY_BUFFER target to refer to the buffer object suggests to OpenGL that we’re planning to use this buffer to store vertex data, but we’ll still be able to take that buffer and bind it to some other target later. There are a handful of ways to get data into the buffer object. You may have noticed the NULL pointer that we pass as the third argument to glNamedBufferStorage() in Listing 5.1. Had we instead supplied a pointer to some data, that data would have been used to initialize the buffer object. Using this pointer, however, allows us to set only the initial data to be stored in the buffer.
清單5.1的代碼執行完畢后,你就拿到了一個大小為1MB的緩沖區對象了。GL_ARRAY_BUFFER告訴OpenGL,我們將使用這個緩沖區對象存儲頂點數據,但是我們依然可以在后面 把它用作其他用途。我們有很多種方法給緩沖區對象里面塞數據,注意到清單5.1的第三個參數,我們給它傳了個NULL,表示我們只申請內存,而不傳數據。如果這里第三個參數是有數據的,那么這些 數據將會被傳遞到緩沖區對象的內存上去
Another way get data into a buffer is to give the buffer to OpenGL and tell it to copy data there. This allows you to dynamically update the content of a buffer after it has already been initialized. To do this, we call either glBufferSubData() or glNamedBufferSubData(), passing the size of the data we want to put into the buffer, the offset in the buffer where we want it to go, and a pointer to the data in memory that should be put into the buffer. glBufferSubData() and glNamedBufferSubData() are declared as follows:
另一個寫數據的方法就是在創建緩沖區之后,使用glBufferSubData或者是glNamedBufferSubData函數,這些API讓你可以動態的去更新緩沖區對象的內容。offset是寫入數據位置的偏移, size是數據的大小,data是我們要寫入的數據
void glBufferSubData(GLenum target,
GLintptr offset,
GLsizeiptr size,
const GLvoid data);
void glNamedBufferSubData(GLuint buffer,
GLintptr offset,
GLsizeiptr size,
const void data);
To update a buffer object using glBufferSubData(), you must have told OpenGL that you want to put data into it that way. To do this, include GL_DYNAMIC_STORAGE_BIT in the flags parameter to glBufferStorage() or glNamedBufferStorage(). Like glBufferStorage() and glNamedBufferStorage(), glBufferSubData() affects the buffer bound to the binding point specified by target, and glNamedBufferSubData() affects the buffer object specified by buffer. Listing 5.2 shows how we can put the data originally used in Listing 3.1 into a buffer object, which is the first step in automatically feeding a vertex shader with data.
為了使用glBufferSubData更新緩沖區對象的內容,你需要在創建它的時候使用GL_DYNAMIC_STORAGE_BIT。 glBufferSubData會影響到綁定節點的緩沖區對象,glNamedBufferSubData會影響由buffer指定的緩沖區對象的數據內容。清單5.2展示了我們如何把清單3.1中的數據用今天學的方式傳給緩沖區對象
// This is the data that we will place into the buffer object
static const float data[] =
{
0.25, -0.25, 0.5, 1.0,
-0.25, -0.25, 0.5, 1.0,
0.25, 0.25, 0.5, 1.0
};// Put the data into the buffer at offset zero
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(data), data);
Listing 5.2: Updating the content of a buffer with glBufferSubData()
清單5.2,使用glBufferSubData更新緩沖區對象的內容
Another method for getting data into a buffer object is to ask OpenGL for a pointer to the memory that the buffer object represents and then copy the data there yourself. This is known as mapping the buffer. Listing 5.3 shows how to do this using the glMapNamedBuffer() function
另一個往緩沖區對象里寫數據的方法是使用mapping方法,如下所示
// This is the data that we will place into the buffer object
static const float data[] =
{
0.25, -0.25, 0.5, 1.0,
-0.25, -0.25, 0.5, 1.0,
0.25, 0.25, 0.5, 1.0
};
// Get a pointer to the buffer's data store
void * ptr = glMapNamedBuffer(buffer, GL_WRITE_ONLY);
// Copy our data into it...
memcpy(ptr, data, sizeof(data));
// Tell OpenGL that we're done with the pointer
glUnmapNamedBuffer(GL_ARRAY_BUFFER);
Listing 5.3: Mapping a buffer’s data store with glMapNamedBuffer()
As with many other buffer functions in OpenGL, there are two versions—one that affects the buffer bound to one of the targets of the current context, and one that operates directly on a buffer whose name you specify. Their prototypes are
跟其他眾多的緩沖區操作函數一樣,mapping方法有兩個版本,如下所示
void glMapBuffer(GLenum target,
GLenum usage);
void glMapNamedBuffer(GLuint buffer,
GLenum usage);
To unmap the buffer, we call either glUnmapBuffer() or glUnmapNamedBuffer(), as shown in Listing 5.3. Their prototypes are
unmapping操作使用對應的glUnmapBuffer或者glUnmapNamedBuffer
void glUnmapBuffer(GLenum target);
void glUnmapNamedBuffer(GLuint buffer);
Mapping a buffer is useful if you don’t have all the data handy when you call the function. For example, you might be about to generate the data, or to read it from a file. If you wanted to use glBufferSubData() (or the initial pointer passed to glBufferData()), you’d have to generate or read the data into temporary memory and then get OpenGL to make another copy of the data into the buffer object. If you map a buffer, you can simply read the contents of the file directly into the mapped buffer. When you unmap it, if OpenGL can avoid making a copy of the data, it will. Regardless of whether we used glBufferSubData() or glMapBuffer() and an explicit copy to get data into our buffer object, it now contains a copy of data[] and we can use it as a source of data to feed our vertex shader
mapping的好處在于,你不必要在C語言中保存一份數據的拷貝,你可以直接去操作GPU上的數據。比如,如果你從文件中讀入數據,去更新緩沖區對象的內容, 如果使用glBufferSubData的方式,你必須先從文件里把內容讀到內存,然后再使用glBufferSubData更新數據,如果你使用mapping的方式,那么 你可以直接從文件中讀入數據到緩沖區對象中。
The glMapBuffer() and glMapNamedBuffer() functions can sometimes be a little heavy handed. They map the entire buffer, and do not provide any information about the type of mapping operation to be performed besides the usage parameter. Even that serves only as a hint. A more surgical approach can be taken by calling either glMapBufferRange() or glMapNamedBufferRange(), whose prototypes are
使用glMapBuffer和glMapNamedBuffer有時候會太暴力了,因為它mapping了整個緩沖區,另一個更推薦的方式是glMapBufferRange和glMapNamedBufferRange
void glMapBufferRange(GLenum target,
GLintptr offset,
GLsizeiptr length,
GLbitfield access);
void glMapNamedBufferRange(GLuint buffer,
GLintptr offset,
GLsizeiptr length,
GLbitfield access);
As with the glMapBuffer() and glMapNamedBuffer() functions, there are two versions of these functions—one that affects a currently bound buffer and one that affects a directly specified buffer object. These functions, rather than mapping the entire buffer object, map only a specific range of the buffer object. This range is given using the offset and length parameters. The parameter contains flags that tell OpenGL how the mapping should be performed. These flags can be a combination of any of the bits listed in Table 5.2.
跟glMapBuffer和glMapNamedBuffer一樣,有兩個版本-一個影響當前綁定節點的緩沖區對象,一個影響由第一個參數指定的緩沖區對象。 比起mapping整個緩沖區,這倆函數只mapping了一部分。offset指出從哪個偏移地址開始mapping,length表示要mapping多大的數據塊。 最后的標志位如表5.2所示。
As with the bits that you can pass to glBufferStorage(), these bits can control some advanced functionality of OpenGL and, in some cases, their correct usage depends on other OpenGL functionality. However, these bits are not hints and OpenGL will enforce their correct usage. You should set GL_MAP_READ_BIT if you plan to read from the buffer and GL_MAP_WRITE_BIT if you plan to write to it. Reading or writing into the mapped range without setting the appropriate bits is an error. The GL_MAP_PERSISTENT_BIT and GL_MAP_COHERENT_BIT flags have similar meanings to their identically named counterparts in glBufferStorage(). All four of these bits are required to match between when you specify storage and when you request a mapping. That is, if you want to map a buffer for reading using the GL_MAP_READ_BIT flag, then you must also specify the GL_MAP_READ_BIT flag when you call glBufferStorage()
與創建緩沖區時那些標志不同的是,這里的標志位設置不是提示,而是強制設置,也就是說,如果你mapping的時候設置了讀的標志位,那么后面卻往里面寫數據,就會出錯。 還有一點需要注意的是,為了這里能夠實現讀寫操作,你需要在創建該緩沖區對象的時候,就設置相應的標志位
We’ll dig deeper into the remaining flags when we cover synchronization primitives a little later in the book. However, because of the additional control and stronger contract provided by glMapBufferRange() and glMapNamedBufferRange(), it isgenerally preferred to call these functions rather than glMapNamedBuffer() (or glMapBuffer()). You should get into the habbit of using these functions even if you’re not using any of their more advanced features
我們將在后面的內容中更加深入的聊其他的那些標志位。你應該更多的使用glMapBufferRange的API而不是glMapBuffer,因為這樣你能獲得更多的性能方面的提升。
本日的翻譯就到這里,明天見,拜拜~~
第一時間獲取最新橋段,請關注東漢書院以及圖形之心公眾號
東漢書院,等你來玩哦
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。