【webgl】基础知识和几何体案例

webgl如何工作

  1. WebGL简介:

WebGL(Web Graphics Library)是一种基于JavaScript API的Web图形编程技术,用于在浏览器中实现高性能的3D图形渲染。它是HTML5的一部分,通过使用HTML5的元素和OpenGL ES 2.0的着色语言(GLSL ES语言以字符串形式存在JS中),使得开发者可以利用浏览器的GPU加速能力来创建复杂的交互式3D图形应用程序。

  1. WebGL特点:

(1)低级别的硬件访问:webgl允许开发者直接访问计算机GPU,图形处理性能更优异;
(2)跨平台支持:webgl基于web标准支持H5,H5的浏览器都可以,无需插件和扩展;
(3)支持2d和3d;

  1. WebGL应用场景:

数字孪生城市规划(数据可视化、地图操作)、虚拟现实(VR)和增强现实(AR)应用、室内设计模拟和仿真、游戏开发、低代码开发等。

  1. webgl绘图流程(与canvas对比)

(1)canvas
获取canvas元素 → 获取2d上下文 → 绘图命令队列 → GPU渲染

绘图命令:fillRect、arc、drawImage等

(2)webgl
获取canvas元素 → 获取webgl上下文 → 编写着色器 → 创建缓冲区和顶点数据 → 绘制命令队列 → GPU渲染

绘图命令:gl.drawArrays或gl.drawElements

总言之,canvas是基于2D绘图的,使用简单的绘图api进行绘制,而webgl是基于opengl,需要编写着色器和操作缓冲区来进行高性能的2D和3D图形渲染。webgl绘图流程需要更多的步骤和概念,但它可以实现更复杂和高性能的图形效果。

  1. GPU渲染过程/渲染管线

(1)顶点处理(Vertex Processing):将顶点几何信息转化为照相机下的坐标信息,如矩阵变换、投影变换、光照计算、纹理坐标变换等。
(2)图元装配及光栅化(Primitive Assembly and Rasterization):根据顶点和屏幕空间/视口将图元(如点、线段、三角形)装配。光栅化是将几何图元转为图像/位图/片元。
(3)片元处理/片元插值(Fragment Processing)
片元:不是像素,是像素前身,除了rgba之外,还包含深度值、法线、纹理坐标等。
通过片元着色器,计算片元最终颜色和深度,并写入颜色缓冲区。
(3)像素处理(Pixel Processing)
计算其最终的颜色值。这包括对纹理进行采样、应用光照模型、执行像素着色器等操作。像素处理阶段是实现图像真实感和特效的关键阶段。
(4)光栅操作(Raster Operations):此阶段涉及对像素的混合、融合、遮罩、深度测试等操作。这些操作可以控制像素在屏幕上的显示方式,实现遮挡、透明度混合、深度测试等效果。

  1. 坐标系

webgl默认为右手坐标,物体距离越远越大;相反左手符合人眼,物体距离越远越小;将右手坐标系转换成左手坐标系,z值取反即可,以下是坐标参考图(图1.1,图1.2)。

图1.1-opengl坐标范围为[-1,1]

图1.2-opengl两种坐标左右手

着色器shader

  1. 顶点着色器:gl_Position和gl_PointSize
  2. 片元着色器:gl_FragColor
  • 创见着色器过程:
    编写opengl着色器源码 → 创建createShader → 指定源码shaderSource → 编译compileShader → 与程序对象关联attachShader → 使用程序linkProgram,useProgram
function initShader(gl, vertexSource, fragmentSource) {
    //创建着色器
    const vertexShader = gl.createShader(gl.VERTEX_SHADER);
    const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
    //指定着色器源码
    gl.shaderSource(vertexShader, vertexSource);
    gl.shaderSource(fragmentShader, fragmentSource);
    //编译着色器
    gl.compileShader(vertexShader);
    gl.compileShader(fragmentShader);

    if (!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS) || !gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)) {
        var info1 = gl.getShaderInfoLog(vertexShader);
        var info2 = gl.getShaderInfoLog(fragmentShader);
        throw `could not compile webgl shader<br>
        vertexShader: ${info1}<br>
        fragmentShader: ${info2}
        `;
    }

    //使用着色器,创建一个程序对象
    const program = gl.createProgram();
    gl.attachShader(program, vertexShader,);
    gl.attachShader(program, fragmentShader);
    gl.linkProgram(program);
    gl.useProgram(program);

    return program;
}

GLSLES语言

强类型语言,每一句都需要有分号。它注释语法和 JS 一样,变量名规则也和 JS 一样,不能使用关键字,保留字,不能以数字、 gl_、webgl_ 或 webgl 开头。

①大小写敏感
②强制分号
③通过main函数为入口,无返回值void,等式写在函数里
④注释单行//,多行/**/
⑤条件判断if(){}else{} elseif,循环for while do while,跳出continue,break,discard(该方法只在片元着色器中使用,表示放弃当前片元直接处理下一片元)

  1. 基本数据类型

GLSL 中主要有三种数据值类型,浮点数、整数和布尔。注意浮点数必须要带小数点。类型转换可以直接使用 float、int 和 bool 函数。

通过int(),float(),bool()转换类型

  1. 矢量类型,用于处理图像数据

vec2、vec3、vec4具有2、3、4个浮点数的矢量;
ivec2、ivec3、ivec4具有2、3、4个整数的矢量;
bvec2、bvec3、bvec4具有2、3、4个布尔数的矢量;
(1)构造赋值vec4 position=vec4(0.5,0.5,0.0,0.0)
(2)访问矢量里的分量顶点坐标x,y,z,w 纹理坐标s,t,p,q,如:position.x,position.xy

  1. 矩阵类型,用于处理图像数据

mat2(22)、mat3(33)、mat4(4*4)浮点矩阵

//注:矩阵构造入参是列主序
mat4 m =mat4(
	    1.0, 5.0, 0.0, 1.0,
        2.0, 6.0, 0.0, 1.0,
        3.0, 7.0, 0.0, 1.0,
        4.0, 8.0, 0.0, 1.0,
)
  1. 取样器 sampler

两种:sampler2D和samplerCube
只能声明成uniform,以下是2D纹理对象使用,webgl是通过纹理单元管理一张纹理图像,可以人为指定纹理编号

    const FRAGMENT_SHADER_SOURCE = `
            precision lowp float;//精度
            uniform sampler2D uSampler;//取样器
            varying vec2 vTex;
            void main(){
                gl_FragColor=texture2D(uSampler,vTex);
            }
        `;//片元
    
        //创建纹理对象
        const texture = gl.createTexture();
        //翻转图片Y轴
        gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1);
        //开启一个纹理单元
        gl.activeTexture(gl.TEXTURE0);//纹理编号
        //绑定纹理对象
        gl.bindTexture(gl.TEXTURE_2D, texture);//(type,texture),type有两种gl.TEXTURE_2D,gl.TEXTURE_CUBE_MAP,texture纹理对象    
        //处理放大缩小逻辑
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);//(type,pname,param)type同上,pname四种,param两种
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);//gl.TEXTURE_MIN_FILTER缩小
        //横向,纵向,平铺
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);//gl.TEXTURE_WRAP_S横向
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);//gl.TEXTURE_WRAP_T纵向
        //配置纹理图像
        gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, img);//(type,level,internalformat,format,dataType,image),internalformat和format值一致

        gl.uniform1i(uSampler, 0);//0为纹理编号
  1. 内置函数

(1)角度函数:radians角度转弧度,degress弧度转角度
(2)三角函数:sin正弦,cos余弦,tan正切,asin反正弦,acos反余弦,atan反正切
(3)指数函数:pow次方,exp自然质数,log对数,sqrt开平方,inversesqrt开平方的倒数
(4)通用函数:abs绝对值,min最小值,max最大值,mod取余数,sign取符号,floor向下取整,ceil向上取整,clamp限定范围,fract获取小数部分
(5)几何函数:length求长度,distance求两点距离(支持vec2,vec3,vec4),dot点积,cross差积,normalize(x)返回方向同x长度为1的向量

补充:

  1. 归一化函数normalize:通常用于将图像中的像素值标准化或映射到特定的范围内,使它们符合特定的要求或条件。
    归一化函数使用场景:
    (1)像素值范围映射;
    (2)对比度增强:通过拉伸或映射像素值;
    (3)光照校正:比如在设置照射方向时,将图像的亮度范围映射到一个更均匀的分布,从而减少光照影响;
    (4)特征提取和分类:图像预处理,使得图像具有相同的尺度和范围;
    (5)深度学习中的输入数据处理:通过将输入图像的像素值转换为 [0, 1] 或 [-1, 1] 的范围,可以帮助网络模型更好地收敛和处理数据;
  1. 存储限定词

声明js与着色器可传递的数据
(1)const常量
(2)atrribute:只在顶点着色器中,只能声明全局变量,存逐个顶点信息,如坐标position
(3)uniform:只读类型,强调一致性,存影响所有顶点的数据,如变换矩阵
(4)varying:从顶点着色器向片元着色器传递数据

精度限定precision,用于提升运行效率,减少内存开销
(1)可单独针对某个变量精度mediump float f;,存在精度歧义,不利于维护
(2)片元着色器使用浮点类型数据可以设置precision mediump float; 三种枚举:高highp、中mediump、低lowp

在顶点着色器中 int 和 float 都是 highp
在片元着色器中 int 是 mediump,float 没有定义
另外在顶点和片元着色器 sampler2D 和 samplerCube 都是 lowp

缓冲区buffer

解决多个顶点绘制问题
用gl.vertexAttribPointer替代gl.vertexAttrib4f方法

    const points = new Float32Array([
        -0.5, -0.5,
        0.5, -0.5,
        0.0, 0.5
    ])
    //创建buffer缓冲区对象
    const buffer = gl.createBuffer();
    //绑定给webgl
    gl.bindBuffer(gl.ARRAY_BUFFER, buffer);//(target,buffer),target有两种:gl.ARRAY_BUFFER顶点数据,gl.ELEMENT_ARRAY_BUFFER顶点索引值
    //const BYTES = points.BYTES_PER_ELEMENT//获取属性字节数
    //添加顶点数据,约定类型
    gl.bufferData(gl.ARRAY_BUFFER, points, gl.STATIC_DRAW);//(target,data,type)target同上保持一致,type数据类型3种,这里gl.STATIC_DRAW表示写入一次多次绘制
    //将缓冲分配给attribute变量
    gl.vertexAttribPointer(aPosition, 2, gl.FLOAT, false, 0, 0);//(location,size,type,normalized,stride,offset)size参数数量,normalized是否将数据归一区间,stride两个数据间隔几个字节,offset数据偏移量
    //启动attribute
    void gl.enableVertexAttribArray(aPosition);
    gl.drawArrays(gl.POINTS, 0, 3);

补充说明:
1、缓冲区的存储数据target两种取值:gl.ARRAY_BUFFER顶点数据,gl.ELEMENT_ARRAY_BUFFER顶点索引值,分别对应绘制图形两种方法gl.drawArrays和gl.drawElements

图4.1-绘制图形类型

  • 动画

requestAnimationFrame解决计时器延迟问题

    function animation() {
    	//更新顶点数据
        gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);//执行绘制命令
        requestAnimationFrame(animation);
    }
    animation();

矩阵matrix

是一个纵横排列的数据表格m行n列(m*n),作用:转换点坐标,(x,y,z,w)*(m*n矩阵)变成(x`,y`,z`,w`)。

  • 平移矩阵,参数是x1,y1,z1方向平移的值,如:x`=x+x1
  • 缩放矩阵,参数是x1,y1,z1方向缩放的倍数,如:x`=x*x1
  • 旋转矩阵,参数是deg角度,x`=x*cos(deg) - y*sin(deg),
    y`=x*sin(deg) + y*cos(deg)
图5.1-旋转矩阵推导过程

  • 投影,包含正射投影和透视投影
    (1)正射投影:把可视空间坐标映射到[-1,-1]范围内,参数
    (2)透视投影/中心投影:视线和z轴平行,参数有角度、画布宽高比、远距离和近距离
  • 视图矩阵:3D图形绘制本质就是webgl通过观察平面创建新的坐标系(映射关系),参数有视点,目标点,上方向/正方向

补充:
(1)3D三要素:视点(眼睛//相机)、目标点(物体)、上方向(正方向)
(2)x轴同时垂直上方向和z轴;上方向垂直z轴;y轴不一定垂直上方向,但同时垂直x轴和z轴;

纹理texture

webgl里使用纹理坐标和图形顶点坐标的映射关系来确定贴图

  1. 纹理坐标

canvas,img坐标相同x轴正向右,y轴正向下,纹理坐标如下图,

图6.1-图像坐标和纹理坐标对比

  1. 纹理单元

webgl通过纹理单元管理一张纹理图像的,图像尺寸尽量是2的整数倍,方便webgl对图形快速采样取值。gl.activeTexture开启纹理单元,可以自定义纹理编号,需要注意图像叠加是有时间顺序的,只有所有图像都加载完才能绘制。以下是实现2D纹理贴图的主要代码:

//片元着色器定义采样器
    const FRAGMENT_SHADER_SOURCE = `
            precision lowp float;
            uniform sampler2D uSampler;
            uniform sampler2D uSampler1;
            varying vec2 vTex;
            void main(){                
                vec4 c1=texture2D(uSampler,vTex);
                vec4 c2=texture2D(uSampler1,vTex);
                gl_FragColor=c1*c2;
            }
        `;
function getImage(src, location, index) {
        return new Promise(resolve => {
            const img = new Image();
            img.onload = function () {
                //创建纹理对象
                const texture = gl.createTexture();
                //翻转图片Y轴
                gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1);
                //开启一个纹理单元
                gl.activeTexture(gl[`TEXTURE${index}`]);//纹理编号
                //绑定纹理对象
                gl.bindTexture(gl.TEXTURE_2D, texture);//(type,texture),type有两种gl.TEXTURE_2D,gl.TEXTURE_CUBE_MAP,texture纹理对象    
                //处理放大缩小逻辑
                gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);//(type,pname,param)type同上,pname四种,param两种
                gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);//gl.TEXTURE_MIN_FILTER缩小
                //横向,纵向,平铺
                gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);//gl.TEXTURE_WRAP_S横向
                gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);//gl.TEXTURE_WRAP_T纵向
                //配置纹理图像
                gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, img);//(type,level,internalformat,format,dataType,image),internalformat和format值一致

                gl.uniform1i(location, index);//0为纹理编号
                resolve();
            }
            //1200*675
            img.src = src;
        })
    }

    const tex1 = getImage('./img/sky.jpg', uSampler, 0);
    const tex2 = getImage('./img/sky2.jpg', uSampler1, 1);
    Promise.all([tex1, tex2]).then(() => {
        gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
    });

几何体案例

功能拆分:

  1. 绘制2个圆锥(着色器,顶点数据缓冲区)

(1)着色器

    // 顶点着色器 
    const VERTEX_SHADER_SOURCE = `
            attribute vec4 aPosition;
            attribute vec4 aColor;
            uniform mat4 uMvpMatrix;
            varying vec4 vColor;
            varying vec3 vNormal; // 用于贴图
            void main(){
                vec4 vertexPosition=uMvpMatrix*aPosition;
                gl_Position=vertexPosition;
                vColor=aColor;
                // 传递法向量。因为位置是以几何中心为原点的
                vNormal = normalize(aPosition.xyz);
            }
        `;
    // 片元着色器 
    const FRAGMENT_SHADER_SOURCE = `
            precision mediump float;
            varying vec4 vColor;
            uniform samplerCube uSampler;
            varying vec3 vNormal;
            void main(){
                gl_FragColor=vColor;
                // gl_FragColor=textureCube(uSampler, normalize(vNormal));
            }
        `;
// 初始化着色器
function initShader(gl, vertexSource, fragmentSource) {
    //创建着色器
    const vertexShader = gl.createShader(gl.VERTEX_SHADER);
    const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
    //指定着色器源码
    gl.shaderSource(vertexShader, vertexSource);
    gl.shaderSource(fragmentShader, fragmentSource);
    //编译着色器
    gl.compileShader(vertexShader);
    gl.compileShader(fragmentShader);

    if (!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS) || !gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)) {
        var info1 = gl.getShaderInfoLog(vertexShader);
        var info2 = gl.getShaderInfoLog(fragmentShader);
        throw `could not compile webgl shader<br>
        vertexShader: ${info1}<br>
        fragmentShader: ${info2}
        `;
    }

    //使用着色器,创建一个程序对象
    const program = gl.createProgram();
    gl.attachShader(program, vertexShader,);
    gl.attachShader(program, fragmentShader);
    gl.linkProgram(program);
    gl.useProgram(program);

    return program;
}

(2)复习圆锥几何构造:

从上面看:是一个圆,严格说是一个正N多边形,N值越大,越接近圆。绘制时要用三角函数计算正N多边形的N个顶点坐标。从侧面看是个三角形:最下面是一个顶点,和上面的正N多边形顶点相连构成圆锥网格(图7.1)。

图7.1-圆锥从上、侧面看效果

// 定义顶点和颜色缓冲区
    function initVertexBuffers(gl) {
        let radius = 0.3;
        let height = 1;
        let divideNum = 60; // 自定义几份数据
        let theta = Math.PI * 2 / divideNum; // 一个圆2Π是360°
        /**
         *     4
         *   /    \
         * 3        1
         *  \      /
         *   \ 2  /
         *    \  /
         *     0
         */
        let vertices = [0, -height / 2, 0];
        // let vertices = [];
        for (let i = 0; i < divideNum; i++) {
            let x = radius * Math.cos(theta * i);
            let z = radius * Math.sin(theta * i);
            vertices.push(x, height / 2, z);
        }

        let colors = [];
        let baseColor = [[1.0, 0.4, 0.4], [0.4, 1.0, 0.4], [0.4, 0.4, 1.0], [0.2, 0.4, 0.0], [0.4, 0.3, 0.8], [0.8, 1, 0.1]];

        colors.push(1.0, 0.4, 0.4);
        for (let i = 1; i < vertices.length; i++) {
            // let color = baseColor[Math.floor(Math.random()*6)];
            // colors = colors.concat(color);
            colors.push(0.4, 1.0, 0.4);
        }

        let indices = [];
        for (let i = 1; i <= divideNum; i++) {
            if (i === divideNum) {
                indices.push(0, i, 1);
            } else {
                indices.push(0, i, i + 1);
            }
        }

        vertices = Float32Array.from(vertices);
        colors = Float32Array.from(colors);
        indices = Uint8Array.from(indices);

        var indexBuffer = gl.createBuffer();
        if (!indexBuffer)
            return -1;

        if (!initArrayBuffer(gl, program, vertices, 3, gl.FLOAT, 'aPosition'))
            return -1;

        if (!initArrayBuffer(gl, program, colors, 3, gl.FLOAT, 'aColor'))
            return -1;

        gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
        gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW);

        return indices.length;
    }

    function initArrayBuffer(gl, program, data, num, type, attribute) {
        var buffer = gl.createBuffer();
        if (!buffer) {
            console.log('Failed to create the buffer object');
            return false;
        }

        gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
        gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW);
        var a_attribute = gl.getAttribLocation(program, attribute);
        if (a_attribute < 0) {
            console.log('Failed to get the storage location of ' + attribute);
            return false;
        }
        gl.vertexAttribPointer(a_attribute, num, type, false, 0, 0);
        gl.enableVertexAttribArray(a_attribute);

        return true;
    }

  1. 混合矩阵(透视、视图、旋转)

Matrix4库是由<<WebGL编程指南>>作者写的提供WebGL的4*4矩阵操作的方法库

threejs里自带的Matrix4库
import { Matrix4 } from ‘https://unpkg.com/three/build/three.module.js’;

    function draw() {
        // 获取着色器变量
        const uMvpMatrix = gl.getUniformLocation(program, 'uMvpMatrix');
        // 设置视觉矩阵,三要素(视点,目标点,上方向)
        rotationAngle += 1;
        let eyex = 0;
        let eyey = 2.0;
        let eyez = 5.0;
        const eye = [eyex, eyey, eyez];
        const lookAt = [0.0, 0.0, 0.0];
        const up = [0.0, 1.0, 0.0];
        // 设置canvas的背景色
        gl.clearColor(0.0, 0.0, 0.0, 1.0);
        // 清理缓冲区
        gl.clear(gl.COLOR_BUFFER_BIT);
        // 开启隐藏面消除
        gl.enable(gl.DEPTH_TEST);
        // 开启半透明
        // gl.enable(gl.BLEND);
        // 设置半透明物体
        // gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
        // 贴纹理
        // initTexture(gl, program);
        var modelMatrix = new Matrix4() //创建模型矩阵        
        var viewMatrix = new Matrix4() //创建视图矩阵   
        var projMatrix = new Matrix4() //创建投影矩阵  
        var mvpMatrix = new Matrix4() //创建模型视图投影矩阵  
        modelMatrix.rotate(rotationAngle, 1.0, 1.0, 1.0) //设置模型矩阵,旋转
        viewMatrix.setLookAt(...eye, ...lookAt, ...up) //设置视点、视线和上方向    
        projMatrix.setPerspective(30, canvas.width / canvas.height, 1, 100) //设置透视投影矩阵
        mvpMatrix.set(projMatrix).multiply(viewMatrix).multiply(modelMatrix) //将模型矩阵、视图矩阵、投影矩阵相乘赋值给模型视图投影矩阵
        gl.uniformMatrix4fv(uMvpMatrix, false, mvpMatrix.elements);
        // 绘制
        gl.drawElements(gl.TRIANGLES, n, gl.UNSIGNED_BYTE, 0);
        // 动画
        requestAnimationFrame(draw);
    }
  1. 贴纹理绘制
    function initTexture(gl, program) {
        const uSampler = gl.getUniformLocation(program, 'uSampler');
        let index = 0;
        // 创建纹理对象
        const texture = gl.createTexture();
        // 翻转图片Y轴
        gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1);
        // 开启一个纹理单元
        gl.activeTexture(gl[`TEXTURE${index}`]);//纹理编号
        // 绑定纹理对象
        gl.bindTexture(gl.TEXTURE_CUBE_MAP, texture);//(type,texture),type有两种gl.TEXTURE_2D,gl.TEXTURE_CUBE_MAP,texture纹理对象    
        // 配置纹理图像
        const ctx = document.createElement("canvas").getContext("2d");

        ctx.canvas.width = 128;
        ctx.canvas.height = 128;

        const faceInfos = [
            { target: gl.TEXTURE_CUBE_MAP_POSITIVE_X, faceColor: '#F00', textColor: '#0FF', text: '+X' },
            { target: gl.TEXTURE_CUBE_MAP_NEGATIVE_X, faceColor: '#FF0', textColor: '#00F', text: '-X' },
            { target: gl.TEXTURE_CUBE_MAP_POSITIVE_Y, faceColor: '#0F0', textColor: '#F0F', text: '+Y' },
            { target: gl.TEXTURE_CUBE_MAP_NEGATIVE_Y, faceColor: '#0FF', textColor: '#F00', text: '-Y' },
            { target: gl.TEXTURE_CUBE_MAP_POSITIVE_Z, faceColor: '#00F', textColor: '#FF0', text: '+Z' },
            { target: gl.TEXTURE_CUBE_MAP_NEGATIVE_Z, faceColor: '#F0F', textColor: '#0F0', text: '-Z' },
        ];

        faceInfos.forEach((faceInfo) => {
            const { target, faceColor, textColor, text } = faceInfo;
            generateFace(ctx, faceColor, textColor, text);

            // 上传画布到立方体贴图的每个面。
            const level = 0;
            const internalFormat = gl.RGBA;
            const format = gl.RGBA;
            const type = gl.UNSIGNED_BYTE;
            // https://developer.mozilla.org/zh-CN/docs/Web/API/WebGLRenderingContext/texImage2D
            gl.texImage2D(target, level, internalFormat, format, type, ctx.canvas);
        });
        gl.generateMipmap(gl.TEXTURE_CUBE_MAP);
        gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR);

        gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
        gl.enable(gl.CULL_FACE);
        gl.enable(gl.DEPTH_TEST);

        gl.uniform1i(uSampler, index);// 0为纹理编号
    }

    function generateFace(ctx, faceColor, textColor, text) {
        const { width, height } = ctx.canvas;
        ctx.fillStyle = faceColor;
        ctx.fillRect(0, 0, width, height);
        ctx.font = `${width * 0.7}px sans-serif`;
        ctx.textAlign = 'center';
        ctx.textBaseline = 'middle';
        ctx.fillStyle = textColor;
        ctx.fillText(text, width / 2, height / 2);
    }
  1. 主流程
    // 获取webgl上下文
    const canvas = document.getElementById('webgl');
    const gl = canvas.getContext('webgl');// 或getWebGLContext(canvas)
    // 着色器
    const program = initShader(gl, VERTEX_SHADER_SOURCE, FRAGMENT_SHADER_SOURCE);
    // 顶点 颜色 索引缓冲区
    const n = initVertexBuffers(gl);
    // 绘制
    draw();
  1. 效果图(查看

3d代码细节

  • 视图对象前后关系,隐藏面消除与深度冲突处理
    开启隐藏面消除gl.enable(gl.DEPTH_TEST);
    绘制之前,常清除颜色深度缓冲区gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
  • 矩阵乘法,非转换性质,A*B和B*A是不同运算,使用时注意顺序
//复合函数矩阵
function mixMatrix(A,B){
    const result=new Float32Array(16);
    for (let i = 0; i < 4; i++) {
        result[i]=A[i]*B[0]+A[i+4]*B[1]+A[i+8]*B[2]+A[i+12]*B[3];//列乘行
        result[i+4]=A[i]*B[4]+A[i+4]*B[5]+A[i+8]*B[6]+A[i+12]*B[7];
        result[i+8]=A[i]*B[8]+A[i+4]*B[9]+A[i+8]*B[10]+A[i+12]*B[11];
        result[i+12]=A[i]*B[12]+A[i+4]*B[13]+A[i+8]*B[14]+A[i+12]*B[15];
    }
    return result;
}
  • 处理缓冲区内存不足报错GL_INVALID_OPERATION: Insufficient buffer size
    图像是一帧一帧绘制的,每一帧图像的状态都被保存到缓冲区叫帧缓冲区,至少包含模板缓冲、深度缓冲、颜色缓冲,使用gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT|gl.STENCIL_CLEAR_VALUE)

github-练习案例源码

学习资料:
《WebGL编程指南》电子书(百度网盘 ,提取码:0619)
知乎-webgl入门知识
WebGL基础文档
绘制圆锥案例
WebGL API查询文档

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值