#::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
#本文核心目的就是熟練圖形的分析與繪製
#::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
目錄
零、目標+準備
一、圖元繪製之線
- 工程目錄
- 繪製單一、交叉的線
- 繪製折線
- 繪製幾何圖形
- 繪製三角化的幾何圖形
- 繪製曲線、圓形
二、圖元繪製之三角形
- 工程目錄
- 繪製基本幾何圖形
三、圖元繪製之點精靈(內容為空)
四、練練手
- 工程目錄
- 繪製一棵卡通樹
- 繪製一張卡片
- 繪製一棵草
零、目標+準備
1) 目標- 準備
- 觀察所有圖形,發現它們都是點與點之間的連線(直線或曲線),組成一個幾何形狀( ^_^ 好像有點廢話);
- 除了點線的問題外,還可以知道幾何形狀,有交疊、閉環、開環三種情況;
- 除此之外,還有填充色有無的問題;
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
- A、根據 OpenGL ES 的特點,歸納總結:
- a. 要繪製這些圖形,需要控制頂點的數量
- b. 控制頂點與頂點之間的連線情況,Strip 或 Loop(Fan) 或 沒關係
- c. 控制圖形的填充色,即 Fragment Shader 與 Vertex Shader 之間的顏色傳遞問題;
- B、OpenGL ES 下控制資料來源與繪製方式的函式有那些?(VBO模式)
- a. 繫結 VBO 資料 glBufferData
- b. 繪製資料 glDrawArrays/glDrawElements
- c. 繪製模式有:
- GL_POINTS (點)
- GL_LINES/GL_LINE_STRIP/GL_LINE_LOOP (線)
- GL_TRIANGLES/GL_TRIANGLE_STRIP/GL_TRIANGLE_FAN (面)
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
所以本文就是根據圖形的形態,選擇適當的繪製方式,去繪製圖形;核心目的就是熟練圖形的分析與繪製; 因為是練習圖元,所以學習的重點在,資料繫結和圖形繪製這一塊;
一、圖元繪製之線
Lines,多條線的意思; Line Strip , 指首尾相接的線段,第一條線和最後一條線沒有連線在一起; Line Loops, 指首尾相接的線段,第一條線和最後一條線連線在一起,即閉合的曲線;模式 | 線與點的數量關係 |
---|---|
GL_LINES | nPoints = 2 * mLines |
GL_LINE_STRIP | nPoints = mLines + 1 |
GL_LINE_LOOP | nPoints = mLines |
ep: 上圖中的圖形
模式 | 線與點的數量關係 |
---|---|
GL_LINES | v0~v5( 6 ) = 2 * 3 |
GL_LINE_STRIP | v0~v3( 4 ) = 3 + 1 |
GL_LINE_LOOP | v0~v4( 5 ) = 5 |
0.工程目錄
完整的線元工程在,這一章的結尾;圖中紅色箭頭所指的就是要修改的類,其中 VFVertexDatasManager 類是核心,它是負責整個工程的資料繫結和圖形繪製的; 藍色框所指的都是工程中的靜態頂點資料(當然你也可以動態生成並進行繫結繪製);
1. 繪製單一、交叉的線
- 圖形分析
- 首先它們都是線,所以選擇的是 線模式;
- 左側就是一條線 -> GL_LINES,有兩個頂點座標,而且座標是左底右高
- 右側是兩條交叉線 -> GL_LINES,有四個頂點座標
nPoints = 2 * mLines
- 開始寫程式碼
- 資料來源準備
// 位於 VFBaseGeometricVertexData.h
// 單線段
static const VFVertex singleLineVertices[] = {
{ 0.5f, 0.5f, 0.0f},
{-0.5f, -0.5f, 0.0f},
};
// 交叉線
static const VFVertex crossLinesVertices[] = {
// Line one
{ 0.5f, 0.5f, 0.0f},
{-0.5f, -0.5f, 0.0f},
// Line Two
{-0.53f, 0.48f, 0.0f},
{ 0.55f, -0.4f, 0.0f},
};
複製程式碼
- 修改資料繫結方法
/**
* 裝載資料
*/
- (void)attachVertexDatas {
self.currentVBOIdentifier = [self createVBO];
self.drawInfo = [self drawInfoMaker];
if (self.drawInfo.elementDataPtr) {
self.currentElementVBOIdentifier = [self createVBO];
[self bindVertexDatasWithVertexBufferID:self.currentElementVBOIdentifier
bufferType:GL_ELEMENT_ARRAY_BUFFER
verticesSize:self.drawInfo.elementDataSize
datasPtr:self.drawInfo.elementDataPtr];
}
[self bindVertexDatasWithVertexBufferID:self.currentVBOIdentifier
bufferType:GL_ARRAY_BUFFER
verticesSize:self.drawInfo.dataSize
datasPtr:self.drawInfo.dataPtr]; // CPU 記憶體首地址
[self attachVertexArrays];
}
複製程式碼
關鍵的方法是- (void)bindVertexDatasWithVertexBufferID: bufferType: verticesSize: datasPtr:
,如下:
/**
* 使用頂點快取物件
*
* @param vertexBufferID 頂點快取物件標識
*/
- (void)bindVertexDatasWithVertexBufferID:(GLuint)vertexBufferID
bufferType:(GLenum)bufferType
verticesSize:(GLsizeiptr)size
datasPtr:(const GLvoid*)dataPtr {
glBindBuffer(bufferType, vertexBufferID);
// 建立 資源 ( context )
glBufferData(bufferType, // 快取塊 型別
size, // 建立的 快取塊 尺寸
dataPtr, // 要繫結的頂點資料
GL_STATIC_DRAW); // 快取塊 用途
}
複製程式碼
還有- (VFLineDrawInfo)drawLineInfoMaker
方法,生成相應圖形的資料來源資訊,如下:
// 位於 VFVertexDatasManager 類的
// - (VFLineDrawInfo)drawLineInfoMaker; 方法中
case VFDrawGeometryType_SingleLine: {
dataSize = sizeof(singleLineVertices);
dataPtr = singleLineVertices;
verticesIndicesCount = (GLsizei)(sizeof(singleLineVertices) /
sizeof(singleLineVertices[0]));
primitiveMode = VFPrimitiveModeLines;
break;
}
case VFDrawGeometryType_CrossLines: {
dataSize = sizeof(crossLinesVertices);
dataPtr = crossLinesVertices;
verticesIndicesCount = (GLsizei)(sizeof(crossLinesVertices) /
sizeof(crossLinesVertices[0]));
primitiveMode = VFPrimitiveModeLines;
break;
}
複製程式碼
其中 @property (assign, nonatomic) VFDrawInfo drawInfo;
是定義的資料來源資訊結構體,具體資訊如下:
// 位於 VFVertexDatasManager 類中
typedef struct {
// 資料所佔的記憶體大小
GLsizeiptr dataSize;
// 資料的記憶體首地址
const GLvoid *dataPtr;
// 需要繪製的點數量
GLsizei verticesIndicesCount;
// 圖元的繪製型別
VFPrimitiveMode primitiveMode;
// 下標資料所佔的記憶體大小
GLsizeiptr elementDataSize;
// 下標記憶體首地址
const GLvoid *elementDataPtr;
// 下標個數
GLsizei elementIndicesCount;
} VFDrawInfo;
複製程式碼
- 修改繪製方法,直接獲取資訊即可
// 位於 VFVertexDatasManager 類中
#define GPUVBOMemoryPtr (0)
/**
* 繪製圖形
*/
- (void)draw {
glLineWidth(DEFAULT_LINE_WITH);
if (self.drawInfo.elementIndicesCount) {
glDrawElements(self.drawInfo.primitiveMode,
self.drawInfo.elementIndicesCount,
GL_UNSIGNED_BYTE,
GPUVBOMemoryPtr); // GPU 記憶體中的首地址
return;
}
glDrawArrays(self.drawInfo.primitiveMode,
StartIndex, // 就是 0
self.drawInfo.verticesIndicesCount);
}
複製程式碼
其中 glLineWidth
函式是修改線的寬度的;
glDrawElements
是繪製下標的方法;這裡不需要用到,所以先不解釋;
- 修改圖形顯示
// 位於 VFVertexDatasManager 類中
/**
* 繪製的幾何圖形型別
*/
@property (assign, nonatomic) VFDrawGeometryType drawGeometry;
// 位於 VFRenderWindow 類
// 位於 .m 檔案的 263 行
/**
* 裝載頂點資料
*/
- (void)prepareVertexDatas {
[self.vertexManager setDrawGeometry:VFDrawGeometryType_CrossLines];
[self.vertexManager attachVertexDatas];
}
複製程式碼
這裡新增了一個列舉型別的變數,drawGeometry ,目的是方便外部類進行操控,而進行何種型別圖形的繪製渲染,VFDrawGeometryType 定義如下:
// VFVertexDatasManager .h 檔案中
typedef NS_ENUM(NSUInteger, VFDrawGeometryType) {
VFDrawGeometryType_SingleLine = 0, // 單條線
VFDrawGeometryType_CrossLines, // 交叉線
VFDrawGeometryType_MountainLines, // 拆線山
VFDrawGeometryType_TriangleLines, // 線三角
VFDrawGeometryType_RectangleLines, // 線正方形
VFDrawGeometryType_PentagonsLines, // 線五邊形
VFDrawGeometryType_HexagonsLines, // 線六邊形
VFDrawGeometryType_TrapezoidLines, // 線梯形
VFDrawGeometryType_PentagramLines, // 線五角星
VFDrawGeometryType_RoundLines, // 線圓
VFDrawGeometryType_LowPolyRectLines,// LP 線正方形
VFDrawGeometryType_LowPolyPentLines,// LP 線五邊形
VFDrawGeometryType_LowPolyHexLines, // LP 線六邊形
VFDrawGeometryType_LowPolyTrazLines,// LP 線梯形
VFDrawGeometryType_LowPolyStarLines,// LP 線五角星
VFDrawGeometryType_BezierMountain, // Bezier 山
VFDrawGeometryType_BezierRound, // Bezier 圓
VFDrawGeometryType_BezierOval, // Beizer 橢圓
};
複製程式碼
這一節只是,單線與交叉線的繪製;
- 程式執行結果
2. 繪製折線
- 圖形分析
- 首先這是一條線,所以選擇的是 線模式;
- 但是它是一條折線,即多段線首尾相接組成的線,而且沒有閉合,GL_LINES_STRIP 模式;
- 有 7 個頂點,6條線 (nPoints = mLines + 1)
- 開始寫程式碼
- 資料來源
// 位於 VFBaseGeometricVertexData.h
// 折線(山丘)
static const VFVertex mountainLinesVertices[] = {
// Point one
{-0.9f, -0.8f, 0.0f},
// Point Two
{-0.6f, -0.4f, 0.0f},
// Point Three
{-0.4f, -0.6f, 0.0f},
// Point Four
{ 0.05f, -0.05f, 0.0f},
// Point Five
{0.45f, -0.65f, 0.0f},
// Point Six
{ 0.55f, -0.345f, 0.0f},
// Point Seven
{ 0.95f, -0.95f, 0.0f},
};
複製程式碼
- 修改資料繫結方法 在 drawLineInfoMaker 類中增加新的內容,其它不變;
// 位於 VFVertexDatasManager 類的
// - (VFLineDrawInfo)drawLineInfoMaker; 方法中
case VFDrawGeometryType_MountainLines: {
dataSize = sizeof(mountainLinesVertices);
dataPtr = mountainLinesVertices;
verticesIndicesCount = (GLsizei)(sizeof(mountainLinesVertices) /
sizeof(mountainLinesVertices[0]));
primitiveMode = VFPrimitiveModeLineStrip;
break;
}
複製程式碼
- 修改圖形的顯示
// 位於 VFRenderWindow 類
// 位於 .m 檔案的 263 行
/**
* 裝載頂點資料
*/
- (void)prepareVertexDatas {
[self.vertexManager setDrawGeometry:VFDrawGeometryType_MountainLines];
[self.vertexManager attachVertexDatas];
}
複製程式碼
- 程式執行結果
3. 繪製幾何圖形
- 圖形分析 多段線首尾相接組成的幾何形狀,GL_LINES_LOOP 模式;
nPoints = mLines
-
開始寫程式碼
-
資料來源(從左至右),其中五角星這個資料,可以利用內五邊形與外五邊形相結合的方法(當然內五邊形的點要做一個角度旋轉),生成相應的點;
所有的點,都通過程式動態生成,如下:
這個類的計算原理是,建立極座標系,確定起始點,再迴圈增加旋轉角度,就可以得到所有的點,包括圓的點*(圓即正多邊形,不過它的邊數已經多到細到人眼無法識別,而出現曲線的效果,就像這一小節開始的動態圖一樣的原理,當然橢圓的點集也可以通過這種方式得到)*;這兩個類在另外的工程裡面, Github: 動態計算點
它的小應用,你可以按照自己的想法盡情改寫......
紅框處的,就是點的生成方法;箭頭所指的函式是把生成的點資料按照一定的格式寫入檔案的方法(檔案會自動建立);下面是具體的資料:
-
// 三角形
static const VFVertex triangleLinesVertices[] = {
// Point one
{0.000000, 0.500000, 0.000000},
// Point Two
{-0.433013, -0.250000, 0.000000},
// Point Three
{0.433013, -0.250000, 0.000000},
};
複製程式碼
// 四邊形
static const VFVertex rectangleLinesVertices[] = {
// Point one
{-0.353553, 0.353553, 0.000000},
// Point Two
{-0.353553, -0.353553, 0.000000},
// Point Three
{0.353553, -0.353553, 0.000000},
// Point Four
{0.353553, 0.353553, 0.000000},
};
複製程式碼
// 五邊形
static const VFVertex pentagonsLinesVertices[] = {
// Line one
{0.000000, 0.500000, 0.000000},
// Line Two
{-0.475528, 0.154509, 0.000000},
// Line Three
{-0.293893, -0.404509, 0.000000},
// Line Four
{0.293893, -0.404509, 0.000000},
// Line Five
{0.475528, 0.154509, 0.000000},
};
複製程式碼
// 六邊形
static const VFVertex hexagonsLinesVertices[] = {
// Point one
{0.000000, 0.500000, 0.000000},
// Point Two
{-0.433013, 0.250000, 0.000000},
// Point Three
{-0.433013, -0.250000, 0.000000},
// Point Four
{-0.000000, -0.500000, 0.000000},
// Point Five
{0.433013, -0.250000, 0.000000},
// Point Six
{0.433013, 0.250000, 0.000000},
};
複製程式碼
// 梯形
static const VFVertex trapezoidLinesVertices[] = {
// Point one
{0.430057, 0.350000, 0.000000},
// Point Two
{-0.430057, 0.350000, 0.000000},
// Point Three
{-0.180057, -0.350000, 0.000000},
// Point Four
{0.180057, -0.350000, 0.000000},
};
複製程式碼
// 五角星形
static const VFVertex pentagramLinesVertices[] = {
// Point one
{0.000000, 0.500000, 0.000000},
// Point Two
{-0.176336, 0.242705, 0.000000},
// Point Three
{-0.475528, 0.154509, 0.000000},
// Point Four
{-0.285317, -0.092705, 0.000000},
// Point Five
{-0.293893, -0.404509, 0.000000},
// Point Six
{-0.000000, -0.300000, 0.000000},
// Point Seven
{0.293893, -0.404509, 0.000000},
// Point Eight
{0.285317, -0.092705, 0.000000},
// Point Nine
{0.475528, 0.154509, 0.000000},
// Point Ten
{0.176336, 0.242705, 0.000000},
};
複製程式碼
圓的頂點資料在單獨的檔案中, VFRound.h
,也是通過動態點生成的【因為點太多,所以單獨放在一個檔案中進行管理】;
- 修改資料繫結方法,在 drawLineInfoMaker 方法中增加新的內容
// 位於 VFVertexDatasManager 類的
// - (VFLineDrawInfo)drawLineInfoMaker; 方法中
case VFDrawGeometryType_TriangleLines: {
dataSize = sizeof(triangleLinesVertices);
dataPtr = triangleLinesVertices;
verticesIndicesCount = (GLsizei)(sizeof(triangleLinesVertices) /
sizeof(triangleLinesVertices[0]));
primitiveMode = VFPrimitiveModeLineLoop;
break;
}
case VFDrawGeometryType_RectangleLines: {
dataSize = sizeof(rectangleLinesVertices);
dataPtr = rectangleLinesVertices;
verticesIndicesCount = (GLsizei)(sizeof(rectangleLinesVertices) /
sizeof(rectangleLinesVertices[0]));
primitiveMode = VFPrimitiveModeLineLoop;
break;
}
case VFDrawGeometryType_PentagonsLines: {
dataSize = sizeof(pentagonsLinesVertices);
dataPtr = pentagonsLinesVertices;
verticesIndicesCount = (GLsizei)(sizeof(pentagonsLinesVertices) /
sizeof(pentagonsLinesVertices[0]));
primitiveMode = VFPrimitiveModeLineLoop;
break;
}
case VFDrawGeometryType_HexagonsLines: {
dataSize = sizeof(hexagonsLinesVertices);
dataPtr = hexagonsLinesVertices;
verticesIndicesCount = (GLsizei)(sizeof(hexagonsLinesVertices) /
sizeof(hexagonsLinesVertices[0]));
primitiveMode = VFPrimitiveModeLineLoop;
break;
}
case VFDrawGeometryType_TrapezoidLines: {
dataSize = sizeof(trapezoidLinesVertices);
dataPtr = trapezoidLinesVertices;
verticesIndicesCount = (GLsizei)(sizeof(trapezoidLinesVertices) /
sizeof(trapezoidLinesVertices[0]));
primitiveMode = VFPrimitiveModeLineLoop;
break;
}
case VFDrawGeometryType_PentagramLines: {
dataSize = sizeof(pentagramLinesVertices);
dataPtr = pentagramLinesVertices;
verticesIndicesCount = (GLsizei)(sizeof(pentagramLinesVertices) /
sizeof(pentagramLinesVertices[0]));
primitiveMode = VFPrimitiveModeLineLoop;
break;
}
case VFDrawGeometryType_RoundLines: {
dataSize = sizeof(roundGeometry);
dataPtr = roundGeometry;
verticesIndicesCount = (GLsizei)(sizeof(roundGeometry) /
sizeof(roundGeometry[0]));
primitiveMode = VFPrimitiveModeLineLoop;
break;
}
複製程式碼
-
圖形顯示類(VFRenderWindow )也做相應的修改即可,位於 .m 檔案的 263 行;
-
程式執行結果
4. 繪製三角化的幾何圖形(Low Poly)
- 圖形分析
- 首先它們都是由線組成,線模式
- 其次,它們的線是閉合的,首尾相接?GL_LINES_LOOP ?
- 所謂首尾相接,形成閉合圖形,是起點直接到達終點,就是說起點只會被經過一次,就是最後閉合的那一次;觀察圖形,起點如果只被經過一次,能不能用線繪製出來,很難吧,特別是最後一個,所以這裡直接用 GL_LINES_STRIP 模式,之後任意編排線經過點的順序,即可。(當然,如果你有興趣的話,也可以寫一個演算法去計算點被經過最少的次數下,圖形可以完整繪製出來)
- 點可能會多次被經過,那麼就是說,這個點要被程式排程多次,但是 glDrawArrays 只能一個頂點被排程一次啊。所以這裡要用它的兄弟函式 glDrawElements 這個函式的意思就是繪製成員,頂點資料的下標就是它的成員,即通過頂點資料的成員來訪問資料而進行靈活繪製。
glDrawElements 根據頂點資料在記憶體的下標進行繪製的方法
glDrawElements | |
---|---|
void glDrawElements(GLenum mode, GLsizei count,GLenum type, const GLvoid* indices) | |
mode 只能是以下幾種:GL_POINTS、GL_LINES、GL_LINE_STRIP、GL_LINE_LOOP、GL_TRIANGLES、GL_TRIANGLE_STRIP、GL_TRIANGLE_FAN | |
count indices 的數量 | |
type 下標的資料型別:GL_UNSIGNED_BYTE、GL_UNSIGNED_SHORT、GL_UNSIGNED_INT(它只能在使用了OES_element_index_uint 才能使用) | |
indices 下標在記憶體中的首地址(如果使用了 VBO,就是 GPU 記憶體中的首地址,若不是,則為 CPU 記憶體中的首地址) |
- 開始寫程式碼
- VFLineDrawInfo 增加了對下標繪製的支援
typedef struct {
// 資料所佔的記憶體大小
GLsizeiptr dataSize;
// 資料的記憶體首地址
const GLvoid *dataPtr;
// 需要繪製的點數量
GLsizei verticesIndicesCount;
// 圖元的繪製型別
VFPrimitiveMode primitiveMode;
// 下標資料所佔的記憶體大小
GLsizeiptr elementDataSize; // 在這.....
// 下標記憶體首地址
const GLvoid *elementDataPtr; // 在這.....
// 下標個數
GLsizei elementIndicesCount; // 在這.....
} VFLineDrawInfo;
複製程式碼
- 在原來的線資料基礎下,增加對應圖形的下標資料 這裡選取下標的原則是,讓每一個點都儘可能少地被經過,從而完成圖形的繪製,目的就是為了節省資源。
// 四邊形的下標資料
static const GLubyte rectangleElementIndeices[] = {
0, 1, 2,
3, 0, 2,
};
// 五邊形的下標資料
static const GLubyte pentagonsElementIndeices[] = {
4, 1, 0, 4,
3, 1, 2, 3,
};
// 六邊形的下標資料
static const GLubyte hexagonsElementIndeices[] = {
5, 1, 0, 5,
4, 1, 2, 4,
3, 2,
};
// 梯形的下標資料
static const GLubyte trapezoidElementIndeices[] = {
1, 2, 3, 0,
1, 3,
};
//五角星形的下標資料
static const GLubyte pentagramElementIndeices[] = {
1, 2, 3, 4,
5, 6, 7, 8,
9, 0, 1,
9, 7, 5, 3, 1,
5, 7, 1
};
複製程式碼
- 修改資料繫結方法 繫結新增加的下標資料支援,使用 VBO 的方式*(雖然前面已經寫過,這裡重溫一下,因為這裡都是真正的應用)*
// 核心方法
/**
* 裝載資料
*/
- (void)attachVertexDatas {
self.currentVBOIdentifier = [self createVBO];
self.lineInfo = [self drawLineInfoMaker];
if (self.lineInfo.elementDataPtr) {
self.currentElementVBOIdentifier = [self createVBO];
[self bindVertexDatasWithVertexBufferID:self.currentElementVBOIdentifier
bufferType:GL_ELEMENT_ARRAY_BUFFER
verticesSize:self.lineInfo.elementDataSize
datasPtr:self.lineInfo.elementDataPtr];
}
[self bindVertexDatasWithVertexBufferID:self.currentVBOIdentifier
bufferType:GL_ARRAY_BUFFER
verticesSize:self.lineInfo.dataSize
datasPtr:self.lineInfo.dataPtr]; // CPU 記憶體首地址
[self attachVertexArrays];
}
複製程式碼
在 drawLineInfoMaker 方法中新增內容:
// drawLineInfoMaker 裡面的新增內容
case VFDrawGeometryType_LowPolyRectLines: {
dataSize = sizeof(rectangleLinesVertices);
dataPtr = rectangleLinesVertices;
elementDataSize = sizeof(rectangleElementIndeices);
elementDataPtr = rectangleElementIndeices;
elementIndicesCount = (GLsizei)(sizeof(rectangleElementIndeices) /
sizeof(rectangleElementIndeices[0]));
primitiveMode = VFPrimitiveModeLineStrip;
break;
}
case VFDrawGeometryType_LowPolyPentLines: {
dataSize = sizeof(pentagonsLinesVertices);
dataPtr = pentagonsLinesVertices;
elementDataSize = sizeof(pentagonsElementIndeices);
elementDataPtr = pentagonsElementIndeices;
elementIndicesCount = (GLsizei)(sizeof(pentagonsElementIndeices) /
sizeof(pentagonsElementIndeices[0]));
primitiveMode = VFPrimitiveModeLineStrip;
break;
}
case VFDrawGeometryType_LowPolyHexLines: {
dataSize = sizeof(hexagonsLinesVertices);
dataPtr = hexagonsLinesVertices;
elementDataSize = sizeof(hexagonsElementIndeices);
elementDataPtr = hexagonsElementIndeices;
elementIndicesCount = (GLsizei)(sizeof(hexagonsElementIndeices) /
sizeof(hexagonsElementIndeices[0]));
primitiveMode = VFPrimitiveModeLineStrip;
break;
}
case VFDrawGeometryType_LowPolyTrazLines: {
dataSize = sizeof(trapezoidLinesVertices);
dataPtr = trapezoidLinesVertices;
elementDataSize = sizeof(trapezoidElementIndeices);
elementDataPtr = trapezoidElementIndeices;
elementIndicesCount = (GLsizei)(sizeof(trapezoidElementIndeices) /
sizeof(trapezoidElementIndeices[0]));
primitiveMode = VFPrimitiveModeLineStrip;
break;
}
case VFDrawGeometryType_LowPolyStarLines: {
dataSize = sizeof(pentagramLinesVertices);
dataPtr = pentagramLinesVertices;
elementDataSize = sizeof(pentagramElementIndeices);
elementDataPtr = pentagramElementIndeices;
elementIndicesCount = (GLsizei)(sizeof(pentagramElementIndeices) /
sizeof(pentagramElementIndeices[0]));
primitiveMode = VFPrimitiveModeLineStrip;
break;
}
複製程式碼
// 修改的資料繫結方法
/**
* 使用頂點快取物件
*
* @param vertexBufferID 頂點快取物件標識
*/
- (void)bindVertexDatasWithVertexBufferID:(GLuint)vertexBufferID
bufferType:(GLenum)bufferType
verticesSize:(GLsizeiptr)size
datasPtr:(const GLvoid*)dataPtr {
glBindBuffer(bufferType, vertexBufferID);
// 建立 資源 ( context )
glBufferData(bufferType, // 快取塊 型別
size, // 建立的 快取塊 尺寸
dataPtr, // 要繫結的頂點資料
GL_STATIC_DRAW); // 快取塊 用途
}
複製程式碼
- 資料繪製方法中的下標繪製支援
// 修改的繪製方法
#define GPUVBOMemoryPtr (0)
/**
* 繪製圖形
*/
- (void)draw {
glLineWidth(DEFAULT_LINE_WITH);
if (self.lineInfo.elementIndicesCount) {
glDrawElements(self.lineInfo.primitiveMode,
self.lineInfo.elementIndicesCount,
GL_UNSIGNED_BYTE,
GPUVBOMemoryPtr); // GPU 記憶體中的首地址
return;
}
glDrawArrays(self.lineInfo.primitiveMode,
0,
self.lineInfo.verticesIndicesCount);
}
複製程式碼
- 程式執行結果
5. 繪製曲線、圓形
-
圖形分析
- 首先,它們都是曲線,它們都可以通過 GL_LINE_STRIP 條帶來進行繪製,而且後者也可能通過 GL_LINE_LOOP 進行繪製;
- 根據上一節的圓可以知道,只要線足夠短,以致人眼無法分辨,那麼折線就可以形成曲線,但是有個問題?左邊的,折線怎麼控制它的方向呢,第一個點與第二個點之間的折線彎曲程度,要怎麼才能生成它的點集呢?
- OpenGL 是以點為基礎進行圖元的繪製的,那麼只要有一個方法動態地根據固定點去控制之間曲線點的生成,問題就解決了。座標與點,那麼肯定是函式,要生成曲線,貝塞爾曲線函式就可以了(如果想不到,回憶你所見過的任一個圖形繪製軟體,就秒懂了,如:PS 的鋼筆工具, skecth 的鋼筆工具......)。
-
知識補充( 貝塞爾曲線 ) 請看下面的 word/pdf 文件《貝塞爾曲線推導》 書寫貝塞爾曲線函式如下,具體實現也在Github: 動態計算點 這裡
-
開始寫程式碼
- 資料來源都在 檔案中,紅框處
- 增加 VFDrawGeometryType 內容
VFDrawGeometryType_BezierMountain,
VFDrawGeometryType_BezierRound,
VFDrawGeometryType_BezierOval,
複製程式碼
- drawLineInfoMaker 裡面的新增內容
case VFDrawGeometryType_BezierMountain: {
dataSize = sizeof(_BEZMountain);
dataPtr = _BEZMountain;
verticesIndicesCount = (GLsizei)(sizeof(_BEZMountain) /
sizeof(_BEZMountain[0]));
primitiveMode = VFPrimitiveModeLineStrip;
break;
}
case VFDrawGeometryType_BezierRound: {
dataSize = sizeof(_BEZRound);
dataPtr = _BEZRound;
verticesIndicesCount = (GLsizei)(sizeof(_BEZRound) /
sizeof(_BEZRound[0]));
primitiveMode = VFPrimitiveModeLineStrip;
break;
}
case VFDrawGeometryType_BezierOval: {
dataSize = sizeof(_BEZOval);
dataPtr = _BEZOval;
verticesIndicesCount = (GLsizei)(sizeof(_BEZOval) /
sizeof(_BEZOval[0]));
primitiveMode = VFPrimitiveModeLineStrip;
break;
}
複製程式碼
-
當然圖形顯示類,也要改咯!
-
程式執行結果
-
完整的線元工程, Github:DrawGeometries_Lines
二、圖元繪製之三角形
Triangles,就是多個三角形; Triangle Strip, 指條帶,相互連線的三角形; Triangle Fan, 指扇面,相互連線的三角形;模式 | 三角形與點的數量關係 |
---|---|
GL_TRIANGLES | nPoints = 3 * mTriangles |
GL_TRIANGLE_STRIP | nPoints = mTriangles + 2 |
GL_TRIANGLE_FAN | nPoints = mTriangles + 2 |
ep: 圖1 中的圖形
模式 | 三角形與點的數量關係 |
---|---|
GL_TRIANGLES | v0~v5( 6 ) = 3 * 2 |
GL_TRIANGLE_STRIP | v0~v4( 5 ) = 3+ 2 |
GL_TRIANGLE_FAN | v0~v4( 5 ) = 3+ 2 |
0. 工程目錄
這裡沒有什麼太大的變化,只是資料的集合發生了一些變化而已;1. 繪製基本幾何圖形
- 圖形分析
- 首先,第一張圖片每一個圖形都是一個面,但是 OpenGL 只能直接繪製三角面,所以必須把圖形分解成三角面才能進行繪製;
- 以下就是分解成三角面之後的圖形:
-
這裡容易出問題的是最後一個圖形(五角星形),三角形與點的關係:10(點的數量) = 10(分割出來的三角形數量) + 2,很明顯是不相等的,所以 10 個點是不可能繪製出來這個圖形的,只能再增加兩個點;除了點的數量問題外,它還不是一個條帶(或者說用條帶來描述並不合適),它更適合用扇面來描述,即 GL_TRIANGLE_FAN;
-
開始寫程式碼
- 資料來源,它們都可以通過 FAN 或 STRIP 進行繪製,當然那個點用得少而且圖形繪製完整,以及方便,就用那個;像五角星那個圖形這麼麻煩,當然不做兩種試驗了;STRIP 模式下的點的分佈要特別注意,偶數下標在上面,奇數下標在下面【把圖形壓扁,你就能看出來了】
// 三角形
static const VFVertex triangleTrianglesVertices[] = {
// Point V0
{0.000000, 0.500000, 0.000000},
// Point V1
{-0.433013, -0.250000, 0.000000},
// Point V2
{0.433013, -0.250000, 0.000000},
};
複製程式碼
// 四邊形( 0,1,2,3,0,2 )
static const VFVertex rectangleTrianglesVertices[] = {
// GL_TRIANGLE_FAN
// Point V0
{-0.353553, 0.353553, 0.000000}, // V0
// Point V1
{-0.353553, -0.353553, 0.000000}, // V1
// Point V2
{0.353553, -0.353553, 0.000000}, // V2
// Point V3
{0.353553, 0.353553, 0.000000}, // V3
// GL_TRIANGLE_STRIP
// // Point V0
// {-0.353553, 0.353553, 0.000000}, // V0
//
// // Point V1
// {-0.353553, -0.353553, 0.000000}, // V1
//
// // Point V3
// {0.353553, 0.353553, 0.000000}, // V3
//
// // Point V2
// {0.353553, -0.353553, 0.000000}, // V2
};
複製程式碼
// 五邊形
static const VFVertex pentagonsTrianglesVertices[] = {
// GL_TRIANGLE_FAN
// // Point V0
// {0.000000, 0.500000, 0.000000},
//
// // Point V1
// {-0.475528, 0.154509, 0.000000},
//
// // Point V2
// {-0.293893, -0.404509, 0.000000},
//
// // Point V3
// {0.293893, -0.404509, 0.000000},
//
// // Point V4
// {0.475528, 0.154509, 0.000000},
// GL_TRIANGLE_STRIP
// Point V1
{-0.475528, 0.154509, 0.000000},
// Point V2
{-0.293893, -0.404509, 0.000000},
// Point V0
{0.000000, 0.500000, 0.000000},
// Point V3
{0.293893, -0.404509, 0.000000},
// Point V4
{0.475528, 0.154509, 0.000000},
};
複製程式碼
// 六邊形
static const VFVertex hexagonsTrianglesVertices[] = {
// GL_TRIANGLE_FAN
// Point V0
{0.000000, 0.500000, 0.000000},
// Point V1
{-0.433013, 0.250000, 0.000000},
// Point V2
{-0.433013, -0.250000, 0.000000},
// Point V3
{-0.000000, -0.500000, 0.000000},
// Point V4
{0.433013, -0.250000, 0.000000},
// Point V5
{0.433013, 0.250000, 0.000000},
// GL_TRIANGLE_STRIP
// // Point V1
// {-0.433013, 0.250000, 0.000000},
//
// // Point V2
// {-0.433013, -0.250000, 0.000000},
//
// // Point V0
// {0.000000, 0.500000, 0.000000},
//
// // Point V3
// {-0.000000, -0.500000, 0.000000},
//
// // Point V4
// {0.433013, -0.250000, 0.000000},
//
// // Point V5
// {0.433013, 0.250000, 0.000000},
//
// // Point V0
// {0.000000, 0.500000, 0.000000},
};
複製程式碼
// 梯形
static const VFVertex trapezoidTrianglesVertices[] = {
// GL_TRIANGLE_FAN
// // Point V0
// {0.430057, 0.350000, 0.000000},
//
// // Point V1
// {-0.430057, 0.350000, 0.000000},
//
// // Point V2
// {-0.180057, -0.350000, 0.000000},
//
// // Point V3
// {0.180057, -0.350000, 0.000000},
// GL_TRIANGLE_STRIP
// Point V0
{0.430057, 0.350000, 0.000000},
// Point V1
{-0.430057, 0.350000, 0.000000},
// Point V3
{0.180057, -0.350000, 0.000000},
// Point V2
{-0.180057, -0.350000, 0.000000},
};
複製程式碼
// 五角星形 10 = (n - 2) -> n = 12
static const VFVertex pentagramTrianglesVertices[] = {
// GL_TRIANGLE_FAN
// Point V0
{0.000000, 0.000000, 0.000000}, // 在原來的基礎上,增加的起點
// Point V1
{0.000000, 0.500000, 0.000000},
// Point V2
{-0.176336, 0.242705, 0.000000},
// Point V3
{-0.475528, 0.154509, 0.000000},
// Point V4
{-0.285317, -0.092705, 0.000000},
// Point V5
{-0.293893, -0.404509, 0.000000},
// Point V6
{-0.000000, -0.300000, 0.000000},
// Point V7
{0.293893, -0.404509, 0.000000},
// Point V8
{0.285317, -0.092705, 0.000000},
// Point V9
{0.475528, 0.154509, 0.000000},
// Point V10
{0.176336, 0.242705, 0.000000},
// Point V11
{0.000000, 0.500000, 0.000000},// 在原來的基礎上,增加的終點
};
複製程式碼
- 資料的繫結(與線元一致),只是修改了 VFDrawGeometryType 列舉和 drawLineInfoMaker 方法而已;
- attachVertexDatas
/**
* 裝載資料
*/
- (void)attachVertexDatas {
self.currentVBOIdentifier = [self createVBO];
self.lineInfo = [self drawLineInfoMaker];
if (self.lineInfo.elementDataPtr) {
self.currentElementVBOIdentifier = [self createVBO];
[self bindVertexDatasWithVertexBufferID:self.currentElementVBOIdentifier
bufferType:GL_ELEMENT_ARRAY_BUFFER
verticesSize:self.lineInfo.elementDataSize
datasPtr:self.lineInfo.elementDataPtr];
}
[self bindVertexDatasWithVertexBufferID:self.currentVBOIdentifier
bufferType:GL_ARRAY_BUFFER
verticesSize:self.lineInfo.dataSize
datasPtr:self.lineInfo.dataPtr]; // CPU 記憶體首地址
[self attachVertexArrays];
}
複製程式碼
- VFDrawGeometryType
複製程式碼
// 在這呢......
typedef NS_ENUM(NSUInteger, VFDrawGeometryType) {
VFDrawGeometryType_TriangleTriangles = 0,
VFDrawGeometryType_RectangleTriangles,
VFDrawGeometryType_PentagonsTriangles,
VFDrawGeometryType_HexagonsTriangles,
VFDrawGeometryType_TrapezoidTriangles,
VFDrawGeometryType_PentagramTriangles,
VFDrawGeometryType_RoundTriangles,
};
複製程式碼
- drawInfoMaker 方法
複製程式碼
// - (VFDrawInfo)drawInfoMaker 方法
// 在這呢......
switch (self.drawGeometry) {
case VFDrawGeometryType_TriangleTriangles: {
dataSize = sizeof(triangleTrianglesVertices);
dataPtr = triangleTrianglesVertices;
verticesIndicesCount = (GLsizei)(sizeof(triangleTrianglesVertices) /
sizeof(triangleTrianglesVertices[0]));
primitiveMode = VFPrimitiveModeTriangles;
break;
}
case VFDrawGeometryType_RectangleTriangles: {
dataSize = sizeof(rectangleTrianglesVertices);
dataPtr = rectangleTrianglesVertices;
verticesIndicesCount = (GLsizei)(sizeof(rectangleTrianglesVertices) /
sizeof(rectangleTrianglesVertices[0]));
primitiveMode = VFPrimitiveModeTriangleFan;
break;
}
case VFDrawGeometryType_PentagonsTriangles: {
dataSize = sizeof(pentagonsTrianglesVertices);
dataPtr = pentagonsTrianglesVertices;
verticesIndicesCount = (GLsizei)(sizeof(pentagonsTrianglesVertices) /
sizeof(pentagonsTrianglesVertices[0]));
primitiveMode = VFPrimitiveModeTriangleStrip;
break;
}
case VFDrawGeometryType_HexagonsTriangles: {
dataSize = sizeof(hexagonsTrianglesVertices);
dataPtr = hexagonsTrianglesVertices;
verticesIndicesCount = (GLsizei)(sizeof(hexagonsTrianglesVertices) /
sizeof(hexagonsTrianglesVertices[0]));
primitiveMode = VFPrimitiveModeTriangleFan;
break;
}
case VFDrawGeometryType_TrapezoidTriangles: {
dataSize = sizeof(trapezoidTrianglesVertices);
dataPtr = trapezoidTrianglesVertices;
verticesIndicesCount = (GLsizei)(sizeof(trapezoidTrianglesVertices) /
sizeof(trapezoidTrianglesVertices[0]));
primitiveMode = VFPrimitiveModeTriangleStrip;
break;
}
case VFDrawGeometryType_PentagramTriangles: {
dataSize = sizeof(pentagramTrianglesVertices);
dataPtr = pentagramTrianglesVertices;
verticesIndicesCount = (GLsizei)(sizeof(pentagramTrianglesVertices) /
sizeof(pentagramTrianglesVertices[0]));
primitiveMode = VFPrimitiveModeTriangleFan;
break;
}
case VFDrawGeometryType_RoundTriangles: {
dataSize = sizeof(roundGeometry);
dataPtr = roundGeometry;
verticesIndicesCount = (GLsizei)(sizeof(roundGeometry) /
sizeof(roundGeometry[0]));
primitiveMode = VFPrimitiveModeTriangleFan;
break;
}
}
複製程式碼
- draw 方法
複製程式碼
#define GPUVBOMemoryPtr (0)
/**
* 繪製圖形
*/
- (void)draw {
if (self.lineInfo.elementIndicesCount) {
glDrawElements(self.lineInfo.primitiveMode,
self.lineInfo.elementIndicesCount,
GL_UNSIGNED_BYTE,
GPUVBOMemoryPtr); // GPU 記憶體中的首地址
return;
}
glDrawArrays(self.lineInfo.primitiveMode,
StartIndex, // 0
self.lineInfo.verticesIndicesCount);
}
複製程式碼
-
同樣要修改圖形顯示類(VFRenderWindow).m 檔案的 263 行;
-
程式執行結果
完整的程式程式碼: Github DrawGeometries_Triangles
三、圖元繪製之點精靈
這裡不進行詳細講解,個人感覺在這裡講沒什麼意思,還是放在 Texture 紋理部分進行詳細講解會比較有用,而且好玩;
如果只是學習 gl_PointSize 的話沒意思,結合 gl_PointCoord 去學習反而更有趣,不過這裡要有紋理的知識,所以先行不講了;
四、練練手
這裡的目的不是為了繪製它們而進行繪製,而是針對圖元繪製做一個深入的學習,要學習分析圖形和尋找合適有效的繪製方式,而且還要做到判斷資料的大致生成方法方式是什麼,不然你永遠都只是一個只會搞程式碼的搬運工而已;程式設計可不僅僅是搞程式碼;0. 工程目錄
取消了採用結構體存取資料的方式,改用 Model 類,方便 OC 處理和傳輸;1. 繪製一棵卡通樹
提示:進行兩次的 glDraw* 呼叫,分別繪製外邊的線和內部的填充圖
2. 繪製一張卡片
提示:把資料分成左、右、右中線,三種,原因是左邊的資料是用貝塞爾曲線生成資料量非常大;主要是利用 glBufferSubData 與 glBufferData 的結合,以及 glVertexAttribPointer 的配合;
3. 繪製一棵草
注意:儘可以地用肉眼去判斷線的走向,用 動態計算點 的類做實驗,不斷成長起來吧。
完整的挑戰專案:Github DrawGeometries_Challenge