OpenGL ES 片元操作
片元着色器后续操作还包括剪裁测试、模板测试、深度测试、混合等,最终才会被送到帧缓冲区。
剪裁测试
剪裁测试可以在渲染时用来限制绘制区域,通过制定一个矩阵进一步限制帧缓冲区可以写入的像素,启用剪裁测试后,绘制不会在整个屏幕(帧缓冲区)进行,而是在指定的矩形区域进行。不在矩形区域中的片元被丢弃,在矩形区域内的片元才能被送往帧缓冲区,实际效果就是在屏幕上开辟了一个小窗口。剪裁矩形确定的范围内的视口区域的物体才会最终进入帧缓冲区。
使用函数glScissor指定剪裁区域
public static native void glScissor(
int x, int y, // 以视口坐标指定的剪裁矩形左下角
int width, // 剪裁矩形像素宽度
int height // 剪裁矩形像素高度
);
指定剪裁矩形后,需要通过glEnable(GL_SCISSOR_TEST)函数启用剪裁测试,启用后所有的渲染操作都只对剪裁矩形生效。
模板测试
模板测试是对每一帧中的每个像素进行测试,通过和一个模板指定的对应像素进行对比决定该像素是否能通过测试,模板测试就是通过模板缓冲区中记录的模板信息完成的,首先需要初始化模板缓冲区,这可以通过渲染几何形状并制定模板缓冲区的更新方式来完成;接下来就可以使用指定的模板缓冲区中的模板信息进行其他的绘制了。
模板测试原理,比如模板缓冲区如图所示,当绘制图形时设置模板缓冲区值等于1时才绘制的话,那么接下来绘制的时候就只会在模板缓冲区设置为1的部分绘制,其他位置的片元都会被丢弃。
使用glEnable(GL_STENCIL_TEST)函数启用模板测试,同时使用glClear和glClearStencil设置模板缓冲区模板值。
使用函数glStencilFunc控制模板测试的运算符
public static native void glStencilFunc(
int func, // 指定模板测试的比较函数
int ref, // 指定模板测试比较值
int mask // 指定在于参考值比较之前与模板缓冲区中的值进行&运算
);
比较函数取值
比较函数 | 含义 |
---|---|
GL_NEVER | 从不通过模板测试 |
GL_ALWAYS | 总是通过模板测试 |
GL_LESS | 只有参考值<(模板缓存区的值&mask)时才通过 |
GL_LEQUAL | 只有参考值<=(模板缓存区的值&mask)时才通过 |
GL_EQUAL | 只有参考值=(模板缓存区的值&mask)时才通过 |
GL_GEQUAL | 只有参考值>=(模板缓存区的值&mask)时才通过 |
GL_GREATER | 只有参考值>(模板缓存区的值&mask)时才通过 |
GL_NOTEQUAL | 只有参考值!=(模板缓存区的值&mask)时才通过 |
设置了模板测试的方式后,还需要设置模板测试通过或是未通过时OpenGL ES将采取什么样的操作,使用glStencilOp函数进行设置。
public static native void glStencilOp(
int fail, // 指定片元未通过模板测试时,对应模板缓冲区模板值(像素)的操作
int zfail, // 指定片元通过模板测试但没有通过深度测试时的操作
int zpass // 既通过了模板测试又通过了深度测试时执行的操作
);
对模板缓冲区中的模板值(像素值)所执行的操作
模板操作 | 含义 |
---|---|
GL_KEEP | 保持当前的模板缓存区值 |
GL_ZERO | 把当前的模板缓存区值设为0 |
GL_REPLACE | 用glStencilFunc函数所指定的参考值替换模板参数值 |
GL_INCR/GL_INCR_WRAP | 增加当前的模板缓存区值,已经是最大值保持不变/变为0 |
GL_DECR/GL_DECR_wrap | 减少当前的模板缓存区值,已经是最大值保持不变/变为0 |
GL_INVERT | 将当前的模板缓存区值进行逐位的翻转 |
利用模板测试可以达到剪裁效果,并且可以实现不规则的剪裁。
- 开启模板测试,使用glClear方法设置模板缓存区中的模板值都为0,然后设置模板测试的操作。
GLES20.glStencilFunc(GLES20.GL_ALWAYS, 1, 1); // 总是通过
GLES20.glStencilOp(GLES20.GL_KEEP, GLES20.GL_KEEP, GLES20.GL_REPLACE); // 通过测试未设置深度测试,认为也通过深度测试,因此是GL_REPLACE,即用glStencilFunction设置的参考值1来代替深度缓冲区中的值
进行上面的设置后再绘制不规则的剪裁区域,绘制玩该剪裁区域后,该区域对应的模板缓冲区的模板值为1,其他区域对应的模板缓冲区中的模板值为0,
- 重新设置模板测试参数
GLES20.glStencilFunc(GLES20.GL_EQUAL, 1, 1); // 只有模板缓冲区中的模板值为1的地方才被绘制
GLES20.glStencilOp(GLES20.GL_KEEP, GLES20.GL_KEEP, GLES20.GL_KEEP);
进行上面的设置后,只有模板缓冲区中的模板值为1的地方才被绘制,也就是只有处于该不规则的图形范围内的部分会被绘制,其他位置的片元都会被丢弃。
绘制一个矩形,用该矩形产生该区域内的模板缓冲区的数据,然后再设置符合需求的模板参数设置,比如模板值为1时通过测试,再绘制三角形。关键代码在onDrawFrame函数中
GLES20.glClear( GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_STENCIL_BUFFER_BIT);
Matrix.setIdentityM(mModuleMatrix, 0);
Matrix.rotateM(mModuleMatrix, 0, xAngle, 1, 0, 0);
Matrix.rotateM(mModuleMatrix, 0, yAngle, 0, 1, 0);
Matrix.multiplyMM(mViewProjectionMatrix, 0, mProjectionMatrix, 0, mViewMatrix, 0);
Matrix.multiplyMM(mMVPMatrix, 0, mViewProjectionMatrix, 0, mModuleMatrix, 0);
// 未开启模板测试
if (!openStencilTest) {
mShape.drawRanctangle(mMVPMatrix);
mShape.drawTrangle(mMVPMatrix);
} else {
// 使用模板测试
GLES20.glEnable(GLES20.GL_STENCIL_TEST);
GLES20.glClear(GLES20.GL_STENCIL_BUFFER_BIT);
GLES20.glStencilFunc(GLES20.GL_ALWAYS, 1, 0xff); // 总是通过
GLES20.glStencilOp(GLES20.GL_KEEP, GLES20.GL_KEEP, GLES20.GL_REPLACE);
mShape.drawRanctangle(mMVPMatrix);
GLES20.glStencilFunc(GLES20.GL_EQUAL, 1, 0xff); // 只有模板缓冲区中的模板值为1的地方才被绘制
GLES20.glStencilOp(GLES20.GL_KEEP, GLES20.GL_KEEP, GLES20.GL_KEEP);
mShape.drawTrangle(mMVPMatrix);
GLES20.glDisable(GLES20.GL_STENCIL_TEST);
}
产生的的效果如图所示
深度测试
深度就是像素点在距离摄象机的距离,对应的也有一个深度缓冲区存储着每个像素点(绘制在屏幕上的)的深度值,深度值越大,表示离摄像机越远。深度缓冲区中保存着渲染表面上每个像素与视点最近物体的距离值。
如果没有深度缓冲区,当要绘制多个物体时,必须先绘制远处的物体再绘制近处的物体,因为如果先绘制近处的物体,那么后绘制的远处的物体会覆盖掉近处的物体。而有了深度缓冲区后,绘制的顺序就不重要了,因为深度缓冲区代表了物体离视点的距离。
深度测试的默认做法:对于每个新的输入片元,将其与视点的距离和深度缓冲区中的值进行比较,如果输入的片元的深度值小于深度缓冲区中的深度值,则表示它离视点更近,则输入片元的深度将代替深度缓冲区中的值,其颜色值将代替颜色缓冲区中的颜色值。否则,表示当前要绘制的片元在已绘制的部分物体后面,则无需绘制该图形,即丢弃。
启用深度测试glEnable(GL_DEPTH_TEST)
- glClear(GL_DEPTH_BUFFER_BIT) 清空 Depth Buffer (赋值为1.0),通常清空 Depth Buffer 和 Color Buffer 同时进行。
- glClearDepthf(float depth) 指定清空 Depth Buffer 是使用的值,缺省为 1.0,通常无需改变这个值。
混合
混合就是把某一像素位置原来的颜色和将要画上去的颜色通过某种方式混在一起。 即某个片元的颜色(源片元)和颜色缓冲区中的像素颜色(目标片元中的像素颜色)进行混合。
其中,
使用函数glBlendFunc和glBlendFuncSeparate设置混合因子
public static native void glBlendFunc(
int sfactor, // 源片元的混合因子
int dfactor // 目标像素的混合因子
);
public static native void glBlendFuncSeparate(
int srcRGB, // 源片元的RGB颜色分量的混合因子
int dstRGB, // 目标像素的RGB颜色分量混合因子
int srcAlpha, // 源片元的alpha值的混合因子
int dstAlpha // 目标像素的alpha值的混合因子
);
混合因子
混合系数枚举值 | RGB混合因子 | Alpha混合因子 |
---|---|---|
GL_ZERO | [0,0,0] | 0 |
GL_ONE | [1,1,1] | 1 |
GL_SRC_COLOR | [Rs,Gs,Bs] | As |
GL_ONE_MINUS_SRC_COLOR | [1-Rs,1-Gs,1-Bs] | 1-As |
GL_DST_COLOR | [Rd,Gd,Bd] | Ad |
GL_ONE_MINUS_DST_COLOR | [1-Rd,1-Gd,1-Bd] | 1-Ad |
GL_SRC_ALPHA | [As,As,As] | As |
GL_SRC_MINUS_SRC_ALPHA | [1-As,1-As,1-As] | 1-As |
GL_DST_ALPHA | [Ad,Ad,Ad] | Ad |
GL_SRC_MINUS_DST_ALPHA | [1-Ad,1-Ad,1-Ad] | 1-Ad |
GL_SRC_ALPHA_STAURATE | [f,f,f] f=min(As,1-Ad) | 1 |
(Rs, Gs, Bs, As)是与输入片段颜色相关的颜色分量,(Rd, Gd , Bd, Ad)是与输入片段颜色相关的颜色分量, (Rc,Gc,Bc,Ac)是通过 glBlendColor 设定的颜色常量。 使用 GL_SRC_ALHPA_SATURATE时,计算出来的最小值只适用于源颜色。
public static native void glBlendColor(
float red, float green, float blue, float alpha // 指定常量混合颜色的分量值
);
混合因子取值含义
- glBlendFunc(GL_ONE, GL_ZERO) 只取源颜色,这也是默认值。
- glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) 典型的半透明遮挡效果,如果源色 alpha 为0,则取目标色,如果源色alpha为1,则取源色,最终绘制物体的颜色与物体的透明度有关。
- glBlendFunc(GL_SRC_COLOR, GL_ONE_MINUS_SRC_COLOR) 实现滤镜效果,透过有色眼镜观察事物的效果。
输入片元和像素颜色乘以各自的混合因子后,由函数glBlendEquation和glBlendEquationSeparate指定的运算来进行组合。默认情况下,混合后的颜色使用GL_FUNC_ADD运算进行加和。
public static native void glBlendEquation(
int mode // 对源片元和目标像素的操作
);
public static native void glBlendEquationSeparate(
int modeRGB, // RGB分量的混合操作
int modeAlpha // alpha分量的混合操作
);
上面的操作可以除了GL_FUNC_ADD还可以使用GL_FUNC_SUBTRACT、GL_FUNC_REVERSE_SUBTRACT,GL_FUNC_SUBTRACT表示从输入的片元值中减去颜色缓冲区中的像素值,GL_FUNC_REVERSE_SUBTRACT表示从当前颜色缓冲区中的像素值减去输入片元的颜色。
在上面模板测试的代码的基础上,修改onDrawFrame中增加混合部分的代码
if (!openStencilTest) {
mShape.drawRanctangle(mMVPMatrix);
GLES20.glEnable(GLES20.GL_BLEND);
GLES20.glBlendFunc(GLES20.GL_SRC_COLOR, GLES20.GL_ONE_MINUS_SRC_COLOR);
mShape.drawTrangle(mMVPMatrix);
GLES20.glDisable(GLES20.GL_BLEND);
} else {
GLES20.glEnable(GLES20.GL_STENCIL_TEST);
GLES20.glClear(GLES20.GL_STENCIL_BUFFER_BIT);
GLES20.glStencilFunc(GLES20.GL_ALWAYS, 1, 0xff);
GLES20.glStencilOp(GLES20.GL_KEEP, GLES20.GL_KEEP, GLES20.GL_REPLACE);
mShape.drawRanctangle(mMVPMatrix);
// 开启混合
GLES20.glEnable(GLES20.GL_BLEND);
// 使用的模式为两个颜色混合
GLES20.glBlendFunc(GLES20.GL_SRC_COLOR, GLES20.GL_ONE_MINUS_SRC_COLOR);
GLES20.glStencilFunc(GLES20.GL_EQUAL, 1, 0xff);
GLES20.glStencilOp(GLES20.GL_KEEP, GLES20.GL_KEEP, GLES20.GL_KEEP);
mShape.drawTrangle(mMVPMatrix);
GLES20.glDisable(GLES20.GL_STENCIL_TEST);
GLES20.glDisable(GLES20.GL_BLEND);
}
产生如下的效果