一、前期基础储备
笔者之前的四篇文综述了Android中使用OpenGL ES绘制基本图形和实现了简单的相机预览,初次接触OpenGL ES开发的读者可能对其中新的概念比较迷惑,尤其是其中的顶点着色器(Vertex Shader)和片元着色器(Fragment Shader),我们知道,在OpenGL中顶点着色器是针对每个顶点执行一次,用于确定顶点的位置。片元着色器是针对每个片元,片元可以理解为每个像素,用于确定每个片元(像素)的颜色或者纹理。如下是在相机预览中使用的两个着色器的代码段:

private final String vertexShaderCode =
"attribute vec4 vPosition;" +
"attribute vec2 inputTextureCoordinate;" +
"varying vec2 textureCoordinate;" +
"void main()" +
"{"+
"gl_Position = vPosition;"+
"textureCoordinate = inputTextureCoordinate;" +
"}";

private final String fragmentShaderCode =
"#extension GL_OES_EGL_image_external : require\n"+ //相机预览时的片元指令
"precision mediump float;" + //相机预览时的片元指令
"varying vec2 textureCoordinate;\n" +
"uniform samplerExternalOES s_texture;\n" + //相机预览时的片元指令
"void main() {" +
" gl_FragColor = texture2D( s_texture, textureCoordinate );\n" +
"}";

两段着色器中,使用的代码不是Java代码,语法也不是Java层面的,而是一种OpenGL特有的语法:GLSL语法

GLSL又叫OpenGL着色语言(OpenGL Shading Language),是用来在OpenGL中着色编程的语言,是一种面向过程的语言,基本的语法和C/C++基本相同,他们是在图形卡的GPU (Graphic Processor Unit图形处理单元)上执行的,代替了固定的渲染管线的一部分,使渲染管线中不同层次具有可编程性。比如:视图转换、投影转换等。GLSL(GL Shading Language)的着色器代码分成2个部分:Vertex Shader(顶点着色器)和Fragment Shader(片元着色器)。
在前面的文章中,我们基本上使用的都是非常简单的着色器,基本上没有使用过GLSL的内置函数,但是在后面我们完成其他的功能的时候应该就会用到这些内置函数了。比如说相机的各种滤镜、各种静态、动态贴纸,其实现少不了特定片元着色器的支持。

在笔者的文章《Android使用GPUImage实现滤镜效果精炼详解(一)》中通过一些示例代码实现了调用GPUImage处理静态图片,为其设置各种滤镜效果,在GitHub上找到GPUImage的开源库打开之后,就可以发现,其实现各种滤镜效果底层依靠的也是OpenGL,每种不同滤镜效果,其片元着色器也是特定的,以黑白滤镜的片元着色器为例:

public static final String GRAYSCALE_FRAGMENT_SHADER = "" +
"precision highp float;\n" +
"\n" +
"varying vec2 textureCoordinate;\n" +
"\n" +
"uniform sampler2D inputImageTexture;\n" +
"\n" +
"const highp vec3 W = vec3(0.2125, 0.7154, 0.0721);\n" +
"\n" +
"void main()\n" +
"{\n" +
" lowp vec4 textureColor = texture2D(inputImageTexture, textureCoordinate);\n" +
" float luminance = dot(textureColor.rgb, W);\n" +
"\n" +
" gl_FragColor = vec4(vec3(luminance), textureColor.a);\n" +
"}";

看起来,一头雾水,实际上该代码段是由OpenGL的特有语言GLSL构成的,GLSL的变量申明、GLSL语言的内置函数、GLSL的各种修饰符…只要掌握了GLSL语法,那么后期构造片元着色器会十分的便利,笔者是做相机开发的,工作中创建新滤镜效果,离不开GPUImage,其原生的片元着色器一部分可以拿来直接使用,但是更多的都是要在其基础上做修改的,使之符合自己的滤镜需要,而在改造过程中,GLSL语法掌握就更为迫切了。
注:任何一种OpenGL程序本质上都可以被分为两部分:CPU端运行的部分,采用C++、Java之类的语言编写;以及GPU端运行的部分,使用GLSL语言编写。

二、上代码,具体实现
(1)基本数据类型
GLSL中的数据类型主要分为标量、向量、矩阵、采样器、结构体、数组、空类型七种类型:
1)标量
标量表示的是只有大小没有方向的量,在GLSL中标量只有bool、int和float三种。对于int,和C一样,可以写为十进制、八进制或者十六进制。对于标量的运算,我们最需要注意的是精度,防止溢出问题。
2)向量
向量我们可以看做是数组,在GLSL通常用于储存颜色、坐标等数据,针对维数,可分为二维、三维和四位向量。针对存储的标量类型,可以分为bool、int和float。共有vec2、vec3、vec4,ivec2、ivec3、ivec4、bvec2、bvec3和bvec4九种类型,数组代表维数、i表示int类型、b表示bool类型。需要注意的是,GLSL中的向量表示竖向量,所以与矩阵相乘进行变换时,矩阵在前,向量在后(与DirectX正好相反)。

作为颜色向量时,用rgba表示分量,就如同取数组的中具体数据的索引值。三维颜色向量就用rgb表示分量。比如对于颜色向量vec4 color,color[0]和color.r都表示color向量的第一个值,也就是红色的分量。
作为位置向量时,用xyzw表示分量,xyz分别表示xyz坐标,w表示向量的模。三维坐标向量为xyz表示分量,二维向量为xy表示分量。
作为纹理向量时,用stpq表示分量,三维用stp表示分量,二维用st表示分量。

3)矩阵
在GLSL中矩阵拥有22、33、4*4三种类型的矩阵,分别用mat2、mat3、mat4表示。我们可以把矩阵看做是一个二维数组,也可以用二维数组下表的方式取里面具体位置的值。
4)采样器
采样器是专门用来对纹理进行采样工作的,在GLSL中一般来说,一个采样器变量表示一副或者一套纹理贴图。所谓的纹理贴图可以理解为我们看到的物体上的皮肤。纹理查找需要指定哪个纹理或者纹理单元将指定查找。

sampler1D // 访问一个一维纹理
sampler2D // 访问一个二维纹理
sampler3D // 访问一个三维纹理
samplerCube // 访问一个立方贴图纹理
sampler1DShadow // 访问一个带对比的一维深度纹理
sampler2DShadow // 访问一个带对比的二维深度纹理

uniform sampler2D grass;
vcc2 coord = vec2(100, 100);
vec4 color = texture2D(grass, coord);

5)结构体
和C语言中的结构体相同,用struct来定义结构体,这是唯一的用户定义类型。

struct light
{
vec3 position;
vec3 color;
};
light ceiling_light;

6)数组
数组知识也和C中相同,不同的是数组声明时可以不指定大小,但是建议在不必要的情况下,还是指定大小的好。

// 创建一个10个元素的数组
vec4 points[10];
// 创建一个不指定大小的数组
vec4 points[];
points[2] = vec4(1.0); // points现在大小为3
points[7] = vec4(2.0); // points现在大小为8

7)空类型
空类型用void表示,仅用来声明不返回任何值得函数。
数据声明示例:

float a=1.5;
int b=2;
bool c=true;
vec2 d=vec2(1.1,2.2);
vec3 e=vec3(1.1,2.2,3.3)
vec4 f=vec4(vec3,1.1);
vec4 g=vec4(1.0); //相当于vec(1.0,1.0,1.0,1.0)
vec4 h=vec4(a,a,1.5,a);
mat2 i=mat2(1.1,1.5,2.2,4.4);
mat2 j=mat2(1.8); //相当于mat2(1.8,1.8,1.8,1.8)
mat3 k=mat3(e,e,1.2,1.4,1.6);

(2)运算符
GLSL中的运算符包括(越靠前,运算优先级越高):

索引:[ ]
前缀自加和自减:++,–-
一元非和逻辑非:~,!
加法和减法:+,-
等于和不等于:==,!=
逻辑异或:^^
三元运算符号,选择:? :
成员选择与混合:.
后缀自加和自减:++,–-
乘法和除法:*,/
关系运算符:>,<,=,>=,<=,<>
逻辑与:&&
逻辑或:||
赋值预算:=,+=,-=,*=,/=

(3)类型转换
GLSL的类型转换与C不同。在GLSL中类型不可以自动提升,比如float a=1;就是一种错误的写法,必须严格的写成float a=1.0,也不可以强制转换,即float a=(float)1; 也是错误的写法,但是可以用内置函数来进行转换,如float a=float(1);还有float a=float(true);(true为1.0,false为0.0)等,值得注意的是,低精度的int不能转换为高精度的float。

(4)限定符
观察文首的两段着色器,可以看到对于变量使用了不同的修饰符,以下是GLSL修饰符的主要类型:

attritude:一般用于各个顶点各不相同的量。如顶点颜色、坐标等。
uniform:一般用于对于3D物体中所有顶点都相同的量。比如光源位置,统一变换矩阵等。
varying:表示易变量,一般用于顶点着色器传递到片元着色器的量。
const:常量。 限定符与java限定符类似,放在变量类型之前,并且只能用于全局变量。在GLSL中,没有默认限定符一说。

(5)流程控制
GLSL中的流程控制与C中基本相同,主要有条件判断和各种循环:

if( )、if( )else、if( )else if( )else
while()和dowhile( )
for( ; ; )
break和continue

(6)函数修饰符
GLSL中也可以定义函数,定义函数的方式也与C语言基本相同。函数的返回值可以是GLSL中的除了采样器的任意类型。对于GLSL中函数的参数,可以用参数用途修饰符来进行修饰,常用函数修饰符如下:

in:输入参数,无修饰符时默认为此修饰符。
out:输出参数。
inout:既可以作为输入参数,又可以作为输出参数。

(6)浮点精度
与顶点着色器不同的是,在片元着色器中使用浮点型时,必须指定浮点类型的精度,否则编译会报错。精度有三种,分别为:

lowp:低精度。8位。
mediump:中精度。10位。
highp:高精度。16位。

当然,也可以在片元着色器中设置默认精度,只需要在片元着色器最上面加上precision <精度> <类型>即可制定某种类型的默认精度。其他情况相同的话,精度越高,画质越好,使用的资源也越多。

(7)着色器代码结构
GLSL程序的结构和C语言差不多,main()方法表示入口函数,可以在其上定义函数和变量,在main中可以引用这些变量和函数。定义在函数体以外的叫做全局变量,定义在函数体内的叫做局部变量。与高级语言不同的是,变量和函数在使用前必须声明,不能在使用的后面声明变量或者函数。

三、GLSL内建变量和内置函数
在上面,我们了解了GLSL的基础语法:数据类型、运算符、控制流,接下来,我们要了解另外一些关键的内容:GLSL为方便调用,内置了一些变量和函数,在着色器构造中,少不了这些知识的运用。在上面glsl提供了非常丰富的函数库,供我们使用,这些功能都是非常有用且会经常用到的. 这些函数按功能区分大改可以分成7类:

(1)顶点着色器的内建变量
gl_Position:顶点坐标
gl_PointSize:点的大小,没有赋值则为默认值1,通常设置绘图为点绘制才有意义。

(2)片元着色器的内建变量
gl_FragCoord:当前片元相对窗口位置所处的坐标。
gl_FragFacing:bool型,表示是否为属于光栅化生成此片元的对应图元的正面。 输出变量:
gl_FragColor:当前片元颜色
gl_FragData:vec4类型的数组。向其写入的信息,供渲染管线的后继过程使用。

GLSL提供了非常丰富的函数库,供我们使用,这些功能都是非常有用而且经常会使用到的,这些函数按照功能区分大致可以分为7类:

(1)通用函数库

(2)三角函数&角度

(3)指数函数

(4)几何函数

(5)矩阵函数

(6)向量函数

(7)纹理查询函数

纹理采样函数中,3D在OpenGLES2.0并不是绝对支持。我们再次暂时不管3D纹理采样函数。重点只对texture2D函数进行说明。texture2D拥有三个参数,第一个参数表示纹理采样器。第二个参数表示纹理坐标,可以是二维、三维、或者四维。第三个参数加入后只能在片元着色器中调用,且只对采样器为mipmap类型纹理时有效。

在看完以上的OpenGL内建变量和内置函数之后,在返回前面,查看黑白滤镜的片元着色器,就会发现,各个变量的声明和使用都可以看得懂了,GPUImage中每种不同类型的滤镜实现都是采用了类似的方式,感兴趣的读者可以查看GPUImage的Android库:android-gpuimage

四、延伸知识,JSON语法
(1)JSON简介
 JSON指的是JavaScript对象表示法(JavaScript Object Notation)
 JSON是轻量级的文本交换格式,类似于XML,是纯文本
 JSON独立于语言,具有层级结构(值中存在值)
 JSON具有自我描述性,更易理解(人类可读)

JSON使用JavaScript语法来描述数据对象,但是JSON仍然独立于语言和平台。

(2)相比于XML的不同之处
JSON比XML更小、更快、更易解析
没有结束标签
更短,读写速度更快
使用数组
不使用保留字

(3)JSON语法规则
JSON语法是JavaScript对象表示语法的子集
数据在 名称/值 对中
数据由逗号隔开
花括号保存对象
方括号保存数组

(4)JSON 名称/值 对
JSON 数据的书写格式是:名称/值对。
名称/值对包括:字段名称(在双引号中),后面写一个冒号,然后是值:
“firstName” : “John”

(5)JSON值
JSON 值可以是:
数字(整数或浮点数)
字符串(在双引号中)
逻辑值(true 或 false)
数组(在方括号中)
对象(在花括号中)
Null

(6)JSON对象
JSON 对象在花括号中书写:对象可以包含多个名称/值对:
{ “firstName”:”John” , “lastName”:”Doe” }

(7)JSON 数组
JSON 数组在方括号中书写:数组可包含多个对象:
{
“employees”: [
{ “firstName”:”John” , “lastName”:”Doe” },
{ “firstName”:”Anna” , “lastName”:”Smith” },
{ “firstName”:”Peter” , “lastName”:”Jones” }
]
}

相关语法可以搜索 GLSL 中文手册 获取更多的语法知识

在Android中使用OpenGL ES开发第(五)节:GLSL基础语法的更多相关文章

  1. 在Android中使用OpenGL ES开发第(四)节:相机预览

    笔者之前写了三篇Android中使用OpenGL ES入门级的文章,从OpenGL ES的相关概念出发,分析了利用OpenGL ES实现3D绘图的重要的两个步骤:定义形状和绘制形状,简单的绘制了一个三 ...

  2. 在Android中使用OpenGL ES进行开发第(三)节:绘制图形

    一.前期基础知识储备笔者计划写三篇文章来详细分析OpenGL ES基础的同时也是入门关键的三个点: ①OpenGL ES是什么?与OpenGL的关系是什么?——概念部分 ②使用OpenGLES绘制2D ...

  3. 在Android中使用OpenGL ES进行开发第(二)节:定义图形

    一.前期基础知识储备笔者计划写三篇文章来详细分析OpenGL ES基础的同时也是入门关键的三个点: ①OpenGL ES是什么?与OpenGL的关系是什么?——概念部分 ②使用OpenGLES绘制2D ...

  4. 在Android中使用OpenGL ES进行开发第(一)节:概念先行

    一.前期基础是知识储备笔者计划写三篇文章来详细分析OpenGL ES基础的同时也是入门关键的三个点: ①OpenGL ES是什么?与OpenGL的关系是什么?——概念部分 ②使用OpenGL ES绘制 ...

  5. 如何使用Android中的OpenGL ES媒体效果

    引自:http://www.2cto.com/kf/201506/404366.html Android的媒体效果框架允许开发者可以很容易的应用多种令人印象深刻的视觉效果到照片或视频之上.作为这个媒体 ...

  6. Android OpenGL ES 开发(五): OpenGL ES 使用投影和相机视图

    OpenGL ES环境允许你以更接近于你眼睛看到的物理对象的方式来显示你绘制的对象.物理查看的模拟是通过对你所绘制的对象的坐标进行数学变换完成的: Projection - 这个变换是基于他们所显示的 ...

  7. Android OpenGL ES 开发(三): OpenGL ES 定义形状

    在上篇文章,我们能够配置好基本的Android OpenGL 使用的环境.但是如果我们不了解OpenGL ES如何定义图像的一些基本知识就使用OpenGL ES进行绘图还是有点棘手的.所以能够在Ope ...

  8. Android OpenGL ES 开发

    OpenGL(Open Graphics Library) 是开放图形库,是一个跨平台的图形 API.OpenGL ES(OpenGL for Embedded System)是专为移动端提供的一个子 ...

  9. Android OpenGL ES 开发教程 从入门到精通

    感谢,摘自:http://blog.csdn.net/mapdigit/article/details/7526556 Android OpenGL ES 简明开发教程 Android OpenGL ...

随机推荐

  1. 浅析ARM协处理器CP15寄存器有关指令:MCR\MRC

    ref:http://blog.csdn.net/gameit/article/details/13169405 背景: 在uboot中,start.s中涉及到了 CP15 的有关操作.查阅有关资料, ...

  2. hdu 4501三重包问题

    好好理解一下背包问题 从01包入手 内层的循环 是为了以后求解记录数据 因为只有一个取舍问题 所以只需要一层循环就可以 这里有三个背包 钱 积分 以及免费物品 那么 就需要三重循环 #include& ...

  3. (错误)启动ActiveMQ报错:Transport Connector could not be registered in JMX: java.io.IOException: Failed to bind to server socket: stomp://0.0.0.0:61613?

    一.错误报告 很明显,端口被占用 二.解决方法 1. 在cmd中输入 netstat -ano 查看61613端口被占用情况,如果有其他进程使用,则使用 taskkill /f /pid 进程PID  ...

  4. (二十二)JSP基础语法

    一.基础语法 1.1 JSP表达式 JSP脚本表达式(expression)用于将程序数据输出到客户端 语法:<%= 变量或表达式 %> 1 <body> 2 5+3=< ...

  5. (五)mybatis之一对一关系

    一.需求分析 需求:查询订单信息关联查询用户信息 分析:一条订单只能由一个消费者来下单,也就是说从订单的角度来说与消费者是一对一的关系. 二.建数据库表和实体对象 其中订单表中的字段user_id对应 ...

  6. 基于【 centos7】二 || 系统时间与网络时间同步

    # date // 查看系统时间 #hwclock // 查看硬件时间 # yum -y install ntp ntpdate 安装ntpdate工具 # ntpdate cn.pool.ntp.o ...

  7. windows的一些常用指令

    持续更新中..... 1.清除系统内 DNS 的缓冲  :  nslookup baidu.com 2.修改hosts文件  :  位置 运行  ->  C:/windows/system32/ ...

  8. K2 BPM_“选BPM还是RPA?”,这不是一道单选题_全业务流程管理专家

    在我们和企业讨论流程自动化的时候,经常会被问到一个问题,“公司目前正在调研市面上的业务流程自动化产品,我们已经把选择范围缩小到了BPM和RPA之间.我们应该怎么选择?BPM能做的好像RPA都能做到.” ...

  9. 二叉树BinaryTree构建测试(无序)

    此测试仅用于二叉树基本的性质测试,不包含插入.删除测试(此类一般属于有序树基本操作). //二叉树树类 public class BinaryTree { public TreeNode root; ...

  10. swoole聊天室

    服务端: <?phpclass Chat{ const HOST = '0.0.0.0';//ip地址 0.0.0.0代表接受所有ip的访问 const PART = 8080;//端口号 pr ...