OpenGL ES入門及GLSL

使用OpenGL ES最關心的問題

1、如何在iOS上搭建OpenGL ES環(huán)境
2、如何鏈接GLSL
3、如何通過GLSL輸入數(shù)據

參考:
《音視頻開發(fā)進階指南》、網絡資料

GLSL介紹

GLSL(OpngGL Shading Language)是OpenGL的著色器語言,開發(fā)人員利用這種語言編寫程序運行在GPU(Graphic Processor Unit,圖形圖像處理單元,可以理解為是一種高并發(fā)的運算器)上以進行圖像的處理或渲染。

1.OpenGL渲染管線

要想學習著色器,并理解著色器的工作機制,就要對OpenGL固定的渲染管線有深入的了解。先來統(tǒng)一一下術語。

  • 幾何圖元:包括點、直線、三角形,均是通過頂點(vertex)來指定。
  • 模型:根據 “幾何圖元 ” 創(chuàng)建的物體。
  • 渲染:計算機根據 “模型” 創(chuàng)建圖像的過程。

最終渲染結束之后,就是人眼看到的圖像。

圖像是由屏幕上的所有像素點組成的。

  • 在內存中:這些像素點可以組織成一個大的一維數(shù)組,每4個Byte即表示一個像素點的RGBA數(shù)據。
  • 在顯卡中:這些像素點可以組織成幀緩沖區(qū)(FrameBuffer)的形式,幀緩沖區(qū)保存了圖形硬件為了控制屏幕上所有像素的顏色和強度所需要的全部信息。

那么OpenGL的 渲染管線 具體是做什么的呢?其實就是OpenGL引擎渲染圖像的流程,也就是OpenGL引擎一步一步將圖片渲染到屏幕上的過程。
渲染管線 分為以下幾個階段:

階段一:指定幾何對象

所謂幾何對象,就是上面說過的幾何圖元,這里將根據具體執(zhí)行的指令繪制幾何圖元
比如,OpenGL提供給開發(fā)者的繪制方法:

 glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); 

這個方法里面的第一個參數(shù)是mode,就是制定繪制方式,可選值有以下幾種。

  • GL_POINTS:以點的形式進行繪制,通常用在繪制粒子效果的場景中。
  • GL_LINES:以線的形式進行繪制,通常用在繪制直線的場景中。
  • GL_TRIANGLE_STRIP:以三角形的形式進行繪制,所有二維圖像的渲染都會使用這種方式。
    可參考鏈接:https://blog.csdn.net/csxiaoshui/article/details/54923763
    image.png

具體選用哪一種繪制方式決定了OpenGL渲染管線的第一階段應如何去繪制幾何圖元。

階段二:頂點處理

不論以上的幾何對象是如何指定的,所有的幾何數(shù)據都會經過這個階段。這個階段所做的操作就是,根據模型視圖和投影矩陣進行變換來改變頂點的位置。
根據紋理坐標和紋理矩陣來改變紋理坐標的位置,如果涉及三維的渲染,那么這里還要處理光照計算和法線變換。
這里的輸出是以gl_Position來表示具體的頂點位置。

 attribute vec4 position;
attribute vec2 textCoordinate;
uniform mat4 rotateMatrix;

varying lowp vec2 varyTextCoord;

void main()
{
    varyTextCoord = textCoordinate;
    
    vec4 vPos = position;

    vPos = vPos * rotateMatrix;

    gl_Position = vPos;
} 

階段三:圖元組裝

在經過階段二的頂點處理操作之后,不論是模型的頂點,還是紋理坐標都是已經確定的。在這個階段,頂點將會根據應用程序送往圖元的規(guī)則(如GL_TRIANGLE_STRIP等),將紋理組裝成圖元。

階段四:柵(shan)格化操作

由階段三傳遞過來的圖元數(shù)據,在此將會被分解成更小的單元并對應于幀緩沖區(qū)的各個像素。這些單元稱為片元。一個片元可能包含窗口顏色、紋理坐標等屬性。片元的屬性是根據頂點坐標利用插值來確定的,這其實就是柵格化操作,也就是確定好每一個片元是什(shen)么。

注:光柵化(Rasterize/rasteriztion)。這個詞兒Adobe官方翻譯成柵格化或者像素化。沒錯,就是把矢量圖形轉化成像素點兒的過程。我們屏幕上顯示的畫面都是由像素組成,而三維物體都是點線面構成的。要讓點線面,變成能在屏幕上顯示的像素,就需要Rasterize這個過程。就是從矢量的點線面的描述,變成像素的描述。如下圖,這是一個放大了1200%的屏幕,前面是告訴計算機我有一個圓形,后面就是計算機把圓形轉換成可以顯示的像素點。這個過程就是Rasterize。
鏈接:https://www.zhihu.com/question/29163054/answer/46695506

注:簡單來講,圖元就是組成圖像的基本單元,比如三維模型中的點、線、面等等,注意圖元(entity)與片元(primitive)的區(qū)別,片元就是以后的像素點,它比像素多一些位置、法向量等屬性。逐個片元操作有像素所有權操作(確定目標像素可見還是被一個重疊的窗口蓋住了),剪切測試、Alpha測試、模板測試、混合等。而片段(fragments)是指具有相同屬性的一小部分像素區(qū)域。
鏈接:https://baike.baidu.com/item/圖元/2303188?fr=aladdin

可參考鏈接:https://www.cnblogs.com/yangai/p/6764383.html

image.png

階段五:片元處理

通過紋理坐標取得紋理(texture)中相對應的片元像素值(texel),根據自己的業(yè)務處理(比如提亮、飽和度調節(jié)、對比度調節(jié)、高斯模糊等)來變換這個片元的顏色。這里的輸出是gl_FragColor,用于表示修改之后的像素的最終結果。

 varying lowp vec2 varyTextCoord;

uniform sampler2D colorMap;


void main()
{
    gl_FragColor = texture2D(colorMap, varyTextCoord);
}
 

階段六:幀緩沖操作

該階段主要執(zhí)行幀緩沖的寫入操作,這也是渲染管線的最后一步,負責將最終的像素值寫到幀緩沖區(qū)(像素在顯卡中的保存形式/位置)中。

著色器代替了渲染管線中的階段二:頂點處理階段五:片元處理

2.GLSL語法與內建函數(shù)

GLSL全稱OpenGL Shading Language,是為了實現(xiàn)著色器的功能而向開發(fā)人員提供的一種開發(fā)語言。

(1)GLSL的修飾符與基本數(shù)據類型

GLSL的語法與C語言非常類似,其數(shù)據類型表示具體如下。

修飾符:

  • const:用于聲明非可寫的編譯時常量變量。
  • attribute:用于經常更改的信息,只能在頂點著色器中使用。
  • uniform:用于不經常更改的信息,可用于頂點著色器和片元著色器。
  • varying:用于修飾從頂點著色器向片元著色器傳遞的變量。

基本數(shù)據類型:
int、float、bool,這些與C語言都是一致的,需要強調的一點就是,這里面的float是有一個修飾符的,即可以指定精度。

 precision highp float; 

三種修飾符的范圍(范圍一般視顯卡而定)和應用情況具體如下。

  • high:32bit,一般用于頂點坐標(vertex Coordinate)。
  • medium:16bit,一般用于紋理坐標(texture Coordinate)。
  • lowp:8bit,一般用于顏色表示(color)。
varying   highp vec2 vv2_Texcoord;
mediump vec3 yuv;
lowp    vec3 rgb; 

向量類型:
向量類型是Shader中非常重要的一個數(shù)據類型,因為在做數(shù)據傳遞的時候需要經常傳遞多個參數(shù),相比較于寫多個基本數(shù)據類型,使用向量類型是非常好的選擇。
列舉一個最經典的例子,要將物體坐標和紋理坐標傳遞到Vertex Shader中,用的就是向量類型,每一個頂點都是一個四維向量,在Vertex Shader中利用這兩個四維向量即可完成自己的紋理坐標映射操作。聲明方式如下(GLSL代碼):

 attribute vec4 position;//物體坐標
attribute vec2 textCoordinate; //紋理坐標

矩陣類型:
矩陣類型在Shader的語法中也是一個非常重要的類型,有一些效果器需要開發(fā)者傳入矩陣類型的數(shù)據,比如懷舊效果器,就需要傳入一個矩陣來改變原始的像素數(shù)據。聲明方式如下(GLSL代碼):

uniform lowp mat4 colorMatrix;
uniform mat4 rotateMatrix;

上面的代碼表示來一個4x4的浮點矩陣。若要傳遞一個矩陣到實際的Shader中,則可以直接調用如下函數(shù)(客戶端代碼):

glUniformMatrix4fv(rotate, 1, GL_FALSE, (GLfloat *)&zRotation[0]); 

紋理類型:
一般僅在Fragment Shader中使用這個類型,二維紋理的聲明方式如下(GLSL代碼):

uniform sampler2D texSampler;

當客戶端接收到這個句柄時,就可以為它綁定一個紋理,代碼如下(客戶端代碼):

 glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texId);
glUniform1i(mGLUniformTexture, 0);//定義哪個uniform采樣器對應哪個紋理單元

第一行代碼激活的紋理句柄是GL_TEXTURE0,對應的第三行代碼中的第二個參數(shù)Index就是0,如果激活的紋理句柄時GL_TEXTURE1,對應的Index就是1,在不同的平臺上句柄的個數(shù)也不一樣,但是一般都會在32個以上。

特殊的傳遞類型varying:
在GLSL中有一個特殊的修飾符就是varying,這個修飾符修飾的變量均用于在Vertex Shader和Fragment Shader之間傳遞參數(shù)。
首先,在頂點著色器中聲明這個類型的變量代表紋理的坐標點,并且對這個變量進行賦值,代碼如下:

attribute vec2 texcoord;
varying vec2 v_texcoord;

void main()
{
  //計算頂點坐標
  v_texcoord = texcoord;
} 

緊接著在Fragment Shader中也聲明同名的變量,然后使用texture2D方法取出二維紋理中該紋理坐標點上的紋理像素值,代碼如下(GLSL代碼):

 varying highp vec2 v_texcoord;
 uniform sampler2D texSampler;
 
 void main()
 {
     vec4 texel = texture2D(texSampler, v_texcoord);
     gl_FragColor = texel;
 }

取出了該坐標上的像素值之后,就可以進行像素變化操作了,比如提高對比度,最終將改變的像素值賦值給gl_FragColor。

(2)GLSL的內置函數(shù)與內置變量

內置變量:

最常見的Vertex Shader和Fragment Shader的輸出變量。

Vertex Shader的內置變量:
先來看Vertex Shader的內置變量(GLSL代碼):

 vec4 gl_Position;

上述代碼用來設置頂點轉換到屏幕坐標的位置。
另外還有一個內置變量,代碼如下(GLSL代碼):

float gl_PointSize 

在粒子效果的場景下,需要為粒子設置大小,改變該內置變量的值就是為了設置每一個粒子矩陣的大小。

Fragment Shader的內置變量:
其次是Fragment Shader的內置變量,代碼如下(GLSL代碼):

  vec4 gl_FragColor;

上述代碼用于指定當前紋理坐標所代表的像素點的最終顏色值。

內置函數(shù)

具體的函數(shù)可以去官方文檔中查詢,這里僅介紹幾個常用的函數(shù)。

  • abs(genType x):絕對值函數(shù)。
  • floor(genType x):向下取整函數(shù)。
  • ceil(genType x):向上取整函數(shù)。
  • mod(genType x, genType y):取模函數(shù)。
  • min(genType x, genType y):取得最小值函數(shù)。
  • max(genType x, genType y);取得最大值函數(shù)。
  • clamp(genType x, genType y, genType z):取得中間值函數(shù)。
  • step(genType edge, genType x):如果x < edge,則返回0.0,否則返回1.0。
  • smooth step(genType edge0, genType edge1, genType x):如果x <= edge0,則返回0.0;如果x >= edge1,則返回1.0;如果edge0 < x < edge1,則執(zhí)行0~1之間的平滑差值。
  • mix(genType x, genType y, genType a):返回線性混合的x和y,用公式表示為:x(1-a)+ya,這個函數(shù)在mix兩個紋理圖像的時候非常有用。

其他的角度函數(shù)、指數(shù)函數(shù)、幾何函數(shù)在這里就不在贅(zhui)述了,大家可以去官方文檔進行查詢。
對于一個語言的語法來說,剩下的就是控制流部分了,而GLSL的控制流與C語言非常類似,既可以使用for、while以及do-while實現(xiàn)循環(huán),也可以使用if和if-else進行條件分支的操作。

如何鏈接GLSL

創(chuàng)建顯卡執(zhí)行程序

前面已經學習了GLSL的語法以及內嵌函數(shù),并且也已經完成了一組Shader的實例,那么如何讓顯卡來運行這一組shader呢?或者說如何用Shader來替換掉OpenGL渲染管線中的那兩個階段(頂點處理與片元處理)呢?下面來學習一下如何將Shader傳遞給OpenGL的渲染管線。

1.創(chuàng)建Shader的過程

第一步:glCreateShader

調用glCreateShader方法創(chuàng)建一個對象,作為Shader的容器,該函數(shù)會返回一個容器的句柄,函數(shù)的原型如下:

 GLuint glCreateShader(GLenum shaderType); 

實例:

 GLuint shader = glCreateShader(type); 

函數(shù)原型中的參數(shù)shaderType有兩種類型,當要創(chuàng)建VertexShader時,開發(fā)者應該傳入類型GL_VERTEX_SHADER;當要創(chuàng)建FragmentShader時,開發(fā)者應該傳入GL_FRAGMENT_SHADER類型。

第二步:glShaderSource

為創(chuàng)建的這個Shader添加源代碼,源代碼就是根據GLSL編寫的兩個著色器程序(Shader),其為字符串類型。函數(shù)原型如下:

  void glShaderSource(GLuint shader, int numOfStrings, const **strings, int *lenOfString);

實例:

 glShaderSource(shader, 1, &sources, NULL); 

上述函數(shù)的作用就是把開發(fā)者編寫的著色器程序加載到著色器句柄所關聯(lián)的內存中。

第三步:glCompileShader

編譯該Shader,編譯Shader的函數(shù)原型如下:

  void glCompileShader(GLuint shader);

實例:

  glCompileShader(shader);

第四步:glGetShaderiv

待編譯完成之后,還需要驗證該Shader是否編譯成功。那么,應該如何驗證呢?使用下面的函數(shù)即可進行驗證:

  void glGetShaderiv(GLuint shader, GLenum pname, GLint *params);

實例:

glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
if (status == GL_FALSE) {
    glDeleteShader(shader);
    NSLog(@"Failed to compile shader:\n");
    return 0;
}

glGetShaderiv的第二個參數(shù)一般選取GL_COMPILE_STATUS。
如果沒有編譯成功,那么開發(fā)者肯定需要知道到底是著色器代碼中的哪一行出了問題,所以還需要調用上面的函數(shù)glGetShaderiv,第二個參數(shù)選取為GL_INFO_LOG_LENGTH,此時返回值返回的是錯誤原因字符串的長度,我們可以利用這個長度分配出一個buffer,然后調用獲取Shader的InfoLog函數(shù),函數(shù)原型如下:

  void glGetShaderInfoLog(GLuint object, int maxLen, int *len, char *log);

實例:

#ifdef DEBUG
    GLint logLength;
    glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &logLength);
    if (logLength > 0)
    {
        GLchar *log = (GLchar *)malloc(logLength);
        glGetShaderInfoLog(shader, logLength, &logLength, log);
        NSLog(@"Shader compile log:\n%s", log);
        free(log);
    }
#endif
    
    glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
    if (status == GL_FALSE) {
        glDeleteShader(shader);
        NSLog(@"Failed to compile shader:\n");
        return 0;
    }

之后可以把InfoLog打印出來,以幫助我們調試實際Shader中的錯誤。
通過上面的步驟可以創(chuàng)建出Vertex Shader和Fragment Shader,那么,如何通過這兩個Shader來創(chuàng)建Program(顯卡可執(zhí)行程序)。

2.創(chuàng)建Program的過程

第一步:glCreateProgram

首先創(chuàng)建一個對象,作為程序的容器,此函數(shù)將返回容器的句柄。函數(shù)原型如下:

 Glint glCreateProgram(void); 

第二步:glAttachShader

下面把前文編譯的Shader附加到剛剛創(chuàng)建的程序中,調用的函數(shù)名稱如下:

 void glAttachShader(GLuint program, Glint shader); 

第一個參數(shù)傳入上一步返回的程序容器的句柄,第二個參數(shù)就是編譯的Shader容器的句柄,當然要為每一個Shader都調用一次這個方法才能把兩個Shader都關聯(lián)到Program中去。

第三步:glLinkProgram

鏈接程序,函數(shù)原型如下:

 void glLinkProgram(GLuint program); 

第四步:glGetProgramiv

檢查鏈接是否成功,函數(shù)原型如下:

  void glGetProgramiv (GLuint program, GLenum pname, GLint* params);

第二個參數(shù)傳入GL_LINK_STATUS,第三個參數(shù)返回0或者1,返回0代表鏈接失敗。
第二個參數(shù)傳入GL_LINFO_LOG_LENGTH,代表獲取該程序的InfoLog的長度,獲取到長度之后我們分配出一個char*的內存空間一獲取InfoLog,函數(shù)原型如下:

 void glGetProgramInfoLog(GLuint object, int maxLen, int *len, char *log); 

實例:

 #ifdef DEBUG
    GLint logLength;
    glGetProgramiv(prog, GL_INFO_LOG_LENGTH, &logLength);
    if (logLength > 0)
    {
        GLchar *log = (GLchar *)malloc(logLength);
        glGetProgramInfoLog(prog, logLength, &logLength, log);
        NSLog(@"Program validate log:\n%s", log);
        free(log);
    }
#endif 

第五步:glUseProgram

調用glUseProgram方法使用這個構建出來的程序,函數(shù)原型如下:

void glUseProgram (GLuint program);

如何在iOS上搭建OpenGL ES環(huán)境

第1步. 重寫layerClass方法
首先創(chuàng)建一個View類,繼承自UIView,然后重寫父類UIView的layerClass方法,并返回CAEAGLLayer類型。

 + (Class)layerClass {
    // 第1步. 重寫layerClass方法
    // 只有 [CAEAGLLayer class] 類型的 layer 才支持在其上描繪 OpenGL 內容。
    return [CAEAGLLayer class];
} 

第2步. 在initWithFrame方法中獲得layer并且強制類型轉換為CAEAGLLayer類型的變量,同時為layer設置參數(shù),其中包括色彩模式等屬性

 - (instancetype)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        //第2步. 在initWithFrame方法中獲得layer并且強制類型轉換為CAEAGLLayer類型的變量,同時為layer設置參數(shù),其中包括色彩模式等屬性
        CAEAGLLayer *eaglLayer = (CAEAGLLayer *)[self layer];
        // 設置描繪屬性,在這里設置不維持渲染內容以及顏色格式為 RGBA8
        NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:NO],kEAGLDrawablePropertyRetainedBacking,kEAGLColorFormatRGBA8,kEAGLDrawablePropertyColorFormat, nil];
        // CALayer 默認是透明的,必須將它設為不透明才能讓其可見
        [eaglLayer setOpaque:YES];
        /// 放大倍數(shù)
        CGFloat scale = [UIScreen mainScreen].scale;
        [eaglLayer setContentsScale:scale];
        [eaglLayer setDrawableProperties:dict];
    }
    return self;
} 

第3步. 建立EAGL與Opengl ES的連接
創(chuàng)建OpenGL ES的上下文:

 //第3步. 建立EAGL與OpenGL ES的連接
    EAGLContext *_context;
    // 指定 OpenGL 渲染 API 的版本,在這里我們使用 OpenGL ES 2.0
    _context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
    // 設置為當前上下文
    [EAGLContext setCurrentContext:_context];
    self.context = _context; 

第4步. 建立EAGL與Layer(設備屏幕)的連接

 //第4步. 建立EAGL與Layer(設備屏幕)的連接
    //創(chuàng)建幀緩沖區(qū)
    glGenFramebuffers(1, &_frameBuffer);
    //創(chuàng)建繪制緩沖區(qū)
    glGenRenderbuffers(1, &_renderbuffer);
    //綁定幀緩沖區(qū)到渲染管線
    glBindFramebuffer(GL_FRAMEBUFFER, _frameBuffer);
    //綁定繪制緩沖區(qū)到渲染管線
    glBindRenderbuffer(GL_RENDERBUFFER, _renderbuffer);
    
    //為繪制緩沖區(qū)分配存儲區(qū),此處將CAEGLLayer的繪制存儲區(qū)作為繪制緩沖區(qū)的存儲區(qū)
    [_context renderbufferStorage:GL_RENDERBUFFER fromDrawable:(CAEAGLLayer *)self.layer];
    
    //獲取繪制緩沖區(qū)的像素寬度
    glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &_backingWidth);
    //獲取繪制緩沖區(qū)的像素高度
    glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &_backingHeight);
    
    //將繪制緩沖區(qū)綁定到幀緩沖區(qū)
    // 將 _renderbuffer 裝配到 GL_COLOR_ATTACHMENT0 這個裝配點上
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, _renderbuffer);
    
    //檢查FrameBuffer的status
    GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
    if (status != GL_FRAMEBUFFER_COMPLETE) {
        //failed to make complete frame buffer object
        return;
    }
    //至此我們就將EAGL與Layer(設備屏幕)連接起來了 

第5步. 繪制幀(包含:導入shader和render)

//第5步. 繪制幀
//[self render]; 

第6步. 將繪制的結果顯示到屏幕上

   [_context presentRenderbuffer:GL_RENDERBUFFER];

OpenGL ES中的紋理

如何加載一張圖片作為OpenGL中的紋理
首先要在顯卡中創(chuàng)建一個紋理對象,函數(shù)原型如下:

 void glGenTextures(GLsizei n, GLuint* textures); 

這個方法傳遞進去的第一個參數(shù)是需要創(chuàng)建幾個紋理對象,并且把創(chuàng)建好的紋理對象的句柄放到第二個參數(shù)中去,所以第二個參數(shù)是一個數(shù)組(指針)的形式。如果只創(chuàng)建一個紋理對象,則只需要聲明一個GLuint類型的texId,然后針對該紋理ID取地址,并將其作為第二個參數(shù),就可以創(chuàng)建出這個紋理對象了,代碼如下:

 glGenTextures(1, &texId); 

在OpenGL ES的操作過程中必須告訴OpenGL ES具體操作的是哪一個紋理對象,所以必須調用一個綁定紋理的方法,代碼如下:

 glBindTexture(GL_TEXTURE_2D, texId); 

對該紋理對象操作完畢之后,我們可以調用一次解綁定的代碼:

 glBindTexture(GL_TEXTURW_2D, 0); 

定義哪個uniform采樣器對應哪個紋理單元

   glUniform1i(_filterInputTextureUniform, 0);

設置紋理縮放模式

雙線性過濾和鄰近過濾,一般的視頻渲染與處理都是使用雙線性過濾,鄰近過濾容易產生鋸齒效果。
鏈接:https://learnopengl-cn.readthedocs.io/zh/latest/01%20Getting%20started/06%20Textures/

image.png

    /*
     GL_TEXTURE_MAG_FILTER和GL_TEXTURE_MIN_FILTER這兩個參數(shù)指定紋理在映射到物體表面上時的縮放效果。GL_TEXTURE_MIN_FILTER是縮小情況;GL_TEXTURE_MAG_FILTER是放大情況。
     */
    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );

設置紋理環(huán)繞模式

鏈接:https://learnopengl-cn.readthedocs.io/zh/latest/01%20Getting%20started/06%20Textures/

image.png

      /*
     參數(shù)GL_TEXTURE_WRAP_S與GL_TEXTURE_WRAP_T。這兩個參數(shù)分別設置紋理s方向(水平方向)和t方向(垂直方向)的包裹方式
     */
    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

把像素內容上傳到顯卡里面texId所代表的紋理對象中去:

 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, fw, fh, 0, GL_RGBA, GL_UNSIGNED_BYTE, spriteData); 

繪制操作(render)/如何通過GLSL輸入數(shù)據

頂點坐標
鏈接:http://www.itdecent.cn/p/355137fa2817

頂點坐標.jpg

鏈接:http://www.itdecent.cn/p/18d6b37363c8

image.png

紋理坐標

鏈接:http://www.itdecent.cn/p/18d6b37363c8

image.png

鏈接:https://blog.csdn.net/xipiaoyouzi/article/details/53609650

image.png

// 設置窗口坐標
    glViewport(0, 0, _backingWidth, _backingHeight);
    
    // 設置清除顏色/設置背景顏色
    glClearColor(0, 1.0, 0, 1.0);//rgba
    // 清除顏色緩沖區(qū)
    glClear(GL_COLOR_BUFFER_BIT);
    
    
    GLint linkSuccess;
    glGetProgramiv(self.program, GL_LINK_STATUS, &linkSuccess);
    if (linkSuccess == GL_FALSE) { //連接錯誤
        GLchar messages[256];
        glGetProgramInfoLog(self.program, sizeof(messages), 0, &messages[0]);
        NSString *messageString = [NSString stringWithUTF8String:messages];
        NSLog(@"error%@", messageString);
        return ;
    }
    else {
        NSLog(@"link ok");
        glUseProgram(self.program); //成功便使用,避免由于未使用導致的的bug
    }
    //頂點坐標
        GLfloat pointArr[] = {
            0.5f, -0.5f,
            -0.5f, 0.5f,
            -0.5f, -0.5f,
            0.5f, 0.5f,
            -0.5f, 0.5f,
            0.5f, -0.5f,
        };
    
    //第(3)步. 加載頂點坐標
    //通過OpenGL程序句柄查找獲取頂點著色器中的頂點坐標句柄
    GLuint position = glGetAttribLocation(self.program, "position");
    //綁定渲染圖形所處的頂點數(shù)據
    glVertexAttribPointer(position, 2, GL_FLOAT, GL_FALSE, 0, pointArr);
    //啟用指向渲染圖形所處的頂點數(shù)據的句柄
    glEnableVertexAttribArray(position);
    
    //紋理坐標
    GLfloat textArr[] = {
        1.0f, 0.0f,
        0.0f, 1.0f,
        0.0f, 0.0f,
        1.0f, 1.0f,
        0.0f, 1.0f,
        1.0f, 0.0f,
    };
    //第(4)步. 加載紋理坐標
    //通過OpenGL程序句柄查找獲取頂點著色器中的紋理坐標句柄
    GLuint textCoor = glGetAttribLocation(self.program, "textCoordinate");
    //綁定紋理坐標數(shù)據
    glVertexAttribPointer(textCoor, 2, GL_FLOAT, GL_FALSE, 0, textArr);
    //啟用指向紋理坐標數(shù)據的句柄
    glEnableVertexAttribArray(textCoor);
    
    //第(5)步. 加載紋理
    _filterInputTextureUniform = glGetUniformLocation(self.program, "colorMap");
    [self setupTexture:@"for_test"];
    
    //獲取shader里面的變量,這里記得要在glLinkProgram后面,后面,后面!
    GLuint rotate = glGetUniformLocation(self.program, "rotateMatrix");
    
    float radians = 360 * 3.14159f / 180.0f;
    float s = sin(radians);
    float c = cos(radians);
    
    /*
     細心的開發(fā)者會發(fā)現(xiàn),這里的z軸旋轉矩陣和上面給出來的旋轉矩陣并不一致。
     究其原因就是OpenGLES是列主序矩陣,對于一個一維數(shù)組表示的二維矩陣,會先填滿每一列(a[0][0]、a[1][0]、a[2][0]、a[3][0])。
     把矩陣賦值給glsl對應的變量,然后就可以在glsl里面計算出旋轉后的矩陣。
     */
    
    //z軸旋轉矩陣
    GLfloat zRotation[16] = { //
        c, -s, 0, 0.0, //
        s, c, 0, 0,//
        0, 0, 1.0, 0,//
        0.0, 0, 0, 1.0//
    };
    
    //設置旋轉矩陣/綁定旋轉矩陣
    glUniformMatrix4fv(rotate, 1, GL_FALSE, (GLfloat *)&zRotation[0]);
    
    //使用GL_TRIANGLES方式繪制紋理
    glDrawArrays(GL_TRIANGLES, 0, 6);//三種繪制方式:GL_TRIANGLES、GL_TRIANGLE_STRIP、GL_TRIANGLE_FAN
    
    
    //第6步. 將繪制的結果顯示到屏幕上
    [self.context presentRenderbuffer:GL_RENDERBUFFER];

glDeleteTextures

如果紋理對象不再使用,則需要將其刪除,否則會造成顯存的泄露。

 glDeleteTextures(1, &texId); 

Demo地址:https://github.com/ProBobo/TestOpenGLES

為什么繪制出來的三角形,矩形(圖片的幾何位置)的高比寬長。

鏈接:http://www.itdecent.cn/p/31738e1136ca
答: 雖然本例中,三角形、矩形在純數(shù)學的OpenGL ES坐標系中,長和寬是相等的。但是在本例中,幀緩存是按像素來匹配屏幕尺寸的。在渲染時候,GPU會轉換 純數(shù)學的OpenGL ES坐標系的X、Y、Z坐標為幀緩存中所對應的真實像素位置。幀緩存中的像素位置叫做視口(viewport)坐標。轉換為視口坐標的后果就是:所繪制的集合圖形被拉伸以適應屏幕大小,也就是高比寬大了。

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

相關閱讀更多精彩內容

友情鏈接更多精彩內容