OpenGL 程式設計/科學 OpenGL 教程 02

在第一個圖形教程中,我們透過為所有資料點建立二維頂點來繪製函式。這很簡單,如果資料不多,效果很好。在本教程中,我們將以截然不同的方式處理繪製資料點的問題。
首先,我們通常對 y 座標更感興趣,x 座標只是在感興趣的域中均勻分佈。如果可以從程式中輕鬆恢復 x 座標,我們不想將其儲存在記憶體中。實際上,從ADC(例如,連線到音效卡的麥克風)獲取資料時,我們只獲得 y 座標流。如果我們能夠將其放入緩衝區而無需進一步處理,讓圖形卡對其進行有用的處理,那將非常棒。
其次,如果我們有成千上萬的資料點,在可能只有幾百個畫素寬的視窗中繪製所有資料點確實毫無意義。因此,如果我們能夠將要繪製的頂點數與我們擁有的資料點數分離,那就太好了。我們也不想在移動或放大和縮小圖形時更改頂點。
解決方案很簡單,我們在一維頂點緩衝區物件 (VBO) 中放置固定的 x 座標,並將 y 座標放置在一維紋理中,讓頂點著色器將兩者組合起來。
注意:雖然核心 OpenGL ES 2.0 支援頂點著色器中的紋理查詢,但允許圖形卡在頂點著色器中使用零個紋理單元。因此,此技術可能在您的卡上無法使用。要檢查可用的頂點紋理單元數量,請使用此程式碼片段
int vertex_texture_units;
glGetIntegerv(GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS, &vertex_texture_units);
if(!vertex_texture_units) {
fprintf(stderr, "Your graphics cards does not support texture lookups in the vertex shader!\n");
// exit here or use another method to render the graph
}
雖然頂點通常是二維或三維的,但 OpenGL 並不反對使用一維頂點。請記住,預設情況下,視窗的 x 座標從 -1 到 1。因此,我們將建立一個 VBO,其中包含 101 個 x 座標,從 -1 到 1。
glGenBuffers(1, &vbo);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
GLfloat line[101];
for(int i = 0; i < 101; i++) {
line[i] = (i - 50) / 50.0;
}
glBufferData(GL_ARRAY_BUFFER, sizeof line, line, GL_STATIC_DRAW);
我們還將我們的頂點屬性重新命名為 "coord1d"
GLint attribute_coord1d = glGetAttribLocation(program, attribute_name);
然後,我們可以幾乎完全像使用二維頂點一樣繪製我們的“線”
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glEnableVertexAttribArray(attribute_coord1d);
glVertexAttribPointer(
attribute_coord1d, // attribute
1, // number of elements per vertex, here just x
GL_FLOAT, // the type of each element
GL_FALSE, // take our values as-is
0, // no space between values
0 // use the vertex buffer object
);
glDrawArrays(GL_LINE_STRIP, 0, 101);
在我們的頂點著色器中,我們必須自己想出 y 座標
attribute float coord1d;
void main(void) {
float y = ...;
gl_Position = vec4(coord1d, y, 0.0, 1.0);
}
練習
- 嘗試在頂點著色器中計算 y 值的各種方法。實際上,您甚至可以讓 OpenGL 評估我們在第一個教程中使用的函式!
根據您擁有的圖形卡和驅動程式,紋理可能非常靈活或非常受限制。一些卡允許以各種格式建立紋理,包括 16 位整數、浮點甚至定點格式。如果您的輸入資料與卡支援的格式匹配,則您無需進行任何轉換,渲染速度將非常快。但是,如果您嘗試使 OpenGL ES 2.0 相容,則有一個限制,即它只支援 8 位整數用於紋理資料。但是,這可能就足夠了。例如,考慮我們在上一個教程中使用的函式,並將 y 座標從 -1..1 對映到 0..255
GLbyte graph[2048];
for(int i = 0; i < 2048; i++) {
float x = (i - 1024.0) / 100.0;
float y = sin(x * 10.0) / (1.0 + x * x);
graph[i] = roundf(y * 128 + 128);
}
現在我們可以建立一個一維紋理。同樣,OpenGL ES 也有一個限制;它沒有明確支援一維紋理。但是,沒有什麼可以阻止我們建立一個非常寬但只有一畫素高的紋理
glActiveTexture(GL_TEXTURE0);
glGenTextures(1, &texture_id);
glBindTexture(GL_TEXTURE_2D, texture_id);
glTexImage2D(
GL_TEXTURE_2D, // target
0, // level, 0 = base, no minimap,
GL_LUMINANCE, // internalformat
2048, // width
1, // height
0, // border, always 0 in OpenGL ES
GL_LUMINANCE, // format
GL_UNSIGNED_BYTE, // type
graph
);
這裡我們使用了GL_LUMINANCE格式來指示我們只有一個顏色分量。
練習
- 嘗試找出您的卡支援哪些紋理格式。
- 一維紋理的大小是否有限制?
- 嘗試使用 glTexSubImage2D() 更改圖形的一部分。
- OpenGL ES 還支援 GL_RGBA 格式,這本質上為我們提供了每個畫素 32 位。我們可以使用它來獲得更高精度的 y 值嗎?
現在我們有了包含 x 座標的 VBO 和包含 y 座標的紋理,我們將它們組合在我們的頂點著色器中。請記住,紋理座標從 0 到 1,而我們的 x 座標從 -1 到 1。此外,我們想要平移和縮放,因此我們將使用上一個教程中的 offset_x 和 scale_x 變數。但是,在這種情況下,由於我們沒有更改 x 座標,因此我們需要反向應用偏移和縮放變換以獲得紋理座標!一旦我們擁有所有座標,我們也可以使用它來為圖形著色,類似於我們在上一個教程中完成的方式。這是完整的頂點著色器原始碼
attribute float coord1d;
varying vec4 f_color;
uniform float offset_x;
uniform float scale_x;
uniform sampler2D mytexture;
void main(void) {
float x = (coord1d / scale_x) - offset_x;
float y = (texture2D(mytexture, vec2(x / 10.24 / 2.0 + 0.5, 0)).r - 0.5) * 2.0;
gl_Position = vec4(coord1d, y, 0.0, 1.0);
f_color = vec4(x / 2.0 + 0.5, y / 2.0 + 0.5, 1.0, 1.0);
}
如您所見,沒有什麼可以阻止您在頂點著色器中使用紋理(儘管在某些圖形卡上,尤其是較舊的圖形卡上,從頂點著色器訪問它們可能比從片段著色器訪問它們更慢)。由於我們有一個 GL_LUMINANCE 格式的紋理,我們必須讀取紅色分量,其他分量是未定義的。還要注意,texture2D() 函式在 0..1 範圍內返回浮點值,而不是在 0..255 範圍內返回整數。片段著色器與上一個教程中的相同,只是將 gl_FragColor 設定為 f_color。
如果您非常放大,您會注意到線條不再平滑,而是看起來像樓梯。您可能會認為這是由於 8 位整數 y 值的精度低。但是,臺階的高度會有所不同,在函式最陡峭的部分,高度將遠遠超過 8 位整數所能解釋的範圍。相反,問題是由水平頂點比紋理中的畫素多造成的。最近鄰插值會導致一組頂點都具有相同的 y 值。為了恢復平滑曲線,我們應該啟用線性插值
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
如果您非常平移或縮小,您會注意到一些非常有趣的事情:函式正在重複!這是因為預設情況下,OpenGL 會環繞紋理座標。我們可以自己在頂點著色器中剪下紋理座標,但我們也可以告訴 OpenGL 自動執行此操作
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
練習
- 讓您可以透過按 F1 和 F2 來切換插值和環繞模式。
- 如果按 F3,它將繪製兩次圖形,一次使用 GL_LINE_STRIP,一次使用 GL_POINTS,使用 5 個畫素的點大小。
- MIN_FILTER 似乎並沒有做太多事情。研究 GL_LINEAR 如何同時用於 MIN_FILTER 和 MAG_FILTER。
- mipmap 會有用嗎?
- 再次考慮使用 GL_RGBA 來獲得 32 位精度。