WebGL

1、WebGL介绍

1.1 WebGL不足

效果较差:较于桌面开发API:Direct3D、OpenGL、UE、Unity。

开发成本:熟悉并掌握一定的数据知识,例如:线性代数。

硬件要求:开发及部署系统硬件要求较高,尤其GPU。

1.2 WebGL学习基础

物理知识、{HTML、CSS、JavaScript、计算机图形学}、数学基础

1.3 WebGL学习资料

网站:

课程:

书籍:

  • WebGL编程指南
  • WebGL高级
  • GPU编程与CG语言之阳春白雪下里巴人(康玉之)

 2、WebGL入门

2.1 WebGL容器(坐标系)

canvas的坐标系如下图,其中canvas坐标的单位都是“px”

 WebGL坐标系如下图,WebGL使用的是正交右手坐标系,每个方向可使用的值的区间都是(-1,1),超出该矩形区间的图像不会绘制。这些值与Canvas的尺寸无关,无论Canvas的长宽比是多少,WebGL的区间值都是一致的。

 2.2 WebGL渲染管线

渲染管线就像一条流水线,由一系列具有特定功能的数字电路单元组成,下一个功能单元处理上一个功能单元生成的数据,逐级处理数据。

顶点着色器和片元着色器是可编程的功能单元,拥有更大的自主性,还有光栅器、深度测试等不可编程的功能单元。

CPU会通过WebGL API和GPU通信,传递着色器程序数据

  • GPU执行的着色器程序可以通过useProgram方法切换。
  • 传递数据就是把CPU主存中的数据传递到GPU显存中。

顶点缓冲区:形状的坐标信息。待加工的数据/数组,将这组数据给到程序,就可以进行处理。

uniform数据:shader里面的数据,把“顶点缓冲区”的数据传递给“顶点着色器”。

图元装配:这些顶点缓冲区用来干什么?做“方便面”。

光栅器:矢量的图形转换成像素图形。

片元着色器:把颜色/纹理加到光栅化后的像素上。

归属测试/模板测试、深度测试(3D):绘制前的测试。

2.3 WebGL关键名词

(1)顶点着色器

GPU渲染管线上一个可以执行着色器语言的功能单元,具体执行的就是顶点着色器程序。

WebGL顶点着色器程序在JavaScript中以字符串的形式存在,通过编译处理后传递给顶点着色器执行。

总结:顶点着色器主要作用就是执行顶点着色器程序对顶点进行变换计算。例如顶点位置坐标进行旋转、平移等矩阵变换,变换后新的顶点坐标然后赋值给内置变量gl_Position,作为顶点着色器的输出,图元装配和光栅化环节的输入。

 (2)图元装配

硬件上具体怎么回事不用思考,从程序的角度来看,就是绘制函数drawArrays()或drawElements()第一个参数绘制模式mode控制顶点如何装配为图元:

  • gl.LINES:把两个顶点装配成一个线条图元。
  • gl.TRIANGLES:把三个顶点装配为一个三角面图元。
  • gl.POINTS:一个点域图元。

 (3)光栅化

分解成一些小的像素。

片元着色器

片元着色器和顶点着色器一样是GPU渲染管线上一个可以执行着色器程序的功能单元。顶点着色器处理的是逐顶点处理顶点数据,片元着色器是逐片元处理片元数据。

通过给内置变量gl_fragColor赋值可以给每一个片元进行着色,值可以是一个确定的RGBA值,可以是一个和片元位置相关的值,也可以是插值后的顶点颜色。

除了给片元进行着色外,通过关键字discard还可以实现哪些片元可以被丢弃,被丢弃的片元不会出现在帧缓冲区,自然不会显示在canvas画布上。

2.4 实例:鼠标动态绘制点

2.5 动态绘制多个点

蓝色:屏幕坐标系

绿色:canvas坐标系 

最里面是:WebGL坐标系,坐标范围是(-1,1)

2.6 绘制命令

(1)drawArrays()

POINTS

LINES LINE_STRIP LINE_LOOP

TRIANGLES TRIANGLE_STRIP TRIANGLES_FAN

(2)drawElements()

好处:当大量数据时,顶点缓冲区数据量很大时,内存会受到限制。使用索引缓冲区,可以节省内存,重复利用点。

用法:需要创建一个索引缓冲区

3 WebGL常用API

3.1 TypeArray类型化数组和Array无类型数组

TypeArray数组最大的作用:提升数组的性能。浏览器事先知道数组中的数据类型,故而处理起来更有效率。

JS中Array的内部实现是链表,可以动态增加减少元素,但是元素多时,性能会比较差。当访问某个元素时,需要通过链表一个一个地找下去。

类型化数组管理的是连续内存区域,知道了这块内存的起始位置,可以通过起始位置+N*偏移量(一次加法一次乘法操作)访问到第N个位置的元素。

类型化数组将实现拆分为缓冲和视图两部分。

  • 一个缓冲(ArrayBuffer)描述的是内存中的一段二进制数据,缓冲没有格式而言,并且不提供机制访问其内容。
  • 为了访问在缓存对象中包含的内存,你需要使用视图。视图可以将二进制数据转换为实际有类型的数组。
  • 一个缓冲可以提供给多个视图进行读取,不同类型的视图读取的内存长度不同,读取出来的数据格式也不同。

3.2 Float32Array和Float64Array

单精度,也即float,一般在计算机中存储占用4字节,也32位,有效位数为7位;双精度(double)在计算机中存储占用8字节,64位,有效位数为16位。

单精度是这样的格式,1位符号,8位指数,23位小数。 双精度是1位符号,11位指数,52位小数。 含义:表明单精度和双精度精确的范围不一样,

3.3 类型化数组的方法、属性和常量

get(index) 、set(index,value)、set(array,offset)、length、BYTES_PER_ELEMENT

3.4 顶点数据配置(顶点缓冲区)

createBuffer()方法会在GPU控制的显存上创建一个缓冲区用来存储顶点或顶点索引数据。通过deleteBuffer(buffer)表示:删除某个缓冲区,参数buffer表示顶点/顶点索引缓冲区的名字,也就是执行createBuffer()方法返回的对象变量名。

bindBuffer(target,buffer):target相同,也就是同类缓冲区在同一个时刻只能绑定一个,只有处于绑定状态才能传入数据。

  • target:gl.ARRAY_BUFFER表示顶点缓冲区;ELEMENT_ARRAY_BUFFER表示顶点索引缓冲区。
  • buffer:顶点缓冲区变量名。

bufferData(target,data,usage):把CPU控制的内存中类型数组传入GPU控制的线程顶点或顶点索引缓冲区。

  • target:gl.ARRAY_BUFFER表示顶点缓冲区;ELEMENT_ARRAY_BUFFER表示顶点索引缓冲区。
  • data:CPU内存
  • usage:通过不同的值控制传入缓冲区数据的方式、GPU使用缓冲区调用数据方式:gl.STATIC_DRAW静态绘制模式(只绘制一次);gl.STREAM_DRAW流绘制模式;gl.DYNAMTC_DRAW动态绘制模式

vertexAttribPointer(location,size,type,normalized,stride,offset)

规定GPU从顶点缓冲区去读取数据的方式,很多时候为了提高顶点数据的传输读取效率,往往会把顶点位置、顶点颜色、顶点法向量、纹理坐标交叉定义在一个类型数组汇总,一次性传入顶点缓冲区中,CPU和GPU不需要多次通信,只需要执行一次bufferData()方法,这时候GPU为了使用顶点缓冲区的不同用于数据,就要按照一定规律读取。

可以在同一个WebGL程序中定义多个该方法,每个方法的参数location分别指向一个不同的顶点变量,然后控制其后面其他的参数。

  • location:顶点着色器程序中顶点的位置(shader中的变量-》JS中的变量)
  • size:每次取几个数据(x,y,z,1.0)。如果该值为1,着色器程序中的顶点变量vec4后的第2,3分量是0,第4分量是1。
  • type:顶点数据类型,所有数据没有分界线,只能靠类型按作用的位bit数来分界。
  • normalized:布尔值,是否归一化到区间[0,1],[-1,1],一般为false
  • stride:点个数*字节数
  • offset:CPU从一组数据中第几元素开始读取数据

enableVertexAttribArray(location)

顶点缓冲区和GPU渲染管线之间存在一个硬件单元可以决定GPU是否能读取顶点缓冲区中的顶点数据。关闭方法:disableVertexAttribArray(location),location是顶点着色器程序中顶点变量的索引位置。

3.5 着色器

createShader:创建着色器对象,参数是着色器类型,标记一个着色器程序会被GPU渲染管线上哪一个着色器执行,

shaderSource:绑定源。把字符串形式的顶点着色器代码、片元着色器代码分配给各自的着色器对象。

compileShader:编译着色器程序。参数指定着色器程序源码。

creatProgram:创建程序对象。程序对象的意义是为了实现CPU和GPU的通信,控制GPU着色器的工作状态,切换不同的着色器程序。

attachShader:绑定着色器对象到一个程序对象上,每个程序对象就关联了一组顶点着色器程序、片元着色器程序。第一个参数表示目标程序对象,第二个参数表示要绑定的着色器对象。

linkProgram:在执行useprogram方法之前,要先链接程序对象program的顶点和片元着色器程序,检查着色器程序的错误(检查顶点、片元着色器程序中同名varying变量是否一一对应;检查顶点着色器程序中是否给varying变量赋值顶点数据;硬件资源有限,要检测attribute\uniform\varying变量的数量是否超出限制范围)。通过链接测试后,才能通过useprogram方法把着色器程序传递给GPU,否则报错。

useProgram:同一时刻GPU只能配置一组顶点、片元着色器程序。当你定义了多个程序对象,分别关联了一组顶点、片元着色器程序,不会同时传递给GPU。在代码中uswProgram的特点是当再次调用方法useProgram,使用新的program程序对象作为新的参数,再次执行绘制函数的时候CPU会与GPU进行通信,给GPU传入新程序对象program对应的顶点、片元着色器程序,这时候就实现了GPU着色器程序的切换,每次切换都会耗费一定的硬件资源,可以简单的类比CPU线程的切换。一般复杂的场景都会编写多套着色器程序,放在文件中,供WebGL程序调用。

3.6 绘制

drawArrays/drawElements:

mode:gl.POINTS画单独的点;gl.LINE_STRIP画一条直线到下一个顶点;gl.LINE_LOOP绘制一条直线到下一个顶点,并将最后一个顶点返回到第一个顶点;gl.LINES在一对顶点之间画一条线;gl.TRIANGLE_STRIP画一个三角形;gl.TRIANGLE_FAN;gl.TRIANGLES为一组三个顶点绘制一个三角形。

count:渲染的元素数量

type:类型,gl.UNSIGNED_BYTE, gl.UNSIGNED_SHORT, gl.UNSIGNED_INT

offset:偏移量。

4、着色器程序

02-打印多个向量_哔哩哔哩_bilibili

类似只有一个.cpp,其中有一个main()函数,函数声明等。

WebGL顶点/片段着色器程序在JavaScript中以字符串的形式存在,通过编译处理后传递给顶点/片段着色器执行。

4.1 GLSL ES 语法测试-打印方法

打印方法

(1)console.log

console.log()只能用在js语言中,GLSL ES语言并不支持。

(2)在canvas画布中获取像素数据

通过canvas.getContext()方法获取2d或webgl上下文对象的同时,也决定了canvas画布的命运。

  • canvas.getContext('2d')方法会让canvas画布变成2d画布,2d画布可以通过ctx.getImageData()方法获取画布中的像素。
  • canvas.getContext('webgl')方法会让canvas画布变成webgl画布,webgl画布可以通过ctx.readPixels()方法获取画布中的像素。

像素中的rgba四个分量相当于片元着色器程序中的vec4的4个数值。

//像素容器
const pixel = new Uint8Array(4);
//抓取像素
webgl.readPixels(500 / 2, 500 / 2, 1, 1, webgl.RGBA, webgl.UNSIGNED_BYTE, pixel);

 (3)案例一:画同心圆

var webgl;
//投影坐标系,顶点着色器内置的变量信息
var vertexString = `
void main(void){
gl_Position = vec4(0,0,0,1);
gl_PointSize = 500.0;//和canvasWidth一致
}
`;
var fragmentString = `
precision mediump float;
vec4 vf = vec4(1.0,200.0,3.0,4.0) + vec4(200.0,40.0,5.0,6.0);
void main(){
gl_FragColor = vf/255.0;
}
`;

//入口函数
function init() {
initWebgl();
initShader();
draw();
}
function initWebgl() {
let webglDiv = document.getElementById("webglCanvas");
webgl = webglDiv.getContext("webgl");
}
function initShader() {
//创建
let vsshader = webgl.createShader(webgl.VERTEX_SHADER);
let fsshader = webgl.createShader(webgl.FRAGMENT_SHADER);
//绑定着色器程序
webgl.shaderSource(vsshader, vertexString);
webgl.shaderSource(fsshader, fragmentString);
//编译着色器
webgl.compileShader(vsshader);
webgl.compileShader(fsshader);

//创建项目
let program = webgl.createProgram();
webgl.attachShader(program, vsshader);
webgl.attachShader(program, fsshader);

webgl.linkProgram(program);
webgl.useProgram(program);

webgl.program = program;
}

function draw() {
webgl.clearColor(0.0, 0.0, 0.0, 0.0);//冲刷颜色
webgl.clear(webgl.COLOR_BUFFER_BIT);
webgl.drawArrays(webgl.POINTS, 0, 1);//绘制
//像素容器
const pixel = new Uint8Array(4);
//抓取像素
webgl.readPixels(500 / 2, 500 / 2, 1, 1, webgl.RGBA, webgl.UNSIGNED_BYTE, pixel);
console.log(pixel);
}

运行结果:

(4)案例二:画4个正方形

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>着色器语言GLSL</title>
</head>

<body>
    <canvas id="webglCanvas" width="500px" height="500px" style="background-color:black"> </canvas>
    <script src="./gl-matrix.js"> </script>
    <script type="module">
        import Poly from './Poly.js';

        //js代码
        var webgl;
        //投影坐标系,顶点着色器内置的变量信息
        var vertexString = `
        attribute vec4 a_Position;
        void main(void){
            gl_Position = a_Position;
        }
        `;
        var fragmentString = `
        precision mediump float;
        uniform vec2 u_CanvasSize;
        float halfW = u_CanvasSize.x/2.0;
        float halfH = u_CanvasSize.y/2.0;
        void main(){
            mat4 mf = mat4(
              255,0,0,255,
              255,255,0,255,
              0,255,0,255,
              0,0,255,255
            );
            bool xb = gl_FragCoord.x < halfW;
            bool yb = gl_FragCoord.y < halfH;
            if (xb && yb) {
                gl_FragColor = mf[0] / 255.0;
            } else if (xb) {
                gl_FragColor = mf[1] / 255.0;
            } else if (yb) {
                gl_FragColor = mf[2] / 255.0;
            } else {
                gl_FragColor = mf[3] / 255.0;
            }
        }
        `;
        //vec4(0,1.0,1.0,1.0)
        //precision mediump float;
        var mat4_matrix = glMatrix.mat4;
        var orthoMat4 = mat4_matrix.create();
        //入口函数
        function init() {
            initWebgl();
            initShader();
            // initBuffer();
            drawT();
        }
        function initWebgl() {
            let webglDiv = document.getElementById("webglCanvas");
            webgl = webglDiv.getContext("webgl");
            //webgl.viewport(0, 0, webglDiv.clientWidth, webglDiv.clientHeight);//可视范围
            //mat4_matrix.ortho(orthoMat4, 0, webglDiv.clientWidth, webglDiv.clientHeight, 0, -1, 1);//设置webgl投影坐标系
        }
        function initShader() {
            //创建
            let vsshader = webgl.createShader(webgl.VERTEX_SHADER);
            let fsshader = webgl.createShader(webgl.FRAGMENT_SHADER);
            //绑定着色器程序
            webgl.shaderSource(vsshader, vertexString);
            webgl.shaderSource(fsshader, fragmentString);
            //编译着色器
            webgl.compileShader(vsshader);
            webgl.compileShader(fsshader);

            //创建项目
            let program = webgl.createProgram();
            webgl.attachShader(program, vsshader);
            webgl.attachShader(program, fsshader);

            webgl.linkProgram(program);
            webgl.useProgram(program);

            webgl.program = program;
        }
        function initBuffer() {
            let pointPosition = new Float32Array([0.0, 0.0, 0.0, 1.0]);//顶点缓冲区只有一个点
            let aPosition = webgl.getAttribLocation(webgl.program, "a_position");
            webgl.vertexAttrib4fv(aPosition, pointPosition);//一个点
            let uniforProj = webgl.getUniformLocation(webgl.program, "proj");
            webgl.uniformMatrix4fv(uniforProj, false, orthoMat4);

        }
        function drawT() {

            webgl.clearColor(0.0, 0.0, 0.0, 1.0);//冲刷颜色
            let webglDiv = document.getElementById("webglCanvas");
            const source = new Float32Array([-1, 1, -1, -1, 1, 1, 1, -1]);
            const rect = new Poly({
                gl: webgl,
                source: source,
                type: 'TRIANGLE_STRIP',
                attributes: {
                    a_Position: {
                        size: 2,
                        index: 0
                    }
                },
                uniforms: {
                    u_CanvasSize: {
                        type: 'uniform2fv',
                        value: [webglDiv.width, webglDiv.height]
                    }
                }
            });
            webgl.clear(webgl.COLOR_BUFFER_BIT);
            rect.draw();//绘制
            //圆环宽度
            for (let i = 0; i < 2; i++) {
                for (let j = 0; j < 2; j++) {
                    const px = webglDiv.width * (i + 0.5) / 2;
                    const py = webglDiv.height * (j + 0.5) / 2;
                    logPixel(px, py);
                }
            }

        }
        function logPixel(px, py) {
            //像素容器
            const pixel = new Uint8Array(4);
            //抓取像素
            webgl.readPixels(px, py, 1, 1, webgl.RGBA, webgl.UNSIGNED_BYTE, pixel);
            console.log(pixel);
        }
        init();
    </script>
</body>

</html>

4.2 GLSL ES 概述和基本规范

(1)概述

  • GLSL ES是在GLSL(OpenGL着色器语言)的基础上,删除和简化了一部分功能后形成的,ES版本主要降低了硬件损耗,减少了性能开销。
  • 实际上WebGL并不支持GLSL ES的所有特性。

(2)基本规范

  • 大小写敏感
  • 语句末尾必须要有分号
  • 以main函数为主函数
  • 注释语法和js一样
  • 基本数据类型:浮点型float;整型int;布尔型true和false

4.3 数据、变量和变量类型

(1)声明变量的方法

GLSL ES 是强类型语言,在声明变量时应指明变量类型,如:

float f = 1.0;
int i = 1;
bool b = true;

 (2)变量命名规范

  • 只能包含a-z;A-Z;0-9;_
  • 变量名首字母不能是数字
  • 不能是GLSL关键字,如attribute,vec4,bool
  • 不能是GLSL保留字,如cast,class,long
  • 不能以下单词开头,如gl_,webgl_,_webgl_

(3)变量的赋值

变量使用等号赋值,变量两侧的数据类型需要保持一致。

int i = 9; //√
int i = 9.0; //×
float f = 9; //×
float f = 9.0; //√

报错提示:

 

(4)变量的类型转换

float f = float(8);
  • 浮点数转整数:int(float)
  • 布尔转整数:int(bool)
  • 整数转浮点:float(int)
  • 布尔转浮点:float(bool)
  • 整数转布尔:bool(int)
  • 浮点转布尔:bool(float)

4.3 向量

(1)创建向量

GLSL ES支持2、3、4维向量,根据分量的数据类型,向量可以分为3类:

  • vec2、vec3、vec4:分量是浮点数
  • ivec2、ivec3、ivec4:分量是整数
  • bvec2、bvec3、bvec4:分量是布尔值

 创建向量,GLSL ES提供了非常灵活的创建方式:

//创建单个向量
vec3 v3 = vec3(1.0,2.0,3.0);//(1.0,2.0,3.0)
vec2 v2 = vec2(v3);//(1.0,2.0)
vec4 v4 = vec4(1.0);//(1.0,1.0,1.0,1.0)
//将多个向量合在一起
vec4 v4b = vec4(v2,v4);//(1.0,2.0,1.0,1.0)
//注意,等号左右两侧的类型应该一致。
vec4 v4 = vec2(1.0);//×

(2)向量分量的访问

  • 通过分量属性访问
v4.x、v4.y、v4.z、v4.w  //齐次坐标
v4.r、v4.g、v4.b、v4.a  //色值
v4.s、v4.t、v4.p、v4.q  //纹理坐标
  • 降分量的多个属性连在一起,可以获取多个向量
vec4 v4 = vec4(1.0,2.0,3.0,4.0);
v4.xy //(1.0,2.0)
v4.yx //(2.0,1.0)
v4.xw //(1.0,4.0)
  • 通过分量索引访问
v4[0]、v4[1]、v4[2]、v4[3]
  • 也可以用=赋值
v4.x = 2.0;
v4[0] = 1.0
v4.xy = vec2(211.0,233.0);

4.4 矩阵

(1)矩阵类型

GLSL ES 支持2、3、4维矩阵:

  • mat2
  • mat3
  • mat4

矩阵中的元素都是浮点型。

(2)矩阵的建立

GLSL ES中的矩阵是列主序的,在建立矩阵的时候,其参数结构有很多种。

  • 浮点数,其参数是安装列主序排列
vec4 v4_1 = vec4(1,2,3,4);
vec4 v4_2 = vec4(10,20,30,40);
vec4 v4_3 = vec4(101,201,301,401);
vec4 v4_4 = vec4(0,202,302,40);
mat4 mf = mat4(
    v4_1,
    v4_2,
    v4_3,
    v4_4);

mat4 mf2 = mat4(
    v4_1,
    v4_2,
    1,2,3,4,
    3,4,5,6);

//单位矩阵
mat4 mf3 = mat4(1);

//报错
mat4 mf4 = mat4(1.9,3.0);//报错

(3)矩阵的访问

vec4 v4_1 = vec4(1,2,3,4);
vec4 v4_2 = vec4(10,20,30,40);
vec4 v4_3 = vec4(101,201,301,401);
vec4 v4_4 = vec4(0,202,302,40);
mat4 mf = mat4(
    v4_1,
    v4_2,
    v4_3,
    v4_4);

//访问某行
mf(0);//1,2,3,4
//访问某个元素
mf[3][1];//202
//m[x]可以理解为一个向量,其内部的元素,可以像访问向量元素一样去访问。
mf[0].x;//1

(4)矩阵运算

 矩阵可以与以下数据进行各种运算:

  • 单独数字
  • 向量
  • 矩阵
vec4 v4_1 = vec4(1,2,3,4);
vec4 v4_2 = vec4(10,20,30,40);
vec4 v4_3 = vec4(101,201,301,401);
vec4 v4_4 = vec4(0,202,302,40);
mat4 mf = mat4(
    v4_1,
    v4_2,
    v4_3,
    v4_4);

//与单个数字运算
mf += 1;//矩阵中的所有元素都加1

//矩阵和矩阵的运算
mat4 mf1 = mat4(
    1,2,3,4,
    5,6,7,8,
    4,5,6,7,
    7,8,9,0);
//矩阵加法:相同索引位置的元素相加
//矩阵减法:相同索引位置的元素相减
//矩阵除法:相同索引位置的元素相除

//矩阵乘法:点积运算 c[0][0] = a[0][0]*b[0][0] + a[0][1]*b[1][0] +a[0][2]*b[2][0]+a[0][3]*b[3][0]

 4.5 struct

类似于js里的构造函数,只是语法规则不一样。 

(1)创建struct

struct Light{
    vec4 color;
    vec3 pos;
};

color 和 pos 既是结构体的属性,也是其形参。 

(2)struct的实例化

Light l1 = Light (
    vec4(3,4,5,6),
    vec3(1,2,3)
);

上面的vec4()和vec3()数据是结构体的实参,分别对应color属性和pos属性。 

(3)访问struct实例对象中的属性

gl_FragColor = l1.color/255.0;

4.6 数组

GLSL ES中数组的特性:

  • 属于类型数组
  • 只支持一维数组
  • 不支持pop()、push()等操作

在建立某个类型的数组时,在数据类型后面加[]即可,[]中要写数组的长度:

vec4 vs[2];
vs[0] = vec4(1,2,3,4);
vs[1] = vec4(5,6,7,8);

4.7 程序流程控制

GLSL中的if判断判断和js里if写法一致。都有if、else if、else判断。注意:if语句写太多会降低着色器的执行速度,但GLSL中没有switch语句,要注意。

GLSL中的for循环和js类似:

  • 循环遍历只能有一个,只能是int或float。
  • 在循环体中也可以使用break或continue。
var fragmentString = `
precision mediump float;
vec4 v4_1 = vec4(1,2,3,4);
vec4 v4_2 = vec4(10,20,30,40);
vec4 v4_3 = vec4(101,201,301,401);
vec4 v4_4 = vec4(0,202,302,40);
struct Light{
vec4 color;
vec3 pos;
};
void main(){
Light l1 = Light (
vec4(3,4,5,6),
vec3(1,2,3)
);
mat4 mf = mat4(v4_1,v4_2,v4_3,l1.color);
float dist = distance(gl_PointCoord,vec2(0.5,0.5));
for(int i = 0; i<4; i++)
{ 
    float r1=0.125 * float(i); 
    float r2=r1 + 0.125; 
    if(dist>= r1 && dist < r2){
        gl_FragColor=mf[i]/255.0; 
        break; 
    }else if (i==3){
        discard; 
    } 
} 
} `;

每个像素赋值后需要break结束循环。

4.8 函数

 返回值类型 函数名(形参){

        函数内容;

        return 返回值;

}

float getNum(vec3 color){
    return dot(color,vec3(0.2126,0.7162,0.0722));//点积运算
}

 4.9 参数限定词

  • in 参数:深拷贝,可读写,不影响原始数据
void getNum(in vec3 color){
    color.x = 0.0;
}
  •  out参数:浅拷贝,可读写,影响原始数据。
void getNum(out vec3 color){
    color.x = 0.0;
}
  •  const in 参数:只读,不可修改
void getNum(const in vec3 color){
     color.x = 0.0; // 报错,不可修改
}

GLSL ES中有许多内置方法。例如sin,cos,tan,atan等。 

4.10 变量的作用域

可以通过函数或{}建立块级作用域,块级作用域内建立的变量都是局部变量。局部变量只在块级作用域内有效。

在函数之外建立的变量就是全局变量。

在代码块内可以直接获取其父级定义域的变量。

变量不存在“变量提升” 现象,变量在使用时,需提前声明。

变量不能重复声明。

通过attribute、uniform、varying限定字声明的变量都是全局变量。

const可以声明常量,常量是只读的。

4.11 精度限定词

webgl提供了三种精度:

  • highp高精度
  • mediump中精度
  • lowp 低精度

一般中精度用得比较多,因为高精度太耗性能,而且有时候片元着色器不支持。

  • 设置某个变量的精度
mediump float size;
high vec4 position;
lowp vec4 color;
  •  设置某种数据类型的精度 precision
precision mediump float;
precision highp int;

着色器中,除了片元着色器的float数据没有默认精度,其他的数据都有默认精度。

因此在片元着色器中要提前声明好浮点类的精度。 

4.12 attribute、uniform和varying变量

着色器语言和C语言一样,通过一个表示特定数据类型的关键字声明一个变量,比如 int num,通过int 关键字声明一个整数型变量num,不过着色器语言还提供了三个关键字attribute、uniform和varying用来声明特定用途的变量。

attribute和uniform关键字目的:

为了javascript语言/C++语言可以通过相关的WebGL API把一些数据传递给着色器

关键字(变量类型)数据传递声明变量
attributejavascript -> 顶点着色器声明顶点数据变量
uniformjavascript -> 顶点、片元着色器声明非顶点数据变量
varying顶点着色器 -> 片元着色器声明需要差值计算的顶点变量

(1)attribute类型变量

attribute关键字通常用来声明与顶点数据相关的变量,比如顶点位置坐标数据、顶点颜色数据、顶点法向量数据等。

因为javascript没必要给片元着色器传递顶点数据,所以规定attribute关键字只能在顶点着色器中声明变量使用。只要注意attribute关键字声明顶点变量代码位于主函数main之外就可以。

//attribute声明顶点位置变量
attribute vec4 position;
//attribute声明顶点颜色变量
attribute vec4 a_color;
//attribute声明顶点法向量
attribute bec4 normal;
//与顶点相关的浮点数
attribute float scale;

void main(){
  gl_Position = vec4(position.x * scale, position.y, position.z, 1.0);
}

(2)uniform类型变量(非顶点类型)

uniform是为了javascript通过相关的WebGL API给着色器变量传递数据,比如传递一个光源的位置数据、一个光源的方向数据、一个光源的颜色数据、一个用于顶点变换的模型矩阵、一个用于顶点变换的视图矩阵。一般用在片元着色器中。

不过要注意如果是顶点相关的变量,比如顶点位置、顶点颜色等顶点数据相关变量不能使用关键字uniform去声明,主要是顶点的数据往往不是一个,通常有很多个顶点,而且这些顶点都要逐顶点执行main函数中的程序,所以为了声明顶点数据相关的变量,着色器语言规定了一个新的关键字attribute。

javascript可以给顶点着色器的变量传递数据,也可以给片元着色器的变量传递数据,也就是说uniform关键字既可以在顶点着色器中使用,也可以在片元着色器中使用。只要注意uniform关键字声明变量需要在主函数main之前声明。

var fragmentString = '
    precision mediump float;
    uniform vec4 u_Color;
    void main(){
    	gl_FragColor=u_Color;
    }';

(3)varying类型变量

如果在顶点着色器中声明了一个顶点的颜色变量,如果想在片元着色器中获得顶点颜色插值计算以后的数据,需要同时在顶点着色器和片元着色中声明插值后的颜色数据。varying vec4 v_color。

顶点着色器

attribute vec4 a_color;//attribute声明的顶点颜色变量
varying vec4 v_color;//varying声明顶点颜色插值后的变量
void main(){
  v_color = a_color;//顶点颜色插值计算
}

片元着色器

varying vec4 v_color;//接收顶点着色器中v_color数据
void main(){
  gl_FragColor = v_color;//插值后颜色数据赋值给对应的片元
}

 变量attribute,注意a_Position大小写敏感。

var vertexString = `
attribute vec4 a_Position;
void main(){
gl_Position = a_Position;
}
`;

4.13 GLSL着色器中的内置变量

着色器语言在GPU的着色器单元执行,javascript语言、C语言在CPU上执行。

普通变量,着色器语言和javascript语言一样需要先声明后使用,所谓内置变量就是不用声明可以直接赋值,主要是为了实现特定的功能。

内置变量含义值数据类型
gl_PointSize点渲染模式,方形点区域渲染像素大小float
gl_Position顶点位置坐标vec4
gl_FragColor片元颜色值vec4
gl_FragCoord片元坐标,单位像素vec2
gl_PointCoord点渲染模式对应点像素坐标vec2

(1)点像素大小gl_PointSize

当WebGL执行绘制函数gl.drawArrays()绘制模式是点模式gl.POINTS的时候,顶点着色器语言main函数中才会用到内置变量gl_PointSize,使用内置变量gl_PointSize主要是用来设置顶点渲染出来的方形点像素大小。

顶点着色器

void main(){
  gl_PointSize = 20.0;//赋值像素大小,注意为浮点数
}

绘制代码

gl.drawArrays(gl.POINTS,0,点数量);//绘制函数绘制模式:点模式gl.POINTS

(2)顶点坐标gl_Position

gl_Position内置变量主要和顶点相关,出现的位置是顶点着色器语言的main函数中。gl_Position内置变量表示最终传入片元着色器片元化要使用的顶点位置坐标

  • 如果只有一个顶点:
void main() {
  //顶点位置,位于坐标原点
  gl_Position = vec4(0.0,0.0,0.0,1.0);
}

内置变量gl_Position的值是四维向量vec4(x,y,z,1.0),前三个参数表示顶点的xyz坐标值,第四个参数是浮点数1.0

  • 如果有多个顶点
<script id="vertexShader" type="x-shader/x-vertex">
  //attribute声明vec4类型变量apos
  attribute vec4 apos;
  void main() {
    //顶点坐标apos赋值给内置变量gl_Position
    //逐顶点处理数据
    gl_Position = apos;
  }
</script>

 多个顶点的时候,内置变量gl_Position对应的值是attribute关键字声明的顶点位置坐标变量apos,顶点位置坐标变量apos变量对应了javascript代码中多个顶点位置数据。

如果你想完全理解内置变量gl_Position,必须建立逐顶点的概念,如果javascript语言中出现一个变量赋值,你可以理解为仅仅执行一次,但是对于着色器中不能直接这么理解,如果有多个顶点,你可以理解为每个顶点都要执行一遍顶点着色器主函数main中的程序

  •  逐顶点处理案例

着色器源码

//矩阵变换
<script id="vertexShader" type="x-shader/x-vertex">
  attribute vec4 apos;
  void main() {
    mat4 m4 = mat4(1,0,0,0,  0,1,0,0,  0,0,1,0,  -0.4,0,0,1);
    // 逐顶点进行矩阵变换
    gl_Position = m4*apos;
  }
</script>

顶点数据传递

<script>
    var vertexShaderSource = document.getElementById( 'vertexShader' ).innerText;//顶点着色器源码
    var fragShaderSource = document.getElementById( 'fragmentShader' ).innerText; //片元着色器源码
    var program = initShader(gl,vertexShaderSource,fragShaderSource);//初始化着色器
    var aposLocation = gl.getAttribLocation(program,'apos'); //获取顶点着色器的位置变量apos,即aposLocation指向apos变量。
    //类型数组构造函数Float32Array创建顶点数组
    var data=new Float32Array([0.5,0.5,-0.5,0.5,-0.5,-0.5,0.5,-0.5]);   
    var buffer=gl.createBuffer();//创建缓冲区对象
    gl.bindBuffer(gl.ARRAY_BUFFER,buffer);//绑定缓冲区对象,激活buffer
    gl.bufferData(gl.ARRAY_BUFFER,data,gl.STATIC_DRAW);//顶点数组data数据传入缓冲区
    gl.vertexAttribPointer(aposLocation,2,gl.FLOAT,false,0,0);//缓冲区中的数据按照一定的规律传递给位置变量apos
    //允许数据传递
    gl.enableVertexAttribArray(aposLocation);
</script>

(3)片元颜色gl_FragColor

用来设置片元像素的颜色。内置变量gl_FragColor的值是四维向量vec4(r,g,b,a),前三个参数表示片元像素颜色值RGB,第四个参数是片元像素透明度A,1.0表示不透明,0.0表示完全透明。

对于内置变量gl_FragColor而言,需要建立逐片元的概念。顶点经过片元着色器片元化以后,得到一个个片元/或者说像素点,然后通过内置变量gl_FragColor给每一个片元设置颜色值,所有片元可以使用同一个颜色值,也可能不是同一个颜色值,可以通过特定算法计算或者纹理像素采样。

  •  根据位置设置渐变色
  void main() {
    gl_FragColor = vec4(gl_FragCoord.x/500.0*1.0,1.0,0.0,1.0);// 片元沿着x方向渐变
  }
  • 纹理采样
varying vec2 v_TexCoord;// 接收插值后的纹理坐标
uniform sampler2D u_Sampler;// 纹理图片像素数据
void main() {
  gl_FragColor = texture2D(u_Sampler,v_TexCoord); // 采集纹素,逐片元赋值像素值
}

(4)片元坐标gl_FragCoord

内置变量gl_FragCoord表示WebGL在canvas画布上渲染的所有片元或者说像素的坐标,坐标原点是canvas画布的左上角,x轴水平向右,y竖直向下,gl_FragCoord坐标的单位是像素,gl_FragCoord的值是vec2(x,y),通过gl_FragCoord.x、gl_FragCoord.y方式可以分别访问片元坐标的纵横坐标。

  • 把canvas画布上不同区域片元设置为不同颜色。
<script id="fragmentShader" type="x-shader/x-fragment">
  void main() {
    if(gl_FragCoord.x < 300.0){     
      gl_FragColor = vec4(1.0,0.0,0.0,1.0);// canvas画布上[0,300)之间片元像素值设置
    }else if (gl_FragCoord.x <= 400.0) {     
      gl_FragColor = vec4(0.0,1.0,0.0,1.0);// canvas画布上(300,400]之间片元像素值设置
    }else {     
      gl_FragColor = vec4(0.0,0.0,1.0,1.0);// canvas画布上(400,500]之间片元像素值设置
    }    
  }
</script>

(5)渲染点片元坐标gl_PointCoord

 绘制函数gl.drawArrays()绘制模式参数设置为点渲染模式gl.POINTS,WebGL会把顶点渲染为一个方形区域。

一个顶点渲染为一个方形区域,每个方形区域可以以方向区域的左上角建立一个直角坐标系,然后使用内置变量gl_PointCoord描述每个方形区域中像素或者说片元的坐标,比如方形区域的左上角坐标是(0.0,0.0),每个方形区域几何中心坐标是(0.5,0.5),右下角坐标是(1.0,1.0)。

应用案例:片元着色器代码设置可以把默认渲染效果更改为圆形区域。

<script id="fragmentShader" type="x-shader/x-fragment">
  precision lowp float;// 所有float类型数据的精度是lowp
  void main() {
    float r = distance(gl_PointCoord, vec2(0.5, 0.5));// 计算方形区域每个片元距离方形几何中心的距离
    if(r < 0.5){
      gl_FragColor = vec4(1.0,0.0,0.0,1.0);//方形区域片元距离几何中心半径小于0.5,像素颜色设置红色
    }else {
      discard;//方形区域距离几何中心半径不小于0.5的片元剪裁舍弃掉
    }
  }

</script>

 4.14 纹理采样函数sampler2D和texture2D

5、WebGL中级

5.1 WebGL的平移、旋转、缩放

平移:

x = x + dx;
y = y + dy;
z = z;

旋转:

x = x * cosp - y * sinp;
y = x * sinp + y * cosp;
z = z;

缩放:

x = x * 2;
y = y * 2;
z = z;

代码示例:

//片段着色器程序
var vertexString = `
attribute vec3 a_position;
uniform float angle;
void main(void){
gl_Position = vec4(a_position.x*cos(angle) -a_position.y*sin(angle),a_position.x*sin(angle) + a_position.y*cos(angle),a_position.z,1.0);
gl_PointSize = 60.0;
}
`;
//angle的赋值
let uAngle = webgl.getUniformLocation(webgl.program, "angle");
let angle = 0 * Math.PI / 180;
webgl.uniform1f(uAngle, angle);

5.2 贪吃蛇示例

requestAnimationFrame:

  • 会把每一帧中的所有DOM操作集中起来,在一次重绘或回流中完成,并紧紧跟随浏览器的刷新频率。
  • 在隐藏或不可见的元素中(用户看不到就不会重绘,setTimeout是DOM中所有元素都会显示),requestAnimationFrame将不会进行重绘和回流,以减少CPU/GPU/内存。
  • 如果页面不是激活状态下,画面会暂停(显示一个标签页,其他标签页不会刷新),有效节省了CPU。
window.requestAnimFrame = (function(){return
window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame || 
window.msRequestAnimationFrame ||
function(callback){window.setTimeout(callback,1000/60);} ;
})();
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>webgl旋转</title>
    <script src="./gl-matrix.js"> </script>
    <script>

        //js代码
        var webgl;
        //投影坐标系,顶点着色器内置的变量信息
        var vertexString = `
        attribute vec3 a_position;
        uniform float anglex;
        uniform float angley;
        void main(void){
            gl_Position = vec4(a_position.x + anglex,a_position.y + angley,a_position.z,1.0);
            gl_PointSize = 60.0;
        }
        `;
        var fragmentString = `
        void main(){
            gl_FragColor = vec4(0,1.0,1.0,1.0);
        }
        `;
        var mat4_matrix = glMatrix.mat4;
        var orthoMat4 = mat4_matrix.create();
        //入口函数
        function init() {
            initWebgl();
            initShader();
            initBuffer();
            initEvent()
            draw();
        }
        function initWebgl() {
            let canvasDiv = document.getElementById("webglCanvas");
            webgl = canvasDiv.getContext("webgl");
            webgl.viewport(0, 0, canvasDiv.clientWidth, canvasDiv.clientHeight);
            mat4_matrix.ortho(orthoMat4, 0, canvasDiv.clientWidth, canvasDiv.clientHeight, 0, -1, 1);
        }
        function initShader() {
            let verShader = webgl.createShader(webgl.VERTEX_SHADER);
            let fraShader = webgl.createShader(webgl.FRAGMENT_SHADER);

            webgl.shaderSource(verShader, vertexString);
            webgl.shaderSource(fraShader, fragmentString);

            webgl.compileShader(verShader);
            webgl.compileShader(fraShader);

            if (!webgl.getShaderParameter(verShader, webgl.COMPILE_STATUS)) {
                var err = webgl.getShaderInfoLog(verShader);
                alert(err);
                return;
            }
            if (!webgl.getShaderParameter(fraShader, webgl.COMPILE_STATUS)) {
                var err = webgl.getShaderInfoLog(fraShader);
                alert(err);
                return;
            }

            let program = webgl.createProgram();
            webgl.attachShader(program, verShader);
            webgl.attachShader(program, fraShader);

            webgl.linkProgram(program);
            webgl.useProgram(program);
            webgl.program = program;
        }
        let lineBuffer;
        function initBuffer() {
            let arr = [
                0.1, 0.1, 0,
                0.2, 0.5, 0,
                0.7, 0.4, 0];
            // let arr = [
            //     100, 400, 0, 1.0,
            //     100, 500, 0, 1.0,
            //     200, 400, 0, 1.0];
            lineBuffer = new Float32Array(arr);
            let vBuffer = webgl.createBuffer();
            webgl.bindBuffer(webgl.ARRAY_BUFFER, vBuffer);
            webgl.bufferData(webgl.ARRAY_BUFFER, lineBuffer, webgl.STATIC_DRAW);
            let aPosition = webgl.getAttribLocation(webgl.program, "a_position");
            webgl.vertexAttribPointer(aPosition, 3, webgl.FLOAT, false, 0, 0);
            webgl.enableVertexAttribArray(aPosition);

            //片段着色器程序
            // let uniforProj = webgl.getUniformLocation(webgl.program, "proj");
            // webgl.uniformMatrix4fv(uniforProj, false, orthoMat4);
            
        }
        let countx = 0;
        let county = 0;
        let numx =0 ;
        let numy =0;
        function initEvent(){
            document.onkeydown = handleKeyDown;
        }
        function handleKeyDown(event){
            if(String.fromCharCode(event.keyCode) == "W"){
                numy++; 
            }
            if(String.fromCharCode(event.keyCode) == "S"){
                numy--;
            }
            if(String.fromCharCode(event.keyCode) == "D"){
                numx++;
            }
            if(String.fromCharCode(event.keyCode) == "A"){
                numx--;
            }
        }
        
        function draw() {
            window.requestAnimationFrame(draw);
            // // //  //片段着色器程序
            let xAngle = webgl.getUniformLocation(webgl.program, "anglex");
            let yAngle = webgl.getUniformLocation(webgl.program, "angley");
            let countx = numx * Math.PI / 180;
            let county = numy * Math.PI / 180;
            webgl.uniform1f(xAngle, countx);
            webgl.uniform1f(yAngle, county);

            webgl.clearColor(0.0, 0.0, 0.0, 1.0);
            webgl.clear(webgl.COLOR_BUFFER_BIT);
            webgl.drawArrays(webgl.TRIANGLES, 0, 3);
        }
    </script>
</head>

<body onload="init()">
    <canvas id="webglCanvas" width="500" height="500" style="background-color: black;"> </canvas>
</body>

</html>

5.3 WebGL纹理

(1)纹理理论

createTexture:创建纹理

bindTexture:绑定纹理,告诉WebGL把本地加载的纹理绑定到WebGL

texImage2D:加载纹理图像,配置属性信息

texParameteri:配置纹理参数,纹理要展示什么效果

activeTexture:激活纹理(选择哪个纹理进行显示)

bindTexture:绑定纹理

uniform1i:给纹理赋值

(2)单纹理绘制

  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值