在2D绘图中的坐标系统,默认情况下是与窗口坐标系统相同,它以canvas的左上角为坐标原点,沿X轴向右为正值,沿Y轴向下为正值。其中canvas坐标的单位都是’px’。
WebGL使用的是正交右手坐标系,且每个方向都有可使用的值的区间,超出该矩形区间的图像不会绘制:
X轴最左边为-1,最右边为1;
Y轴最下边为-1,最上边为1;
Z轴朝向你的方向最大值为1,远离你的方向最大值为-1;
注:这些值与Canvas的尺寸无关,无论Canvas的长宽比是多少,WebGL的区间值都是一致的。
渲染管线就像一条流水线,由一系列具有特定功能的数字电路单元组成,下一个功能单元处理上一个功能单元生成的数据,逐级处理数据。
顶点着色器和片元着色器是可编程的功能单元,拥有更大的自主性,还有光删器,深度测试等不可编程的功能单元,CPU会通过WebGL API和GPU通信,传递着色器程序和数据,GPU执行的着色器程序可以通过useProgram方法切换,传递数据就是把CPU主存中的数据传送到GPU的显存中。
顶点着色器
顶点着色器是GPU渲染管线上一个可以执行着色器语言的功能单元,具体执行的就是顶点着色器程序,webGL顶点着色器程序在JavaScript中以字符串的形式存在,通过编译器处理后传递给顶点着色器执行。顶点着色器主要作用就是执行顶点着色器程序对顶点进行变换计算,比如顶点位置坐标执行进行旋转,平移等矩阵变换,变换后新的顶点坐标然后赋值给内置变量gl_Position,作为顶点着色器的输出,图元装配和光栅化环节的输入。
图元装配
顶点变换后的操作是图元装配(primitive assembly),硬件上具体是怎么回事不用思考,从程序的角度来看,就是绘制函数drawArrays()或drawELement()第一个参数绘制模式mode控制顶点如何装配为图元,gl.LINES的定义的是把两个顶点装配成一个线条图元,gl.TRIANGLES定义的是三个顶点装配为一个三角面图元,gl.POINTS定义的是一个点域图元。
光删化
片元着色器和顶点着色器一样是GPU渲染管线上一个可以执行着色器程序的功能单元,顶点着色器处理的是逐顶点处理顶点数据,片元着色器是逐片元处理片元数据,通过给内置变量gl_FragColor赋值可以给每一个片元进行着色,值可以是一个确定的RGBA值,可以是一个和片元位置相关的值,也可以是插值后的顶点颜色,除了给片元进行着色之外通过关键字discard还可以实现哪些片元可以被丢弃,被丢弃的片元不会出现在帧缓冲区,自然不会显示在canvas画布上。
着色器(shader)是用来实现图像渲染的,用来替代固定渲染管线的可编辑程序。其中Vertex Shader(顶点着色器)主要负责顶点的几何关系等的运算,Fragment Shader(片元着色器)主要负责片源颜色等的计算。
(1)顶点着色器
顶点着色器,顾名思义,是对顶点进行一系列操作的着色器。顶点除了有基本的位置属性,还可以包含很多其他属性,比如纹理,法线等等。通过顶点着色器,显卡就知道顶点应该绘制在具体什么位置。顶点着色器是非常重要的着色器,且必须要我们自己去定义。
顶点着色器作用于每个顶点,可以生成每个顶点的最终位置。针对每个顶点,它都会执行一次,一旦每个顶点的最终位置确定了,GPU就可以把这些可见的集合组装成点,直线以及三角形,从而提高渲染场景和模型的速度。
(2)片元着色器
如果你已经有使用电脑绘图的经验,你就会知道在这个过程中你会画一个圆,然后是一个矩形,一条线,一些三角形,直到你组成你想要的图像。这个过程与手写一封信或一本书非常相似——它是一组执行一项又一项任务的指令。着色器也是一组指定,但指令是针对屏幕上的每个像素一次性执行的,这意味着你编写的代码必须根据屏幕上像素的位置表现出不同的行为,就像打字机一样,你的程序将作为一个接收位置并返回颜色的函数工作,并且当它被编译时,它会运行得非常快。
语言特性
是一种强类型语言,赋值时等号左右两侧的数据类型必须一致,否则报错,定义函数时必须指定函数的返回值类型
对大小写敏感
每个语句都以英文分号结束
程序自上而下逐行执行
Shader颜色
1.颜色分为红®,绿(g),蓝(b),透明度(a)四个值,组合为一个4维向量(r,g,b,a)。
2.每个分量的范围是0 ~1 的浮点数,即颜色值 /255
3.可以每个分量进行加减乘除计算,也可对整个颜色向量进行加减乘除计算。
(0.0,0.0,0.0,0.0) : 黑色
(1.0,1.0,1.0,1.0) : 白色
(1.0,0.0,0.0,1.0) : 红色
(0.0,1.0,0.0,1.0) : 绿色
(0.0,0.0,1.0,1.0) : 蓝色
Shader中颜色计算
经常遇到问题:
1.底色比如是黑色,无论乘上什么数都还是黑色;
2.如何是白色,乘以什么就是他本身。
3.底色是黑色,加上什么颜色就是颜色本身。
4.如果颜色系数小于0,那么返回的效果也都是黑色。
5.如果颜色系数大于1,那么返回的效果也都是白色。
shader的核心在与数学公式,在于数字的加减乘除,我们的学习过程一定是不断的学习数学,并且总结数学,将规律封装成关键公式去使用。
Shader中坐标系统
GLSL ES语言的变量,需要遵循以下原则:
1.变量名只能包含英文字符,数字和下划线即(a-z,A-Z,0-9,_ )
2.变量名首字母不能是数字
3.不能以gl_, webgl_或 webgl开头,这些前缀已经被OpenGL ES保留了
4.不能是GLSL ES内置的关键字和保留的关键字
GLSL ES关键字
GLSL ES保留字
变量基本类型
GLSL ES中变量类型中的基本类型主要有布尔类型,整型和浮点型。
基本类型的赋值和类型转换
使用等号(=)可以将值赋给变量,GLSL ES是强类型语言,在语义上 8 和 8.0是一个值,但是,将8 赋值给浮点型变量时会出错。
矢量类型
向量指的是具有大小(Magnitude)和方向(Direction)量。在物理学中也称为矢量,向量的表示通常使用小写字母加上向右的箭头——>表示,例如a;或者使用粗体的小写字母表示,例如a。向量具有平移不变形。向量只与大小和方向有关系,和向量的起点和终点没有关系。向量也只包含两个属性:大小和方向。对于空间中的两个点A和B,从A到B的向量可以表示为AB,计算方法为AB=B-A。GLSLES支持矢量类型,这种数据类型适合用来处理计算机图形。矢量类型的变量包含多个元素,每个元素是一个数值(整型数,浮点数或布尔值)。矢量将这些元素排成一列,可以用来表示顶点坐标或颜色值等。
矢量构造,赋值与取值
构造
在GLSL ES中,矢量非常重要,所以GLSL ES提供了丰富灵活的方式来创建矢量。
赋值
使用等号(=)来对矢量进行赋值操作,记住,赋值运算符左右两边的变量/值的类型必须一致,左右两边的元素个数也必须相同。
例:
vec3 v3=vec3(1.0,1.2,1.5);//三维矢量v3的设置为(1.0,1.2,1.5),即v3的三个分量x,y,z分别是1.0,1.2,1.5
vec2 v2=vec2(v3);//使用三维矢量v3赋值给二维矢量v2,实际是使用v3的前两个元素给v2赋值
vec4 v4=vec4(1.0);//将四位矢量v4设置为(1.0,1.0,1.0,1.0)
vec4 v4b=vec4(v2,v4);//使用v2和v4赋值v4b,结果为(1.0,1.2,1.0,1.0)
注:如果向构造函数中只传一个参数,构造函数会自动将整个参数赋值所有元素,但是如果给构造函数传了不止一个值,但比所需的参数个数少,就会报错。
取值
矢量即可以通过点运算符(.)来访问也可以通过方括号运算符([])来访问。通常矢量通过点运算符来访问,只需要在矢量变量名后接点运算符,然后在接上分量名。
x,y,z,w 用来获取顶点坐标的分量
r,g,b,a 用来获取颜色分量
s,t,p,q 用来获取纹理坐标的分量
矢量可以用来存储顶点坐标,颜色和纹理坐标,所以GLSL ES支持以上三种分量名,这样大家在使用的时候便于理解,实际上,一个矢量的x分量或r分量还少s分量都会返回这个矢量的第1个分量,一个矢量的y分量或g分量还少t分量都会返回这个矢量的第2个分量。
注:这点实际上咋们前端JavaScript很类似哈。
注:这块需要大家留意的需要多个分量搭配使用的方法,进行灵活运用。
矢量常见用法
向量归一化
向量的大小(长度)称为向量的模(norm),一般记作 shader中用法:length()
单位向量(Unit vector)指的是模为1的向量。单位向量的计算公式为:
单位向量的长度为1,一般只用来表示方向。shader中用法:normalize()
向量乘法
点乘(Dot product),又称为向量的内积,计算公式为:
点乘满足交换律以及分配律。
代数上表示
shader中用法:float f=dot(vec2(0.,2.),vec2(0.,1.))
向量加法与减法
向量的求和可以用两种法则:平行四边形法则或者三角形法则。
在代数上,对于在笛卡尔坐标系中定义的向量,向量求和可以简化为求向量各个对应坐标值的和。
shader中用法:vec2 f +=vec2(1.,1.)
向量点乘应用
1.计算两个向量夹角
2.计算一个向量到另一个向量上的投影
向量b在向量a上的投影满足
k的大小为:k= ||b⊥||=||b|| cos0.已知两个向量的内积,一个向量在另一个向量上的投影长度为
向量乘法
叉乘(Crossproduct),又称作向量的外积。两个向量叉乘的结果还是一个向量,这个向量和原来的两个向量垂直。叉乘的结果向量的长度为:
代数上表示
几何意义
在几何中,叉积得到的向量与a和b所在平面垂直,长度等于向量a和b组成的平行四边形的面积,改向量被称为法向量。如图
法向量反向:使用右手定则,首先伸出右手,并竖起大拇指,并将其余的四个手指握紧。
以最小角度旋转向量a,使其与向量b的方向一致,四个手指朝向如下图,大拇指所指的方向就是法向量的方向。
shader中用法:vec2 f=cross (vec2(.1,.2,.3),vec2(.3,.2,.1))
矩阵定义
矩阵(Matrix)是一个按照长方阵列排列的复数或实数集合,最早来自于方程组的系数及常数所构成的方阵。这一概念由19世纪英国数学家凯利首先提出。
矩阵类型介绍
mat2 2x2 的浮点数矩阵 mat2x2 2x2 的浮点数矩阵
mat3 3x3 的浮点数矩阵 mat2x3 2x3 的浮点数矩阵
mat4 4x4 的浮点数矩阵 mat2x4 2x4 的浮点数矩阵
mat3x2 3x2 的浮点数矩阵 mat4x2 4x2 的浮点数矩阵
mat3x3 3x3 的浮点数矩阵 mat4x3 4x3 的浮点数矩阵
mat3x4 3x4 的浮点数矩阵 mat4x4 4x4 的浮点数矩阵
在学习矩阵构造和赋值前先搞明白两个概念,按行主序和按列主序。webGL中矩阵元素是按列主序存储在数组中。
在webGL中使用的矩阵都是按列主序的,例如你用构造函数创建一个4x4的矩阵时,向矩阵构造函数中传入矩阵的每一个元素的数值来构造矩阵,注意传入值的顺序必须是列主序的。
左乘
右乘
矩阵构造,赋值与取值
取值
矩阵常见用法
矩阵和矩阵相乘
结构体定义
着色器中的机构体类似于我们其他语言中的类,GLSL的结构体可以组合基本类型和数组来形成用户自定义的类型,在定义结构体的时候,你可以直接定义一个结构体实例,或者在后面定义结构体实例。
结构体访问与使用
1,=为结构体赋值。
2.==,!=来判断两个结构体是否相等。
只有结构体中的每个成分都相等,那么这两个结构体才是相等的。
3.访问结构体的内部成员使用.(点语法)来访问。
4.固定大小的数组也可以被包含在结构体中。GLSL的结构体不支持嵌套定义。只有预先声明的结构体可以嵌套其中。
结构体注意事项
1.GLSL的结构体不支持嵌套定义,只有预先声明的结构体才可以嵌套其中。
2.结构体定义后面一定要加分号。
3.结构体定义是和main函数并列的。
4.结构体至少包含一个成员。
数组定义
在GLSL中,可以像C一样声明和访问数组
只支持一维数组
下标不能为负
//声明含有4个数浮点数的数组
float Array1[4];
//声明含2个vec4的对象数组
vec4 Array2[2];
注意:
1.整型字面量
2.数组的长度必须大于0;
3.数组前面定义的是每个子量的类型
数组的赋值和访问
1.只有整型常量表达式和uniform变量可以被用作数组的索引值。此外,与JavaScript或C不同,数组不能在声明时被一次性地初始化,而必须是显式地对每个元素进行初始化。如下所示:
vec4 Array[0]=vec4(4.0,5.0,6.0,1.0);
vec4 Array[1]=vec4(3.0,2.0,0.0,1.0);
2.数组元素可以通过索引值来访问,和JS等语言一样,索引值也是从0开始的。比如,下面这句代码就可以访问float Array变量的第3个元素:
float aa=Array[2];
数组运算
数组本身只支持[]运算符,但数组元素能够参与其自身类型支持的任意运算,如下:
//将floatArray的第2个元素乘以3.14
float f=Array[1] * 3.14
//将vec4Array的第1个元素乘以vec4
vec4 v4=vec4Array[0] * vec4(1.0,2.0,3.0,4.0);
函数定义
函数结构
GLSL ES的函数定义与C语言接近,基本构成包括返回类型,函数名,参数和函数体,若无返回值,需要使用void代替。具体结构如下:
返回类型 函数名 (type0 arg0,type1 arg1,…,typen argn,){
函数计算
retutn 返回值;
}
注意:GLSL 的函数不能递归,不能嵌套。
规范声明
与C语言一样,如果函数定义在其调用之后,那么必须在进行调用前先声明该函数的规范,规范声明预先告诉GLSL系统函数的参数,参数类型,返回值等信息。
流程控制
着色器中的流程控制与C和JavaScript语言中的流程控制几乎无异,主要是通过if语句和for语句等控制流程。
if语句
if语句有三种控制流程的语句模型,分别是if模型。if…else…模型和if…else if…else模型
for语句
大多数循环程序都是通过for语句实现的,GLSL ES语言一样,for语句的格式如下:
for(初始化表达式;条件表达式;循环歩进表达式){
重复执行的语句;
}
GLSL ES语言的for循环的循环变量有一些特殊的限制,具体如下:
1.一个for循环中只允许有一个循环变量,且只能是int类型或float类型;
2.循环歩进表达式必须是以下的形式中的一种,i++,i–,i+= 常量表达式,i-=常量表达式;
3.条件表达式必须是循环变量与整形常量的比较;
4.循环体内,循环变量不可以被赋值。
参数限定词
参数限定词顾名思义就是来限制函数参数的,根据参数不同的行为将它们分为以下几类:
只向函数中传值
在函数中被赋值
即向函数中传值也在函数中被赋值
存储限定词
存储限定词其实就是我们用来向着色器传值的变量类型,GLSL ES提供了attribute,uniform和varying三个限定词来声明特定用途的变量,此外,我们有时也会使用const限定字,它表示着色器中的某个变量是恒定的常量。
attribute变量
attribute变量只能出现在顶点着色器中,只能被声明为全局变量,被用来表示逐顶点的信息。顶点着色器中能够容纳的attribute变量的最大数目与设备有关,你可以通过访问内置的全局常量来获取该值(最大数目),支持webGL的环境都支持最少8个attribute变量。
uniform变量
uniform变量可以用在顶点着色器和片元着色器中,且必须是全局变量,uniform变量是只读的,不支持数组和结构体类型。
varying变量
varying限定词声明的变量也要求是全局变量,它担任着一个很重要的角色,就是从顶点着色器向片元着色器传值。
精度限定词
GLSL ES 新引入了精度限定字,目的是帮助着色器程序提高运行效率,削减内存开支。
精度默认值写法:
#ifdef GL_ES
precision mediump float;
#endif
WebGL程序支持三种精度,限定词分别是highp,mediump和lowp
通常会使用precision关键字为某一类型的变量设置默认精度,这个设置必须放置在程序的顶部。使用方式如下:
precision <精度限定词><类型名称>
默认精度限定参照表:
预处理指令
预处理指令用来在着色器程序真正编译之前对代码进行预处理,预处理指令都是以(#)开始。
当我们编写GLSL代码时,编译器需要先对代码进行处理,然后才能将其编译成机器可以执行的指令。预处理指令就是在这个处理过程中对代码进行一些操作。
预处理指令可以让我们更好地组织代码,提高代码的可读性和可维护性,同时也能够帮助我们调试和诊断代码问题。
内置变量
内置常量
光圈特效
fm.glsl文件
vt.glsl文件
vite引入
//在导入时,告诉vite,以字符串形式加载.glsl文件即可,即在文件后面加上“raw”参数即可:import vertexShader from './vt.glsl?raw'import fragmentShader from './fm.glsl?raw'function initshader(){material = new THREE.ShaderMaterial({vertexShader,fragmentShader,side: THREE.DoubleSide,uniforms: {innerCircleWidth: {value: 0},circleWidth: {value: 300},diff: {value: new THREE.Color('#e2fb00')},color: {value: new THREE.Color('#041cf3')},opacity: {value: 0.3},center: {value: new THREE.Vector3(0, 0, 0)}},transparent: true,});var groundGeo = new THREE.PlaneBufferGeometry( 600, 600 );var ground = new THREE.Mesh( groundGeo, material );ground.name='光效'ground.position.y=0.65ground.rotation.x = -Math.PI/2scene.add( ground );}//封装一个渲染函数 动画const animate=()=>{renderer.render(scene, camera)requestAnimationFrame(animate);if (material) {material.uniforms.innerCircleWidth.value += 10if(material.uniforms.innerCircleWidth.value > 600){material.uniforms.innerCircleWidth.value = -300}}}