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
本節學習目標
使用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。
需要代碼的親,請點贊