opengl es 好文

http://blog.oo87.com/opengl/8732.html  //大神地址來自

目錄結構:

第一步,明確要幹嗎

a.目標
 b.效果
 c.分析

第二步,怎麼去畫(純理論)

a.OpenGL ES 2 的渲染管線
 b.簡述繪製流程的每個單元【至左向右】
     1) OpenGL ES 2.0 API
     2) Vertex Arrays / Buffer Objects
     3) Vertex Shader
     4) Primitive Assembly
     5) Rasterization 
     6) Texture Memory
     7) Fragment Shader
     8) Per-Fragment Operations
     9) Render Buffer & Frame Buffer
     10) EAGL API
 c. OpenGL ES Shader Language 簡述
      *) 簡單流程圖

第三步,怎麼去畫(實戰)

a.OpenGL ES 2 的渲染流程 細化
    1) 配置環境
    2) 初始化數據
    3) 配置 OpenGL ES Shader
    4) 渲染繪製
 b.流程代碼化
  1、配置渲染環境
    1) 配置渲染窗口 [ 繼承自 UIView ]
    2) 配置渲染上下文
    3) 配置幀渲染
    4) 配置渲染緩存
    5) 幀緩存裝載渲染緩存的內容
    6) 渲染上下文綁定渲染窗口(圖層)
  2、修改背景色
  3、 初始化數據
  4、 配置 OpenGL ES Shader
    1) 編寫 Vertex Shader Code 文件
    2) 編寫 Fragment Shader Code 文件
    3) 配置 Vertex Shader
    4) 配置 Fragment Shader
    5) 建立 Shader Program
    6) 裝載 Vertex ShaderFragment Shader
    7) 連接 Shader Program
  5、渲染繪製
    1) 清空舊渲染緩存
    2) 設置渲染窗口
    3) 使用 Shder Program
    4) **關聯數據**
    5) 繪製圖形
 c.面向對象的從新設計

第四步,練練手

a.修改背景色
 b.修改三角形的填充色
 c.修改三角形的三個頂點的顏色(填充色)


第一步,明確要幹嗎

1. 目標:

使用 OpenGL ES 2.0 在 iOS 模擬器中繪製一個三角形。php

2. 效果:

3. 分析圖形:

1) 背景顏色是藍色
--> 修改背景顏色css

2) 直角三角形
--> 繪製三角形html

4. 繪製三角形?三角形由什麼組成?

--> 三個端點 + 三條線 + 中間的填充色,即三個點連成線造成一個三角面。nginx

1). 三個什麼端點(屏幕座標點)?
要回答這個問題要先了解 OpenGL ES 的座標系在屏幕上是怎樣分佈的:git

OpenGL ES 的座標系 {x, y, z}程序員

注:圖片截自 《Learning OpenGL ES For iOS》一書github

a. 經過圖片的三維座標系能夠知道:正則表達式

- 它是一個三維座標系 {x, y, z}
- 三維座標中心在正方體的幾何中心 {0, 0, 0}
- 整個座標系是 [0, 1] 的點,也就是說 OpenGL 中只支持 0 ~ 1 的點

注意,這裏所講的 0 和 1 ,最好理解成 0 --> 無限小, 1 --> 無限大 ,它並非指 0 個單位的長度,或 1 個單位的長度。算法

b. 再來看看咱們繪製的三角形,在 iOS 模擬器 或真機上 的座標是怎樣構成的:sql

三維座標 + 座標值 演示圖

注:圖片經過 CINEMA4D (c4d)三維軟件繪製

二維就是長這樣的了:

二維座標( z = 0 )

2) 三條線?

a. 鏈接三個端點造成封閉的三角面,那麼 OpenGL ES 能不能直接繪製三角形 ? --> 答案是能。

b. 那麼 OpenGL 能直接畫正方形麼?
--> 答案是不能。

c. 那 OpenGL 能直接繪製什麼?
--> 答案是:點精靈、線、三角形,它們統稱爲 圖元(Primitive)。

注:答案來自於《OpenGL ES 2.0 Programming Guide》 7. Primitive Assembly and Rasterization 一章,截圖以下:

1) 線元

Line Strip , 指首尾相接的線段,第一條線和最後一條線沒有鏈接在一塊兒;
Line Loops, 指首尾相接的線段,第一條線和最後一條線鏈接在一塊兒,即閉合的曲線;

Line

2) 三角圖元

Triangle Strip, 指條帶,相互鏈接的三角形
Triangle Fan, 指扇面,相互鏈接的三角形

Triangle

扇面

3) 點精靈 【主要應用在 紋理 方面】

3)填充色?

就是指 RGBA 的顏色值;( ^_^ 感受好廢但仍是要說)


第二步,怎麼去畫(純理論)

怎麼去畫,就是經過多少個步驟完成一個完整的繪製渲染流程,固然這裏指 OpenGL ES 2 的渲染管線流程)

OpenGL ES 2 的渲染管線

圖形管線(Graphics Pipeline)

由於這裏是 iOS 端的圖,因此從新繪製了一下:

OpenGL ES 2 渲染流程圖

注:此圖根據 《OpenGL ES 2.0 programming guide》的 Graphics Pipeline 和 Diney Bomfim [All about OpenGL ES 2.x - (part 2/3)] 的管線圖進行從新繪製。【繪製的軟件爲:Visio 2016】

1. 簡述繪製流程的每個單元【至左向右】

OpenGL ES 2.0 API :

iOS 環境下

gltypes.h 是包含了 OpenGL ES 2.0 的基本數據類型的定義;
glext.h 是包含各類宏定義,以及矩陣運算等經常使用的函數;
gl.h 是 OpenGL ES 2.0 全部的核心函數(命令);

擴展
OpenGL ES 2.0 Reference (函數查詢)在線

左邊選擇要查詢的函數便可

離線的函數 Card

紅框處單擊打開

紅箭頭處選擇保存便可

本人推薦使用離線的卡,不受網絡影響,並且一目瞭然。配合官方的編程指南使用就最佳了。

2. Vertex Arrays / Buffer Objects :

1) Vertex Arrays Objects (簡稱:VAOs),頂點數組對象,就是一個數組,包含頂點座標、顏色值、紋理座標等數據;經過 CPU 內存關聯到 GPU 的內存區被 GPU 所使用;

【官方解釋:Vertex data may be sourced from arrays that are stored in application memory (via a pointer) or faster GPU memory (in a buffer object).(意指:頂點數組保存在程序內存或快速 GPU 內存中,前者經過數組指針訪問數據,後者直接經過 Buffer Objects 訪問。【就是指 VAOs 或 VBOs 方式訪問】)】

繪製的三角形的數組(三個頂(端)點座標)以下圖:

頂點數組

VFVertex

這是 C 語言的知識,應該不難理解。

2) Vertex Buffer Objects , (簡稱:VBOs [ Vertex Buffer Objects]),緩存對象,就是持有頂點數組數據或數據下標的對象【並非指面向對象裏面的對象哦,其實一塊 GPU 內存塊】。

【官方解釋:Buffer objects hold vertex array data or indices in high-performance server memory. (意指:VBOs 是持有保存在 GPU 快速內存區的頂點數據或頂點數據下標的緩存對象。)】

a. 爲何是 server ?
--> 答,OpenGL 是基於 CS 模式的設計而成,客戶端操做就至關於咱們寫的 OpenGL API ( OpenGL commands ) 的各類操做,服務器就是圖形處理相關的硬件。( ES 固然也是這意思咯。)

【官方解釋:OpenGL is implemented as a client-server system, with the application you write being considered the client, and the OpenGL implementation provided by the manufacturer of your computer graphics hardware being the server.】

注:
1) a.b. 裏面的【官方解釋...】在 OpenGL ES 2.0 Reference Card 能夠找到。
2) b.1 的【官方解釋...】在《OpenGL Programming Guide》第八版 Introduction OpenGL 一章的第一小節 What Is OpenGL 中的解釋。

3. Vertex Shader (頂點着色器) :

處理頂點相關的數據,包括頂點在屏幕的位置(矩陣變換),頂點處的光照計算,紋理座標等。

頂點着色器的信號圖:

注:圖片截自:《OpenGL ES 2.0 Programming Guide》 1. Introduction to OpenGL ES 2.0 -- OpenGL ES 2.0 -- Vertex Shader 一節中

1) 輸入信號:Attributes、Uniforms、Samplers (optional)

a. Attributes : 屬性的意思,指每個頂點數據;

b. Uniforms :

b-1. 統一的意思 , 是一個只讀全局常量,存儲在程序的常量區;
b-2. 當 Vertex Shader 和 Fragment Shader 定義了同名同類型的 Uniform 常量時,此時的 Uniform 常量就變成了全局常量(指向同一塊內存區的常量);

c. Samplers (可選的) : 
是一個特殊的 Uniforms 保存的是 Texteures(紋理) 數據;

2) 輸出信號: Varying

Varying : 
a. 它是 Vertex Shader 與 Fragment Shader 的接口,是爲了解決功能性問題(兩個 Shader 的信息交互);

b. 儲存 Vertex Shader 的輸出信息;

c. Vertex Shader 與 Fragment Shader 中必需要有必需要同名同類型的 Varying 變量,否則會編譯錯誤;(由於它是兩個 Shader 的信息接口啊,不同還接什麼口啊。)

3) 交互信息: Temporary Variables

Temporary Variables :
a. 指臨時變量;
b. 儲存 Shader 處理過程當中的中間值用的;
c. 聲明在 Funtions(函數) 或 Variable(變量) 內部;

4) 輸出的內建變量:gl_Position、gl_FrontFacing、gl_PointSize

a. gl_Position (highp vec4 變量) :
就是 Vertex Position,Vertex Shader 的輸出值,並且是必需要賦值的變量;只有在 Vertex Shader 中使用纔會有效

注:highp vec4, highp (high precision) 高精度的意思,是精度限定符;vec4 ( Floating Point Vector ) 浮點向量 , OpenGL ES 的數據類型。

b. gl_PointSize (mediump float 變量) :
告訴 Vertex Shader 柵格化點的尺寸(pixels, 像素化),想要改變繪製點的大小就是要用這個變量 只有在 Vertex Shader 中使用纔會有效

注:mediump , mediump (medium precision) 中等精度的意思,是精度限定符;還有最後一個精度限制符是 lowp ( low precision ),低精度的意思。

c. gl_FrontFacing (bool 變量) : 
改變渲染物體的 Front Facing 和 Back Facing , 是用於處理物體光照問題的變量,雙面光照(3D 物體裏外光照)問題的時候纔會使用的變量,只能在 Vertex Shader 中進行設置, Fragment Shader 是隻讀的

4. Primitive Assembly (圖元裝配) :

1) 第一步,把 Vertex Shader 處理後的頂點數據組織成 OpenGL ES 能夠直接渲染的基本圖元:點、線、三角形;

2) 第二步,裁剪 (Clipping) ,只保留在渲染區域(視錐體,視覺區域)內的圖元;

3) 第二步,剔除 (Culling),可經過編程決定剔除前面、後面、仍是所有;

注:
視錐體,其實是一個三維錐體包含的空間區域,由攝影機和物體的捕捉關係造成;

視錐體

圖片來源 《透視投影詳解》一文

5. Rasterization (光柵化) :

光柵化的信號圖:

做用是,將基本圖元(點、線、三角形)轉換成二維的片元(Fragment, 包含二維座標、顏色值、紋理座標等等屬性), 像素化基本圖元使其能夠在屏幕上進行繪製(顯示)。

6. Texture Memory (紋理內存) :

Texture 就是指保存了圖片(位圖)的全部顏色的緩存;Texture Memory 就是圖片的顏色(像素)內存;每個嵌入式系統對 Texture Memory 的大小都是有限制的;

1) 完整的 iOS 渲染繪製管線圖中,向上指向 Vertex Shader 的虛線,意指 Texture Coordinate (紋理座標)信息是經過程序提供給它的;

2) 完整的 iOS 渲染繪製管線圖中,指向 Fragment Shader 的實線,由於 Fragment Shader 處理的是光柵化後的數據,即像素數據,而 Texture 自己就是像素數據,因此 Texture Memory 能夠直接當成 Fragment Shader 的輸入;

7. Fragment Shader (片元着色器) :

片元着色器信號圖:

1) 輸入信號: Varying、Uniforms、Samples
與 Vertex Shader 的輸入是同一個意思,具體請查看 Vertex Shader 處的解釋~~~;

2) 輸入的內建變量:gl_FragCoord、gl_FrontFacing、gl_PointCoord

a. gl_FragCoord (mediump vec4 只讀變量) :
是保存窗口相對座標的 {x, y, z, 1/w} 的變量,z 表示深度 (will be used for the fragment's depth), w 表示旋轉;

b. gl_PointCoord (mediump int 只讀變量) : 
是包含了當前片元原始點位置的二維座標;點的範圍是 [0, 1] ;

c. gl_FrontFacing 
請查看 Vertex Shader 處的解釋;

3) 輸出信號 (內建變量) : gl_FragColor、gl_FragData (圖上沒寫)

a. gl_FragColor (mediump vec4) :
片元的顏色值;

b. gl_FragData (mediump vec4) : 
是一個數組,片元顏色集;

注:兩個輸出信號只能同時存在一個,就是 寫了 gl_FragColor 就不要寫 gl_FragData , 反之亦然;【If a shader statically assigns a value to gl_FragColor, it may not assign a value to any element of gl_FragData. If a shader statically writes a value to any element of gl_FragData, it may not assign a value to gl_FragColor. That is, a shader may assign values to either gl_FragColor or gl_FragData, but not both.

-

補充知識 (For Shader)

8. Per-Fragment Operations :

信號圖:

1) Pixel ownership test (像素歸屬測試) :
判斷像素在 Framebuffer 中的位置是否是爲當前 OpenGL ES Context 全部,即測試某個像素是否屬於當前的 Context 或是否被展現(是否被用戶可見);

2) Scissor Test (裁剪測試) :
判斷像素是否在由 glScissor* 定義的裁剪區域內,不在該剪裁區域內的像素就會被丟棄掉;

3) Stencil Test (模版測試):
將模版緩存中的值與一個參考值進行比較,從而進行相應的處理;

4) Depth Test (深度測試) :
比較下一個片斷與幀緩衝區中的片斷的深度,從而決定哪個像素在前面,哪個像素被遮擋;

5) Blending (混合) :
將片斷的顏色和幀緩存中已有的顏色值進行混合,並將混合所得的新值寫入幀緩存 (FrameBuffer) ;

6) Dithering (抖動) :
使用有限的色彩讓你看到比實際圖象更爲豐富的色彩顯示方式,以緩解表示顏色的值的精度不夠大而致使顏色劇變的問題。

9. Render Buffer & Frame Buffer:

關係圖:

1) Render Buffer (渲染緩存) :

a. 簡稱 RBO , Render Buffer Object;
b. 是由程序(Application)分配的 2D 圖片緩存;
c. Render Buffer 能夠分配和存儲顏色(color)、深度(depth)、模版(stectil)值,也能夠把這三種值裝載到 Frame Buffer 裏面;

2) Frame Buffer (幀緩存) :

a. 簡稱 FBO , Frame Buffer Object;
b. 是顏色、深度、模板緩存裝載在 FBO 上全部裝載點的合集;
c. 描述顏色、深度、模板的大小和類型的屬性狀態;
d. 描述 Texture 名稱的屬性狀態;
e. 描述裝載在 FBO 上的 Render Buffer Objects (渲染緩存對象) 的屬性狀態;

擴充知識(FBO):

FBO API 支持的操做以下:
1) 只能經過 OpenGL ES 命令 (API) 建立 FBO 對象;
2) 使用一個 EGL Context 去建立和使用多個 FBO , 即不要爲每個 FBO 對象建立一個正在渲染的上下文(rendering context);
3) 建立 off-screen 的顏色、深度、模板渲染緩存和紋理須要裝載在 FBO 上;
4) 經過多個 FBO 來共享顏色、深度、模板緩存;
5) 正確地裝載紋理的顏色或深度到 FBO 中,避免複製操做;

10. EAGL API :

官方的是 EGL API 與平臺無關,由於它自己是能夠進行平臺定製的,因此 iOS 下就被 Apple 定製成了 EAGL API 。

EAGL.h : 裏面的核心類是 EAGLContext , 上下文環境;
EAGLDrawable.h : 用於渲染繪製輸出的 EAGLContext 分類;

注:除了上面的兩個外,還有一個類 CAEAGLLayer ,它就是 iOS 端的渲染窗口寄宿層;

【 看這裏:
1) EGL API 設計出來的目的就是爲了在 OpenGL ES 2 能在窗口系統 (屏幕 ,iOS 是 CAEAGLLayer 類爲寄宿層的 View)進行渲染繪製;

2) 能夠進行 EGL 渲染的前提是:

a. 能夠進行顯示的設備( iOS 下固然是手機或模擬器 )
b. 建立渲染面(rendering surface), 設備的屏幕 (on-screen) 或 像素緩存 ( pixel Buffer ) ( off-screen )

注: pixel Buffer , 這種 buffer 是不能直接顯示的,只能成爲渲染面或經過其它 API 分享出去,如: pbuffers 常常被用於 Texture 的 maps , 由於 Texture 自己也是像素嘛;

3) 建立渲染上下文 ( rendering context ), 即 OpenGL ES 2 Rendering Context ;

注:

OpenGL ES Context : 保存了渲染過程當中的全部數據和狀態信息;
圖示解釋:

圖片截自, RW. Beginning. OpenGL ES.and.GLKit Tutorials 教程

OpenGL ES Shader Language 簡述

流程圖中出現的 Vertex Shader 與 Fragment Shader 都是要使用 GLSL ES 語言來進行編程操做的

1. GLSL ES 版本:

OpenGL ES 2.0 對應的 GLSL ES 版本是 1.0,版本編號是 100;

2. iOS Shader 類:

iOS 環境下 GLKit 提供了一個簡單的 Shader 類——GLKBaseEffect 類;

GLKit APIs

3. OpenGL 自己是 C Base 的語言,能夠適應多個平臺,而在 iOS 下的封裝就是 GLKit ;

4. GLSL ES (也稱 ESSL) ?

簡單流程圖:

OpenGL ES Shader 流程圖

1) 編寫 Shader 代碼:

a. 同時編寫 Vertex Code 和 Fragment Code
b. 建議以文件的形式來編寫,不建議使用 "......" 字符串的形式進行編寫,前者會有編譯器的提示做爲輔助防止必定的輸入錯誤,但後者不會,爲了避免必要的麻煩,使用前者;
c. 文件的名稱使用應該要形如 xxxVertexShader.glsl / xxxFragmentShader.glsl;

注:(其實文件名和後綴均可以隨意的,可是你在編程的時候爲了可讀性,建議這樣寫,也是爲了防止沒必要要的麻煩);【 Xcode 只會在 glsl 的文件後綴的文件進行提示,固然有時候會抽一風也是正常的 】

d. 要掌握的知識點是 Shader 的 Data Typies(數據類型,如:GLfloat 等)、Build-in Variables(內置變量,如:attribute 等)、流程控制語句(if、while 等);

2) 除編寫 Shader Code 外,其它的流程都由一個對應的 GLSL ES 的 API (函數) 進行相應的操做;

注:此處只是作了一個 Program 的圖,不是隻能有一個 Program,而是能夠有多個,須要使用多少個,由具體項目決定。


第三步,怎麼去畫(實戰)

以本文的小三角爲例,開始浪吧~~~!

e981fd1c1e0c35f7e91735fb473b2bec.gif

OpenGL ES 2 的渲染流程 實際繪製環境,流程細化

OpenGL ES 2 iOS 渲染邏輯流程圖. png

1. 配置環境:

1) 主要工做是,EAGL API 的設置。

EAGL Class

2) 核心操做:

a. CAEAGLLayer 替換默認的 CALayer,配置繪製屬性;
b. EAGLContext,即 Render Context ,設置成 OpenGL ES 2 API 環境,並使其成爲當前活躍的上下文環境;
c. Frame Buffers / Render Buffer 的建立和使用,以及內容綁定;
d. EAGLContext 綁定渲染的窗口 (on-screen),CAEAGLLayer 

擴展:
CAEAGLLayer 
1) 繼承鏈:

CALayer 有的,固然 CAEAGLLayer 也有;

2) 做用:
a. The CAEAGLLayer class supports drawing OpenGL content in iPhone applications. If you plan to use OpenGL for your rendering, use this class as the backing layer for your views by returning it from your view’s layerClass class method. The returned CAEAGLLayer object is a wrapper for a Core Animation surface that is fully compatible with OpenGL ES function calls.
--> 大意就是,CAEAGLLayer 是專門用來渲染 OpenGL 、OpenGL ES 內容的圖層;若是要使用,則要重寫 layerClass 類方法。

b. Prior to designating the layer’s associated view as the render target for a graphics context, you can change the rendering attributes you want using the drawableProperties property.
--> 大意就是,在 EAGLContext 綁定 CAEAGLLayer 爲渲染窗口以前,能夠經過修改 drawableProperties 屬性來改變渲染屬性。

3) 使用注意:
a. 修改 opaque 屬性爲 YES (CAEAGLLayer.opaque = YES;);
b. 不要修改 Transform ;
c. 當橫豎屏切換的時候,不要去修改 CAEAGLLayer 的 Transform 而進行 Rotate, 而是要經過 OpenGL / OpenGL ES 來 Rotate 要渲染的內容。

EAGLContext 
是管理 OpenGL ES 渲染上下文(包含,信息的狀態、openGL ES 的命令(API)、OpenGL ES 須要繪製的資源)的對象,要使用 OpenGL ES 的 API (命令) 就要使該 Context 成爲當前活躍的渲染上下文。(原文: An EAGLContext object manages an OpenGL ES rendering context—the state information, commands, and resources needed to draw using OpenGL ES. To execute OpenGL ES commands, you need a current rendering context.)

2. 初始化數據

這裏主要是考慮是否使用 VBOs ,因爲移動端對效率有所要求,因此通常採用 VBOs 快速緩存;

3. 配置 OpenGL ES Shader

1) 這裏的核心工做是 Shader Code ,即學習 GLSL ES 語言;
2) iOS 端採用 glsl 後綴的文件來編寫代碼;

4. 渲染繪製

1) 這裏要注意的是 清空舊緩存、設置窗口,雖然只是一句代碼的問題,但仍是很重要的;
2) 核心是學習 glDraw* 繪製 API ;


流程代碼化

1. 配置渲染環境

1) 配置渲染窗口 [繼承自 UIView]

a. 重寫 layerClass 類方法

+ (Class)layerClass {
   return [CAEAGLLayer class]; }

b. 配置 drawableProperties ,就是繪製的屬性

- (void)commit {

    CAEAGLLayer *glLayer = (CAEAGLLayer *)self.layer;

    // Drawable Property Keys
    /* // a. kEAGLDrawablePropertyRetainedBacking // The key specifying whether the drawable surface retains its contents after displaying them. // b. kEAGLDrawablePropertyColorFormat // The key specifying the internal color buffer format for the drawable surface. */

    glLayer.drawableProperties = @{kEAGLDrawablePropertyRetainedBacking : @(YES), // retained unchange
                                   kEAGLDrawablePropertyColorFormat     : kEAGLColorFormatRGBA8 // 32-bits Color
                                   };

    glLayer.contentsScale = [UIScreen mainScreen].scale;
    glLayer.opaque = YES;

}

2) 配置渲染上下文

// a. 定義 EAGLContext
@interface VFGLTriangleView ()
@property (assign, nonatomic) VertexDataMode vertexMode;
@property (strong, nonatomic) EAGLContext *context;
@end
// b. 使用 OpenGL ES 2 的 API,並使該 Context ,成爲當前活躍的 Context
- (void)settingContext {

    self.context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
    [EAGLContext setCurrentContext:self.context];

}

3) 配置幀渲染

- (GLuint)createFrameBuffer {

    GLuint ID;

    glGenFramebuffers(FrameMemoryBlock, &ID);
    glBindFramebuffer(GL_FRAMEBUFFER, ID);

    return ID;

}
函數 描述
glGenFramebuffers 建立 幀緩存對象
glBindFramebuffer 使用 幀緩存對象
glGenFramebuffers
void glGenFramebuffers (GLsizei n, GLuint * framebuffers)
n 指返回多少個 Frame Buffer 對象
framebuffers 指 Frame Buffer 對象的標識符的內存地址
glBindFramebuffer
void glBindFramebuffer (GLenum target, GLuint framebuffer)
target _只能填 GLFRAMEBUFFER
framebuffer 指 Frame Buffer 對象的標識符

4) 配置渲染緩存

- (GLuint)createRenderBuffer {

    GLuint ID;

    glGenRenderbuffers(RenderMemoryBlock, &ID);
    glBindRenderbuffer(GL_RENDERBUFFER, ID);

    return ID;

}
函數 描述
glGenRenderbuffers 建立 渲染緩存對象
glBindRenderbuffer 使用 渲染緩存對象
glGenRenderbuffers
void glGenRenderbuffers(GLsizei n, GLuint *renderbuffers)
n 指返回多少個 Render Buffer 對象
renderbuffers 指 Render Buffer 對象的標識符的內存地址
glBindRenderbuffer
void glBindRenderbuffer(GLenum target, GLuint renderbuffer)
target _只能填 GLRENDERBUFFER
renderbuffers 指 Render Buffer 對象的標識符

5) 幀緩存裝載渲染緩存的內容

- (void)attachRenderBufferToFrameBufferWithRenderID:(GLuint)renderBufferID {

    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, renderBufferID);

}
函數 描述
glFramebufferRenderbuffer 裝載 渲染緩存的內容到幀緩存對象中
glFramebufferRenderbuffer
void glFramebufferRenderbuffer (GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer)
target _只能填 GLFRAMEBUFFER
attachment _只能是三個中的一個:GL_COLOR_ATTACHMENT0 (顏色緩存)、GL_DEPTH_ATTACHMENT ( 深度緩存 )、GL_STENCILATTACHMENT (模板緩存)
renderbuffertarget _只能填 GLRENDERBUFFER
renderbuffer 指 Render Buffer 對象的標識符,並且當前的 Render Buffer 對象必定要是可用的

6) 渲染上下文綁定渲染窗口(圖層)

- (void)bindDrawableObjectToRenderBuffer {

    [self.context renderbufferStorage:GL_RENDERBUFFER fromDrawable:(CAEAGLLayer *)self.layer];

}
函數 描述
renderbufferStorage: fromDrawable: 關聯 當前渲染上下文和渲染窗口
renderbufferStorage: fromDrawable:
- (BOOL)renderbufferStorage:(NSUInteger)target fromDrawable:(id<EAGLDrawable>)drawable
target _只能填 GLRENDERBUFFER
drawable 只能是 CAEAGLLayer 對象

函數解釋:
1) 爲了使建立的 Render Buffer 的內容能夠顯示在屏幕上,要使用這個函數綁定 Render Buffer 並且分配共享內存;
2) 要顯示 Render Buffer 的內容, 就要使用 presentRenderbuffer:來顯示內容;
3) 這個函數的功能等同於 OpenGL ES 中的它【內容太多,簡書很差排版】

函數 描述
glRenderbufferStorage 保存渲染緩存內容
glRenderbufferStorage
void glRenderbufferStorage(GLenum target, GLenum internalformat, GLsizei width, GLsizei height)
target _只能填 GLRENDERBUFFER
internalformat 分三種 color render buffer、 depth render buffer、stencil render buffer
width _像素單位,大小必須 <= GL_MAX_RENDERBUFFERSIZE
height _像素單位,大小必須 <= GL_MAX_RENDERBUFFERSIZE
internalformat
color render buffer [01] GL_RGB565, GL_RGBA4, GL_RGB5_A1,
color render buffer [02] GL_RGB8_OES, GL_RGBA8_OES
depth render buffer [01] GL_DEPTH_COMPONENT16,
depth render buffer [02] GL_DEPTH_COMPONENT24_OES, GL_DEPTH_COMPONENT32_OE
stencil render buffer GL_STENCIL_INDEX8, GL_STENCIL_INDEX4_OES, GL_STENCIL_INDEX1_OE

2. 修改背景色

typedef struct {
    CGFloat red;
    CGFloat green;
    CGFloat blue;
    CGFloat alpha;
} RGBAColor;

static inline RGBAColor RGBAColorMake(CGFloat red, CGFloat green, CGFloat blue, CGFloat alpha) {

    RGBAColor color = {

        .red = red,
        .green = green,
        .blue = blue,
        .alpha = alpha,

    };

    return color;

}

- (void)setRenderBackgroundColor:(RGBAColor)color {

    glClearColor(color.red, color.green, color.blue, color.alpha);

}
函數 描述
glClearColor 清空 Render Buffer 的 Color Render Buffer 爲 RGBA 顏色
glClearColor
void glClearColor (GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha);
red 指 [0, 1] 的紅色值
green 指 [0, 1] 的綠色值
blue 指 [0, 1] 的藍色值
alpha 指 [0, 1] 的透明度值

注: 不想定義 RGBAColor 的話,能夠直接使用 GLKit 提供的 GLKVector4 ,原型是

#if defined(__STRICT_ANSI__)
struct _GLKVector4
{
    float v[4];
} __attribute__((aligned(16)));
typedef struct _GLKVector4 GLKVector4;  
#else
union _GLKVector4
{
    struct { float x, y, z, w; };
    struct { float r, g, b, a; };  // 在這呢......
    struct { float s, t, p, q; };
    float v[4];
} __attribute__((aligned(16)));
typedef union _GLKVector4 GLKVector4; // 是一個共用體
#endif
GLK_INLINE GLKVector4 GLKVector4Make(float x, float y, float z, float w) {
    GLKVector4 v = { x, y, z, w };
    return v;
}

3. 初始化數據

若是要使用 VBOs 最好在這裏建立 VBOs 對象並綁定頂點數據,固然直接在關聯數據一步作也沒問題;

#define VertexBufferMemoryBlock (1)

- (GLuint)createVBO {

    GLuint vertexBufferID;
    glGenBuffers(VertexBufferMemoryBlock, &vertexBufferID);

    return vertexBufferID;

}

#define PositionCoordinateCount (3)

typedef struct {
    GLfloat position[PositionCoordinateCount];
} VFVertex;

static const VFVertex vertices[] = {
    {{-0.5f, -0.5f, 0.0}}, // lower left corner
    {{ 0.5f, -0.5f, 0.0}}, // lower right corner
    {{-0.5f,  0.5f, 0.0}}, // upper left corner
};

- (void)bindVertexDatasWithVertexBufferID:(GLuint)vertexBufferID {

    glBindBuffer(GL_ARRAY_BUFFER, vertexBufferID);

    // 建立 資源 ( context )
    glBufferData(GL_ARRAY_BUFFER,   // 緩存塊 類型
                 sizeof(vertices),  // 建立的 緩存塊 尺寸
                 vertices,          // 要綁定的頂點數據
                 GL_STATIC_DRAW);   // 緩存塊 用途

}
函數 描述
glGenBuffers 申請 VBOs 對象內存
glBindBuffer 綁定 VBOs 對象
glBufferData 關聯頂點數據,並建立內存
glGenBuffers
void glGenBuffers (GLsizei n, GLuint * buffers)
n 指返回多少個 VBO
buffers 指 VBO 的標識符內存地址
glBindBuffer
void glBindBuffer (GLenum target, GLuint buffer)
target _可使用 GL_ARRAY_BUFFER 或 GL_ELEMENT_ARRAYBUFFER
buffer 指 VBO 的標識符
glBufferData
void glBufferData(GLenum target, GLsizeiptr size, const void *data, GLenum usage)
target _可使用 GL_ARRAY_BUFFER 或 GL_ELEMENT_ARRAYBUFFER
size 字節單位,數據在內存中的大小(sizeof(...))
data 頂點數據的內存指針
usage 告訴程序怎麼去使用這些頂點數據
usage
GL_STATIC_DRAW 程序只指定一次內存對象的數據(頂點數據),並且數據會被屢次(很是頻繁地)用於繪製圖元。
GL_DYNAMIC_DRAW 程序不斷地指定內存對象的數據(頂點數據),並且數據會被屢次(很是頻繁地)用於繪製圖元。
GL_STREAM_DRAW 程序只指定一次內存對象的數據(頂點數據),並且數據會被數次(不肯定幾回)用於繪製圖元。

glGenBuffers 、glBindBuffer、glBufferData 都幹了什麼?

1) glGenBuffers 會在 OpenGL ES Context (GPU) 裏面,申請一塊指定大小的內存區;

2) glBindBuffer 會把剛纔申請的那一塊內存聲明爲 GL_ARRAY_BUFFER ,就是以什麼類型的內存來使用;

3) glBufferData 把存放在程序內存的頂點數據 (CPU 內存) 關聯到剛纔申請的內存區中;

注: 圖片截自, RW. Beginning. OpenGL ES.and.GLKit Tutorials 教程;圖片中的 「~~ 3) 拷貝頂點數據~~ 」 更正爲 「 3) 關聯頂點數據 」, 由於從 CPU 拷貝數據到 GPU 是在 OpenGL ES 觸發繪製方法(後面會進到)的時候纔會進行;

4. 配置 OpenGL ES Shader

1) 編寫 Vertex Shader Code 文件

a. 這是文件形式的,建議使用這種, Xcode 會進行關鍵字提示

#version 100

attribute vec4 v_Position;

void main(void) {
    gl_Position = v_Position;
}

a 對應的圖片

b. 這是直接 GLchar * 字符串形式

+ (GLchar *)vertexShaderCode {
    return  "#version 100 \n"
            "attribute vec4 v_Position; \n"
            "void main(void) { \n"
                "gl_Position = v_Position;\n"
            "}";
}

b 對應的圖片

很是明顯地看出,a 無論編寫和閱讀都很輕鬆,而 b 就是一堆紅,不知道是什麼鬼,看久了眼睛會很累;

 代碼解釋:
a. #version 100 ,首先 OpenGL ES 2 使用的 GLSL ES 版本是 100, 這個沒什麼好解釋的。《OpenGL ES 2 programming Guide》有說起

同時也說明了,咱們編寫 GLSL Code 的時候,要使用 《OpenGL ES Shading Language》的語言版本;

b. attribute vec4 v_Position;
b-1. attribute 存儲類型限定符,表示連接,連接 OpenGL ES 的每個頂點數據到頂點着色器(一個一個地);

注:
1) attribute 只能定義 float, vec2, vec3, vec4, mat2, mat3,mat4 這幾種類型的變量,不能是結構體或數組;
2) 只能用在頂點着色器中,不能在片元着色器中使用,否則會編譯錯誤;

補充:其它的存儲類型限定符

限定符 描述
none (默認) 表示本地的可讀寫的內存  輸入的參數
const 表示編譯期固定的內容  只讀的函數參數
attribute 表示連接,連接 OpenGL ES 的每個頂點數據到頂點着色器(一個一個地)
uniform 表示一旦正在被處理的時候就不能改變的變量,連接程序、OpenGL ES 、着色器的變量
varying 表示連接頂點着色器和片元着色器的內部數據

b-2. [ vec4 ],基本的數據類型,直接上圖

注: 圖片截自,OpenGL ES Shading Language 1.0 Quick Reference Card - Page 3

c. gl_Position 內建變量
由於頂點數據裏面

只是用到了 Position 頂點數據;

2) 編寫 Fragment Shader Code 文件

a. 文件形式

#version 100

void main(void) {
    gl_FragColor = vec4(1, 1, 1, 1); // 填充色,白色
}

b. 字符串形式

+ (GLchar *)fragmentShaderCode {
    return  "#version 100 \n"
            "void main(void) { \n"
                "gl_FragColor = vec4(1, 1, 1, 1); \n"
            "}";
}

3) 配置 Vertex Shader

- (GLuint)createShaderWithType:(GLenum)type {

    GLuint shaderID = glCreateShader(type);

    const GLchar * code = (type == GL_VERTEX_SHADER) ? [[self class] vertexShaderCode] : [[self class] fragmentShaderCode];
    glShaderSource(shaderID,
                   ShaderMemoryBlock,
                   &code,
                   NULL);

    return shaderID;
}

- (void)compileVertexShaderWithShaderID:(GLuint)shaderID type:(GLenum)type {

    glCompileShader(shaderID);

    GLint compileStatus;
    glGetShaderiv(shaderID, GL_COMPILE_STATUS, &compileStatus);
    if (compileStatus == GL_FALSE) {
        GLint infoLength;
        glGetShaderiv(shaderID, GL_INFO_LOG_LENGTH, &infoLength);
        if (infoLength > 0) {
            GLchar *infoLog = malloc(sizeof(GLchar) * infoLength);
            glGetShaderInfoLog(shaderID, infoLength, NULL, infoLog);
            NSLog(@"%s -> %s", (type == GL_VERTEX_SHADER) ? "vertex shader" : "fragment shader", infoLog);
            free(infoLog);
        }
    }

}
函數 描述
glCreateShader 建立一個着色器對象
glShaderSource 關聯頂點、片元着色器的代碼
glCompileShader 編譯着色器代碼
glGetShaderiv 獲取着色器對象的相關信息
glGetShaderInfoLog 獲取着色器的打印消息
glCreateShader
GLuint glCreateShader (GLenum type)
type _只能是 GL_VERTEX_SHADER、GL_FRAGMENTSHADER 中的一個
return GLuint 返回着色器的內存標識符
glShaderSource
void glShaderSource (GLuint shader, GLsizei count, const GLchar * const_ _string, const GLint length)
shader 着色器的內存標識符
count 有多少塊着色代碼字符串資源
string 着色代碼字符串首指針
length 着色代碼字符串的長度
glCompileShader
void glCompileShader(GLuint shader)
shader 着色器的內存標識符
glGetShaderiv
void glGetShaderiv(GLuint shader, GLenum pname, GLint *params)
shader 着色器的內存標識符
pname _指定獲取信息的類型,有 GL_COMPILE_STATUS、GL_DELETE_STATUS、GL_INFO_LOG_LENGTH、GL_SHADER_SOURCE_LENGTH、GL_SHADERTYPE 五種
params 用於存儲當前獲取信息的變量內存地址
glGetShaderInfoLog
void glGetShaderInfoLog(GLuint shader, GLsizei maxLength, GLsei_ _length, GLchar infoLog)
shader 着色器的內存標識符
maxLength 指最大的信息長度
length 獲取的信息長度,若是不知道能夠是 NULL
infoLog 存儲信息的變量的內存地址

4) 配置 Fragment Shader
與 3) 方法同樣;

5) 建立 Shader Program

- (GLuint)createShaderProgram {

    return glCreateProgram();

}
函數 描述
glCreateProgram 建立 Shader Program 對象
glCreateProgram
GLuint glCreateProgram()
return GLuint 返回着色器程序的標識符

6) 裝載 Vertex Shader 和 Fragment Shader

- (void)attachShaderToProgram:(GLuint)programID vertextShader:(GLuint)vertexShaderID fragmentShader:(GLuint)fragmentShaderID {

    glAttachShader(programID, vertexShaderID);
    glAttachShader(programID, fragmentShaderID);

}
函數 描述
glAttachShader 裝載 Shader 對象
glAttachShader
void glAttachShader(GLuint program, GLuint shader)
program 着色器程序的標識符
shader 要裝載的着色器對象標識符

7) 連接 Shader Program

- (void)linkProgramWithProgramID:(GLuint)programID {

    glLinkProgram(programID);

    GLint linkStatus;
    glGetProgramiv(programID, GL_LINK_STATUS, &linkStatus);
    if (linkStatus == GL_FALSE) {
        GLint infoLength;
        glGetProgramiv(programID, GL_INFO_LOG_LENGTH, &infoLength);
        if (infoLength > 0) {
            GLchar *infoLog = malloc(sizeof(GLchar) * infoLength);
            glGetProgramInfoLog(programID, infoLength, NULL, infoLog);
            NSLog(@"%s", infoLog);
            free(infoLog);
        }
    }

}
函數 描述
glLinkProgram 連接 Shader Program 對象
glGetProgramiv 獲取 着色器程序的相關信息
glGetProgramInfoLog 獲取 着色器程序的打印信息
glLinkProgram
void glLinkProgram(GLuint program)
program 着色器程序的標識符
glGetProgramiv
void glGetProgramiv(GLuint program, GLenum pname,GLint *params)
program 着色器程序的標識符
pname _能夠選擇的消息類型有以下幾個,GL_ACTIVE_ATTRIBUTES、GL_ACTIVE_ATTRIBUTE_MAX_LENGTH、GL_ACTIVE_UNIFORMS、GL_ACTIVE_UNIFORM_MAX_LENGTH、GL_ATTACHED_SHADERS、GL_DELETE_STATUS、GL_INFO_LOG_LENGTH、GL_LINK_STATUS、GL_VALIDATESTATUS
params 存儲信息的變量的內存地址
glGetProgramInfoLog
void glGetProgramInfoLog(GLuint program,GLsizei maxLength, GLsizei_ _length, GLchar infoLog)
program 着色器程序的標識符
maxLength 指最大的信息長度
length 獲取的信息長度,若是不知道能夠是 NULL
infoLog 存儲信息的變量的內存地址

5. 渲染繪製

1) 清空舊渲染緩存

- (void)clearRenderBuffer {

    glClear(GL_COLOR_BUFFER_BIT);

}
函數 描述
glClear 清空 渲染緩存的舊內容
glClear
void glClear (GLbitfield mask)
mask _三者中的一個 GL_COLOR_BUFFER_BIT (顏色緩存),GL_DEPTH_BUFFER_BIT ( 深度緩存 ), GL_STENCIL_BUFFERBIT (模板緩存)

2) 設置渲染窗口

- (void)setRenderViewPortWithCGRect:(CGRect)rect {

    glViewport(rect.origin.x, rect.origin.y, rect.size.width, rect.size.height);

}
函數 描述
glViewport 設置 渲染視窗的位置和尺寸
glViewport
void glViewport(GLint x, GLint y, GLsizei w, GLsizei h)
x,y 渲染窗口偏移屏幕座標系左下角的像素個數
w,h 渲染窗口的寬高,其值必需要大於 0

3) 使用 Shder Program

- (void)userShaderWithProgramID:(GLuint)programID {

    glUseProgram(programID);

}
函數 描述
glUseProgram 使用 Shader Program
glUseProgram
void glUseProgram(GLuint program)
program 着色器程序的標識符

4) 關聯數據

#define VertexAttributePosition (0)
#define StrideCloser (0)

- (void)attachTriangleVertexArrays {

    glEnableVertexAttribArray(VertexAttributePosition);

    if (self.vertexMode == VertexDataMode_VBO) {

        glVertexAttribPointer(VertexAttributePosition,
                              PositionCoordinateCount,
                              GL_FLOAT,
                              GL_FALSE,
                              sizeof(VFVertex),
                              (const GLvoid *) offsetof(VFVertex, position));

    } else {

        glVertexAttribPointer(VertexAttributePosition,
                              PositionCoordinateCount,
                              GL_FLOAT,
                              GL_FALSE,
                              StrideCloser,
                              vertices);

    }

}
函數 描述
glEnableVertexAttribArray 使能頂點數組數據
glVertexAttribPointer 關聯頂點數據

a. 使能頂點緩存

glEnableVertexAttribArray
void glEnableVertexAttribArray(GLuint index)
index _attribute 變量的下標,範圍是 [ 0, GL_MAX_VERTEXATTRIBS - 1]

b. 關聯頂點數據

glVertexAttribPointer
void glVertexAttribPointer(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void *ptr)
index _attribute 變量的下標,範圍是 [ 0, GL_MAX_VERTEXATTRIBS - 1]
size 指頂點數組中,一個 attribute 元素變量的座標份量是多少(如:position, 程序提供的就是 {x, y ,z} 點就是 3 個座標份量 ),範圍是 [1, 4]
type _數據的類型,只能是 GL_BYTE、GL_UNSIGNED_BYTE、GL_SHORT、GL_UNSIGNED_SHORT、GL_FLOAT、GL_FIXED、GL_HALF_FLOATOES *
normalized _指是否進行數據類型轉換的意思,GL_TRUE 或 GLFALSE
stride 指每個數據在內存中的偏移量,若是填 0(零) 就是每個數據牢牢相挨着。
ptr 數據的內存首地址

知識擴展:
1) 獲取最大 attribute 下標的方法

GLint maxVertexAttribs; // n will be >= 8
glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &maxVertexAttribs);

2) 關於 size 補充

注, 圖片截自,《OpenGL ES 2 Programming Guide》第 6 章

3) 使能頂點數組數據?
其實頂點着色器中處理的數據有兩種輸入類型,CVOs ( Constant
Vertex Objects )、VAOs (Vertex Array Objects);
 glEnableVertexAttribArrayglDisableVertexAttribArray 函數就是使用 CVOs 仍是 VAOs 的一組開關,看圖 :

注: 圖片截自,《OpenGL ES 2 Programming Guide》第 6 章

若使用了 CVOs 做爲輸入數據的,要使用如下處理函數來替代 glVertexAttribPointer 函數:

4) OpenGL ES 只支持 float-pointer 類型的數據,因此纔會有 normalized 參數;

5) 頂點着色器的數據傳遞圖,

注: 圖片截自,《OpenGL ES 2 Programming Guide》第 6 章

特別提醒,VBOs 只是一種爲了加快數據訪問和渲染調度的一種手段,而不是數據輸入方式的一種;

強烈建議您去看一下 《OpenGL ES 2 Programming Guide》的 6. Vertex Attributes, Vertex Arrays, and Buffer Objects 這一章;

5) 繪製圖形

#define PositionStartIndex (0)
#define DrawIndicesCount (3)

- (void)drawTriangle {

    glDrawArrays(GL_TRIANGLES,
                 PositionStartIndex,
                 DrawIndicesCount);

}
函數 描述
glDrawArrays 繪製全部圖元
glDrawArrays
void glDrawArrays(GLenum mode, GLint first, GLsizei count)
mode _繪製的圖元方式,只能是 GL_POINTS、GL_LINES、GL_LINE_STRIP、GL_LINE_LOOP、GL_TRIANGLES、GL_TRIANGLE_STRIP、GL_TRIANGLEFAN 的一種
first 從第幾個頂點下標開始繪製
count 指有多少個頂點下標須要繪製

6) 渲染圖形

- (void)render {

    [self.context presentRenderbuffer:GL_RENDERBUFFER];

}
函數 描述
presentRenderbuffer: 把 Renderbuffer 的內容顯示到窗口系統 (CAEAGLLayer) 中
presentRenderbuffer:
- (BOOL)presentRenderbuffer:(NSUInteger)target
target _只能是 GLRENDERBUFFER
return BOOL 返回是否綁定成功

補充:同時,這個函數也說明了 kEAGLDrawablePropertyRetainedBacking 爲何要設爲 YES 的緣由:

若是要保存 Renderbuffer 的內容就要把 CARAGLLayer 的 drawableProperties 屬性的 kEAGLDrawablePropertyRetainedBacking 設置爲 YES 。

上面全部代碼的工程文件, 在 Github 上 DrawTriangle_OneStep


面向對象的從新設計:

消息處理的主流程就是上面的信號流程圖的步序。
面向對象,就是把全部的消息交給對象來處理咯,關注的就是消息的傳遞和處理。【能夠按照你的喜愛來設計,反正可擴展性和可維護性都比較好就好了,固然也不能把消息的傳遞變得很複雜咯】

OpenGL ES 2 iOS 渲染邏輯流程圖_面向對象化

項目文件結構:

完整代碼在 Github 上 DrawTriangle_OOP


第四步,練練手

建議按照本身的思路從新寫一個項目

1. 修改背景色

提示:glClear 函數

2. 修改三角形的填充色:

提示:CVOs,三個頂點是統一的顏色數據

3. 修改三角形的三個頂點的顏色(填充色):

提示:VAOs / VBOs ,在三個頂點的基礎上添加新的顏色數據

它們三個主要是爲了 [學 + 習] 如何關聯數據,對應的項目是:Github: DrawTriangle_OOP_Challenges_1

若是你發現文章有錯誤的地方,請在評論區指出,不勝感激!!!

目錄

1、分析拉伸的緣由

一、修復先後照片對比
二、從問題到目標,分析緣由

2、準備知識,三維變換

一、4 x 4 方陣
二、線性變換(縮放與旋轉)
三、平移
四、向量(四元數)
五、w 與 其它

3、OpenGL 下的三維變換

一、OpenGL 的座標系
二、OpenGL 的 gl_Position 是行向量仍是列向量
三、單次三維變換與屢次三維變換問題
四、OpenGL 的變換是在那個階段發生的,如何發生

4、修復拉伸問題

一、改寫 Shader Code
二、應用 3D 變換知識,從新綁定數據
  1) 在 glLinkProgram 函數以後,利用 glGetUniformLocation 函數
     獲得 uniform 變量的 location (內存標識符)
  2) 從 Render Buffer 獲得屏幕的像素比(寬:高)值,即爲縮小的值
  3) 使用 Shader Program , 調用 glUseProgram 函數
  4) 使用 3D 變換知識,獲得一個縮放矩陣變量 scaleMat4
  5) 使用 glUniform* 函數把 scaleMat4 賦值給 uniform 變量
三、完整工程

1、分析拉伸的緣由

一、修復先後照片對比

問題與目標

圖片經過 sketch 製做

二、從問題到目標,分析緣由

一、它們的頂點數據均爲:

頂點數組

VFVertex

二、藉助 Matlab 把頂點數據繪製出來:

分佈圖

從圖能夠看出,這三個數據造成的實際上是一個等邊直角三角形,而在 iOS 模擬器中經過 OpenGL ES 繪製出來的是直角三角形,因此是有問題的,三角形被拉伸了。
三、on-Screen (屏幕) 的像素分佈狀況:
1) iPhone6s Plus 屏幕:5.5 寸,1920 x 1080 像素分辨率,明顯寬高比不是 1:1 的;
2) OpenGL ES 的屏幕座標系 與 物理屏幕的座標系對比:

OpenGL ES 的屏幕座標系

物理屏幕的座標系

分析:前者是正方體,後者長方體,不拉伸纔怪。
3) 首先,OpenGL 最後生成的都是像素信息,再顯示在物理屏幕上;經過 1) 和 2) 能夠知道 Y 方向的像素數量大於 X 方向的像素數量,致使真實屏幕所生成的 Y 軸與 X 軸的刻度不一致(就是 Y=0.5 > X=0.5),從而引發了最後渲染繪製出來的圖形是向 Y 方向拉伸了的。
動畫演示修復:

FixTriangle.gif

因此要作的事情是,把頂點座標的 Y 座標變小,並且是要根據當前顯示屏幕的像素比來進行縮小。

Gif 圖片,由 C4D 製做,PS 最終導出;

4) 在 Shader 裏面,v_Position 的數據類型是 vec4 ,即爲 4 份量的向量數據 {x,y,z,w}; 就是說,要把這個向量經過數學運算變成適應當前屏幕的向量。


2、準備知識,三維變換

::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
-- 建議 --:若是向量、矩陣知識不熟悉的能夠看看《線性代數》一書;若是已經有相應的基礎了,能夠直接看《3D 數學基礎:圖形與遊戲開發》,瞭解 3D 的世界是如何用向量和矩陣知識描述的;若對 3D 知識有必定的認識,能夠直接看《OpenGL Programming Guide》8th 的變換知識, 或 《OpenGL Superblble》7th 的矩陣與變換知識,明確 OpenGL 是如何應用這些知識進行圖形渲染的。
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

注:如下核心知識均來源於,《3D 數學基礎:圖形與遊戲開發》,建議看一下第 8 章;

4x4 總體

圖片經過 sketch 製做,請放大看

一、4 x 4 方陣

4X4 方陣

  • 1) 它其實就是一個齊次矩陣,是對 3D 運算的一種簡便記法;
  • 2) 3x3 矩陣並無包含平移,因此擴展到 4x4 矩陣,從而能夠引入平移的運算;

二、線性變換(縮放與旋轉)

線性變換

  • n,是標準化向量,而向量標準化就是指單位化:

normalied

-----> a、 v 不能是零向量,即零向量爲 {0,0,0};
-----> b、||v|| 是向量的模,即向量的長度;
-----> c、例子是 2D 向量的,3D/4D 向量都是同樣的
-------->【 sqrt(pow(x,2)+pow(y,2)+pow(w,2)...) 】

圖片來源於《3D 數學基礎:圖形與遊戲開發》5.7

  • k,是一個常數;

  • a,是一個弧度角;

1) 線性縮放

線性縮放

  • XYZ 方向的縮放:

X 方向,就是 {1,0,0};Y 方向,就是 {0,1,0};Z 方向,就是 {0,0,1};分別代入上面的公式便可獲得。

圖片來源於《3D 數學基礎:圖形與遊戲開發》8.3.1

2) 線性旋轉

線性旋轉

  • X 方向 {1,0,0} 的旋轉:

  • Y 方向 {0,1,0} 的旋轉:

  • Z 方向 {0,0,1} 的旋轉:

圖片來源於《3D 數學基礎:圖形與遊戲開發》8.2.2

三、平移

平移

直接把平移向量,按份量 {x, y, z} 依次代入齊次矩陣便可;

圖片來源於《3D 數學基礎:圖形與遊戲開發》9.4.2

四、向量(四元數)

四元數

a. 向量,即 4D 向量,也稱齊次座標 {x, y, z, w}; 4D->3D,{x/w, y/w, z/w};
b. 四元數,[ w, v ]或 [w, (x,y,z) ] 兩種記法,其中 w 就是一個標量,即一個實數;
c. 點乘

矩陣乘法,點乘

c.1 上面兩種是合法的,而下面兩種是不合法的,就是沒有意義的;
c.2 第一個爲 A(1x3) 行向量 (矩陣) 與 B(3x3)方陣的點乘,第二個是 A(3x3) 的方陣與 A(3x1) 的列向量 (矩陣) 的點乘;

圖片來源於《3D 數學基礎:圖形與遊戲開發》7.1.7

五、w 與 其它

這塊內容如今先不深究,不影響對本文內容的理解。

  • W

w

w,與平移向量 {x, y, z} 組成齊次座標;通常狀況下,都是 1;

  • 投影

投影

這裏主要是控制投影,如透視投影;如:

圖片來源於《3D 數學基礎:圖形與遊戲開發》9.4.6


3、OpenGL 下的三維變換

:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
這裏主要討論第一階段 Vertex 的 3D 變換,對於視圖變換、投影變換,不做過多討論;若是要徹底掌握後面兩個變換,還須要掌握 OpenGL 下的多座標系系統,以及攝像機系統的相關知識。
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

一、OpenGL 的座標系

  • 座標系方向定義分兩種:

圖片來源於,《3D 數學基礎:圖形與遊戲開發》8.1;左右手座標系是用來定義方向的。

  • 旋轉的正方向

右手座標

圖片來源於,Diney Bomfim 的《Cameras on OpenGL ES 2.x - The ModelViewProjection Matrix》;這個就是 OpenGL 使用的座標系,右手座標系;其中白色小手演示了在各軸上旋轉的正方向(黑色箭頭所繞方向);

二、OpenGL 的 gl_Position 是行向量仍是列向量

  • 這裏討論的核心是,gl_Position 接收的是 行向量,仍是列向量?

行向量

列向量

  • 討論行列向量的目的是明確,3D 矩陣變換在作乘法的時候是使用左乘仍是右乘;

圖片來源於,《線性代數》矩陣及其運算一節

從圖中的結果就能夠看出,左乘和右乘運算後是徹底不同的結果;雖然圖片中的矩陣是 2 x 2 方陣,可是擴展到 n x n 也是同樣的結果;

  • 那麼 OpenGL 使用的是什麼向量?

圖 1,列向量

* **英文大意**:矩陣和矩陣乘法在處理座標系顯示模型方面是一個很是有用的途徑,並且對於處理線性變換而言也是很是方便的機制。

圖 2

紅框處的向量就是 v_Position 頂點數據;即 OpenGL 用的是列向量;(木有找到更有力的證據,只能這樣了)

  • 左乘右乘問題?

圖 3

* **英文大意**:在咱們的視圖模型中,咱們想經過一個向量來與矩陣變換進行乘法運算,這裏描述了一個矩陣乘法,向量先乘以 A 矩陣再乘以 B 矩陣:

很明顯,例子使用的就是左乘,即 OpenGL 用的是左乘;

圖 一、3 來源於,《OpenGL Programming Guide 8th》第 5 章第二節
圖 2 來源於,《3D 數學基礎:圖形與遊戲開發》7.1.8

三、單次三維變換與屢次三維變換問題

屢次變換

1) OpenGL 的三維變換總體圖:

4x4 總體 OpenGL

由於列向量的影響,在作點乘的時候,平移放在下方與右側是徹底不同的結果,因此進行了適應性修改

  • 平移部分的內容:

4X4 方陣 OpenGL

平移 OpenGL

* 矩陣平移公式

等式左側:A(4x4)方陣點乘 {v.x, v.y, v.z, 1.0} 是頂點數據列向量;右側就是一個 xyz 均增長必定偏移的列向量

圖片來源於,《OpenGL Superblble》7th, Part 1, Chapter 4. Math for 3D Graphics

  • 投影(就是零)

投影 OpenGL

2) 全部的變換圖例演示
物體的座標是否與屏幕座標原點重疊

Linaer Transforms

  • 單次變換(原點重疊)

Identity

無變換,即此矩陣與任一貫量相乘,不改變向量的全部份量值,能作到這種效果的就是單位矩陣,而咱們使用的向量是齊次座標 {x, y, z, w},因此使用 4 x 4 方陣;{w === 1}.

  • 縮放

Scale

單一的線性變換——縮放,縮放變換是做用在藍色區域的 R(3x3) 方陣的正對角線(從 m11(x)->m22(y)->m33(z))中; 例子是 X、Y、Z 均放大 3 倍。

  • 旋轉

Rotate

單一的線性變換——旋轉,旋轉變換是做用在藍色區域的 R(3x3) 方陣中; 例子是繞 Z 軸旋轉 50 度。

  • 平移

Translation

單一的線性變換——平移,平移變換是做用在綠色區域的 R(3x1) 矩陣中({m11, m21, m31} 對應 {x, y, z}); 例子是沿 X 正方向平移 2.5 個單位。

  • 單次變換(原點不重疊)

Translation&Scale

Translation&Rotate

以上圖片內容來源於《OpenGL Programming Guide》8th, Linear Transformations and Matrices 一小節,使用 skecth 從新排版並導出

3) 屢次變換

連續變換

這裏的問題就是先旋轉仍是後旋轉。旋轉先後,變化的是物體的座標系(虛線(變換後),實線(變換前)),主要是看你要什麼效果,而不是去評論它的對錯。
圖片來源於,《OpenGL Superblble》7th, Matrix Construction and Operators 一節;

四、OpenGL 的變換是在那個階段發生的,如何發生

3D 變換

ES 主要看紅框處的頂點着色階段便可,因此咱們的變換代碼是寫在 Vertex Shader 的文件中。

變換轉換

這裏描述了三個變換階段,第一個階段是模型變換,第二個是視圖變換階段,第三個是投影變換階段,最後出來的纔是變換後的圖形。本文討論的是第一個階段。

詳細過程

做爲了解便可
以上圖片均來源於,《OpenGL Programming Guide》8th, 5. Viewing Transformations, Clipping, and Feedback 的 User Transformations 一節;


4、修復拉伸問題

一、改寫 Shader Code

增長了一個 uniform 變量,並且是 mat4 的矩陣類型,同時左乘於頂點數據;

  • 爲何使用 uniform 變量?
    • 首先, Vertex Shader 的輸入量能夠是 : attribute、unforms、samplers、temporary 四種;
    • 其次,咱們的目的是把每個頂點都縮小一個倍數,也就是它是一個固定的變量,即常量,因此排除 arrribute、temporary ;
    • 同時,既然是一個常量數據,那麼 samplers 能夠排除,因此最後使用的是 uniforms 變量;
  • 爲何使用 mat4 類型?
    v_Position 是 {x, y, z, w} 的列向量,即爲 4 x 1 的矩陣,若是要最終生成 gl_Position 也是 4 x 1 的列向量,那麼就要左乘一個 4 x 4 方陣;而 mat4 就是 4 x 4 方陣。

補充:n x m · 4 x 1 -> 4 x 1,若是要出現最終 4 x 1 那麼,n 必需要是 4;若是矩陣點乘成立,那麼 m 必需要是 4; 因此最終結果是 n x m = 4 x 4 ;

二、應用 3D 變換知識,從新綁定數據

這裏主要解決,如何給 uniform 變量賦值,並且在何時進行賦值的問題

::::::::::::::::::::::::::::::::::::::::::: 核心步驟::::::::::::::::::::::::::::::::::::::::::::::::::::::
*
》》》 一、在 glLinkProgram 函數以後,利用 glGetUniformLocation 函數
》》》 獲得 uniform 變量的 location (內存標識符)
》》》 二、從 Render Buffer 獲得屏幕的像素比(寬:高)值,即爲縮小的值
》》》 三、使用 Shader Program , 調用 glUseProgram 函數
》》》 四、使用 3D 變換知識,獲得一個縮放矩陣變量 scaleMat4
》》》 五、使用 glUniform
 函數把 scaleMat4 賦值給 uniform 變量 **

:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

  • 如何給 uniform 變量賦值?

》》》一、獲得 uniform 的內存標識符

要在 glLinkProgram 後,再獲取 location 值,由於只有連接後 Program 纔會 location 的值

- (BOOL)linkShaderWithProgramID:(GLuint)programID {
    // 綁定 attribute 變量的下標
    // 若是使用了兩個或以上個 attribute 必定要綁定屬性的下標,否則會找不到數據源的
    // 由於使用了一個的時候,默認訪問的就是 0 位置的變量,必然存在的,因此纔不會出錯
    [self bindShaderAttributeValuesWithShaderProgramID:programID];
    // 連接 Shader 到 Program
    glLinkProgram(programID);
    // 獲取 Link 信息
    GLint linkSuccess;
    glGetProgramiv(programID, GL_LINK_STATUS, &linkSuccess);
    if (linkSuccess == GL_FALSE) {
        GLint infoLength;
        glGetProgramiv(programID, GL_INFO_LOG_LENGTH, &infoLength);
        if (infoLength > EmptyMessage) {
            GLchar *messages = malloc(sizeof(GLchar *) * infoLength);
            glGetProgramInfoLog(programID, infoLength, NULL, messages);
            NSString *messageString = [NSString stringWithUTF8String:messages];
            NSLog(@"Error: Link Fail %@ !", messageString);
            free(messages);
        }
        return Failure;
    }
    // 在這裏
    [self.shaderCodeAnalyzer updateActiveUniformsLocationsWithShaderFileName:@"VFVertexShader"
                                                                   programID:programID];
    return Successfully;
}
- (void)updateActiveUniformsLocationsWithShaderFileName:(NSString *)fileName programID:(GLuint)programID {

    NSDictionary *vertexShaderValueInfos = self.shaderFileValueInfos[fileName];
    ValueInfo_Dict *uniforms = vertexShaderValueInfos[UNIFORM_VALUE_DICT_KEY];

    NSArray *keys = [uniforms allKeys];
    for (NSString *uniformName in keys) {
        const GLchar * uniformCharName = [uniformName UTF8String];
        // 在這裏
        GLint location = glGetUniformLocation(programID, uniformCharName); 
        VFShaderValueInfo *info = uniforms[uniformName];
        info.location = location;
    }

}

補充:

glGetActiveUniform
void glGetActiveUniform(GLuint program, GLuint index, GLsizei bufSize, GLsizei length, GLint size, GLenum type, char name)
program 指 Shader Program 的內存標識符
index 指下標,第幾個 uniform 變量,[0, activeUniformCount]
bufSize _全部變量名的字符個數,如:v_Projection , 就有 12 個,若是還定義了 vTranslation 那麼就是 12 + 13 = 25 個
length NULL 便可
size 數量,uniform 的數量,若是不是 uniform 數組,就寫 1,若是是數組就寫數組的長度
type _uniform 變量的類型,GL_FLOAT, GL_FLOAT_VEC2,GL_FLOAT_VEC3, GL_FLOAT_VEC4,GL_INT, GL_INT_VEC2, GL_INT_VEC3, GL_INT_VEC4, GL_BOOL,GL_BOOL_VEC2, GL_BOOL_VEC3, GL_BOOL_VEC4,GL_FLOAT_MAT2, GL_FLOAT_MAT3, GL_FLOAT_MAT4,GL_SAMPLER_2D, GL_SAMPLERCUBE
name uniform 變量的變量名
// 這個函數能夠獲得,正在使用的 uniform 個數,便可以知道 index 是從 0 到幾;
// 還有能夠獲得,bufSize 的長度
glGetProgramiv(progObj, GL_ACTIVE_UNIFORMS, &numUniforms);
glGetProgramiv(progObj, GL_ACTIVE_UNIFORM_MAX_LENGTH,
&maxUniformLen);

》》》》》》》》》》》》》》》

注:VFShaderValueRexAnalyzer 類就是一個方便進行調用的一種封裝而已,你可使用你喜歡的方式進行封裝;

圖片來源於,《OpenGL ES 2.0 Programming Guide》4. Shaders and Programs,Uniforms and Attributes 一節

  • 在何時進行賦值操做?
    必定要在 glUseProgram 後再進行賦值操做,否則無效
- (void)drawTriangle {

    [self.shaderManager useShader];
    [self.vertexManager makeScaleToFitCurrentWindowWithScale:[self.rboManager windowScaleFactor]];
    [self.vertexManager draw];
    [self.renderContext render];

}

》》》二、獲得屏幕的像素比

- (CGFloat)windowScaleFactor {

    CGSize renderSize = [self renderBufferSize];
    float scaleFactor = (renderSize.width / renderSize.height);

    return scaleFactor;

}

補充:renderBufferSize

- (CGSize)renderBufferSize {
    GLint renderbufferWidth, renderbufferHeight;
    glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &renderbufferWidth);
    glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &renderbufferHeight);
    return CGSizeMake(renderbufferWidth, renderbufferHeight);
}

》》》三、使用 Shader Program

- (void)useShader {

    glUseProgram(self.shaderProgramID);

}```

》》》**4、使用 3D 變換知識,獲得一個縮放矩陣變量 scaleMat4**

VFMatrix4 scaleMat4 = VFMatrix4MakeScaleY(scale);```

擴展 1:

VFMatrix4 VFMatrix4MakeXYZScale(float sx, float sy, float sz) {
        VFMatrix4 r4 = VFMatrix4Identity;
        VFMatrix4 _mat4 = {
              sx  , r4.m12, r4.m13, r4.m14,
            r4.m21,   sy  , r4.m23, r4.m24,
            r4.m31, r4.m32,   sz  , r4.m34,
            r4.m41, r4.m42, r4.m43, r4.m44,
        };
        return _mat4;
    };
    VFMatrix4 VFMatrix4MakeScaleX(float sx) {
        return VFMatrix4MakeXYZScale(sx, 1.f, 1.f);
    };
    VFMatrix4 VFMatrix4MakeScaleY(float sy) {
        return VFMatrix4MakeXYZScale(1.f, sy, 1.f);
    };
    VFMatrix4 VFMatrix4MakeScaleZ(float sz) {
        return VFMatrix4MakeXYZScale(1.f, 1.f, sz);
    };

它們都定義在:

VFMath

注:若是不想本身去寫這些函數,那麼能夠直接使用 GLKit 提供的

數學函數

》》》》》》 我的建議,本身去嘗試寫一下會更好

》》》*五、使用 glUniform 函數把 scaleMat4 賦值給 uniform 變量 **

- (void)makeScaleToFitCurrentWindowWithScale:(float)scale {

    NSDictionary *vertexShaderValueInfos = self.shaderCodeAnalyzer.shaderFileValueInfos[@"VFVertexShader"];
    ValueInfo_Dict *uniforms = vertexShaderValueInfos[UNIFORM_VALUE_DICT_KEY];
// NSLog(@"uniforms %@", [uniforms allKeys]);

    // v_Projection 投影
// VFMatrix4 scaleMat4 = VFMatrix4Identity;
    VFMatrix4 scaleMat4 = VFMatrix4MakeScaleY(scale);
    VFMatrix4 transMat4 = VFMatrix4Identity; //VFMatrix4MakeTranslationX(0.3)
    glUniformMatrix4fv((GLint)uniforms[@"v_Projection"].location,   // 定義的 uniform 變量的內存標識符
                       1,                                           // 不是 uniform 數組,只是一個 uniform -> 1
                       GL_FALSE,                                    // ES 下 只能是 False
                       (const GLfloat *)scaleMat4.m1D);             // 數據的首指針

    glUniformMatrix4fv((GLint)uniforms[@"v_Translation"].location,   // 定義的 uniform 變量的內存標識符
                       1,                                           // 不是 uniform 數組,只是一個 uniform -> 1
                       GL_FALSE,                                    // ES 下 只能是 False
                       (const GLfloat *)transMat4.m1D);             // 數據的首指針

}

擴展 2:

  • 賦值函數有那些?
    它們分別是針對不一樣的 uniform 變量進行的賦值函數

 
三、完整工程:Github: [DrawTriangle_Fix](https://github.com/huangwenfei/OpenGLES2Learning/tree/master/02-DrawTriangle_Fix/DrawTriangle_Fix)

> glsl 代碼分析類 
> > 
> ![](/usr/uploads/2017/10/4046991714.png) 
> > 
> > 核心的知識是**正則表達式**,主要是把代碼中的變量解析出來,能夠對它們作大規模的處理。有興趣能夠看一下,沒有興趣的能夠忽略它徹底不影響學習和練習本文的內容。

* * *

#### **學習這篇文章的大前提是,你得有[《OpenGL ES 2.0 (iOS): 一步從一個小三角開始》](http://www.jianshu.com/p/d22cf555de47)的基礎知識。**

* * *

# ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

# **本文核心目的就是熟練圖形的分析與繪製**

# ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

* * *

> ## 目錄
> > ### 零、目標+準備
> > ### 1、圖元繪製之線

0. 工程目錄

  1. 繪製單1、交叉的線
  2. 繪製折線
  3. 繪製幾何圖形
  4. 繪製三角化的幾何圖形
  5. 繪製曲線、圓形

2、圖元繪製之三角形

0.工程目錄
1. 繪製基本幾何圖形

3、圖元繪製之點精靈(內容爲空)

4、練練手

0.工程目錄
1. 繪製一棵卡通樹
2. 繪製一張卡片
3. 繪製一棵草

零、目標 + 準備

1) 目標

Geometries

2) 準備

  • 觀察全部圖形,發現它們都是點與點之間的連線(直線或曲線),組成一個幾何形狀( ^_^ 好像有點廢話);
  • 除了點線的問題外,還能夠知道幾何形狀,有交疊、閉環、開環三種狀況;
  • 除此以外,還有填充色有無的問題;

:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

  • 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 (面)

:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

因此本文就是根據圖形的形態,選擇適當的繪製方式,去繪製圖形;核心目的就是熟練圖形的分析與繪製;
由於是練習圖元,因此學習的重點在,數據綁定和圖形繪製這一塊;


1、圖元繪製之線

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. 繪製單1、交叉的線

LINES

  • 圖形分析

    • 首先它們都是線,因此選擇的是 線模式;
    • 左側就是一條線 -> 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. 繪製折線

LINE STRIP MOUN

  • 圖形分析
    • 首先這是一條線,因此選擇的是 線模式;
    • 可是它是一條折線,即多段線首尾相接組成的線,並且沒有閉合,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. 繪製幾何圖形

Triangle2Round.gif

LINE LOOP

  • 圖形分析
    多段線首尾相接組成的幾何形狀,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 行;

  • 程序運行結果

TRI-ROUND

4. 繪製三角化的幾何圖形(Low Poly)

TRIANGLE STRIP FAN PLO

  • 圖形分析
    • 首先它們都是由線組成,線模式
    • 其次,它們的線是閉合的,首尾相接?GL_LINES_LOOP ?
    • 所謂首尾相接,造成閉合圖形,是起點直接到達終點,就是提及點只會被通過一次,就是最後閉合的那一次;觀察圖形,起點若是隻被通過一次,能不能用線繪製出來,很難吧,特別是最後一個,因此這裏直接用 GL_LINESSTRIP 模式,以後任意編排線通過點的順序,便可。(固然,若是你有興趣的話,也能夠寫一個算法去計算點被通過最少的次數下,圖形能夠完整繪製出來)_
    • 點可能會屢次被通過,那麼就是說,這個點要被程序調度屢次,可是 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_TRIANGLEFAN
count indices 的數量
type _下標的數據類型:GL_UNSIGNED_BYTE、GL_UNSIGNED_SHORT、GL_UNSIGNED_INT(它只能在使用了 OES_element_indexuint 才能使用)
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);
}
  • 程序運行結果

Rect-Star

5. 繪製曲線、圓形

BAISER

  • 圖形分析

    • 首先,它們都是曲線,它們均可以經過 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;
   }
  • 固然圖形顯示類,也要改咯!

  • 程序運行結果

Bezier


2、圖元繪製之三角形

Triangles,就是多個三角形;
Triangle Strip, 指條帶,相互鏈接的三角形;
Triangle Fan, 指扇面,相互鏈接的三角形;

圖 1:三角形模式

圖 2:STRIP

圖 3: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. 繪製基本幾何圖形

TRIANGLE STRIP FAN

  • 圖形分析

    • 首先,第一張圖片每個圖形都是一個面,可是 OpenGL 只能直接繪製三角面,因此必須把圖形分解成三角面才能進行繪製;
    • 如下就是分解成三角面以後的圖形:

TRIANGLE LINESON

固然你也能夠按照本身的方式進行分解,必定要遵照這裏的點、三角形關係

否則圖形是不能正確地繪製出來的;

  • 這裏容易出問題的是最後一個圖形(五角星形),三角形與點的關係: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 行;

  • 程序運行結果

TRI-ROUND Triangle

完整的程序代碼: Github DrawGeometries_Triangles


3、圖元繪製之點精靈

這裏不進行詳細講解,我的感受在這裏講沒什麼意思,仍是放在 Texture 紋理部分進行詳細講解會比較有用,並且好玩;

若是隻是學習 gl_PointSize 的話沒意思,結合 gl_PointCoord 去學習反而更有趣,不過這裏要有紋理的知識,因此先行不講了;


4、練練手

Challenges

這裏的目的不是爲了繪製它們而進行繪製,而是針對圖元繪製作一個深刻的學習,要學習分析圖形和尋找合適有效的繪製方式,並且還要作到判斷數據的大體生成方法方式是什麼,否則你永遠都只是一個只會搞代碼的搬運工而已;編程可不只僅是搞代碼;

0. 工程目錄

取消了採用結構體存取數據的方式,改用 Model 類,方便 OC 處理和傳輸;

1. 繪製一棵卡通樹

Tree

提示:進行兩次的 glDraw* 調用,分別繪製外邊的線和內部的填充圖

2. 繪製一張卡片

Card

提示:把數據分紅左、右、右中線,三種,緣由是左邊的數據是用貝塞爾曲線生成數據量很是大;主要是利用 glBufferSubData 與 glBufferData 的結合,以及 glVertexAttribPointer 的配合;

3. 繪製一棵草

Grass

注意:盡能夠地用肉眼去判斷線的走向,用 動態計算點 的類作實驗,不斷成長起來吧。

完整的挑戰項目:Github DrawGeometries_Challenge


目錄

1、多座標系

1. 世界座標系
2. 物體(模型)座標系
3. 攝像機座標系
4. 慣性座標系

2、座標空間

1. 世界空間
2. 模型空間
3. 攝像機空間
4. 裁剪空間
5. 屏幕空間

3、OpenGL ES 2 3D 空間

1. 變換髮生的過程
2. 各個變換流程分解簡述
3. 四次變換與編程應用

4、工程例子

5、參考書籍


1、多座標系

1. 世界座標系

  • 即物體存在的空間,以此空間某點爲原點,創建的座標系

  • 世界座標系是最大的座標系,世界座標系不必定是指 「世界」,準確來講是一個空間或者區域,就是足以描述區域內全部物體的最大空間座標,是咱們關心的最大座標空間;

  • 例子

    • ep1:
      好比我如今身處廣州,要描述我如今所在的空間,對我而言最有意義就是,我身處廣州的那裏,而此時的廣州就是我關心的 「世界座標系」,而不用描述我如今的經緯座標是多少,不須要知道我身處地球的那個經緯位置。
      這個例子是以物體的方向思考的最合適世界座標系;(固然是排除我要與廣州之外的區域進行行爲交互的狀況咯!)

    • ep2:
      若是如今要描述廣州城的全貌,那麼對於咱們而言,最大的座標系是否是就是廣州這個世界座標系,也就是所謂的咱們最關心的座標系;
      這個例子是以全局的方向思考的最合適世界座標系;
  • 世界座標系主要研究的問題:
    1) 每一個物體的位置和方向
    2) 攝像機的位置和方向
    3) 世界的環境(如:地形)
    4) 物體的運動(從哪到哪)

2. 物體(模型)座標系

  • 模型自身的座標系,座標原點在模型的某一點上,通常是幾何中心位置爲原點

  • 模型座標系是會跟隨模型的運動而運動,由於它是模型自己的 「一部份」 ;

  • 模型內部的構件都是以模型座標系爲參考進而描述的;

  • ep:
    好比有一架飛機,機翼位於飛機的兩側,那麼描述機翼最合適的座標系,固然是相對於飛機自己,機翼位於那裏;飛機在飛行的時候,飛機自己的座標系是否是在跟隨運動,機翼是否是在飛機的座標中同時運動着。

3. 攝像機座標系

  • 攝像機座標系就是以攝像機自己爲原點創建的座標系,攝像機自己並不可見,它表示的是有多少區域能夠被顯示(渲染)

  • 白色線所圍成的空間,就是攝像機所能捕捉到的最大空間,而物體則位於空間內部;

  • 位於攝像機捕捉空間外的圖形會直接被剔除掉;

4. 慣性座標系

  • 它的 X 軸與世界座標系的 X 軸平行且方向相同,Y 軸亦然,它的原點與模型座標系相同

  • 它的存在的核心價值是,簡化座標系的轉換,即簡化模型座標系到世界座標系的轉換;

2、座標空間

  • 座標空間就是座標系造成的空間


1. 世界空間

世界座標系造成的空間,光線計算通常是在此空間統一進行;

2. 模型空間

模型座標系造成的空間,這裏主要包含模型頂點座標和表面法向量的信息;


第一次變換
模型變換(Model Transforms):就是指從模型空間轉換到世界空間的過程


3. 攝像機空間

攝像機空間

攝像機空間,就是黃色區域所包圍的空間;
攝像機空間在這裏就是透視投影,透視投影用於 3D 圖形顯示,反映真實世界的物體狀態;

透視知識擴展 《透視》


第二次變換
視變換(View Transforms):就是指從世界空間轉換到攝像機空間的過程


  • 攝像機空間,也被稱爲眼睛空間,便可視區域;
  • 其中,LookAt(攝像機的位置) 和 Perspective(攝像機的空間) 都是在調整攝像空間;

4. 裁剪空間

圖形屬於裁剪空間則保留,圖形在裁剪空間外,則剔除(Culled)

攝像機 帶註解

標號(3)[視景體] ,所指的空間即爲裁剪空間,這個空間就由 Left、Right、Top、Bottom、Near、Far 六個面組成的四棱臺,即視景體。

視景體

圖中紫色區域爲視場角

fov & zoom

從而引出,視場縮放爲:

zoom

  • 其次,頂點是用齊次座標表示 {x, y, z, w}, 3D 座標則爲{x/w, y/w, z/w} 而 w 就是判斷圖形是否屬於裁剪空間的關鍵:
錐面 關係
Near z < -w
Far z > w
Bottom y < -w
Top y > w
Left x < -w
Right x > w

即座標值,不符合這個範圍的,都會被裁剪掉

座標 值範圍
x [-w , w]
y [-w, w]
z [-w, w]

第三次變換
投影變換(Projection Transforms): 固然包括正交、透視投影了,就是指從攝影機空間到視景體空間的變換過程


5. 屏幕空間

它就是顯示設備的物理屏幕所在的座標系造成的空間,它是 2D 的且以像素爲單位,原點在屏幕的幾何中心點

屏幕座標空間. jpg


第四次變換(最後一次)
視口變換(ViewPort Transforms): 指從裁剪空間到屏幕空間的過程,即從 3D 到 2D


這裏主要是關注像素的分佈,即像素縱橫比;由於圖形要從裁剪空間投影映射到屏幕空間中,須要知道真實的環境的像素分佈狀況,否則圖形就會出現變形;

《OpenGL ES 2.0 (iOS)[02]:修復三角形的顯示》這篇文章就是爲了修復屏幕像素比例不是 1 : 1 引發的拉伸問題,而它也就是視中變換中的一個組成部分。

  • 像素縱橫比計算公式

像素縮放比

3、OpenGL ES 2 3D 空間

1. 變換髮生的過程

OpenGL ES 2 變換流程圖

  • 這個過程代表的是 GPU 處理過程(渲染管線);
  • 變換過程發生在,頂點着色與光柵化之間,即圖元裝配階段;
  • 編寫程序的時候,變換的操做是放在頂點着色器中進行處理;
  • 右下角寫明瞭,總共就是四個變換過程:模型變換、視變換、投影變換、視口變換,通過這四個變換後,圖形的點就能夠正確並如願地顯示在用戶屏幕上了;
  • 側面反應,要正確地渲染圖形,就要掌握這四種變換;

2. 各個變換流程分解簡述

  • 階段一:追加 w 份量爲 1.0 (第一個藍框)

這個階段不須要程序員操做

這裏的緣由是,OpenGL 須要利用齊次座標去進行矩陣的運算,核心緣由固然就是方便矩陣作乘法咯(R(4x4) 點乘 R(4x1) 嘛)!

  • 階段二:用戶變換 (第二個藍框)

這個階段須要程序員操做,在 Vertex Shader Code 中進行操做

這個階段主要是把模型正確地經過 3D 變換 (旋轉、縮放、平移) 放置於攝像機的可視區域(視景體)中,包括處理攝像機的位置、攝像機的可視區域佔整個攝像機空間的大小。

這個階段事後,w 就不在是 1.0 了

  • 階段三:從新把齊次座標轉換成 3D 座標 (第三個藍框)

這個階段不須要程序員操做

要從新轉換回來的緣由,也很簡單 ---- 齊次座標只是爲了方便作矩陣運算而引入的,而 3D 座標點纔是模型真正須要的點位置信息。

這個階段事後,全部的點座標都會標準化(所謂標準化,就是單位爲 1),x 和 y 值範圍均在 [-1.0, 1.0] 之間,z 就在 [ 0.0, 1.0 ] 之間;

x 和 y 值範圍均在 [-1.0, 1.0] 之間,才能正確顯示,緣由是 OpenGL 的正方體值範圍就是 [ -1.0, 1.0 ] 不存在其它範圍的值;而 z 的值範圍是由攝像機決定的,攝像機所處的位置就是 z = 0,的位置,因此 0 是指無限近,攝像機可視區的最遠處就是 z = 1, 因此 1 是指無限遠;

  • 階段四:從新把齊次座標轉換成 3D 座標 (第四個藍框)

* 這個階段須要程序員操做,在圖形渲染前要進行操做,即在 gldraw 前 **

這個階段核心的就是 ViewPort 和 DepthRange 兩個,前者是指視口,後者是深度,分別對應的 OpenGL ES 2 的 API 是:

函數 描述
glViewport 調整視窗位置和尺寸
glDepthRange 調整視景體的 near 和 far 兩個面的位置 (z)
glViewport
void glViewport(GLint x, GLint y, GLsizei w, GLsizei h)
x, y 以渲染的屏幕座標系爲參考的視口原點座標值(如:蘋果的移動設備都是是以左上角爲座標原點)
w, h 要渲染的視口尺寸,單位是像素
glDepthRange
void glDepthRange(GLclampf n, GLclampf f)
n, f n, f 分別指視景體的 near 和 far ,前者的默認值爲 0 ,後者的默認值爲 1.0, 它們的值範圍均爲 [0.0, 1.0], 其實就是 z 值

3. 四次變換與編程應用

  • 下面這兩張圖片就是 Vertex Shader Code 中的最終代碼
#version 100

attribute vec4 v_Position;
uniform mat4 v_Projection, v_ModelView;

attribute vec4 v_Color;
varying mediump vec4 f_color;

void main(void) {
    f_color = v_Color;
    gl_Position  = v_Projection * v_ModelView * v_Position;
}
v_Projection 表示投影變換;v_ModelView 表示模型變換和視變換;
  • 第一次變換:模型變換,模型空間到世界空間 ( 1 -> 2 )

請看《OpenGL ES 2.0 (iOS)[02]:修復三角形的顯示》 這篇文章,專門講模型變換的。

  • 餘下的幾回變換,都是和攝像機模型在打交道
    攝像機裏面的模型

Camera Model

要完成攝像機正確地顯示模型,要設置攝像機位置、攝像機的焦距:

1. 設置攝像機的位置、方向 --&gt; (視變換) gluLookAt (ES 沒有這個函數),使要渲染的模型位於攝像機可視區域中;【完成圖中 1 和 2】
2. 選擇攝像機的焦距去適應整個可視區域 --&gt; (投影變換) glFrustum(視景體的六個面)、gluPerspective(透視) 、glOrtho(正交)( ES 沒有這三個函數) 【完成圖中 3】
3. 設置圖形的視圖區域,對於 3D 圖形還能夠設置 depth- range --&gt; glViewport 、glDepthRange
  • 第二次變換:視變換,世界空間到攝像機空間 ( 2 -> 3 )

上面提到, ES 版本沒有 gluLookAt 這個函數,可是咱們知道,這裏作的都是矩陣運算,因此能夠本身寫一個功能同樣的矩陣函數便可;

// 我不想寫,因此能夠用 GLKit 提供給咱們的函數
/* Equivalent to gluLookAt. */
GLK_INLINE GLKMatrix4 GLKMatrix4MakeLookAt(float eyeX, float eyeY, float eyeZ, float centerX, float centerY, float centerZ, float upX, float upY, float upZ);

Frustum

函數的 eye x、y、z 就是對應圖片中的 Eye at ,即攝像機的位置;
函數的 center x、y、z 就是對應圖片中的 z-axis 可視區域的中心點;
函數的 up x、y、z 就是對應圖片中的 up 指攝像機上下的位置(就是角度);

  • 第三次變換:投影變換,攝像機空間到裁剪空間 ( 3 -> 4 )

view frustum

當模型處於視景體外時會被剔除掉,若是模型有一部分在視景體內時,模型的點信息只會剩下在視景體內的,其它的點信息不渲染;

/* Equivalent to glFrustum. */
GLK_INLINE GLKMatrix4 GLKMatrix4MakeFrustum(float left, float right, float bottom, float top, float nearZ, float farZ);

這個是設置視景體六個面的大小的;

  • 透視投影

透視投影

對應的投影公式 :

完整的透視投影公式

使用 GLKit 提供的函數:

/* Equivalent to gluPerspective. */
GLK_INLINE GLKMatrix4 GLKMatrix4MakePerspective(float fovyRadians, // 視場角 float aspect, // 屏幕像素縱橫比 float nearZ, // 近平面距攝像機位置的距離 float farZ); // 遠平面攝像機位的距離
  • 正交投影

Orthographic projection

對應的投影公式 :

完整的正交投影公式

/* Equivalent to glOrtho. */
GLK_INLINE GLKMatrix4 GLKMatrix4MakeOrtho(float left, float right, float bottom, float top, float nearZ, float farZ);
  • 第四次變換:視口變換,裁剪空間到屏幕空間 ( 4 -> 5 )

這裏就是設置 glViewPort 和 glDepthRange 固然 2D 圖形不用設置 glDepthRange ;

  • 實際編程過程當中的使用過程

  • 第一步,若是是 3D 圖形的渲染,那麼要綁定深度渲染緩存(DepthRenderBuffer),如果 2D 能夠跳過,由於它的頂點信息中沒有 z 信息 ( z 就是頂點座標的深度信息 );

    1. Generate ,請求 depth buffer ,生成相應的內存標識符
    2. Bind,綁定申請的內存標識符
    3. Configure Storage,配置儲存 depth buffer 的尺寸
    4. Attach,裝載 depth buffer 到 Frame Buffer 中
      具體的程序代碼:

  • 第二步,縮寫 Vertex Shader Code
#version 100

attribute vec4 v_Position;
uniform mat4 v_Projection, v_ModelView; // 投影變換、模型視圖變換

attribute vec4 v_Color;
varying mediump vec4 f_color;

void main(void) {
     f_color = v_Color;
     gl_Position = v_Projection * v_ModelView * v_Position;
}

通常是把四次變換寫成這兩個,固然也能夠寫成一個;由於它們是一矩陣,等同於一個常量,因此使用的是 uniform 變量,變量類型就是 mat4 四乘四方陣(齊次矩陣);

  • 第三步,就是外部程序賦值這兩個變量

* 注意,要在 glUseProgram 函數後,再使用 glUniform 函數來賦值變量,否則是無效的;**

依次完成 模型變換、視變換、投影變換,便可;它們兩兩用矩陣乘法進行鏈接便可;

如:modelMatrix 點乘 viewMatrix , 它們的結果再與 projectionMatrix 點乘,即爲 ModelViewMatrix ;

GLKit 點乘函數,
GLK_INLINE GLKMatrix4 GLKMatrix4Multiply(GLKMatrix4 matrixLeft, GLKMatrix4 matrixRight);

  • 第四步,若是是 3D 圖形,有 depth buffer ,那麼要清除深度渲染緩存

使用 glClear(GL_DEPTH_BUFFER_BIT); 進行清除,固然以後就是要使能深度測試 glEnable(GL_DEPTH_TEST); 否則圖形會變形;

最好,也使能 glEnable(GL_CULL_FACE); 這裏的意思就是,把在屏幕後面的點剔除掉,就是不渲染;判斷是前仍是後,是利用提供的模型頂點信息中點與點依次鏈接造成的基本圖元的時鐘方向進行判斷的,這個 OpenGL 會自行判斷;

ClockWise & Counterclockwise

左爲順時針,右爲逆時針;

  • 第五步,設置 glViewPort 和 glDepthRange

使用 OpenGL ES 提供的 glViewPort 和 glDepthRange 函數便可;


4、工程例子

Github: 《DrawSquare_3DFix》


5、參考書籍

《OpenGL ES 2.0 Programming Guide》
《OpenGL Programming Guide 8th》
《3D 數學基礎:圖形與遊戲開發》
《OpenGL 超級寶典 第五版》
《Learning OpenGL ES For iOS》

目錄

1、目標

1. 基礎知識準備
2. 圖形分析

2、編寫程序

0. 工程結構與總體渲染管線
1. Depth Render Buffer
2. 數據源的編寫與綁定
3. 深度測試與繪製
4. 讓正方體動起來

3、參考書籍、文章


1、目標

正方體. gif

1. 基礎知識準備

a. 渲染管線的基礎知識
《OpenGL ES 2.0 (iOS)[01]: 一步從一個小三角開始》

b. 3D 變換
《OpenGL ES 2.0 (iOS)[04]:座標空間 與 OpenGL ES 2 3D 空間》

2. 圖形分析

a. 它是一個正方體,由六個正方形面組成,有 8 個頂點;

b. 正方體並非二維圖形,而是三維圖形,即頂點座標應爲 {x, y, z},並且 z 不可能一直爲 0;

c. 若由 OpenGL ES 繪製,z 座標表示深度(depth)信息;

d. 六個面均有不同的顏色,即 8 個頂點都帶有顏色信息,即渲染的頂點要提供相應的顏色信息;

e. 六個正方形面,若由 OpenGL ES 繪製,須要由兩個三角面組合而成,即繪製模式爲 GL_TRIANGLE*;

f. 正方體的每個頂點都包含在三個面中,即一個頂點都會被使用屢次,即繪製的時候應該使用 glDrawElements 方法而不是 glDrawArrays 方法,因此除 8 個頂點的數據外還需增長下標數據纔有可能高效地繪製出正方體;

g. 正方體在不斷地旋轉運動,便可能要實時改變頂點的信息並進行從新繪製以達到運動的效果(思路:動圖就是靜態圖的快速連續變化,只要變化的速度大於人眼能夠辨別的速度,就會產生天然流暢的動圖)

分析可程序化:
1) 結合 a、b、c、d 四點能夠知道,頂點的數據格式能夠爲:

#define PositionCoordinateCount (3)
#define ColorCoordinateCount (4)
typedef struct {
    GLfloat position[PositionCoordinateCount];
    GLfloat color[ColorCoordinateCount];
} VFVertex;
static const VFVertex vertices[] = {
    {{...}, {...}}
    ......
};

固然你也能夠把 position 和 color 分開來,只不過我認爲放在一塊兒更好管理罷了。

2) 從 e、f 兩點能夠知道,增長的數據及繪製的方式:

由於使用 element 方式,因此增長下標信息;

static const GLubyte indices[] = {
    ......
};
glDrawElements(GL_TRIANGLES,
                   sizeof(indices) / sizeof(indices[0]),
                   GL_UNSIGNED_BYTE,
                   indices);

3) 從 g 點能夠知道:

圖形的運動,代表圖形在必定時間內不斷地進行更新(從新繪製並渲染),即只要使用具備定時功能的方法便可處理圖形的運動,NSTimer 就能夠勝任這個工做,不過 iOS 提供了一個 CADisplayLink 類來專門作定時更新的工做,因此能夠選用它進行運動更新;


2、編寫程序

0. 工程結構與總體渲染管線

結構目錄簡述
1) 藍框是包含 CADisplayLink 子類的類,用於更新渲染,就是讓圖形動起來;

2) 紅框就是總體的渲染管線,全部的繪製渲染工做均在此處;

渲染管線 + Depth
Render Buffer 有三種緩存,Color 、Depth 、Stencil 三種;而單純繪製 2D 圖形的時候由於沒有引入 z 座標(z != 0)而只使用了 Render Buffer 的 Color Render Buffer ;
而現在要進行渲染的正方體,是帶有 z 座標,即深度信息,因此天然要引入 Depth Render Buffer 了;
引入 Depth Render Buffer 並使其工做的步驟:

Depth Render Buffer

ViewController 的程序調度

#import "ViewController.h"

#import "VFGLCubeView.h"

@interface ViewController ()
@property (strong, nonatomic) VFGLCubeView *cubeView;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.

    CGRect rect = CGRectOffset(self.view.frame, 0, 0);
    self.cubeView = [[VFGLCubeView alloc] initWithFrame:rect];

    [_cubeView prepareDisplay];
    [_cubeView drawAndRender];

    [self.view addSubview:_cubeView];

}

- (void)viewDidAppear:(BOOL)animated {

    [super viewDidAppear:animated];

    [self.cubeView update];

}

- (void)viewWillDisappear:(BOOL)animated {

    [super viewWillDisappear:animated];

    [self.cubeView pauseUpdate];

}

@end

內容並不複雜,因此此處不進行贅述;

渲染管線
prepareDisplay + drawAndRender

prepareDisplay 渲染管線的準備部分

- (void)prepareDisplay {

    // 1. Context
    [self settingContext];

    // 2 要在 Render Context setCurrent 後, 再進行 OpenGL ES 的操做
    // [UIColor colorWithRed:0.423 green:0.046 blue:0.875 alpha:1.000]
    // [UIColor colorWithRed:0.423 green:0.431 blue:0.875 alpha:1.000]
    [self setRenderBackgroundColor:RGBAColorMake(0.423, 0.431, 0.875, 1.000)];

    // 2.? Vertex Buffer Object
    self.vboBufferID = [self createVBO];
    [self bindVertexDatasWithVertexBufferID:_vboBufferID
                               bufferTarget:GL_ARRAY_BUFFER
                                   dataSize:sizeof(vertices)
                                       data:vertices
                                   elements:NO];

    [self bindVertexDatasWithVertexBufferID:kInvaildBufferID
                               bufferTarget:GL_ELEMENT_ARRAY_BUFFER
                                   dataSize:sizeof(indices)
                                       data:indices
                                   elements:YES];

    // 3. Shader
    GLuint vertexShaderID = [self createShaderWithType:GL_VERTEX_SHADER];
    [self compileVertexShaderWithShaderID:vertexShaderID type:GL_VERTEX_SHADER];

    GLuint fragmentShaderID = [self createShaderWithType:GL_FRAGMENT_SHADER];
    [self compileVertexShaderWithShaderID:fragmentShaderID type:GL_FRAGMENT_SHADER];

    self.programID = [self createShaderProgram];
    [self attachShaderToProgram:_programID
                  vertextShader:vertexShaderID
                 fragmentShader:fragmentShaderID];

    [self linkProgramWithProgramID:_programID];

    [self updateUniformsLocationsWithProgramID:_programID];

    // 4. Attach VBOs
    [self attachCubeVertexArrays];

}

基於這部分,本文的工做在如下兩處進行:

// 1. Context
    [self settingContext];

它負責肯定渲染上下文,以及 Render Buffer 與 Frame Buffer 的資源綁定處理;
[self settingContext]; 詳見 本章 1.Depth Render Buffer 一節

// 2.? Vertex Buffer Object
    self.vboBufferID = [self createVBO];
    [self bindVertexDatasWithVertexBufferID:_vboBufferID
                               bufferTarget:GL_ARRAY_BUFFER
                                   dataSize:sizeof(vertices)
                                       data:vertices
                                   elements:NO];

    [self bindVertexDatasWithVertexBufferID:kInvaildBufferID
                               bufferTarget:GL_ELEMENT_ARRAY_BUFFER
                                   dataSize:sizeof(indices)
                                       data:indices
                                   elements:YES];

它是處理頂點緩存數據的;
VBO 與 數據源 詳見 本章 2. 數據源的編寫與綁定

drawAndRender 渲染管線的餘下部分

- (void)drawAndRender {

    // 5. Draw Cube
    // 5.0 使用 Shader
    [self userShaderWithProgramID:_programID];

    // 5.1 應用 3D 變換
    self.modelPosition = GLKVector3Make(0, -0.5, -5);
    [self transforms];

    // 5.2 清除舊渲染緩存
    [self clearColorRenderBuffer:YES depth:YES stencil:NO];

    // 5.3 開啓深度測試
    [self enableDepthTesting];

    // 5.4 繪製圖形
    [self drawCube];

    // 5.5 渲染圖形
    [self render];

}

基於這部分,本文的工做在此處進行:

// 5.2 清除舊渲染緩存
    [self clearColorRenderBuffer:YES depth:YES stencil:NO];

    // 5.3 開啓深度測試
    [self enableDepthTesting];

    // 5.4 繪製圖形
    [self drawCube];

詳見 本章 3. 深度測試與繪製 一節

關於實時更新的內容

 [self.cubeView update];
 [self.cubeView pauseUpdate];

詳見 本章 4. 讓正方體動起來

1. Depth Render Buffer

[self settingContext];
它的內容爲:

- (void)setContext:(EAGLContext *)context {

    if (_context != context) {

        [EAGLContext setCurrentContext:_context];

        [self deleteFrameBuffer:@[@(self.frameBufferID)]];
        self.frameBufferID = kInvaildBufferID;

        [self deleteRenderBuffer:@[@(self.colorRenderBufferID), @(self.depthRenderBufferID)]];
        self.colorRenderBufferID = self.depthRenderBufferID = kInvaildBufferID;

        _context = context;

        if (context != nil) {

            _context = context;
            [EAGLContext setCurrentContext:_context];

            // 2. Render / Frame Buffer

            // 2.0 建立 Frame Buffer
            [self deleteFrameBuffer:@[@(self.frameBufferID)]];

            self.frameBufferID = [self createFrameBuffer];

            // 2.1 Color & Depth Render Buffer
            [self deleteRenderBuffer:@[@(self.colorRenderBufferID)]];

            self.colorRenderBufferID = [self createRenderBuffer];

            [self renderBufferStrogeWithRenderID:self.colorRenderBufferID];

            [self attachRenderBufferToFrameBufferWithRenderBufferID:self.colorRenderBufferID
                                                         attachment:GL_COLOR_ATTACHMENT0];

            // 2.2 檢查 Frame 裝載 Render Buffer 的問題
            [self checkFrameBufferStatus];

            // 2.3 Add Depth Render Buffer
            [self enableDepthRenderBuffer];

            [self deleteRenderBuffer:@[@(self.depthRenderBufferID)]];

            if ( ! CGSizeEqualToSize(self.renderBufferSize, CGSizeZero) &&
                self.depthMode != VFDrawableDepthMode_None) {

                self.depthRenderBufferID = [self createRenderBuffer];

                if (self.depthRenderBufferID == kInvaildBufferID) {
                    return;
                }

                [self renderBufferStrogeWithRenderID:self.depthRenderBufferID];

                [self attachRenderBufferToFrameBufferWithRenderBufferID:self.depthRenderBufferID
                                                             attachment:GL_DEPTH_ATTACHMENT];

            }

            // 2.4 檢查 Frame 裝載 Render Buffer 的問題
            [self checkFrameBufferStatus];

        }

    }

}

- (void)settingContext {

    self.context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];

}

這裏重寫了 setContext: 方法,核心內容是
// 2.3 Add Depth Render Buffer

// 2.3 Add Depth Render Buffer
    [self enableDepthRenderBuffer];

    [self deleteRenderBuffer:@[@(self.depthRenderBufferID)]];

    if ( ! CGSizeEqualToSize(self.renderBufferSize, CGSizeZero) &&
        self.depthMode != VFDrawableDepthMode_None) {

        self.depthRenderBufferID = [self createRenderBuffer];

        if (self.depthRenderBufferID == kInvaildBufferID) {
            return;
        }

        [self renderBufferStrogeWithRenderID:self.depthRenderBufferID];

        [self attachRenderBufferToFrameBufferWithRenderBufferID:self.depthRenderBufferID
                                                     attachment:GL_DEPTH_ATTACHMENT];

    }

步驟分解:

Step One

第一步,建立並綁定深度渲染緩存,對應程序代碼爲:

self.depthRenderBufferID = [self createRenderBuffer];
- (GLuint)createRenderBuffer {

    GLuint ID = kInvaildBufferID;
    glGenRenderbuffers(RenderMemoryBlock, &ID);  // 申請 Render Buffer
    glBindRenderbuffer(GL_RENDERBUFFER, ID); // 建立 Render Buffer

    return ID;

}

第二步,存儲新建立的渲染緩存,對應程序代碼爲:

[self renderBufferStrogeWithRenderID:self.depthRenderBufferID];
- (void)renderBufferStrogeWithRenderID:(GLuint)renderBufferID {

    if (renderBufferID == self.colorRenderBufferID) {

        // 必需要在 glbindRenderBuffer 以後 (就是使用 Render Buffer 以後), 再綁定渲染的圖層
        [self bindDrawableObjectToRenderBuffer];

        self.renderBufferSize = [self getRenderBufferSize];

    }

    if (renderBufferID == self.depthRenderBufferID) {

        glRenderbufferStorage(GL_RENDERBUFFER,
                              GL_DEPTH_COMPONENT16,
                              self.renderBufferSize.width,
                              self.renderBufferSize.height);

    }

}

核心函數:存儲渲染信息

glRenderbufferStorage
void glRenderbufferStorage(GLenum target,GLenum internalformat,GLsizei width, GLsizei height)
target _只能是 GLRENDERBUFFER
internalformat 可用選項見下表
width 渲染緩存的寬度(像素單位)
height 渲染緩存的高度(像素單位)
internalformat 存儲格式(位 = bit)
顏色方面 GL_RGB565(5 + 6 + 5 = 16 位)、GL_RGBA4(4 x 4 = 16)、GL_RGB5_A1(5 + 5 + 5 + 1 = 16)、GL_RGB8_OES(3 x 8 = 24 )、GL_RGBA8_OES(4 x 8 = 32)
深度方面 GL_DEPTH_COMPONENT16(16 位)、GL_DEPTH_COMPONENT24_OES(24 位)、GL_DEPTH_COMPONENT32_OES(32 位)
模板方面 GL_STENCIL_INDEX八、GL_STENCIL_INDEX1_OES、GL_STENCIL_INDEX4_OES
深度與模板 GL_DEPTH24_STENCIL8_OES

第三步,裝載渲染緩存到幀緩存中,對應程序代碼爲:

[self attachRenderBufferToFrameBufferWithRenderBufferID:self.depthRenderBufferID attachment:GL_DEPTH_ATTACHMENT];
- (void)attachRenderBufferToFrameBufferWithRenderBufferID:(GLuint)renderBufferID attachment:(GLenum)attachment {

    glFramebufferRenderbuffer(GL_FRAMEBUFFER, attachment, GL_RENDERBUFFER, renderBufferID);

}
2. 數據源的編寫與綁定

數據源的書寫
從 2D 到 3D :

右下方,線框正方體的 8 個頂點座標分佈,其實 0~7 的編號是你決定的,也就是說 0 放在那裏開始都是能夠的,只要是 8 個點便可;

Cube

static const VFVertex vertices[] = {
    // Front
    // 0 [UIColor colorWithRed:0.438 green:0.786 blue:1.000 alpha:1.000]
    {{ 1.0, -1.0,  1.0}, {0.438, 0.786, 1.000, 1.000}}, // 淡(藍) -- 0

    // 1 [UIColor colorWithRed:1.000 green:0.557 blue:0.246 alpha:1.000]
    {{ 1.0,  1.0,  1.0}, {1.000, 0.557, 0.246, 1.000}}, // 淡(橙) -- 1

    // 2 [UIColor colorWithRed:0.357 green:0.927 blue:0.690 alpha:1.000]
    {{-1.0,  1.0,  1.0}, {0.357, 0.927, 0.690, 1.000}}, // 藍(綠) -- 2

    // 3 [UIColor colorWithRed:0.860 green:0.890 blue:0.897 alpha:1.000]
    {{-1.0, -1.0,  1.0}, {0.860, 0.890, 0.897, 1.000}}, // 超淡藍 偏(白) -- 3

    // Back
    // 4 [UIColor colorWithRed:0.860 green:0.890 blue:0.897 alpha:1.000]
    {{-1.0, -1.0, -1.0}, {0.860, 0.890, 0.897, 1.000}}, // 超淡藍 偏(白) -- 4

    // 5 [UIColor colorWithRed:0.357 green:0.927 blue:0.690 alpha:1.000]
    {{-1.0,  1.0, -1.0}, {0.357, 0.927, 0.690, 1.000}}, // 藍(綠) -- 5

    // 6 [UIColor colorWithRed:1.000 green:0.557 blue:0.246 alpha:1.000]
    {{ 1.0,  1.0, -1.0}, {1.000, 0.557, 0.246, 1.000}}, // 淡(橙) -- 6

    // 7 [UIColor colorWithRed:0.438 green:0.786 blue:1.000 alpha:1.000]
    {{ 1.0, -1.0, -1.0}, {0.438, 0.786, 1.000, 1.000}}, // 淡(藍) -- 7
};

只要你空間想像不是特別差,估計能看出每一個點的座標吧!你能夠把這樣的點 {1.0, -1.0, -1.0} 改爲你喜歡的數值亦可,只要最終是正方體便可;

真正重要的數據實際上是下標數據:

static const GLubyte indices[] = {
    // Front ------------- 藍橙綠白 中間線(藍綠)
    0, 1, 2, // 藍橙綠
    2, 3, 0, // 綠白藍
    // Back ------------- 藍橙綠白 中間線(白橙)
    4, 5, 6, // 白綠橙
    6, 7, 4, // 橙藍白
    // Left ------------- 白綠
    3, 2, 5, // 白綠綠
    5, 4, 3, // 綠白白
    // Right ------------- 藍橙
    7, 6, 1, // 藍橙橙
    1, 0, 7, // 橙藍藍
    // Top ------------- 橙綠
    1, 6, 5, // 橙橙綠
    5, 2, 1, // 綠綠橙
    // Bottom ------------- 白藍
    3, 4, 7, // 白白藍
    7, 0, 3  // 藍藍白
};

這些下標的值由兩個因素決定,第一個因素是上面 8 個頂點數據的下標;第二個因素是時鐘方向;

如今看看時鐘方向:

有沒有發現,每個正方形的兩個小三角,都是逆時針方向的;固然你也能夠換成順時針方向,相應的下標數據就要發生改變;

EP: 如 Front 這個面,若是使用順時針來寫數據爲:

// Front ------------- 白綠橙藍 中間線(白橙)
    3, 2, 1, // 白綠橙
    1, 0, 2, // 橙藍綠

你也能夠從 2 或 1 開始,看你的喜愛咯;

方向只有兩個:

資源綁定
這裏主要是 VBO 的數據綁定,增長 Element 的支持而已;

[self bindVertexDatasWithVertexBufferID:kInvaildBufferID
                               bufferTarget:GL_ELEMENT_ARRAY_BUFFER
                                   dataSize:sizeof(indices)
                                       data:indices
                                   elements:YES];
- (void)bindVertexDatasWithVertexBufferID:(GLuint)vertexBufferID bufferTarget:(GLenum)target dataSize:(GLsizeiptr)size data:(const GLvoid *)data elements:(BOOL)isElement {

    if ( ! isElement) {
        glBindBuffer(target, vertexBufferID);
    }

    // 建立 資源 ( context )
    glBufferData(target,            // 緩存塊 類型
                 size,              // 建立的 緩存塊 尺寸
                 data,              // 要綁定的頂點數據
                 GL_STATIC_DRAW);   // 緩存塊 用途

}

此處再也不贅述;
若是實在不懂,請移步至
《OpenGL ES 2.0 (iOS)[03]:熟練圖元繪製,玩轉二維圖形》練習練習;

3. 深度測試與繪製

Step Two

清除舊的深度緩存信息

[self clearColorRenderBuffer:YES depth:YES stencil:NO];
- (void)clearColorRenderBuffer:(BOOL)color depth:(BOOL)depth stencil:(BOOL)stencil {

    GLbitfield colorBit     = 0;
    GLbitfield depthBit     = 0;
    GLbitfield stencilBit   = 0;

    if (color)      { colorBit      = GL_COLOR_BUFFER_BIT;     }
    if (depth)      { depthBit      = GL_DEPTH_BUFFER_BIT;     }
    if (stencil)    { stencilBit    = GL_STENCIL_BUFFER_BIT;   }

    glClear(colorBit | depthBit | stencilBit);

}

啓用深度測試

[self enableDepthTesting];
- (void)enableDepthTesting {

    glEnable(GL_DEPTH_TEST);
    glEnable(GL_CULL_FACE);

}

這裏多了一個 GL_CULL_FACE 的啓用,它的意思就是,把看不見的像素信息剔除掉,只保留能看見的信息(留前去後);
若是沒有啓用 GL_DEPTH_TEST 程序運行後是這樣的:

關掉 GL_DEPTH_TEST.gif

很明顯圖形是有穿透性的,若是去掉 GL_DEPTH_TEST 就不是實體的正方體了;固然若是你喜歡這種效果,也能夠關掉 GL_DEPTH_TEST (反正我我的以爲關掉也蠻好看的);

從新綁定 Color Render Buffer
緣由,由於當綁定 Depth Render Buffer 以後,渲染管線從原來的綁定(激活)的 Color Render Buffer 切換成了,綁定(激活)Depth Render Buffer ,從而致使渲染出來的結果,不是指望中的那樣;因此在繪製前要從新綁定(激活)Color Render Buffer .

Step Three

- (void)drawCube {

    // 失敗的核心緣由
    // 由於 depth buffer 是最後一個綁定的,因此當前渲染的 buffer 變成了 depth 而不是 color
    // 因此 渲染的圖形沒有任何變化,沒法產生深度效果
    // Make the Color Render Buffer the current buffer for display
    [self rebindRenderBuffer:@[@(self.colorRenderBufferID)]];

    [self rebindVertexBuffer:@[@(self.vboBufferID)]];

    glDrawElements(GL_TRIANGLES,
                   sizeof(indices) / sizeof(indices[0]),
                   GL_UNSIGNED_BYTE,
                   indices);

}

這是註釋了代碼中,[self rebindRenderBuffer:@[@(self.colorRenderBufferID)]]; 的運行結果;

4. 讓正方體動起來

ViewController 的調度
其實就是,view 顯示的時候更新,不顯示的時候中止更新;

- (void)viewDidAppear:(BOOL)animated {

    [super viewDidAppear:animated];

    [self.cubeView update];

}

- (void)viewWillDisappear:(BOOL)animated {

    [super viewWillDisappear:animated];

    [self.cubeView pauseUpdate];

}

CubeView 的應用

#pragma mark - DisplayLink Update

- (void)preferTransformsWithTimes:(NSTimeInterval)time {

    GLfloat rotateX = self.modelRotate.x;
// rotateX += M_PI_4 * time;

    GLfloat rotateY = self.modelRotate.y;
    rotateY += M_PI_2 * time;

    GLfloat rotateZ = self.modelRotate.z;
    rotateZ += M_PI * time;

    self.modelRotate = GLKVector3Make(rotateX, rotateY, rotateZ);

}

本類提供的改變參數有:

@property (assign, nonatomic) GLKVector3 modelPosition, modelRotate, modelScale;
@property (assign, nonatomic) GLKVector3 viewPosition , viewRotate , viewScale ;
@property (assign, nonatomic) GLfloat projectionFov, projectionScaleFix, projectionNearZ, projectionFarZ;

已經包含了全部的變換操做;

如下的幾個方法均是處理 VFRedisplay 類的實時更新問題;

// <VFRedisplayDelegate>
- (void)updateContentsWithTimes:(NSTimeInterval)times {

    [self preferTransformsWithTimes:times];
    [self drawAndRender];

}

#pragma mark - Update

- (void)update {

    self.displayUpdate = [[VFRedisplay alloc] init];
    self.displayUpdate.delegate = self;
    self.displayUpdate.preferredFramesPerSecond = 25;
    self.displayUpdate.updateContentTimes = arc4random_uniform(650) / 10000.0;
    [self.displayUpdate startUpdate];

}

- (void)pauseUpdate {

    [self.displayUpdate pauseUpdate];

}

#pragma mark - Dealloc

- (void)dealloc {

    [self.displayUpdate endUpdate];

}
self.displayUpdate.preferredFramesPerSecond = 25; //更新頻率
    self.displayUpdate.updateContentTimes = arc4random_uniform(650) / 10000.0; // 控制變化率(快慢)

核心是 - (void)updateContentsWithTimes:(NSTimeInterval)times 方法,這個方法是用於更新時,實時調用的方法;由VFRedisplay 類提供的協議 @interface VFGLCubeView ()<VFRedisplayDelegate> 方法;

VFRedisplay.h 主要內容

@protocol VFRedisplayDelegate <NSObject>

- (void)updateContentsWithTimes:(NSTimeInterval)times;

@end

......

- (void)startUpdate;
- (void)pauseUpdate;
- (void)endUpdate;

VFRedisplay.m 主要內容
開始更新的方法:

- (void)startUpdate {

    if ( ! self.delegate ) {
        return;
    }

    self.displayLink = [CADisplayLink displayLinkWithTarget:self
                                                   selector:@selector(displayContents:)];

    self.displayLink.frameInterval = (NSUInteger)MAX(kLeastSeconds,
                                                     (kTotalSeconds / self.preferredFramesPerSecond));

    [self.displayLink addToRunLoop:[NSRunLoop currentRunLoop]
                           forMode:NSDefaultRunLoopMode];

    self.displayPause = kDefaultDisplayPause;

}

- (void)displayContents:(CADisplayLink *)sender {

    if ([self.delegate respondsToSelector:@selector(updateContentsWithTimes:)]) {

        [self.delegate updateContentsWithTimes:self.updateContentTimes];

    }

}

四步走:
第一步,建立相應的更新調度方法- (void)displayContents:(CADisplayLink *)sender,這個方法必須是- (void)selector:(CADisplayLink *)sender這種類型的;
第二步,指定一個更新頻率(就是一秒更新多少次)frameInterval 通常是 2四、2五、30,默認是 30 的;
第三步,把 CADisplayLink 的子類添加到當前的 RunLoop [NSRunLoop currentRunLoop] 上,否則程序是沒法調度指定的方法的;
第四步,啓動更新 static const BOOL kDefaultDisplayPause = NO; 

displayPause 屬性

@property (assign, nonatomic) BOOL displayPause;
@dynamic displayPause;
- (void)setDisplayPause:(BOOL)displayPause {
    self.displayLink.paused = displayPause;
}
- (BOOL)displayPause {
    return self.displayLink.paused;
}

中止更新的方法:

- (void)pauseUpdate {

    self.displayPause = YES;

}

結束更新的方法:

- (void)endUpdate {

    self.displayPause = YES;
    [self.displayLink invalidate];
    [self.displayLink removeFromRunLoop:[NSRunLoop currentRunLoop]
                                forMode:NSDefaultRunLoopMode];

}

不用的時候,固然要先中止更新,再關掉時鐘(CADisplayLink 就是一個時鐘類),最後要從當前 RunLoop 中移除;

5. 工程文件

Github: DrawCube

Github:DrawCube_Onestep

增長魔方色開關,RubikCubeColor 宏定義;

開關

數據源

正方體_魔方色. gif


3、參考書籍、文章

《OpenGL ES 2 Programming Guide》
《OpenGL Programming Guide》8th
《Learning OpenGL ES For iOS》
RW.OpenGLES2.0

前言:若是你沒有 OpenGL ES 2 的基礎知識,請先移步 《OpenGL ES 2.0 (iOS) 筆記大綱》 學習一下基礎的知識。

目錄

1、軟件運行效果演示
(一)、最終效果
(二)、信息提取
2、紋理處理的流程【核心】
(一)、Texture 是什麼?
(二)、Texture
(三)、引入了 Texture 的 Shader 文件
(四)、Texture 正確的 「書寫」 順序
3、知識擴充:圖片加載
使用 Quartz Core 的知識加載圖片數據


1、軟件運行效果演示

(一)、最終效果

工程地址:Github

Texture-Base.gif

(二)、信息提取

  1. 不一樣的模型【2D & 3D】,不一樣維度下,Texture 的處理區別
  2. 單一像素信息【pixelBuffer】 與 複雜像素信息【圖片】的顯示區別
  3. 正方圖【單張或多張圖片】 與 長方圖,像素的顯示控制區別

2、紋理處理的流程【核心】

(一)、Texture 是什麼?

Texture 紋理,就是一堆被精心排列過的像素;

  1. 由於 OpenGL 就是圖像處理庫,因此 Texture 在 OpenGL 裏面有多重要,可想而知;
  2. 其中間接地鑑明了一點,圖片自己能夠有多大變化,OpenGL 就能夠有多少種變化。

學好 Texture 很是重要

(二)、Texture

Texture 在 OpenGL 裏面有不少種類,但在 ES 版本中就兩種——Texture_2D + Texture_CubeMap;

Texture_2D: 就是 {x, y} 二維空間下的像素呈現,也就是說,由效果圖上演示可知,很難作到使正方體的六個面出現不一樣的像素組合;圖片處理通常都使用這個模式;[x 、y 屬於 [0, 1] 這個範圍]

Texture_CubeMap: 就是 {x, y, z} 三維空間下的像素呈現,也就如效果圖中演示的正方體的六個面能夠出現不一樣的像素組合;它通常是用於作環境貼圖——就是製做一個環境,讓 3D 模型如同置身於真實環境中【卡通環境中也行】。[x、y、z 屬於 [-1, 1] 這個範圍,就是與 Vertex Position 的值範圍一致]

注:上面提到的全部座標範圍是指有效渲染範圍,也就是說你若是提供的紋理座標超出了這個範圍也沒有問題,只不過超出的部分就不渲染了;

感覺一下怎麼具體表達:

// VYVertex
typedef struct {
    GLfloat position[3];
    GLfloat texCoord[2];
    GLfloat normalCoord[3];
}VYVertex;

Texture_2D:

// Square
static const VYVertex tex2DSquareDatas[] = {
    {{-1.0, -1.0, 0.0}, {0.0, 0.0}},
    {{ 1.0, -1.0, 0.0}, {1.0, 0.0}},
    {{ 1.0,  1.0, 0.0}, {1.0, 1.0}},
    {{-1.0,  1.0, 0.0}, {0.0, 1.0}},
};
// Cube
static const VYVertex tex2DCubeDatas[] = {

    // Front [Front 的 z 是正的]
    {{-1.0, -1.0,  1.0}, {0.0, 0.0}}, // 0
    {{ 1.0, -1.0,  1.0}, {1.0, 0.0}}, // 1
    {{ 1.0,  1.0,  1.0}, {1.0, 1.0}}, // 2
    {{-1.0,  1.0,  1.0}, {0.0, 1.0}}, // 3
    // Back [Back 的 z 是負的]
    {{-1.0,  1.0, -1.0}, {0.0, 0.0}}, //4[3: -Z]
    {{ 1.0,  1.0, -1.0}, {1.0, 0.0}}, //5[2: -Z]
    {{ 1.0, -1.0, -1.0}, {1.0, 1.0}}, //6[1: -Z]
    {{-1.0, -1.0, -1.0}, {0.0, 1.0}}, //7[0: -Z]
    // Left [Left 的 x 是負的]
    {{-1.0, -1.0,  1.0}, {0.0, 0.0}}, //8[0]
    {{-1.0,  1.0,  1.0}, {1.0, 0.0}}, //9[3]
    {{-1.0,  1.0, -1.0}, {1.0, 1.0}}, //10[4]
    {{-1.0, -1.0, -1.0}, {0.0, 1.0}}, //11[7]
    // Right [Right 的 x 是正的]
    {{ 1.0, -1.0,  1.0}, {0.0, 0.0}}, //12[1]
    {{ 1.0, -1.0, -1.0}, {1.0, 0.0}}, //13[6]
    {{ 1.0,  1.0, -1.0}, {1.0, 1.0}}, //14[5]
    {{ 1.0,  1.0,  1.0}, {0.0, 1.0}}, //15[2]
    // Top [Top 的 y 是正的]
    {{-1.0,  1.0,  1.0}, {0.0, 0.0}}, //16[3]
    {{ 1.0,  1.0,  1.0}, {1.0, 0.0}}, //17[2]
    {{ 1.0,  1.0, -1.0}, {1.0, 1.0}}, //18[5]
    {{-1.0,  1.0, -1.0}, {0.0, 1.0}}, //19[4]
    // Bottom [Bottom 的 y 是負的]
    {{-1.0, -1.0,  1.0}, {0.0, 0.0}}, //20[0]
    {{-1.0, -1.0, -1.0}, {1.0, 0.0}}, //21[7]
    {{ 1.0, -1.0, -1.0}, {1.0, 1.0}}, //22[6]
    {{ 1.0, -1.0,  1.0}, {0.0, 1.0}}, //23[1]

};

Texture_CubeMap:

// Cube Map
static const VYVertex texCubemapCubeDatas[] = {

    // Front [Front 的 z 是正的]
    {{-1.0, -1.0,  1.0}, {}, {-1.0, -1.0,  1.0}}, // 0
    {{ 1.0, -1.0,  1.0}, {}, { 1.0, -1.0,  1.0}}, // 1
    {{ 1.0,  1.0,  1.0}, {}, { 1.0,  1.0,  1.0}}, // 2
    {{-1.0,  1.0,  1.0}, {}, {-1.0,  1.0,  1.0}}, // 3
    // Back [Back 的 z 是負的]
    {{-1.0,  1.0, -1.0}, {}, {-1.0,  1.0, -1.0}}, //4[3: -Z]
    {{ 1.0,  1.0, -1.0}, {}, { 1.0,  1.0, -1.0}}, //5[2: -Z]
    {{ 1.0, -1.0, -1.0}, {}, { 1.0, -1.0, -1.0}}, //6[1: -Z]
    {{-1.0, -1.0, -1.0}, {}, {-1.0, -1.0, -1.0}}, //7[0: -Z]
    // Left [Left 的 x 是負的]
    {{-1.0, -1.0,  1.0}, {}, {-1.0, -1.0,  1.0}}, //8[0]
    {{-1.0,  1.0,  1.0}, {}, {-1.0,  1.0,  1.0}}, //9[3]
    {{-1.0,  1.0, -1.0}, {}, {-1.0,  1.0, -1.0}}, //10[4]
    {{-1.0, -1.0, -1.0}, {}, {-1.0, -1.0, -1.0}}, //11[7]
    // Right [Right 的 x 是正的]
    {{ 1.0, -1.0,  1.0}, {}, { 1.0, -1.0,  1.0}}, //12[1]
    {{ 1.0, -1.0, -1.0}, {}, { 1.0, -1.0, -1.0}}, //13[6]
    {{ 1.0,  1.0, -1.0}, {}, { 1.0,  1.0, -1.0}}, //14[5]
    {{ 1.0,  1.0,  1.0}, {}, { 1.0,  1.0,  1.0}}, //15[2]
    // Top [Top 的 y 是正的]
    {{-1.0,  1.0,  1.0}, {}, {-1.0,  1.0,  1.0}}, //16[3]
    {{ 1.0,  1.0,  1.0}, {}, { 1.0,  1.0,  1.0}}, //17[2]
    {{ 1.0,  1.0, -1.0}, {}, { 1.0,  1.0, -1.0}}, //18[5]
    {{-1.0,  1.0, -1.0}, {}, {-1.0,  1.0, -1.0}}, //19[4]
    // Bottom [Bottom 的 y 是負的]
    {{-1.0, -1.0,  1.0}, {}, {-1.0, -1.0,  1.0}}, //20[0]
    {{-1.0, -1.0, -1.0}, {}, { 1.0, -1.0,  1.0}}, //21[7]
    {{ 1.0, -1.0, -1.0}, {}, { 1.0, -1.0, -1.0}}, //22[6]
    {{ 1.0, -1.0,  1.0}, {}, {-1.0, -1.0, -1.0}}, //23[1]

};

這種座標,是恰好貼合【徹底覆蓋】的狀態;

數據特色:一個頂點數據綁定一個紋理數據;

【有沒有注意到,CubeMap 裏面就是直接拷貝頂點數據到紋理座標上,就好了。(CubeMap 中間那個空的 {} 是結構體中的 2D 紋理數據(就是空的))】

其它的數據形態【對於不是正方的圖片】,
【但願大一點,或小一點,即只顯示某一部分】:

都是相似圖中的分割同樣,劃分紅多個小圖片【小的 TexCoord】,最終的數據形態是:

static const VYVertex tex2DElongatedDDCubeDatas[] = {

    // Front [Front 的 z 是正的]
    {{-1.0, -1.0,  1.0}, {0.000, 0.000}}, // 0
    {{ 1.0, -1.0,  1.0}, {0.250, 0.000}}, // 1
    {{ 1.0,  1.0,  1.0}, {0.250, 0.500}}, // 2
    {{-1.0,  1.0,  1.0}, {0.000, 0.500}}, // 3
    // Back [Back 的 z 是負的]
    {{-1.0,  1.0, -1.0}, {0.000, 0.500}}, //4[3: -Z]
    {{ 1.0,  1.0, -1.0}, {0.250, 0.500}}, //5[2: -Z]
    {{ 1.0, -1.0, -1.0}, {0.250, 1.000}}, //6[1: -Z]
    {{-1.0, -1.0, -1.0}, {0.000, 1.000}}, //7[0: -Z]
    // Left [Left 的 x 是負的]
    {{-1.0, -1.0,  1.0}, {0.250, 0.000}}, //8[0]
    {{-1.0,  1.0,  1.0}, {0.500, 0.000}}, //9[3]
    {{-1.0,  1.0, -1.0}, {0.500, 0.500}}, //10[4]
    {{-1.0, -1.0, -1.0}, {0.250, 0.500}}, //11[7]
    // Right [Right 的 x 是正的]
    {{ 1.0, -1.0,  1.0}, {0.250, 0.500}}, //12[1]
    {{ 1.0, -1.0, -1.0}, {0.500, 0.500}}, //13[6]
    {{ 1.0,  1.0, -1.0}, {0.500, 1.000}}, //14[5]
    {{ 1.0,  1.0,  1.0}, {0.250, 1.000}}, //15[2]
    // Top [Top 的 y 是正的]
    {{-1.0,  1.0,  1.0}, {0.500, 0.000}}, //16[3]
    {{ 1.0,  1.0,  1.0}, {0.750, 0.000}}, //17[2]
    {{ 1.0,  1.0, -1.0}, {0.750, 0.500}}, //18[5]
    {{-1.0,  1.0, -1.0}, {0.500, 0.500}}, //19[4]
    // Bottom [Bottom 的 y 是負的]
    {{-1.0, -1.0,  1.0}, {0.750, 0.000}}, //20[0]
    {{-1.0, -1.0, -1.0}, {1.000, 0.000}}, //21[7]
    {{ 1.0, -1.0, -1.0}, {1.000, 0.500}}, //22[6]
    {{ 1.0, -1.0,  1.0}, {0.750, 0.500}}, //23[1]

};

也能夠是沒有填充完整的圖片,只取其中的一部分,數據形態也是上面的:

擴展:
CubeMap 用於作環境貼圖,還須要 Light + Shadow 【光 + 陰影】的知識,爲何?環境,有物體 + 天然光 + 人造光 + 光與物體產生的陰影 + 光與物體做用後的顏色;【顏色和陰影是由於有光才產生的,OpenGL 自己默認有一個全局光,否則你沒有寫光的代碼,爲何能夠看到你渲染的模型體】
即只有在具有了 光 + 影 的知識,去學習 環境貼圖纔好理解;【貼圖:HDR 圖片 (效果中的那張藍色森林就是 HDR 圖,沒有作 CubeMap) + CubeMap 格式】

CubeMap 圖片格式,就是把下圖中的 HDR 圖片直接轉換成,六個黃色框框的圖像,框框之間的邊緣是鏈接的哦:

鏈接

MipMapping: 根據不一樣的情形加載不一樣大小的圖片進行渲染;【不一樣情形,指不一樣遠近,不一樣光影環境下對圖片 「看清」「看不清」 的程度,OpenGL 自動選擇合適的圖片大小】【不一樣大小的圖片,程序員要事先加載一張圖片的不一樣大小 ( 2^n , 2^m ) 的像素數據(0 ~ n level),又由於 ES 是基於移動端的,因此內存容易告急,即能不用則不用】

Fliter + 特效 : 咱們每天看到的最多的東西,就是給圖片像素加入各類 「想法」 變成你想要的效果【加霧、馬賽克、調色、鏡像、模糊、素描、液化、疊加、藝術化 ......】,它的核心知識在 Fragment Shader【重點】 + OpenGL ES 提供的基礎混合模式【濾波 + Blend】,放在下一篇文章專門講;

粒子系統:Texture + Point Sprites,製做雨水、下雪、飛舞的花瓣...... 只要渲染效果要求有多個類似點在那動來動去的,均可以用它們來實現;【數學中的分形理論好像也能夠用上】【粒子,會用專門的一篇文章講】

全部的 「花樣」 特效,無論被稱之爲何,都與 數學知識【算法】 和 顏色構成知識【光構成、色彩構成】 密不可分;

因此我就要怕了嗎?
錯,你應該興奮;由於~~ 反正我也沒有什麼能夠失去的了,上來不就是幹了嗎? ^ _ ^ + ~_~ + $-$

(三)、引入了 Texture 的 Shader 文件

Texture_2D:

2D Vertex:

#version 100

uniform mat4 u_modelViewMat4;
uniform mat4 u_projectionMat4;

attribute vec4 a_position;
attribute vec2 a_texCoord;

varying highp vec2 v_texCoord;

void main(void) {
    gl_Position = u_projectionMat4 * u_modelViewMat4 * a_position;
    v_texCoord  = a_texCoord;
}

紋理輸入輸出:

...
attribute vec2 a_texCoord;
varying highp vec2 v_texCoord;

void main(void) {
    ...
    v_texCoord  = a_texCoord;
}

輸入:
vec2 a_texCoord,上面提到過它是 {x, y} 的座標,因此使用的也是 vec2 ;

輸出:
一樣是 vec2 ,可是必定要記住加 highp 精度限定符,否則編譯會報錯哦;

不知道,你是否還記得渲染管線中的 Texture Memory ,看下圖:

渲染管線

紅色框框住的虛線,就是指代 Vertex Shader 中的紋理座標信息;

直接給的,爲何是虛線?
看清楚 Shader 代碼,這裏是直接就賦值【輸入 = 輸出,通過其它變換也行】了,也就是 Vertex Shader 內部不須要使用到它,它只是爲了傳到 Fragment 裏面使用的【varying 的做用】,因此就使用虛線來表示;

2D Fragment:

#version 100

uniform sampler2D us2d_texture;

varying highp vec2 v_texCoord;

void main(void) {
// gl_FragColor = vec4(1, 1, 0.5, 1);
    gl_FragColor = texture2D(us2d_texture, v_texCoord);
}

上面的渲染管線圖中,黃色框框住的實線,就是指代 Fragment Shader 中的像素數據【sampler2D】來源;

這裏是核心,輸入輸出:

uniform sampler2D us2d_texture;
...

void main(void) {
    gl_FragColor = texture2D(us2d_texture, ...);
}

輸入:
sampler2D 就是一堆靜態數據的意思,像素信息就是一堆固定【無論是寫死,仍是程序自動生成,都同樣】的顏色信息,因此要使用這種常量塊的類型限定符;

輸出:
這裏要使用 texture2D 內置函數來處理像素信息生成 vec4 的顏色信息,原型 vec4 texture2D(sampler2D s, vec2 texCoord);

因此剩下的問題就是如何獲得 sampler2D 數據,並如何將像素數據寫入到 Shader 中

Texture_CubeMap:

#version 100

uniform mat4 u_modelViewMat4;
uniform mat4 u_projectionMat4;

attribute vec4 a_position;
attribute vec3 a_normalCoord;
varying highp vec3 v_normalCoord;

void main(void) {
    gl_Position = u_projectionMat4 * u_modelViewMat4 * a_position;
    v_normalCoord  = a_normalCoord;
}
#version 100

uniform samplerCube us2d_texture;
varying highp vec3 v_normalCoord;

void main(void) {
    gl_FragColor = textureCube(us2d_texture, v_normalCoord);
}

CubeMap 與 2D 的 Fragment 區別並不大,原理同樣的;
CubeMap Vertex ,只要把 vec2 --> vec3 便可;
CubeMap Fragment , 只要把 sampler2D --> samplerCube , texture2D 函數改爲 textureCube 便可;

(四)、Texture 正確的 「書寫」 順序

前提,假設基本的渲染管線已經配置完成了,這裏只重點講紋理相關的;

一、 綁定 Texture Coord 紋理座標:

GLuint texCoordAttributeComCount = 2;

glEnableVertexAttribArray(texCoordAttributeIndex);
if ( texture2D ) {
    glVertexAttribPointer(texCoordAttributeIndex,
                          texCoordAttributeComCount,
                          GL_FLOAT, GL_FALSE,
                          sizeof(VYVertex),
                          (const GLvoid *) offsetof(VYVertex, texCoord));
 } else {
    texCoordAttributeComCount = 3;
    glVertexAttribPointer(texCoordAttributeIndex,
                          texCoordAttributeComCount,
                          GL_FLOAT, GL_FALSE,
                          sizeof(VYVertex),
                          (const GLvoid *) offsetof(VYVertex, normalCoord));
 }

【若是看不懂,請回去看看第一篇文章,裏面有詳細講】

二、 請求 Texture 內存:

GLuint texture = 0;
    glGenTextures(1, &texture);

    GLenum texMode = texture2D ? GL_TEXTURE_2D : GL_TEXTURE_CUBE_MAP;
    glBindTexture(texMode, texture);

glGenTextures(GLsizei n, GLuint* textures); 和 glGenBuffers 等的使用是同樣的;它的意思就是,向 GPU 請求一塊 Texture 內存;
glBindTexture (GLenum target, GLuint texture); 和其它的 glBind... 方法同樣;它的意思是,告訴 GPU 請求一塊 target 【只有 2D 和 CubeMap 兩種】 類型的內存,只有當這個方法完成請求後,這塊 Texture 內存纔會生成【若是當前內存標識符指向的內存已經存在,則不會再建立,只會指向此處】;

三、 加載像素數據:

glUseProgram(programObject);

    [self setTextureWithProgram:programObject 
                        texture:texture
                        texMode:texMode];

(1)必定要在 glUseProgram 函數後進行這個步驟,爲何?
由於 Fragment 使用的是 uniform samplerXXX 的數據,uniform 常量數據要在 glUseProgram 後再加載纔有效,並且它的內存標識符【內存】要在 link Program 以後 OpenGL 纔會分配;

(2)進入 setTextureWithProgram: texture: texMode:方法
先準備像素數據【pixelsDatas 或 ImageDatas】:
這裏的是,Pixels 的數據,就是寫死的數據

// 2 * 2 For Texture_2D
static const GLfloat tex2DPixelDatas[3*4] = {
    1.000, 1.000, 0.108,//[UIColor colorWithRed:1.000 green:1.000 blue:0.108 alpha:1.000]
    0.458, 1.000, 0.404,//[UIColor colorWithRed:0.458 green:1.000 blue:0.404 alpha:1.000]
    0.458, 1.000, 0.770,//[UIColor colorWithRed:0.458 green:1.000 blue:0.770 alpha:1.000]
    0.729, 0.350, 0.770,//[UIColor colorWithRed:0.729 green:0.350 blue:0.770 alpha:1.000]
};

// (2 * 2 * 6) For Texture_CubeMap
static const GLfloat texCubemapPixelDatas[6][3*4] = {
    1.000, 1.000, 0.108,//[UIColor colorWithRed:1.000 green:1.000 blue:0.108 alpha:1.000]
    0.458, 1.000, 0.404,//[UIColor colorWithRed:0.458 green:1.000 blue:0.404 alpha:1.000]
    0.458, 1.000, 0.770,//[UIColor colorWithRed:0.458 green:1.000 blue:0.770 alpha:1.000]
    0.729, 0.350, 0.770,//[UIColor colorWithRed:0.729 green:0.350 blue:0.770 alpha:1.000]

    0.145,
相關文章
相關標籤/搜索