OpenGL ES案例04_1-GLSL使用索引繪圖

本案例的主要目的是理解GLSL中如何使用索引繪圖。最終效果如下:


GLSL使用索引繪圖效果圖

什么是索引繪圖

一個圖形有許多頂點,如本案例最終效果中的金字塔,一共有5個面6個三角形組成。由這6個三角形根據圖元裝配繪制金字塔效果。在繪制過程中,共使用了18個頂點,因為圖元裝配方式使得有些頂點是進行了復用,而實際肉眼可見頂點個數只有5個。如下圖所示。


image.png

索引繪制技巧就是將圖形中肉眼可見的頂點,通過索引的方式表示頂點之間的連接。
在上圖中,各三角形的頂點構成方式如下:

  • 左面的三角形由頂點{0,2,4}構成。
  • 右面的三角形由頂點{1,4,3}構成。
  • 前面的三角形由頂點{2,3,4}構成。
  • 后面的三角形由頂點{0,4,1}構成。
  • 底部由兩個三角形組成,兩個三角形分別是由頂點{0,3,2}和頂點{0,1,3}構成。

使用GLSL索引繪圖的思路如下:


GLSL索引繪圖的思路

準備工作

  1. 新建一個iOS工程,并將新工程中的view的繼承關系,修改成繼承于自定義View。
  2. 在圖形進行旋轉時需要用到矩陣相乘的操作,本案例中引入一個數學庫,完成矩陣相乘等操作。

自定義著色器文件

自定義著色器文件的內容如下:


自定義著色器

頂點著色器

  • 由于本案例中點擊按鈕將實現旋轉,這將涉及矩陣變換,因此需要在頂點著色器中定義兩個uniform變量:1,投影矩陣;2,模型視圖矩陣。
  • 在計算頂點最終坐標時,需要將投影矩陣模型視圖矩陣源頂點坐標,再將最終結果值賦值給內建變量gl_Position。
  • 注意:在頂點數據與矩陣相乘時,需要注意相乘的順序。因為在OpenGL ES中是以列向量為主,position是列向量,即41矩陣。而投影矩陣、模型視圖矩陣都是44矩陣,因此需要按投影矩陣模型視圖矩陣position的順序。
attribute vec4 position;
attribute vec4 positionColor;
uniform mat4 projectionMatrix;
uniform mat4 modelViewMatrix;
varying lowp vec4 varyColor;
void main(){
    varyColor = positionColor;
    gl_Position = projectionMatrix * modelViewMatrix * position;
}

片元著色器

  • 本案例中不涉及紋理操作,因此只需要在片元著色器中將頂點顏色直接賦值給內建變量gl_FragColor即可。
  • 如果頂點顏色少于頂點個數,則會對缺少顏色的頂點進行顏色插值,產生一個類似顏色漸變的效果。
  • 如果不想引入插值,則需要指定整個面的顏色而不是頂點顏色。
varying lowp vec4 varyColor;
void main(){
    gl_FragColor = varyColor;
}

重寫layoutSubviews函數

在layoutSubviews函數中,主要完成以下工作:


image.png
  1. 設置繪制圖層
    創(chuàng)建繪制內容的載體。
-(void)setupLayer{
    self.myEagLayer = (CAEAGLLayer*)self.layer;
    [self setContentScaleFactor:[[UIScreen mainScreen] scale]];
    self.myEagLayer.opaque = YES;
    self.myEagLayer.drawableProperties = @{
        kEAGLDrawablePropertyRetainedBacking:@false,
        kEAGLDrawablePropertyColorFormat:kEAGLColorFormatRGBA8
    };
}

+ (Class)layerClass{
    return [CAEAGLLayer class];
}
  1. 設置上下文
    用于記錄繪制過程中的一些狀態(tài)信息。
-(void)setupContext{
    self.myContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
    [EAGLContext setCurrentContext:self.myContext];
}
  1. 清空緩沖區(qū)
    避免之前緩沖區(qū)的數據對本次繪制造成影響。
-(void)deletBuffer{
    glDeleteRenderbuffers(1, &_myColorRenderBuffer);
    self.myColorRenderBuffer = 0;
    
    glDeleteFramebuffers(1, &_myColorFrameBuffer);
    self.myColorFrameBuffer = 0;
}
  1. 設置頂點緩沖區(qū)
    設置一下用于存儲頂點數據或顏色數據等的緩沖區(qū)。
-(void)setupRenderBuffer{
    glGenRenderbuffers(1, &_myColorRenderBuffer);
    glBindRenderbuffer(GL_RENDERBUFFER, self.myColorRenderBuffer);
    [self.myContext renderbufferStorage:GL_RENDERBUFFER fromDrawable:self.myEagLayer];
}
  1. 設置幀緩沖區(qū)
    設置管理頂點緩沖區(qū)的幀緩沖區(qū)。
-(void)setupFrameBuffer{
    glGenFramebuffers(1, &_myColorFrameBuffer);
    glBindFramebuffer(GL_FRAMEBUFFER, self.myColorFrameBuffer);
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, self.myColorRenderBuffer);
}
  1. 根據著色器文件創(chuàng)建program
    6.1 讀取著色器文件;
    6.2 創(chuàng)建頂點著色器和片元著色器;
    6.3 分別著色器文件將內容加載到頂點著器和片元著色器中;
    6.4 編譯著色器;
    6.5 創(chuàng)建program;
    6.6 將著色器鏈附著到program上;
    6.7 刪除已經附著過的著色器;
    以上是一個編譯著色器文件并附著至program中的流程,在OpenGL ES案例03 - 使用GLSL完成紋理圖片加載中也有描述過該流程,詳細的代碼可參見本案例的Demo。
-(void)setupProgram{
    NSString* vertFile = [[NSBundle mainBundle] pathForResource:@"shaderv" ofType:@"vsh"];
    NSString* fragFile = [[NSBundle mainBundle] pathForResource:@"shaderf" ofType:@"fsh"];
    
    self.myProgram = [self loadShader:vertFile frag:fragFile];
    glLinkProgram(self.myProgram);
    glUseProgram(self.myProgram);
}
  1. 設置頂點數據
    這部分主要是將頂點數據從CPU傳遞給GPU,并打開頂點屬性通道。
-(void)setupVertexData{
    //(1)頂點數組 前3頂點值(x,y,z),后3位顏色值(RGB)
    GLfloat attrArr[] = {
        -0.5f, 0.5f, 0.0f,      1.0f, 0.0f, 1.0f, //左上0
        0.5f, 0.5f, 0.0f,       1.0f, 0.0f, 1.0f, //右上1
        -0.5f, -0.5f, 0.0f,     1.0f, 1.0f, 1.0f, //左下2
        
        0.5f, -0.5f, 0.0f,      1.0f, 1.0f, 1.0f, //右下3
        0.0f, 0.0f, 1.0f,       0.0f, 1.0f, 0.0f, //頂點4
    };
    
    GLuint bufferID;
    glGenBuffers(1, &bufferID);
    glBindBuffer(GL_ARRAY_BUFFER, bufferID);
    glBufferData(GL_ARRAY_BUFFER, sizeof(attrArr), attrArr, GL_DYNAMIC_DRAW);
    
    GLuint position = glGetAttribLocation(self.myProgram, "position");
    glEnableVertexAttribArray(position);
    glVertexAttribPointer(position, 3, GL_FLOAT, GL_FALSE, 6*sizeof(GLfloat), NULL);
    
    GLuint positionColor = glGetAttribLocation(self.myProgram, "positionColor");
    glEnableVertexAttribArray(positionColor);
    glVertexAttribPointer(positionColor, 3, GL_FLOAT, GL_FALSE, 6*sizeof(GLfloat), (GLfloat*)NULL+3);
}
  1. 設置投影矩陣
    本案例中繪制的是一個立體圖形,為了更逼近真實效果,需要設置一個投影矩陣。
    8.1 創(chuàng)建一個4*4的矩陣;
    8.2 通過ksMatrixLoadIdentity函數,將投影矩陣初始化為一個單元矩陣;
    8.3 通過ksPerspective函數,設置透視投影;
    8.4 通過glUniformMatrix4fv函數,將投影矩陣傳遞給頂點著色器;
-(void)setupProjectionData{
    GLuint projectionMatrixSlot = glGetUniformLocation(self.myProgram, "projectionMatrix");
    KSMatrix4 _projectionMatrix;
    ksMatrixLoadIdentity(&_projectionMatrix);
    float aspect = self.frame.size.width/self.frame.size.height;
    
    ksPerspective(&_projectionMatrix, 30, aspect, 5.0, 20.0);
    ksTranslate(&_projectionMatrix, 0, 0, -10.0);
    glUniformMatrix4fv(projectionMatrixSlot, 1, GL_FALSE, &_projectionMatrix.m[0][0]);
}
  1. 設置模型視圖矩陣
    初始化一個模型視圖矩陣,后續(xù)可將旋轉、平移等操作與該矩陣進行相乘。
    9.1 定義一個4*4的模型視圖矩陣;
    9.2 通過ksMatrixLoadIdentity函數,將模型視圖矩陣初始化為一個單元矩陣;
    9.3 通過glUniformMatrix4fv函數,將模型視圖矩陣傳遞給頂點著色器;
- (void)setupModelViewData{
    GLuint modelViewMatrixSlot = glGetUniformLocation(self.myProgram, "modelViewMatrix");
        
    KSMatrix4 _modelViewMatrix;
    ksMatrixLoadIdentity(&_modelViewMatrix);
    glUniformMatrix4fv(modelViewMatrixSlot, 1, GL_FALSE, &_modelViewMatrix.m[0][0]);
}
  1. 開始繪制
    至此,基本的操作已經完成了,之前的案例中使用的默認繪圖方式,這里通過使用glDrawElements函數來完成索引繪圖。
-(void)render{
    glClearColor(0, 0.0, 0, 1.0);
    glClear(GL_COLOR_BUFFER_BIT);
    GLfloat scale = [[UIScreen mainScreen] scale];
    glViewport(self.frame.origin.x * scale, self.frame.origin.y * scale, self.frame.size.width * scale, self.frame.size.height * scale);
    glEnable(GL_CULL_FACE);
    
    //(2).索引數組
    GLuint indices[] = {
        0, 3, 2,
        0, 1, 3,
        0, 2, 4,
        0, 4, 1,
        2, 3, 4,
        1, 4, 3,
    };
    
    /*
    GLenum mode:繪圖的模型
        GL_POINTS
        GL_LINES
        GL_LINE_LOOP
        GL_LINE_STRIP
        GL_TRIANGLES
        GL_TRIANGLE_STRIP
        GL_TRIANGLE_FAN
    GLsizei count:繪圖所需要索引的個數
    GLenum type:索引數組中數據類型
    const GLvoid* indices:索引數組
    */
    glDrawElements(GL_TRIANGLES, sizeof(indices)/sizeof(indices[0]), GL_UNSIGNED_INT, indices);
    
    [self.myContext presentRenderbuffer:GL_RENDERBUFFER];
}

至此,已經完成了整個圖形的繪制。接下來就是完成點擊按鈕后可以執(zhí)行旋轉的效果了。

實現按鈕點擊后圖形旋轉效果

為了讓圖形能沿著x、y、z軸旋轉,我們定義三個按鈕,分別命名為x、y、z,當點擊不同按鈕時,沿著不同的軸進行旋轉,點擊一次為啟動旋轉,再次點擊為停止旋轉。
重點:此部分的重點為如何發(fā)生旋轉。

  1. 定義一個4*4的模型視圖矩陣;
  2. 通過ksMatrixLoadIdentity函數將模型視圖矩陣初始化為一個單元矩陣;
  3. 定義一個4*4的旋轉矩陣;
  4. 通過ksMatrixLoadIdentity函數,將定義的旋轉矩陣也初始化為一個單元矩陣;
  5. 通過ksRotate函數,實現沿某個軸發(fā)生旋轉;
  6. 通過ksMatrixMultiply函數,將旋轉矩陣與模型視圖矩陣相乘,并將結果放入模型視圖矩陣中;
  7. 通過glUniformMatrix4fv函數將最終模型視圖矩陣傳遞給頂點著色器;
  8. 重新繪制;
-(void)reDegree{
    xDegree += bX*5;
    yDegree += bY*5;
    zDegree += bZ*5;
    
    GLuint modelViewMatrixSlot = glGetUniformLocation(self.myProgram, "modelViewMatrix");
    
    KSMatrix4 _modelViewMatrix;
    ksMatrixLoadIdentity(&_modelViewMatrix);
    
    KSMatrix4 _rotationMatrix;
    ksMatrixLoadIdentity(&_rotationMatrix);
    ksRotate(&_rotationMatrix, xDegree, 1, 0, 0);
    ksRotate(&_rotationMatrix, yDegree, 0, 1, 0);
    ksRotate(&_rotationMatrix, zDegree, 0, 0, 1);
    
    ksMatrixMultiply(&_modelViewMatrix, &_rotationMatrix, &_modelViewMatrix);
    glUniformMatrix4fv(modelViewMatrixSlot, 1, GL_FALSE, &_modelViewMatrix.m[0][0]);
    
    [self render];
}

完整代碼見:GLSL三角形變換Demo地址

?著作權歸作者所有,轉載或內容合作請聯系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內容