GLSL ES 1.0

GLSL ES 概述

写在前面

  • 程序是大小写敏感的

  • 每一个语句都应该以英文分号结束

  • 一个shader必须包含一个main函数,该函数不接受任何参数,并且返回void

    void main()
    {
    }
    

数据值类型

GLSL支持三种数据类型:

  • 整型
  • 浮点型:必须包含小数点,不然会被认为是浮点型,比如1表示整形,1.0才表示浮点型
  • 布尔类型

GLSL是强类型语言,这意味着:

  • 将浮点数赋值给一个整型变量是不对的,同理,将一个整数赋值给浮点数变量也不被允许

    // 会报错,错误信息如下:
    // Failed to compile shader: ERROR: 0:56: '=' :
    // cannot convert from 'const int' to 'mediump float'
    float a1 = 1;
    
    // 会报错,错误信息如下:
    // Failed to compile shader: ERROR: 0:56: '=' : 
    // cannot convert from 'const float' to 'mediump int'
    int a1 = 1.0;
    
  • 在一个计算表达式中,必须统一数据类型,比如一个包含浮点数的表达式中,不能使用整型数据,这个经常由于不小心的书写导致编译失败。

    // 会报错
    // Failed to compile shader: ERROR: 0:57: '*' : 
    // wrong operand types - no operation '*' exists that 
    // takes a left-hand operand of type 'mediump float' 
    // and a right operand of type 'const int'
    float a1 = 1.0;
    float a2 = a1 * 3;
    

数据转换

虽然GLSL是强类型语言,但是我们可以通过显示转换实现数据类型的转变,比如我们可以使用float()将数据类型转换为浮点数,GLSL支持float、int和bool的相互转换

// 将整数转换为浮点数
float(int)
// true被转换为1.0 false被转换为0.0
float(bool)
// 将浮点数的小数删去,整数部分转换为整型
int(float)
// 	true被转换为1 false被转换为0
int(bool)
// 0.0被转换为false,其他值转换为true
bool(float)
// 0被转换为false,其他值转换为true
bool(int)

矢量和矩阵

GLSL可以通过基本数值类型组合成矢量和矩阵,矢量的数据类型可以是浮点数,整数和bool值,但是矩阵WebGL1.0只支持浮点数

矢量

  • vec2, vec3, vec4:具有2、3、4浮点数元素的矢量
  • iec2, ivec3, ivec4:具有2、3、4整数元素的矢量
  • bec2, bec3, bec4:具有2、3、4bool值元素的矢量

矩阵

  • mat2:2x2浮点数矩阵
  • mat3:3x3浮点数矩阵
  • mat4:4x4浮点数矩阵

赋值构造

在给矢量或者矩阵赋值的时候,必须遵循:

  • 赋值两边的数据类型必须一致,不然会报错
  • 赋值两边的元素个数必须一致
  • 对于矩阵,默认的构造顺序是按照列主序的
  • 对于矩阵,如果只传递了一个数值,会构造一个对角线都是该值,其他值都是0的矩阵

访问

访问矢量和矩阵可以有两种方式,一种是通过.运算符,一种是通过[]

使用点运算符访问有三种访问方式:

  • x,y,z,w:分别对应矢量的第1、2、3、4个分量,注意:矩阵是不能用该方式访问的
  • r,g,b,a:分别对应矢量的第1、2、3、4个分量,注意:矩阵是不能用该方式访问的
  • s,t,p,q:分别对应矢量的第1、2、3、4个分量,注意:矩阵是不能用该方式访问的

对于每一种访问方式,都可以混合使用,比如:

// 下面这些访问方式都是正确的
vec4 a1 = vec4(1.0, 1.1, 1.2, 1.3);
float b1 = a1.x;
vec2 c1 = a1.xy;
vec2 d1 = a1.yx;
vec2 e1 = a1.xx;

但是不同访问方式之间不可以混合使用

矩阵的访问更简单,类似于C语言的数组访问,使用[],从0开始,0表示第一项,使用[]访问的时候需要注意:

  • 矩阵是列主序的
  • []必须是整型字面值或者const整型或者循环索引或者三者的组合表达式

运算

如果一个矢量和一个数值进行运算,结果是该数值和矢量的每个分量进行运算

如果一个矩阵和一个数值进行运算,结果是该数值和矩阵的每个分量进行运算

结构体

GLSL使用如下方式定义一个结构体:

struct light
{
	vec4 color;
	vec3 position;
};
light l1;
light l2;

使用点运算符访问结构体的成员。

数组

GLSL中可以使用数组,但是只可以使用一维数组

以下是数组的声明方式:

float floatArray[4];
vec4 vec4Array[3];

数组的长度必须是大于0的整型常量表达式:

  • 整型字面值
  • 用const限定符修饰的全局变量或者局部变量
  • 由前面两条组成的表达式

注意:在使用数组的时候,数组的索引只可以是整型常量表达式或者uniform变量。

注意:数组不可以在声明的时候直接初始化,必须显示的对每个元素进行初始化。

采样器

GLSL支持一种内置类型-采样器,我们只能通过采样器来获取纹理的数据。

GLSL ES1.0支持两种采样器类型:

  • sampler2D:二维纹理贴图
  • samplerCube:三维纹理贴图

采样器只能是uniform变量

我们通过GLSL提供的内置函数来从采样器中访问纹理数据,内置函数有两类,可以参考文章WebGL 1.0 内置函数

唯一能赋值给采样器变量的就是纹理单元编号,而且你必须使用WebGL方法

gl.uniform1i(u_Sampler, 纹理单元编号);

for循环

GLSL中可以使用for循环,和C语言很像,但是有些限制:

  • 循环变量只能有一个
  • 循环变量只能是int或者float
  • 循环变量只能和整型常量做比较
    有些时候,循环变量的比较对象我们可能期望是一个uniform传递过来的动态值。这个时候直接进行比较是不可以的,一种处理的办法是给一个足够大的比较值,然后在循环体内部判断循环变量和uniform变量的大小
  • 在循环体内,循环变量不可以被赋值

discard

discard只能在片元着色器使用,表示放弃当前片元的处理。

函数

GLSL的函数和C语言类似,如果我们在函数定义前就调用了函数,需要先对函数进行声明。

对于函数的参数,我们可以为参数指定限定词,以控制参数的行为。

函数参数的限定词有下面几种:

  • in:这是默认的限定词。值传递,函数内部修改参数的值不会影响传入的值
  • const in:也是值传递,但是函数内部不可以修改该值
  • out:引用传递,类似于C语言的指针或者C++的引用,内部修改会影响传入参数的值。
  • inout:和out一样也是引用传递,但是和out不同之处为out不应该对传入的值抱有期待,out主要为了传递出函数内部的值。而inout表明该参数既要被函数使用,也要被函数修改。

存储限定符

GLSL中的存储限定符有四种:

  • attribute
  • uniform
  • varying
  • const

const

表明当前变量不可以被修改,是一个常量

const变量定义时就需要赋值。

attribute

  • attribute变量只能出现在顶点着色器
  • attribute只能声明为全局变量
  • 在GLSL ES1.0中,attribute只能是float、vec2、vec3、vec4、mat2、mat3、mat4
  • 可以通过访问内置的全局变量gl_MaxVertexAttribs来获取attribute变量支持的数目,对于WebGL环境,最小为8

attribute表示逐顶点数据,应该传递顶点独有的属性数据,对于所有顶点共有的属性,应该使用uniform

uniform

  • uniform变量在顶点着色器和片元着色器中都可以使用
  • uniform只能声明为全局变量
  • uniform变量是只读的
  • 对于GLSL ES1.0,uniform不能声明为数组或者结构
  • 如果在顶点着色器和片元着色器声明了同名的uniform变量,该变量会被两个着色器共享。
  • 可以通过访问内置的全局变量gl_MaxVertexUniformVectors获取顶点着色器支持的uniform变量数量,通过访问内置的全局变量gl_MaxFragmentUniformVectors获片元着色器支持的uniform变量数量,对于WebGL环境,gl_MaxVertexUniformVectors最小为128,gl_MaxFragmentUniformVectors最小为16

varying

  • varying的目的是从顶点着色器传递数据到片元着色器。
  • varying成对出现,并且在顶点着色器和片元着色器中的名称和类型一致。
  • varying只能是全局变量
  • varying的类型和attribute一致

varying的中文翻译是变化的意思,既然为了传递数据,为什么用这么个名字呢?

原因就是我们在片元着色器中看到的varying变量虽然和顶点着色器中的名称和类型一致,但数据已经不一样了,看下面的代码:

// 顶点着色器
attribute vec2 a_Position;
attribute vec3 a_Color;
varying vec3 v_Color;
void main() {
	v_Color = a_Color;
  	gl_Position = vec4(a_Position.x,a_Position.y,0.0,1.0);
}`;

// 片元着色器
varying vec3 v_Color;
void main()
{
	gl_FragColor = vec4(v_Color,1.0);
}	

我们过一下其中的过程:

  1. 顶点着色器将每个顶点的颜色赋值给v_Color,如果有100个顶点,就有100个v_Color值
  2. 从顶点着色器到片元着色器的过程中,发生了光栅话,就是说在这个过程中,根据绘制的图形,会对v_Color进行插值
  3. 片元着色器中的v_Color是每个片元插值后的值。所以,采用了varying这个名称来标记光栅话的过程

从上面的解释可以看出,一般而言,varying都是针对于attribute变量的传递,所以,varying变量的数据类型和attribute变量是一致的。

可以通过访问内置的全局变量gl_MaxVaryingVectors获取着色器支持的varying变量数量,对于WebGL环境,gl_MaxVaryingVectors最小为8

最后,给出一张不同限定符数据在GPU中的传递图

精度限定符

定义

GLSL ES 新引入了精度限定符,目的是帮助着色器程序提高运行效率,削减内存开支。 顾名思义,精度限定符用来表示每种数据具有的精度 (比特数)。简而言之,高精度的程序需要更大的开销 (包括更大的内存和更久的计算时间),而低精度的程序需要的开销则小得多。使用精度限定符,你就能精细地控制程序在效果和性能间的平衡。

作用

精度限定符有两个作用:

  • 对于浮点数和采样器,精度限定符限制了精度和取值范围
  • 对于整型数据,精度限定符限制了取值范围

三种精度类型

因为我们使用采样器的时候,传递的也是浮点纹理坐标,所以我们总结一下不同的精度限定符对float和int的影响:

  • highp:高精度,顶点着色器的最低精度
    • 对于float,取值范围为 ( − 2 62 , 2 62 ) (-2^{62},2^{62}) (−262,262),精度范围 2 − 16 2^{-16} 2−16
    • 对于int,取值范围为 ( − 2 16 , 2 16 ) (-2^{16},2^{16}) (−216,216)
  • mediump:中精度,片元着色器的最低精度
    • 对于float,取值范围为 ( − 2 14 , 2 14 ) (-2^{14},2^{14}) (−214,214),精度范围 2 − 10 2^{-10} 2−10
    • 对于int,取值范围为 ( − 2 10 , 2 10 ) (-2^{10},2^{10}) (−210,210)
  • lowp:低精度
    • 对于float,取值范围为 ( − 2 , 2 ) (-2,2) (−2,2),精度范围 2 − 8 2^{-8} 2−8
    • 对于int,取值范围为 ( − 2 8 , 2 8 ) (-2^{8},2^{8}) (−28,28)

实际上,对于现在的机器,大部分的值和上面的默认值不一样,

我们可以使用下面的js方法获取不同精度的描述信息:

getShaderPrecisionFormat(shaderType, precisionType)
  • shaderType :表示着色器类型,可以是gl.FRAGMENT_SHADER或者gl.VERTEX_SHADER

  • precisionType :要查询的精度限定符类型,可以是:

    • gl.LOW_FLOAT
    • gl.MEDIUM_FLOAT
    • gl.HIGH_FLOAT
    • gl.LOW_INT
    • gl.MEDIUM_INT
    • gl.HIGH_INT
  • 返回值:如果成功,返回WebGLShaderPrecisionFormat对象的实例,如果失败,返回null,下面是一个具体的例子:

    // 返回对象有三个值,表示精度和取值范围,一看就能明白
    // 精度precision: 23
    // rangeMax: 127
    // rangeMin: 127
    var precisionFormat = gl.getShaderPrecisionFormat(gl.VERTEX_SHADER,gl.MEDIUM_FLOAT);

对变量使用精度限定符有两种方式:

  • 直接在变量定义的前面添加精度限定符highp、mediump或者lowp

  • 给指定类型统一设置精度限定符

    // 给float类型统一设置mediump
    precision mediump float;

    // 给变量单独设置精度限定
    mediump float a;
    highp vec4 position;
    lowp vec3 color;

默认值

  • 对于采样器,默认精度值都是lowp

  • 对于顶点着色器,int和float默认精度值都是highp

  • 对于片元着色器,int的默认精度值是mediump

  • 对于片元着色器,float没有默认精度值,所以我们需要在片元着色器的最前边设置float的默认精度,不然会导致编译错误。

    precision mediump float;
    

预处理

GLSL ES支持预处理,所谓的预处理就是在代码编译之前的处理过程

常用预处理指令

#if 条件表达式
	如果条件表达式为真,执行这里
#endif

#ifdef 某宏
	如果定义了某宏,执行这里
#endif

#ifndef 某宏
	如果没有定义某宏,执行这里
#endif

// 定义宏
#define 宏名 宏内容

// 取消定义宏
#undef 宏名

// 使用#else
#define NUM 100
#if NUM==100
	如果NUM等于100,执行这里
#else
	否则执行这里
#endif

内置宏定义

  • GL_ES:在OpenGL ES 2.0中定义为1
  • GL_FRAGMENT_PRECISION_HIGH:片元着色器是否支持highp

可以使用下面的方式对片元着色器的float进行精度设置

#ifdef GL_ES
#ifdef GL_FRAGMENT_PRECISION_HIGH
precision highp float;
#else
precision mediump float;
#endif
#endif

设置版本

可以在着色器的顶部设置WEBGL的版本号

// 100表示设置WEBGL 1.00
#version 100
相关推荐
_oP_i10 小时前
Unity Addressables 系统处理 WebGL 打包本地资源的一种高效方式
unity·游戏引擎·webgl
新中地GIS开发老师20 小时前
WebGIS和WebGL的基本概念介绍和差异对比
学习·arcgis·webgl
_oP_i2 天前
Unity 中使用 WebGL 构建并运行时使用的图片必须使用web服务器上的
前端·unity·webgl
flying robot5 天前
Three.js简化 WebGL 的使用
webgl
小彭努力中5 天前
114. 精灵模型标注场景(贴图)
前端·3d·webgl·贴图
小彭努力中5 天前
109. 工厂光源(环境贴图和环境光)
前端·深度学习·3d·webgl·贴图
小彭努力中6 天前
112. gui辅助调节光源阴影
前端·深度学习·3d·webgl
refineiks7 天前
three.js绘制宽度大于1的线,并动态新增顶点
3d·图形渲染·webgl
小彭努力中8 天前
102. 管道漫游案例
前端·3d·webgl
小彭努力中8 天前
107. 阴影范围.shadow.camera
前端·深度学习·3d·webgl