http://blog.oo87.com/opengl/6209.html
OpenGL ES 是基于 OpenGL 三维图形 API 的子集,主要针对于手机以及 PDA 等嵌入式设备设计的。
随着 Android 系统版本以及硬件水平的提升,OpenGL ES 版本也由原先仅支持固定渲染管线的 OpenGL ES 1.X 升级为
支持自定义渲染管线的 OpenGL ES 2.0。这使得使用 OpenGL ES 2.0 渲染的 3D 场景更加真实从而能够创造全新的用户体验。
现今较为知名的 3D 图形 API 有 OpenGL、DirectX 以及 OpenGL ES,它们各自的应用领域如下:
DirectX:主要应用与 Windows 下游戏开发。
OpenGL:应用领域比较广泛,适用于 UNIX、Mac OS、Linux 以及 Microsof 等几乎所有操作系统,可以开发游戏、工业建模以及嵌入式设备。
注意:各位巴友请注意,基于 OpenGL ES 2.0 的 3D 应用不能在模拟器上运行,必须使用配置了 GPU(GPU 要求支持 OpenGL ES 2.0)的真机(Android 版本要求最低为 2.2)才可以。没有真机的巴友如果想学习本教程的话可能需要购置一款符合要求的真机。关于 GPU 硬件的信息小弟在以后的教程中会有讲解。
先为大家提供一个 Hello World 的程序,该程序涉及到了着色器的概念,大家可能一头雾水,不过不要紧,在以后的教程里我会为大家仔细讲解。
首先提供一个着色器工具类,用来创建着色器和检查 GL 错误。代码如下。
package com.bn.Sample3_1;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import android.content.res.Resources;
import android.opengl.GLES20;
import android.util.Log;
//加载顶点Shader与片元Shader的工具类
public class ShaderUtil {
// 加载制定shader的方法
public static int loadShader(int shaderType, String source) {
// 创建一个新shader
int shader = GLES20.glCreateShader(shaderType);
// 若创建成功则加载shader
if (shader != 0) {
// 加载shader的源代码
GLES20.glShaderSource(shader, source);
// 编译shader
GLES20.glCompileShader(shader);
// 存放编译成功shader数量的数组
int[] compiled = new int[1];
// 获取Shader的编译情况
GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0);
if (compiled[0] == 0) {// 若编译失败则显示错误日志并删除此shader
Log.e("ES20_ERROR", "Could not compile shader " + shaderType + ":");
Log.e("ES20_ERROR", GLES20.glGetShaderInfoLog(shader));
GLES20.glDeleteShader(shader);
shader = 0;
}
}
return shader;
}
// 创建shader程序的方法
public static int createProgram(String vertexSource, String fragmentSource) {
// 加载顶点着色器
int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource);
if (vertexShader == 0) {
return 0;
}
// 加载片元着色器
int pixelShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource);
if (pixelShader == 0) {
return 0;
}
// 创建程序
int program = GLES20.glCreateProgram();
// 若程序创建成功则向程序中加入顶点着色器与片元着色器
if (program != 0) {
// 向程序中加入顶点着色器
GLES20.glAttachShader(program, vertexShader);
checkGlError("glAttachShader");
// 向程序中加入片元着色器
GLES20.glAttachShader(program, pixelShader);
checkGlError("glAttachShader");
// 链接程序
GLES20.glLinkProgram(program);
// 存放链接成功program数量的数组
int[] linkStatus = new int[1];
// 获取program的链接情况
GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0);
// 若链接失败则报错并删除程序
if (linkStatus[0] != GLES20.GL_TRUE) {
Log.e("ES20_ERROR", "Could not link program: ");
Log.e("ES20_ERROR", GLES20.glGetProgramInfoLog(program));
GLES20.glDeleteProgram(program);
program = 0;
}
}
return program;
}
// 检查每一步操作是否有错误的方法
public static void checkGlError(String op) {
int error;
while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) {
Log.e("ES20_ERROR", op + ": glError " + error);
throw new RuntimeException(op + ": glError " + error);
}
}
// 从sh脚本中加载shader内容的方法
public static String loadFromAssetsFile(String fname, Resources r) {
String result = null;
try {
InputStream in = r.getAssets().open(fname);
int ch = 0;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
while ((ch = in.read()) != -1) {
baos.write(ch);
}
byte[] buff = baos.toByteArray();
baos.close();
in.close();
result = new String(buff, "UTF-8");
result = result.replaceAll("\\r\\n", "\n");
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
}
代码的 loadShader 方法通过 glCreateShader 方法创建了一个着色器;如果着色器创建成功则加载着色器源代码,并编译着色器。编译完成后检查编译情况。若编译成功则返回着色器的 ID,反之删除着色器并且打印错误信息。
代码的 createProgram 方法通过调用 loadShader 方法分别加载顶点着色器与片元着色器的源代码进 GPU,并分别进行编译。
下面来看一个我们熟悉的类。TestActivity,该类继承自 Activity,在程序开始时执行。主要工作是创建 MyTDView 类的对象,然后调用 setConentView 方法跳转到相关界面。代码如下:
package com.manyou.opengl;
import android.app.Activity;
import android.content.pm.ActivityInfo;
import android.os.Bundle;
public class TestActivity extends Activity {
MyTDView mview;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 设置为竖屏模式
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
mview = new MyTDView(this);
mview.requestFocus();
mview.setFocusableInTouchMode(true);
setContentView(mview);
}
@Override
public void onResume() {
super.onResume();
mview.onResume();
}
@Override
public void onPause() {
super.onPause();
mview.onPause();
}
}
下面是我们最主要的类,Triangle 类。该类主要功能是初始化顶点数据、初始化着色器、设置相应的平移矩阵以及旋转矩阵。
关于物体的 3D 变换矩阵、摄像机参数矩阵、投影矩阵计算产生最终总变换矩阵的方法以及各种矩阵的问题,巴友们不用担心,小弟会在之后的教程中为大家详细的说明。
代码如下:
package com.manyou.opengl;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import android.opengl.GLES20;
import android.opengl.Matrix;
//三角形
public class Triangle {
public static float[] mProjMatrix = new float[16];// 4x4矩阵 投影用
public static float[] mVMatrix = new float[16];// 摄像机位置朝向9参数矩阵
public static float[] mMVPMatrix;// 最后起作用的总变换矩阵
int mProgram;// 自定义渲染管线程序id
int muMVPMatrixHandle;// 总变换矩阵引用id
int maPositionHandle; // 顶点位置属性引用id
int maColorHandle; // 顶点颜色属性引用id
String mVertexShader;// 顶点着色器
String mFragmentShader;// 片元着色器
static float[] mMMatrix = new float[16];// 具体物体的移动旋转矩阵,旋转、平移
FloatBuffer mVertexBuffer;// 顶点坐标数据缓冲
FloatBuffer mColorBuffer;// 顶点着色数据缓冲
int vCount = 0;
float xAngle = 0;// 绕x轴旋转的角度
public Triangle(MyTDView mv) {
// 初始化顶点坐标与着色数据
initVertexData();
// 初始化shader
initShader(mv);
}
public void initVertexData() {
// 顶点坐标数据的初始化
vCount = 3;
final float UNIT_SIZE = 0.2f;
float vertices[] = new float[] { -4 * UNIT_SIZE, 0, 0, 0, -4 * UNIT_SIZE, 0, 4 * UNIT_SIZE, 0, 0 };
ByteBuffer vbb = ByteBuffer.allocateDirect(vertices.length * 4);
vbb.order(ByteOrder.nativeOrder());
mVertexBuffer = vbb.asFloatBuffer();
mVertexBuffer.put(vertices);
mVertexBuffer.position(0);
float colors[] = new float[] { 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0 };
ByteBuffer cbb = ByteBuffer.allocateDirect(colors.length * 4);
cbb.order(ByteOrder.nativeOrder());
mColorBuffer = cbb.asFloatBuffer();
mColorBuffer.put(colors);
mColorBuffer.position(0);
}
// 初始化shader
public void initShader(MyTDView mv) {
// 加载顶点着色器的脚本内容
mVertexShader = ShaderUtil.loadFromAssetsFile("vertex.sh", mv.getResources());
// 加载片元着色器的脚本内容
mFragmentShader = ShaderUtil.loadFromAssetsFile("frag.sh", mv.getResources());
// 基于顶点着色器与片元着色器创建程序
mProgram = ShaderUtil.createProgram(mVertexShader, mFragmentShader);
// 获取程序中顶点位置属性引用id
maPositiOnHandle= GLES20.glGetAttribLocation(mProgram, "aPosition");
// 获取程序中顶点颜色属性引用id
maColorHandle = GLES20.glGetAttribLocation(mProgram, "aColor");
// 获取程序中总变换矩阵引用id
muMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");
}
public void drawSelf() {
// 制定使用某套shader程序
GLES20.glUseProgram(mProgram);
// 初始化变换矩阵
Matrix.setRotateM(mMMatrix, 0, 0, 0, 1, 0);
// 设置沿Z轴正向位移1
Matrix.translateM(mMMatrix, 0, 0, 0, 1);
// 设置绕x轴旋转
Matrix.rotateM(mMMatrix, 0, xAngle, 1, 0, 0);
//
GLES20.glUniformMatrix4fv(muMVPMatrixHandle, 1, false, Triangle.getFianlMatrix(mMMatrix), 0);
// 为画笔指定顶点位置数据
GLES20.glVertexAttribPointer(maPositionHandle, 3, GLES20.GL_FLOAT, false, 3 * 4, mVertexBuffer);
GLES20.glVertexAttribPointer(maColorHandle, 4, GLES20.GL_FLOAT, false, 4 * 4, mColorBuffer);
// 允许顶点位置数据数组
GLES20.glEnableVertexAttribArray(maPositionHandle);
GLES20.glEnableVertexAttribArray(maColorHandle);
// 绘制三角形
GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vCount);
}
public static float[] getFianlMatrix(float[] spec) {
mMVPMatrix = new float[16];
Matrix.multiplyMM(mMVPMatrix, 0, mVMatrix, 0, spec, 0);
Matrix.multiplyMM(mMVPMatrix, 0, mProjMatrix, 0, mMVPMatrix, 0);
return mMVPMatrix;
}
}
最后是我们的 View 类,也就是 3D 场景类。该类继承自 GLSurfaceView,并且在该类中通过内部类的形式创建了渲染器,代码如下:
package com.manyou.opengl;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
import android.content.Context;
import android.opengl.GLES20;
import android.opengl.GLSurfaceView;
import android.opengl.Matrix;
public class MyTDView extends GLSurfaceView {
final float ANGLE_SPAN = 0.375f;
RotateThread rthread;
SceneRenderer mRenderer;
public MyTDView(Context context) {
super(context);
this.setEGLContextClientVersion(2);
mRenderer = new SceneRenderer();
this.setRenderer(mRenderer);
this.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
}
private class SceneRenderer implements GLSurfaceView.Renderer {
Triangle tle;
public void onDrawFrame(GL10 gl) {
// 清除深度缓冲与颜色缓冲
GLES20.glClear(GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);
// 绘制三角形对
tle.drawSelf();
}
public void onSurfaceChanged(GL10 gl, int width, int height) {
// 设置视窗大小及位置
GLES20.glViewport(0, 0, width, height);
// 计算GLSurfaceView的宽高比
float ratio = (float) width / height;
// 调用此方法计算产生透视投影矩阵
Matrix.frustumM(Triangle.mProjMatrix, 0, -ratio, ratio, -1, 1, 1, 10);
// 调用此方法产生摄像机9参数位置矩阵
Matrix.setLookAtM(Triangle.mVMatrix, 0, 0, 0, 3, 0f, 0f, 0f, 0f, 1.0f, 0.0f);
}
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
// 设置屏幕背景色RGBA
GLES20.glClearColor(0, 0, 0, 1.0f);
// 创建三角形对对象
tle = new Triangle(MyTDView.this);
// 打开深度检测
GLES20.glEnable(GLES20.GL_DEPTH_TEST);
rthread = new RotateThread();
rthread.start();
}
}
public class RotateThread extends Thread {
public boolean flag = true;
@Override
public void run() {
while (flag) {
mRenderer.tle.xAngle = mRenderer.tle.xAngle + ANGLE_SPAN;
try {
Thread.sleep(20);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
好了,代码就这么多。完全手打好累啊。不知道看了今天的教程大家是不是对 OpenGL ES 2.0 产生了兴趣呢?
希望在以后的教程里能带给大家更多收获的喜悦。
废话少说,把工程源码放出. 点击下载
欢迎来到我的 OpenGL ES 2.0 的系列教程。本教程是第二集。
写在前面:
这一节主要是概念问题,有些巴友可能感觉很无聊、很没用。但我还是希望你们可以专心的看完,因为这会对你们了解以后的教程起到事半功倍的效果。
上一节给大家演示了一个 Hello World 程序,这一节应一些巴友的要求先给大家介绍一下着色器的概念。
介绍着色器概念之前先说一下什么是片元,如上节教程我们绘制的旋转的三角形中,可以看做是无数个点组成,那么其中的每个点就是一个片元。
OpenGL ES 2.0 有两种重要的着色器:顶点着色器和片元着色器。
顶点着色器是一个可编程的处理单元,功能是执行顶点的变换、光照、材质的应用与计算等顶点的相关操作,每顶点执行一次。其工作过程为首先将原始的顶点几何信息以及其他属性传送到顶点着色器中,经过自己开发的着色器处理之后产生纹理坐标、颜色、点位置等后继流程需要的各项顶点属性信息。
片元着色器适用于处理片元值及其相关数据的可编程单元,其可以执行纹理的采样、颜色的汇总、计算雾颜色等操作,每个片元执行一次。
介绍完着色器再给大家简单说一说着色器和渲染管线的概念。
OpenGL ES 1.X 的渲染管线
学习 Open GL ES 2.0 的渲染管线之前,应该先了解一下 Open GL ES 1.X 的渲染管线,这对于进一步了解 Open GL ES 2.0 的渲染管线也是很有好处的。
渲染管线有时也称之为渲染流水线,一般是由显示芯片 (GPU) 内部处理图形信号的并行处理单元组成。这些并行处理单元两两之间是相互独立的,在不同型号的硬件上独立处理硬件单元的数量也有很大的差异,一般越高端的硬件,其中独立处理单元的数量也就越多。
从另一个角度看,Open GL ES 中的渲染管线实质上指的是一些列绘制的过程。这些过程输入的待渲染的 3D 物体的相关描述信息数据,经过渲染的管线,输出的是一帧想要的图像。具体过程如下:
基本处理
该阶段设定 3D 空间中物体的顶点坐标、顶点对应的颜色、顶点的纹理坐标等属性,并且指定绘制方式,如:点绘制、线段绘制或者三角形绘制。
顶点缓冲对象
这部分功能在应用中是可选的,对于某些在整个场景中顶点的基本上数据不变的情况。可以在初始化阶段将顶点数据经过基本处理后送入顶点缓冲对象,再绘制每一帧想要的图像时就省去了顶点数据 IO 的麻烦。
变换和光照
该阶段的主要工作是进行顶点变换以及根据程序中设置的光照属性对顶点进行光照计算。
图元装配
这个阶段主要有两个任务,一个是图元组装,另一个是图元处理。
光栅化
虽然虚拟 3D 世界中的几何信息是三维的,但由于目前用于显示的设备都是二维的。因此在真正执行光栅化工作之前,首先需要将虚拟 3D 世界中的物体投影到视平面上。
纹理环境和颜色求和
纹理采样:主要是根据当前需要处理片元的纹理坐标以及采用的纹理 ID 对相应的纹理图进行纹理采样,获取采样值。
颜色求和:执行颜色的变化,起根据纹理采样以及光照计算的结果综合生成需要处理片元的颜色。
雾
根据程序中设置的雾的相关参数,如:颜色、浓度、范围等来计算当前处理的片元受雾影响后的颜色。
Alpha 测试
如果程序中启用了 Alpha 测试,Open GL ES 会检查每个片元的 Alpha 值,只有 Alpha 值符合测试条件的片元才会送入到下一个阶段。
剪裁测试
如果程序中启用了剪裁测试,Open GL ES 会检查每个片元在帧缓冲中对应的位置,若对应位置在剪裁窗口中,则将此片元送到下一个阶段。
模板测试的主要功能为将绘制区域限定在一定的范围内,一般用在湖面倒影、镜像等场合。
颜色缓冲混合
若程序中开启了 Alpha 混合,则根据混合因子将上一阶段送来的片元与帧缓冲中对应位置的片元进行 Alpha 混合。
抖动
自己理解吧,打字太累了。
Open GL ES 2.0 的渲染管线
Open GL ES 1.X 只是对开发人员开放了其中的一部分 API 接口,但在整个渲染管线的运行过程中开发人员是不能直接干预的。因此,虽然 Open GL ES 1.x 的渲染管线功能已经很强大,但是其留给开发人员的发挥空间并不大,很多特效难以开发,而 Open GL ES 2.0 为开发人员提供了更多的发挥空间。
Open GL ES 2.0 中 “顶点着色器” 取代了 Open GL ES 1.x 渲染管线中的 “变换和光照” 这使得开发 3D 场景时对顶点的变换、法向量的计算、纹理坐标的变换、光照材质的应用等均由开发者使用着色器代码完成,灵活性大大提高。
Open GL ES 2.0 中 “片元着色器” 取代了 Open GL ES 1.x 渲染管线中的 “纹理环境和颜色求和”、“雾” 以及 “Alpha 测试” 等阶段,这使得纹理处理、颜色求和以及雾效果均由开发者自己开发,大大增强了程序对片元的处理能力。
写在前面:(建议大家务必读完)
说实话,很不愿意写这两篇教程,很多人会觉得我的教程是教 OpenGL ES 2.0 在 Android 手机上的开发知识,没有必要将这些在你们看来没有用的东西,但对于 3D 游戏开发人员来说,掌握这门语言尤为重要。本教程的后继内容将从多个方面介绍 OpenGL ES 着色语言的基本知识,使巴友们初步了解着色语言,为以后深入的学习打下坚实的基础。
我希望我能提供给大家的不只是知识,还是一种学习的信念。
由于 Android 平台下的可编程图形硬件支持是 OpenGL ES 2.0 标准,因此本教程向巴友们介绍 OpenGL ES 着色语言。 OpenGL ES 着色语言是一种高级的图形编程语言。其源自于应用广泛的 C 语言,同时具有 RendeMan 以及其他着色语言的一些优良特性,易于被开发人员掌握。 OpenGL ES 的着色语言主要包括以下特性:
OpenGL ES 2.0 着色语言是一种高级的过程语言(注意,不是面向对象的)。
对顶点着色器、片元着色器使用的是同样的语言。
基于 C/C++ 的语法及流程控制。
完美支持向量与矩阵的各种操作。
通过类型限定符来管理输入与输出。
拥有大量的内置函数来提供丰富的功能。
数据类型概述
标量也被称为 “无向量” 其值只有大小,并不具有方向。标量之间的运算遵循简单的代数法则,如质量、密度、体积、时间以及温度等都属于标量。OpenGL ES 着色语言支持的标量类型有布尔型 (bool)、整形(int) 和浮点型(float)。
OpenGL ES 着色语言中,向量可以看做是用同样类型的标量组成,其基本类型也分为 bool、int 和 float 三种。每个向量可以由 2 个、3 个、4 个相同的标量组成,具体情况如下:
向量类型 | 说明 | 向量类型 | 说明 |
---|---|---|---|
vec2 | 包含了 2 个浮点数的向量 | ivec4 | 包含了 4 个整数的向量 |
vec3 | 包含了 3 个浮点数的向量 | bvec2 | 包含了 2 个布尔数的向量 |
vec4 | 包含了 4 个浮点数的向量 | bvec3 | 包含了 3 个布尔数的向量 |
ivec2 | 包含了 2 个整数的向量 | bvec4 | 包含了 4 个布尔数的向量 |
ivec3 | 包含了 3 个整数的向量 |
向量在着色器代码的开发中有着十分重要的作用,可以很方面的存储以及存储颜色、位置、纹理坐标等不仅包含一个组成部分的量。开发中,有时可能需奥单独访问向量中的某个分量,基本的语法为 “<向量名>.<分量名 >”,根据目的的不同,主要有以下几种用法:
将一个向量看做颜色时,可以使用 r,g,b,a 四个分量名,分别代表红、绿、蓝、透明度 4 个色彩通道。具体用法如下:
//给向量aColor的红色通道赋值
aColor.r = 0.6;
将一个向量看做位置时,可以使用 x,y,z,w 等 4 个分量名,分别代表 X 轴,Y 轴,Z 轴和向量的模四个分量,具体用法和颜色类似。
将一个向量看做纹理坐标时,可以使用 s,t,p,q 四个分量名,期分别代表纹理坐标的不同分量,具体用法同颜色。(对纹理坐标中的 s,t 等分量巴友可能不是很明白,不用担心,在后面介绍纹理贴图的教程会进行详细的介绍)
访问向量中的各个不同的分量不但可以采用 “.” 加上不同的分量名,还可以将向量看做一个数组,用下标来进行访问,具体用法如下:
//给向量aColor的红色通道赋值
aColor[0] = 0.6;
有一些基础的开发人员都知道,3D 场景中的移位、旋转、缩放等变换都是由矩阵的运算来实现的。因此 3D 场景的开发中会非常多的使用矩阵,矩阵按尺寸分为 2&#215;2 矩阵、3&#215;3 矩阵、4&#215;4 矩阵,具体情况如下表所示:
矩阵类型 | 说明 |
---|---|
mat2 | 2&#215;2 浮点数矩阵 |
mat3 | 3&#215;3 浮点数矩阵 |
mat4 | 4&#215;4 浮点数矩阵 |
对于矩阵的访问,可以讲矩阵作为列向量的数组来访问。如 matrix 为一个 mat4,可以使用 matrix[2] 取到该矩阵的第三列,其为一个 vec4;也可以使用 matix[2][2] 取得第三列向量的第 3 个分量。
采样器是着色语言中不同于 C 语言的一种特殊的基本数据类型,其专门用来进行纹理采样的相关操作。一般情况下,一个采样器变量代表一幅或一套纹理贴图,其具体情况如下:
采样器 | 说明 |
---|---|
sampler2D | 用于访问二维纹理 |
smapler3D | 用于访问三维纹理 |
samplerCube | 用于访问立方贴图纹理 |
需要注意的是,与前面介绍的几种变量不同,采样器变量不能再着色器中初始化。一般情况下采样器变量都用 uniform 限定符来修饰,从宿主语言 (如 java) 接受传递进着色器的值。
OpenGL ES 着色语言还提供了类似 C 语言中的用户自定义结构体,同样也是使用 struct 关键字进行声明。其基本用法如下:
struct info{
vec3 color;
vec3 position;
vec2 textureCoor;
}
声明数组的方式主要有两种,
在声明数组的同时,指定数组的大小:
vec3 position[20];
在声明数组时,也可以不指定数组的大小,但是必须符合下列两种情况之一。
u 引用数组之前,要再次使用第一种声明方式来生命该数组:
//声明了一个大小不定的vec3数组
vec3 position[];
//再次声明该数组,并且指定大小。
vec3 position[5];
u 代码中访问数组的下标都是编译时常量,这时编译器会自动创建适当大小的数组,使得数组尺寸足够存储编译器看到的最大索引值对应的元素。
//声明了一个大小不定的vec3数组
vec3 position[];
//position需要一个大小为4的数组
position[3] = vec3(3.0);
//position需要一个大小为21的数组
position[20] = vec3(6.0);
void main() {
}
数据类型的基本使用
声明、作用域及初始化
变量的声明以及作用域与 Java/C++ 语法类似,可以再任何需要的位置声明变量,同时期作用域也同样分为局部变量和全局变量:
//声明了全局变量a和b
int a,b;
//声明了全局变量aPosition并赋值
vec3 aPosition = vec3(1.0, 2.2, 3.3);
void myFunction() {
//声明了局部变量c并赋值
int c = 14;
//给全局变量a赋值
a = 4;
//给全局变量b赋值
b = a * c;
}
向量的初始化还有一些很灵活的技巧,巴友们体会一下下面的代码:
//声明浮点变量a并赋值
float a = 12.3;
//声明浮点变量b并赋值
float b = 11.4;
//声明2维向量va并赋值
vec2 va = vec2(2.3, 2.5);
//声明2维向量vb并赋值
vec2 vb = vec2(a, b);
//声明3维向量vc并赋值
vec3 vc = vec3(vb, 12.5);
//声明4维向量vd并赋值
vec4 vd = vec4(va, vb);
//声明4维向量ve并赋值, 相当于vec4(0.2 , 0.2 , 0.2, 0.2);
vec4 ve = vec4(0.2);
运算符 | 说明 | 运算符 | 说明 | |
---|---|---|---|---|
[] | 用于索引 | . | 成员选择与混合 | |
++ &#8212; | 自加 1 与自减 1 后缀 | ++ &#8212; | 自加 1 与自减 1 前缀 | |
&#8211; ! | 一元非与逻辑非 | * / | 乘法与除法 | |
+ &#8211; | 加法与减法 | <> <=>= | 关系运算符 | |
== != | 等于和不等于 | && | 逻辑与 | |
^^ | 逻辑异或 | 逻辑或 | ||
?: | 选择 | = += -= *= /= | 赋值运算符 |
限定符 | 说明 |
---|---|
attribute | 一般用于每个顶点都各不相同的量,如顶点位置、颜色等。 |
uniform | 一般用于对同一组顶点组成的单个 3D 物体中所有顶点都相同的量,如当前光源的位置。 |
varying | 用于从顶点着色器传递到片元着色器的量 |
const | 用于声明常量 |
attribute 限定符
顾名思义为属性限定符,其修饰的变量用来接收渲染管线传递进顶点着色器的当前待处理顶点的各种属性值。这些属性值每个顶点各自拥有独立的副本,用于描述顶点的各项特征,如顶点坐标、法向量、颜色、纹理坐标等。
用 attribute 限定符修士的变量其值是由宿主程序批量出入渲染管线的,管线进行基本处理后再传递给顶点着色器。数据中有多少个顶点,管线就调用多少次顶点着色器,每次讲一个顶点的各种属性数据传递给顶点着色器中对应 atribute 变量。因此,顶点着色器每次执行将完成对一个顶点各项属性数据的处理。
从上面的介绍中可以看出,atribute 限定符只能用于顶点着色器中,不能再片元着色器中使用,且 attribute 限定符只能用来修饰浮点数标量、浮点向量以及矩阵变量,不能用来修饰其他类型的变量。下面的代码片段给出了在顶点着色器中正确使用 attribute 限定符的情况:
//顶点位置
attribute vec3 aPosition;
//顶点法向量
attribute vec3 aNormal;
前面已经提到,对于用 attribute 限定符修饰的变量的值是由宿主程序批量传入渲染管线的,相关代码如下:
// 声明顶点位置属性引用
int maPositionHandle;
// 获取顶点位置属性引用的值,
// mProgram为着色器程序ID,
// aPosition为着色器中对应属性的变量名称。
maPositiOnHandle= GLES20.glGetAttribLocation(mProgram, "aPosition");
// 将顶点位置数据传入渲染管线
// maPositionHandle:顶点位置属性引用
// 3:每顶点一组的数据个数(这里是X,Y,Z坐标,因此为3)
// GLES20.GL_FLOAT:数据类型
// false:是否格式化
// 3 * 4:每组数据的尺寸,这个魅族3个浮点数值(X,Y,Z坐标),每个浮点数4个字节
// mVertexBuffer:存放了数据的缓冲
GLES20.glVertexAttribPointer(maPositionHandle, 3, GLES20.GL_FLOAT, false, 3 * 4, mVertexBuffer);
具体代码可以参考第一个教程中的 Triangle 类。
http://www.apkbus.com/blog-99192-39498.html
uniform 限定符
uniform 为一致变量限定符,一致变量指的是对于同一组顶点组成的单个 3D 物体中所有顶点都相同的量。Uniform 变量可以用在顶点着色器或片元着色器中,其支持用来修饰所有的基本数据类型。与属性限定符类似,一致变量的值也是从宿主程序传入的。
下面的代码片给出了在顶点或片元着色器中正确使用 uniform 限定符的情况:
//总变换矩阵
uniform mat4 uMVPMatrix;
//变换矩阵
uniform mat4 uMMatrix;
//光源位置
uniform vec3 uLightLocation;
//摄像机位置
uniform vec3 uCamera;
将一致变量的值由宿主程序传入渲染管线的代码如下:
//总变换矩阵一致变量引用
int muMVPMatrixHandle;
//获取着色器程序中总变换矩阵一致变量的引用
muMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");
//通过一致变量引用将一致变量值传入渲染管线
GLES20.glUniformMatrix4fv(muMVPMatrixHandle, 1, false, Triangle.getFianlMatrix(mMMatrix), 0);
需要注意的是,随一致性变量类型不同将值传入渲染管线的方法也有所不同,这些方法的名称都以 glUniform 开头,常用的如下所列:
glUniformNf/glUniformNfv 方法,将 N 个浮点数传入管线,以备管线传递给由 N 个浮点数组成的一致变量,N 的取值为 1,2,3,4。
glUniformNi/glUniformNiv 方法,将 N 个整数传入管线,以备管线传递给由 N 个整数组成的一致变量,N 的取值为 1,2,3,4。
glUniformMatrixNfv 方法,将 N N 的矩阵传入管线,以备管线传递给 N N 矩阵类型的一致变量,N 的取值为 2,3,4。
verying 限定符
想要将顶点着色器中的信息传入到片元着色器中,必须使用 varying 限定符。欧诺个 varying 限定符修饰的全局变量又称为易变变量,易变变量可以看成是顶点着色器和片元着色器之间的动态接口,方便顶点着色器与片元着色器之间信息的传递。下图给出了易变变量的工作原理:
从上图可以看出,首先顶点着色器再每个顶点中都对一边变量 vPosition 进行了赋值。接着在片元着色器中接受易变变量 vPosition 的值时得到的并不是某个顶点赋的特定值,而是根据片元所在的位置以及图元中各个顶点的位置进行差值计算产生的值。
如图中顶点 1、2、3 的 vPosition 值分别为 vec3(0,7,0)、vec3(5,0,0)、vec3(-5,0,0), 则插值后片元 a 的 vPosition 值为 vec3(1.45, 2.06, 0)。
从上述介绍中可以看到,光栅化后产生了多少个片元,就会插值计算出多少套易变变量。同时渲染管线就会调用多少次片元着色器。可以看出,3D 物体的渲染中,片元着色器执行的次数会大大超过顶点着色器。因此 GPU 硬件中配置的片元着色器硬件数量往往多于顶点着色器硬件数量以提高渲染速度。
const 限定符
用 const 限定符修饰的变量其值是不可以变的,也就是常量,又称为编译时常量。编译时常量在声明的时候必须进行初始化。例如:
const int tempx = 1;
好了今天着色语言就讲到这里,明天会讲解流程控制、函数的声明与使用、着色语言的内置函数等。
我们接着上一节讲,上一节我们讲了着色语言的基本语法,这一节我们来了解一下流程控制以及内建变量。 由于这一节的内容与 Java 非常类似,所以我讲的比较肤浅,我相信大家都应该是 Java 高手,就不用我在这班门弄斧了。
OpenGL ES 着色语言提供了 3 种流程控制的方式,分别是 if-else、while(do-while) 循环、与 for 循环。这些控制语句的语法基本与 Java 一样,所以接受起来很简单。
if-else 语句
if 语句的基本语法是:
int tempx = 1;
if (tempx == 0) {
//执行处理逻辑
}
int tempx = 1;
while (tempx > 0) {
//执行处理逻辑
}
do-while 循环的基本语法是:
int tempx = 1;
do {
//执行处理逻辑
} while(tempx > 0);
for (int i = 0; i <13; i++) {
//执行处理逻辑
}
函数的语法为:
<返回类型> 函数名称 ([<参数序列>]){
/*函数体*/
}
着色器代码的开发中会用到很多变量,其中大部分可能是由开发人员根据需求自定义的,但着色器中也提供了一些用来满足特定需求的内建变量。这些内建变量不需要声明就可以使用,一般用来实现渲染管线固定功能部分与自定义顶点或片元着色器之间的信息交互。
顶点着色器中的内建变量
顶点着色器中的内建变量主要是输出变量,包括 gl_Position、gl_PointSize 等。在顶点着色器中应该根据需要给这些内建变量赋值,以便由渲染管线中的图元装配与光栅化等后续固定功能阶段进行进一步的操作。
gl_Position:顶点着色器从应用程序中获得原始的顶点位置的数据,这些原始的顶点数据在顶点着色器中经过平移、旋转、缩放等数学变换后,生成新的顶点位置。
gl_PointSize:顶点着色器中可以计算一个点的大小 (单位为像素),并将其赋值给 gl_PointSize(标量,float 类型) 以传递给渲染管线,如果没有明确赋值的话,就是采用默认值 1,gl_PointSize 的值一般只有在采用了点绘制方式之后才有意义。
gl_FrontFacing:布尔型内建变量,通过读取内建变量的值可以判断正在处理的片元是否属于在光栅化阶段生成此片元的对应图元的正面。如果属于正面,那么该值为 true,否则为 false。