OpenGLES首先需要创建顶点着色器和片元着色器
代码采用gles语言编写
顶点着色器的代码如下:
//顶点着色器glsl
#define GET_STR(x) #x
static const char *s_vertexCode = GET_STR(
attribute vec4 aPosition; //顶点坐标
attribute vec2 aTexCoord; //纹理顶点坐标(和顶点坐标位置对应)
varying vec2 vTexCoord; //输出的材质坐标(输出给片元着色器)
void main() {
vTexCoord = vec2(aTexCoord.x, 1.0 - aTexCoord.y);
gl_Position = aPosition;
}
);
OpenGL的所有图形都是有顶点、直线、三角形组成,顶点坐标用于确定这些顶点、直线、三角形,然后再分别绘制这些顶点、直线和三角形
纹理坐标用于告诉OpenGL每个顶点坐标从纹理的哪个位置开始取值
图像的形状是一个长方形,需要使用两个三角形,所以顶点坐标需要四个(两个三角形有两个顶点重合),纹理坐标也需要四个顶点(类似顶点坐标)。
片元着色器的代码如下:
//片元着色器,软解码和部分x86硬解码
static const char *s_fragCodeYUV420P = GET_STR(
precision mediump float; //精度
varying vec2 vTexCoord; //顶点着色器传递的坐标
uniform sampler2D yTexture; //输入的材质(不透明灰度,单像素)
uniform sampler2D uTexture;
uniform sampler2D vTexture;
void main() {
vec3 yuv;
vec3 rgb;
yuv.r = texture2D(yTexture, vTexCoord).r;
yuv.g = texture2D(uTexture, vTexCoord).r - 0.5;
yuv.b = texture2D(vTexture, vTexCoord).r - 0.5;
rgb = mat3(1.0, 1.0, 1.0,
0.0, -0.39465, 2.03211,
1.13983, -0.58060, 0.0) * yuv;
//输出像素颜色, 通过三层纹理叠加求出最终的rgb颜色值
gl_FragColor = vec4(rgb, 1.0);
}
);
OpenGL会将直线、三角形光栅化成一个个片段,然后分别对这些片段着色,片元着色器的作用就是对这些片段分别着色
如上述代码,片元着色器用了三层纹理,通过将yuv转化为rgb,最终求出每个片元的颜色值
着色器代码需要先进行编译,其过程如下:
static GLuint InitShader(const char *code, GLint type)
{
//创建shader
GLuint shader = glCreateShader(type);
if (shader == 0)
{
XLOGE("glCreateShader %d failed!", type);
return 0;
}
//加载shader
glShaderSource(shader,
1, //shader数量
&code, //shader代码
0); //代码长度
//编译shader
glCompileShader(shader);
//获取编译情况
GLint status;
glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
if (status == 0)
{
XLOGE("glCompileShader failed!");
return 0;
}
XLOGE("glCompileShader success!");
return shader;
}
然后需要创建渲染程序,并把编译后的着色器附加到渲染程序,代码如下:
bool XShader::Init(XShaderType type)
{
Close();
//顶点和片元shader初始化
//顶点shader初始化
m_mutex.lock();
m_vertexShader = InitShader(s_vertexCode, GL_VERTEX_SHADER);
if (m_vertexShader == 0)
{
m_mutex.unlock();
XLOGE("InitShader GL_VERTEX_SHADER failed!");
return false;
}
XLOGE("InitShader GL_VERTEX_SHADER success! %d", type);
//片元yuv420 shader初始化
switch (type)
{
case XSHADER_YUV420P:
m_fragmentShader = InitShader(s_fragCodeYUV420P, GL_FRAGMENT_SHADER);
break;
case XSHADER_NV12:
m_fragmentShader = InitShader(s_fragCodeNV12, GL_FRAGMENT_SHADER);
break;
case XSHADER_NV21:
m_fragmentShader = InitShader(s_fragCodeNV21, GL_FRAGMENT_SHADER);
break;
default:
m_mutex.unlock();
XLOGE("XSHADER format is error");
return false;
}
if (m_fragmentShader == 0)
{
m_mutex.unlock();
XLOGE("InitShader GL_FRAGMENT_SHADER failed!");
return false;
}
XLOGE("InitShader GL_FRAGMENT_SHADER success!");
/////////////////////////////////////////////////////////////
//创建渲染程序
m_program = glCreateProgram();
if (m_program == 0)
{
m_mutex.unlock();
XLOGE("glCreateProgram failed!");
return false;
}
//渲染程序中加入着色器代码
glAttachShader(m_program, m_vertexShader);
glAttachShader(m_program, m_fragmentShader);
//链接程序
glLinkProgram(m_program);
GLint status = 0;
glGetProgramiv(m_program, GL_LINK_STATUS, &status);
if (status != GL_TRUE)
{
m_mutex.unlock();
XLOGE("glLinkProgram failed!");
return false;
}
XLOGE("glLinkProgram success!");
glUseProgram(m_program);
//加入三维顶点数据 两个三角形组成正方形
static float vertexArray[] = {
1.0f, -1.0f, 0.0f,
-1.0f, -1.0f, 0.0f,
1.0f, 1.0f, 0.0f,
-1.0f, 1.0f, 0.0f,
};
//下面的代码主要用于向gles代码中的变量传值,首先要获取变量的位置,必须在编译程序后获取
GLuint aPositionPos = (GLuint)glGetAttribLocation(m_program, "aPosition");
glEnableVertexAttribArray(aPositionPos);
//第一个参数表示该变量的位置,第二个参数表示该变量由几个分量组成,
//倒数第二参数表示步长间隔是多少个字节,最后一个参数表示从哪个缓冲区或者数组中读取
glVertexAttribPointer(aPositionPos, 3, GL_FLOAT, GL_FALSE, 12, vertexArray);
//加入材质坐标数据
static float textureArray[] = {
1.0f, 0.0f, //右下
0.0f, 0.0f,
1.0f, 1.0f,
0.0, 1.0
};
GLuint aTexCoordPos = (GLuint)glGetAttribLocation(m_program, "aTexCoord");
glEnableVertexAttribArray(aTexCoordPos );
glVertexAttribPointer(aTexCoordPos, 2, GL_FLOAT, GL_FALSE, 8, textureArray);
//材质纹理初始化
//设置纹理层
glUniform1i(glGetUniformLocation(m_program, "yTexture"), 0); //对于纹理第1层
switch (type) {
case XSHADER_YUV420P:
glUniform1i(glGetUniformLocation(m_program, "uTexture"), 1); //对于纹理第2层
glUniform1i(glGetUniformLocation(m_program, "vTexture"), 2); //对于纹理第3层
break;
case XSHADER_NV21:
case XSHADER_NV12:
glUniform1i(glGetUniformLocation(m_program, "uvTexture"), 1); //对于纹理第2层
break;
}
m_mutex.unlock();
XLOGI("初始化Shader成功!");
return true;
}
每次绘制需要传递纹理数据
virtual void Draw(unsigned char *data[], int width, int height)
{
m_mtx.lock();
shader.GetTexture(0, width, height, data[0]); // Y
if (type == XTEXTURE_YUV420P)
{
shader.GetTexture(1, width / 2, height / 2, data[1]); // U
shader.GetTexture(2, width / 2, height / 2, data[2]); // V
}
else
{
shader.GetTexture(1, width / 2, height / 2, data[1], true); // UV
}
shader.Draw();
XEGL::Get()->Draw();
m_mtx.unlock();
}
传递纹理数据的过程如下:
void XShader::GetTexture(unsigned int index, int width, int height, unsigned char *buf, bool isa)
{
unsigned int format = GL_LUMINANCE;
if (isa)
format = GL_LUMINANCE_ALPHA;
m_mutex.lock();
//如下代码,每层纹理只会调用一次
if (m_textureArray[index] == 0)
{
//材质初始化
glGenTextures(1, &m_textureArray[index]);
//设置纹理属性
glBindTexture(GL_TEXTURE_2D, m_textureArray[index]);
//缩小的过滤器
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
//设置纹理的格式和大小
glTexImage2D(GL_TEXTURE_2D,
0, //细节基本 0默认
format, //gpu内部格式 亮度,灰度图
width, height, //拉升到全屏
0, //边框
format, //数据的像素格式 亮度,灰度图 要与上面一致
GL_UNSIGNED_BYTE, //像素的数据类型
NULL //纹理的数据
);
}
//激活第1层纹理,绑定到创建的opengl纹理
glActiveTexture(GL_TEXTURE0 + index);
glBindTexture(GL_TEXTURE_2D, m_textureArray[index]);
//替换纹理内容
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, format, GL_UNSIGNED_BYTE, buf);
m_mutex.unlock();
}
绘制的过程如下:
void XShader::Draw()
{
m_mutex.lock();
if (!m_program)
{
m_mutex.unlock();
return;
}
//三维绘制
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
m_mutex.unlock();
}
显示的过程如下:
virtual void Draw()
{
m_mutex.lock();
if (m_eglDisplay == EGL_NO_DISPLAY || m_eglSurface == EGL_NO_SURFACE)
{
m_mutex.unlock();
return;
}
eglSwapBuffers(m_eglDisplay, m_eglSurface);
m_mutex.unlock();
}
参考:OpenGL ES应用开发实践指南 Android卷
https://learnopengl-cn.github.io/
网友评论