如果你进到这里,相信你一定是不满足基于JavaScript的前端页面。其实,无论是视觉效果还是渲染效率,GLSL都会给你带来不一样的惊喜,仿佛打开一个潘多拉的魔盒!虽然,操纵着色器语言GLSL比起高级语言JavaScript将更有难度,但是与底层汇编语言相比,GLSL仍是人类可读的。这里,研习君着重针对与JavaScript的不同,对GLSL语言做一些介绍。
JavaScript和GLSL都是在浏览器中运行的编程语言,都是用来在屏幕上绘制一些有趣的东西的。相比较于广为人知的JavaScript,或许大部分人对GLSL仍比较陌生。GLSL为OpenGL着色语言(OpenGL Shading Language),它和JavaScript之间有些什么不同呢?
解释语言 vs 编译语言
首先,二者之间最根本的差别就是JavaScript为解释语言,而GLSL为编译语言。编译后的程序是在操作系统上本机执行的,它是低级的,而且通常速度很快。一个解释程序需要一个虚拟机(VM)来执行,它的级别很高,而且通常很慢。
当浏览器(JavaScript虚拟机)执行或解释一段JavaScript脚本时,它不知道哪个变量是什么,哪个函数做什么。因此,它不能预先优化任何东西,因此需要一些时间来读取代码,从被使用的情况来推断变量和方法的类型(更多内容可搜索Chrome的V8引擎是如何工作的)。最糟糕的是,每一个浏览器都会以自己的方式优化JS,而这个过程对外界是隐藏的;即使身为程序员的你,也无能为力。
与之不同的是,编译后的程序不会被解释;操作系统运行它,如果程序有效,则执行该程序。这是一个很大的变化;如果你忘记了一个分号在行尾,你的代码是无效的,它将无法编译,因为你的代码根本不会变成一个程序。听上去很残酷,但GLSL就是这样:在GPU上执行的编译程序。不要害怕!作为一名程序员,编译器,作为一个验证和确保你的代码合法有效的程序,会成为你最好的朋友。
弱类型 vs 强类型
在JavaScript中变量和方法都是不用指定类型的,我们可以动态地添加、删除类成员,刷新页面,看看它是否有效,再更改、刷新,重复下去,生活如此美好。所以,从JavaScript迈向GLSL,初始化变量将是最困难的一步。必须显式地指定所使用的每个变量的类型,这样编译器一看到它们,就知道如何有效进行处理。所幸GLSL中的变量类型还是很有限的,如bool(布尔值)、int(整数)、float(浮点数)等,因此也是很容易理解和掌握的。
下面的片段展示了在JavaScript和GLSL中声明变量的不同:
//a Boolean value:
JS: var b = true;
GLSL:boolb = true;
//an Integer value
JS: var i = 1;
GLSL:inti = 1;
//a Float value (a Number)
JS: var f = 3.14159;
GLSL:floatf = 3.14159;
不是那么难吧?如果你仍有质疑,请相信这样做是为了让我们的程序比JavaScript快得多。
重载(Overloads)
JavaScript中不允许重载,但在GLSL中,重载几乎无处不在。例如,运算符的重载:
vec2a =vec2( 1.0, 1.0 );
vec2b =vec2( 1.0, 1.0 );
//overloaded addition
vec2c = a + b; // c = vec2( 2.0, 2.0 );
纳尼?不是数值也可以直接相加?是的,而且这也适用于其他的运算符(+, -, * & /) 。看下面的代码:
vec2a =vec2( 0.0, 0.0 );
vec2b =vec2( 1.0, 1.0 );
//overloaded constructor
vec4c =vec4( a , b ); // c = vec4( 0.0, 0.0, 1.0, 1.0 );
我们用两个二维向量构造了一个四维向量,这就是函数的重载。其实,这个vec4构造函数还允许很多不同版本的实现,例如以下所有的声明语句都是合法的,我们唯一需要的就是确保提供足够的参数给向量:
vec4a =vec4(1.0, 1.0, 1.0, 1.0);
vec4a =vec4(1.0);// x, y, z, w all equal 1.0
vec4a =vec4( v2,float, v4 );// vec4( v2.x, v2.y, float, v4.x );
vec4a =vec4( v3,float);// vec4( v3.x, v3.y, v3.z, float );
限定符(qualifiers)
限定符也是在JavaScript中没有的概念。GLSL在变量类型之上,提供了限定符,从而让编译器知道哪个变量是什么。例如,有些数据只能由CPU提供给GPU,这些数据称为attributes和uniforms。attributes是为顶点着色器保留的,uniforms在顶点着色器和片段着色器中均可使用。还有一个用于在顶点和片段着色器之间传递变量的varying限定符。例如下面的语句:
uniform vec2u_resolution;
我们在变量类型之前加了一个uniform的限定符,这意味着我们正在处理的画布的分辨率从CPU传递给着色器。当编译器看到前面有这个限定符的变量时,它将确保我们不能在运行时设置这些值。
此外,GLSL中为函数提供了了3个额外的限定符:in、out和inout。在JavaScript中,当我们向函数传递标量参数时,它们的值是只读的,如果我们在函数内部更改它们的值,则这些更改不会应用于函数外部的变量。但在GLSL中可以通过参数限定符来指定参数的行为:
in:将为只读(默认)
out:意味着它是 write only,无法读取此参数的值,但可以设置它
inout:意味着read-write,可以获取并设置此变量的值
这与JS非常不同,功能也非常强大。
空间坐标
最后一点,在基于JavaScript操纵的DOM和Canvas2d中,我们习惯于让Y轴指向“向下”。这在DOM上下文中是有意义的,因为它遵循web页面展开的方式;导航栏位于顶部,内容向底部展开。在WebGL画布中,Y轴则指向上。这意味着原点(0,0)位于WebGL上下文的左下角,而不是像在2D画布中那样位于左上角。
参考文献:
《An introduction for those coming from JS》/Nicolas Barradeau;
《The Book of Shaders》/Patricio Gonzalez Vivo & Jen Lowe;
更多技术分享,欢迎关注研习书社,一个技术研究和分享的角落!
领取专属 10元无门槛券
私享最新 技术干货