OpenGLES可编程管线流程:
一、固定功能片段着色器
在OpenGLES 1.1中(固定功能桌面OpenGL),可以使用一组有限的方程式,确定如何组合片段着色器的不同输入。可以使用三种输入:插值顶点颜色、纹理颜色和常量颜色。顶点颜色通常保存一个预先计算的颜色或顶点照明计算的结果。纹理颜色来自于使用图元纹理坐标绑定的纹理中读取的值,而常量颜色可以对每个纹理单元设置。组合这些输入相当有限,输入的A、B、C可能来自于顶点颜色、纹理颜色或常量颜色,组合能够实现大量有趣的特效,但是比起可编程管线有很大的距离。可用的方程式如下图所示
二、片段着色器概述
片段着色器为片段提供了通用功能的可编程方法。输入部分包括:
输入 —— 顶点着色器生成的插值数据
统一变量 —— 片段着色器使用的状态,常量值
采样器 —— 用于访问着色器中的纹理图像
代码 —— 片段着色器源代码或二进制代码,描述在片段上执行的操作
片段着色器的输入输出如下:
内建特殊变量:
gl_FragCoord —— 片段着色器中的只读变量。保存片段的窗口相对坐标
gl_FrontFacing —— 片段着色器中的一个只读变量。表示片段是否是正面图片的一部分
gl_PointCoord —— 只读变量,渲染点时使用。
gl_FragDepth —— 只写输出变量,在片段着色器中写入时,覆盖片段的固定功能深度值。慎用,因为可能会导致GPU深度优化禁用,即“Early-Z”功能被禁用。“Early-Z”会使得不能通过深度测试的片段不会被着色。
内建常量
gl_MaxFragmentInputVectors —— 片段着色器输入的最大数量,15
gl_MaxTextureImageUints —— 可用纹理图像单元的最大数量,16
gl_MaxFragmentUniformVectors —— 片段着色器内可以使用的vec4统一变量项目的最大数量,224
gl_MaxDrawBuffers —— 多重渲染目标(MRT)的最大支持数量,4
gl_MinProgramTexelOffset/gl_MaxProgramTexelOffset —— 通过内建ESSL函数 texture * Offset() 便宜参数支持的最小和最大偏移量,分别是 -8 和 7
可以通过glGetIntegerv 来查询
精度限定符
片段着色器中没有默认精度,因此每个片段着色器程序都必须声明一个默认精度。
三、多重纹理
多重纹理用于组合多个纹理贴图。下面来介绍一下多重纹理的用法
顶点着色器代码如下:
#version 300 es
layout(location = 0) in vec4 a_position;
layout(location = 1) in vec2 a_texCoord;
out vec2 v_texCoord;
void main() {
gl_Position = a_position;
v_texCoord = a_texCoord;
}
片段着色器代码如下:
#version 300 es
precision mediump float;
in vec2 v_texCoord;
layout(location = 0) out vec4 outColor;
uniform sampler2D s_baseMap;
uniform sampler2D s_lightMap;
void main() {
vec4 baseColor;
vec4 lightColor;
baseColor = texture(s_baseMap, v_texCoord);
lightColor = texture(s_lightMap, v_texCoord);
outColor = baseColor * (lightColor + 0.25);
}
意思就是将两个texture进行矩阵相乘合并。
加载Texture代码如下:
typedef struct {
GLuint programObject;
GLint baseMapLoc;
GLint lightMapLoc;
GLuint baseMapTexId;
GLuint lightMapTexId;
} UserData;
GLuint loadTexture(void *ioContext, char *fileName) {
int width, height;
char *buffer = loadTGA(ioContext, fileName, &width, &height);
GLuint textureId;
if (buffer == NULL) {
ALOGE("Error loading (%s) image.\n", fileName);
return 0;
}
glGenTextures(1, &textureId);
glBindTexture(GL_TEXTURE_2D, textureId);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, buffer);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
free(buffer);
return textureId;
}
上述代码就是生成一个Texture,并加载TGA图片并利用glTexImage2D方法创建一个Texture,设置相应的过滤参数。简化版的TGA图片加载代码如下,下面的代码只保证加载非压缩的TGA图片,不支持采用RLE压缩的TGA图片:
// #pragma pack指定内存对齐方式
#pragma pack(push,x1)
// 这里表示按照1字节对齐方式对齐
#pragma pack(1)
// TGA图片格式,RLE算法
typedef struct {
unsigned char IdSize, // 图像信息字段长度, 范围 0 ~ 255, 0表示没有图像的信息字段
MapType, // 颜色表类型,0表示没有颜色。
ImageType; // 图像类型码,2表示非压缩格式,10表示压缩RGB格式,后面的图像数据采用RLE压缩
unsigned short PaletteStart, // 颜色表首地址,颜色表头的入口,整形(低位 —— 高位)
PaletteSize; // 颜色表的长度,整形(低位 —— 高位)
unsigned char PaletteEntryDepth; // 颜色表的位数,16表示16位TGA,24表示24位TGA,32表示32位TGA
unsigned short X, // 图像X坐标起始位置,左下角的X坐标
Y, // 图像Y坐标起始位置,左下角的Y坐标
Width, // 图像的宽度
Height; // 图像的高度
unsigned char ColorDepth, // 图像没像素存储占用位数
Descriptor; // 图像描述符字节
} TGA_HEADER;
#pragma pack(pop,x1)
/**
* 加载一个 8bit,24bit或32bit TGA图片
* @param context
* @param fileName
* @param width
* @param height
* @return
*/
char* loadTGA(void* context, const char *fileName, int *width, int *height) {
char* buffer;
AAsset* file;
TGA_HEADER header;
int bytes;
file = fileOpen(context, fileName);
if (file == nullptr) {
ALOGE("fialed to load: {%s}\n", fileName);
return nullptr;
}
bytes = fileRead(file, sizeof(TGA_HEADER), &header);
*width = header.Width;
*height = header.Height;
//
if (header.ColorDepth == 8 || header.ColorDepth == 24 || header.ColorDepth == 32) {
int bytesToRead = sizeof(char) * (*width) * (*height) * header.ColorDepth / 8;
buffer = (char *)malloc(bytesToRead);
if (buffer) {
bytes = fileRead(file, bytesToRead, buffer);
fileClose(file);
return buffer;
}
}
return nullptr;
}
接下来是初始化部分,初始化主要完成加载glsl程序代码,创建program以及绑定统一变量,代码如下:
int init(ESContext *esContext) {
UserData *userData = (UserData *) esContext->userData;
char *vertexStr = readAssetFile("vertex_multitexture.glsl",
esContext->activity->assetManager);
char *fragmentStr = readAssetFile("fragment_multitexture.glsl",
esContext->activity->assetManager);
userData->programObject = loadProgram(vertexStr, fragmentStr);
userData->baseMapLoc = glGetUniformLocation(userData->programObject, "s_baseMap");
userData->lightMapLoc = glGetUniformLocation(userData->programObject, "s_lightMap");
userData->baseMapTexId = loadTexture(esContext->activity->assetManager, "basemap.tga");
userData->lightMapTexId = loadTexture(esContext->activity->assetManager, "lightmap.tga");
if (userData->baseMapTexId == 0 || userData->lightMapTexId == 0) {
return FALSE;
}
glClearColor(1.0f, 1.0f, 1.0f, 0.0f);
return TRUE;
}
完成初始化后,便是绘制部分,绘制部分主要完成使能Texture0 和 Texture1,然后使用glUniform1i方法绑定到对应的纹理等功能,代码如下:
void onDraw(ESContext *esContext) {
UserData *userData = (UserData *) esContext->userData;
// 顶点和texture
GLfloat vertices[] = {
-0.5f, 0.5f, 0.0f, 0.0f, 0.0f,
-0.5f, -0.5f, 0.0f, 0.0f, 1.0f,
0.5f, -0.5f, 0.0f, 1.0f, 1.0f,
0.5f, 0.5f, 0.0f, 1.0f, 0.0f
};
// 索引
GLushort indices[] = {0, 1, 2, 0, 2, 3};
glViewport(0, 0, esContext->width, esContext->height);
glClear(GL_COLOR_BUFFER_BIT);
glUseProgram(userData->programObject);
glVertexAttribPointer(0, 3, GL_FLOAT,
GL_FALSE, 5 * sizeof(GLfloat), vertices);
glVertexAttribPointer(1, 2, GL_FLOAT,
GL_FALSE, 5 * sizeof(GLfloat), &vertices[3]);
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, userData->baseMapTexId);
glUniform1i(userData->baseMapLoc, 0);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, userData->lightMapTexId);
glUniform1i(userData->lightMapLoc, 1);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, indices);
}