OPenGL ES _ 著色器_實戰2

OpenGL ES _ 入門_01
OpenGL ES _ 入門_02
OpenGL ES _ 入門_03
OpenGL ES _ 入門_04
OpenGL ES _ 入門_05
OpenGL ES _ 入門練習_01
OpenGL ES _ 入門練習_02
OpenGL ES _ 入門練習_03
OpenGL ES _ 入門練習_04
OpenGL ES _ 入門練習_05
OpenGL ES _ 入門練習_06
OpenGL ES _ 著色器 _ 介紹
OpenGL ES _ 著色器 _ 程序
OpenGL ES _ 著色器 _ 語法
OpenGL ES_著色器_紋理圖像
OpenGL ES_著色器_預處理
OpenGL ES_著色器_頂點著色器詳解
OpenGL ES_著色器_片斷著色器詳解
OpenGL ES_著色器_實戰01
OpenGL ES_著色器_實戰02
OpenGL ES_著色器_實戰03

學習是一件開心的額事情
手機截圖
動畫演示.gif

本節學習目標

使用OpenGLES + 著色器語言打造多屏顯示視頻框架。


實現步驟

1.使用AVPlayer 獲取視頻每一幀的YUV 像素數據
2.通過CoreVideo 框架中的幾個方法,將Y分量和UV 分量進行分離
3.創建著色器,對Y分量和UV 分量進行采樣.
4.在著色器中,將YUV 轉換為RGB
5.計算視口的位置,分別進行渲染.

部分核心講解

提醒各位,代碼太多不能夠全部講解,我只講關于OpenGLES 和著色器的部分,視頻像素獲取的代碼,請自行搞定! 開發吧!

1.視頻獲取的代碼封裝了一下,文件名為"XJVRPlayerViewController.h" 和 "XJVRPlayerViewController.h" 請自定查閱.
2.我們核心講解得是OSOpenGLESViewController控制器中的內容.
如果代碼看不懂請從這里起步.

第一步.初始化

-(void)setup{
// 1.創建EALContext 對象管理GPU 
self.eagContext = [[EAGLContext alloc]initWithAPI:kEAGLRenderingAPIOpenGLES2];
[EAGLContext setCurrentContext:self.eagContext];
GLKView *view = (GLKView*)self.view;
view.drawableDepthFormat = GLKViewDrawableDepthFormat24;
view.context = self.eagContext;
// 2.設置渲染的幀率,這里我們調到最大
self.preferredFramesPerSecond = 60;
// 3.創建著色器管理對象,這個是我自己封裝的
self.shaderManager = [[OSShaderManager alloc]init];
// 4.編譯連個shader 文件
GLuint vertexShader,fragmentShader;
NSURL *vertexShaderPath = [[NSBundle mainBundle]URLForResource:@"Shader" withExtension:@"vsh"];
NSURL *fragmentShaderPath = [[NSBundle mainBundle]URLForResource:@"Shader" withExtension:@"fsh"];
if (![self.shaderManager compileShader:&vertexShader type:GL_VERTEX_SHADER URL:vertexShaderPath]||![self.shaderManager compileShader:&fragmentShader type:GL_FRAGMENT_SHADER URL:fragmentShaderPath]){
    return ;
}
// 5.注意獲取綁定屬性要在連接程序之前,為啥呢!經驗,一會解釋!
[self.shaderManager bindAttribLocation:GLKVertexAttribPosition andAttribName:"position"];
[self.shaderManager bindAttribLocation:GLKVertexAttribTexCoord0 andAttribName:"texCoord0"];


// 6.將編譯好的兩個對象和著色器程序進行連接
if(![self.shaderManager linkProgram]){
    [self.shaderManager deleteShader:&vertexShader];
    [self.shaderManager deleteShader:&fragmentShader];
}
// 7.獲取著色器全局變量
_textureBufferY = [self.shaderManager getUniformLocation:"sam2DY"];
 _textureBufferUV = [self.shaderManager getUniformLocation:"sam2DUV"];
_rgbaFactor = [self.shaderManager getUniformLocation:"rgbaFactor"];

// 8.刪除著色器對象
[self.shaderManager detachAndDeleteShader:&vertexShader];
[self.shaderManager detachAndDeleteShader:&fragmentShader];

// 9.啟用著色器程序
[self.shaderManager useProgram];

//10.高危警告 一定要放在使用程序之后,不然程序不知道你下面的操作是干神馬滴!!!

glUniform1i(_textureBufferY, 0); // 0 代表GL_TEXTURE0
glUniform1i(_textureBufferUV, 1); // 0 代表GL_TEXTURE0

// 11.創建視頻紋理緩沖區
if (!_videoTextureCache) {
    CVReturn err = CVOpenGLESTextureCacheCreate(kCFAllocatorDefault, NULL, self.eagContext, NULL, &_videoTextureCache);
    if (err != noErr) {
        NSLog(@"Error at CVOpenGLESTextureCacheCreate %d", err);
        return;
    }
}

// 計算視頻的寬度和高度
self.videoWidth = self.view.bounds.size.width/4.0;
self.videoHeight = self.videoWidth;;
}

2.加載頂點坐標和紋理坐標
頂點坐標數據

static GLfloat vertex[8] = {
1,1, //1
-1,1,//0
-1,-1, //2
1,-1, //3 
};

紋理坐標

static GLfloat textureCoords[8] = {
 1,0,
 0,0,
 0,1,
 1,1
 };

 下面的代碼是將頂點坐標和紋理坐標加載到GPU中去,這段代碼不解釋,請看實戰1.

-(void)loadVertex{
glGenVertexArraysOES(1, &_vertexArray);
glBindVertexArrayOES( _vertexArray);
// 加載頂點坐標
glGenBuffers(1, &_vertexBuffer);
glBindBuffer(GL_ARRAY_BUFFER, _vertexBuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertex), &vertex, GL_STATIC_DRAW);
glEnableVertexAttribArray(GLKVertexAttribPosition);
glVertexAttribPointer(GLKVertexAttribPosition, 2, GL_FLOAT, GL_FALSE, 8, NULL);

//加載紋理坐標
glGenBuffers(1, &_textureCoordBuffer);
glBindBuffer(GL_ARRAY_BUFFER, _textureCoordBuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(textureCoords), textureCoords, GL_STATIC_DRAW);
glEnableVertexAttribArray(GLKVertexAttribTexCoord0);
glVertexAttribPointer(GLKVertexAttribTexCoord0, 2, GL_FLOAT, GL_FALSE, 8, NULL);
glEnableVertexAttribArray(_vertexArray);  
}

3.將視頻幀數據進行Y分量和UV 分量分解,加載到GPU 中去.

-(void)loadTexture{

if (!_videoTextureCache) {
    NSLog(@"No video texture cache");
    return;
}

// 下面這個是判斷視頻數據格式的,我在轉換的時候,直接寫死的.可以不要!
CFTypeRef colorAttachments = CVBufferGetAttachment(_pixelbuffer, kCVImageBufferYCbCrMatrixKey, NULL);
if (colorAttachments == kCVImageBufferYCbCrMatrix_ITU_R_601_4) {
   
}
else {
  
}

CGFloat width = CVPixelBufferGetWidth(self.pixelbuffer);
CGFloat height = CVPixelBufferGetHeight(self.pixelbuffer);

[self cleanUpTextures];
 CVReturn err;
 // 啟用紋理緩沖區0
glActiveTexture(GL_TEXTURE0);
 // 獲取Y 分量數據
err = CVOpenGLESTextureCacheCreateTextureFromImage(kCFAllocatorDefault,
                                                   _videoTextureCache,
                                                   _pixelbuffer,
                                                   NULL,
                                                   GL_TEXTURE_2D,
                                                   GL_RED_EXT,
                                                   width,
                                                   height,
                                                   GL_RED_EXT,
                                                   GL_UNSIGNED_BYTE,
                                                   0,
                                                   &_lumaTexture);
if (err) {
    NSLog(@"Error at CVOpenGLESTextureCacheCreateTextureFromImage %d", err);
    
}
// 綁定到紋理0 緩沖區
glBindTexture(CVOpenGLESTextureGetTarget(_lumaTexture), CVOpenGLESTextureGetName(_lumaTexture));
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

// UV-plane.
glActiveTexture(GL_TEXTURE1);
err = CVOpenGLESTextureCacheCreateTextureFromImage(kCFAllocatorDefault,
                                                   _videoTextureCache,
                                                   _pixelbuffer,
                                                   NULL,
                                                   GL_TEXTURE_2D,
                                                   GL_RG_EXT,
                                                   width /2,
                                                   height /2,
                                                   GL_RG_EXT,
                                                   GL_UNSIGNED_BYTE,
                                                   1,
                                                   &_chromaTexture);
if (err) {
    NSLog(@"Error at CVOpenGLESTextureCacheCreateTextureFromImage %d", err);
}


glBindTexture(CVOpenGLESTextureGetTarget(_chromaTexture), CVOpenGLESTextureGetName(_chromaTexture));
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
}

5.繪制

-(void)glkView:(GLKView *)view drawInRect:(CGRect)rect{
glClearColor(1, 1, 1, 1);
glClear(GL_COLOR_BUFFER_BIT);
[self.shaderManager useProgram];

for (int i = 0 ; i< 4 ;i++){
   // 設置視口的位置大小
    glViewport(i%4 * self.videoWidth*2,i/4 *self.videoWidth*2 , self.videoWidth*2, self.videoWidth*2);
    switch (i) {
        case 0:
            glUniform4f(_rgbaFactor, self.redSlider.value, 1, 1, 1);
            break;
        case 1:
            glUniform4f(_rgbaFactor, 1.0, self.greenSlider.value, 1.0, 1.0);
            break;
        case 2:
            glUniform4f(_rgbaFactor, 1.0, 1.0, self.blueSlider.value, 1.0);
            break;
        case 3:
            glUniform4f(_rgbaFactor, 1.0, 1.0, 1.0, self.alphaSlider.value);                
            break;                
        default:
            break;
    }
    glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
} 
}
看看著色器里面的源碼

Shader.vsh

attribute vec4 position; // 頂點位置屬性
attribute vec2 texCoord0;// 紋理坐標屬性
varying  vec2 texCoordVarying;// 著色器輸入變量
void main (){
texCoordVarying = texCoord0;
gl_Position = position;
}

Shader.fsh

precision mediump float;//設置float 精度 這個一定要設置
varying  vec2 texCoordVarying;
uniform sampler2D sam2DY; // Y分量的采樣器
uniform sampler2D sam2DUV;// UV分量的采樣器
uniform  vec4 rgbaFactor; // 控制reba數據的因子矢量
void main(){
mediump vec3 yuv;
lowp vec3 rgb;
mediump mat3 convert = mat3(1.164,  1.164, 1.164,
                            0.0, -0.213, 2.112,
                            1.793, -0.533,   0.0);
yuv.x = texture2D(sam2DY,texCoordVarying).r - (16.0/255.0);
yuv.yz = texture2D(sam2DUV,texCoordVarying).rg - vec2(0.5, 0.5);
rgb = convert*yuv;
rgb.r = rgb.r * rgbaFactor.x;
rgb.g = rgb.g * rgbaFactor.y;
rgb.b = rgb.b * rgbaFactor.z;
rgb = rgb*rgbaFactor.w;
gl_FragColor = vec4(rgb,1);
}

經驗分享

  • 綁定著色器屬性,一定要放在連接程序之前 .因為程序連接后,會將著色器對象刪除掉,這時候,你就找不到著色器對象的屬性了,還怎么綁定呢?

    [self.shaderManager bindAttribLocation:GLKVertexAttribPosition andAttribName:"position"];
    [self.shaderManager bindAttribLocation:GLKVertexAttribTexCoord0 andAttribName:"texCoord0"];
    
  • 給著色器變量設置值得時候,一定要調用一次 glUseProgram(); 這個函數, 是為了告訴GPU 我設置屬性是針對這個著色器程序的。因為著色器程序有可能被創建多個。他們可能還有相同的變量.下面是我封裝的方法

    [self.shaderManager useProgram];
    
  • 著色器視口的設置,我們一般設置視圖大小的時候,都是按照物理尺寸來的,但是視口設置是按照像素來計算的,所以設置視口的時候,一般要根據你手機的分辨率進行設置,我手機是2倍屏,所以x2.

     glViewport(i%4 * self.videoWidth*2,i/4 *self.videoWidth*2 , self.videoWidth*2, self.videoWidth*2);
    

總結

先說到這里,你以為這就結束了,這才剛剛開始,后面還有幾十個案例,持續講解! 如有疑問請加群號交流:578734141。
需要代碼的親,請點贊

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,461評論 6 532
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,538評論 3 417
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,423評論 0 375
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,991評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,761評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,207評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,268評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,419評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,959評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,782評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,983評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,528評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,222評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,653評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,901評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,678評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,978評論 2 374

推薦閱讀更多精彩內容