WebGL实战解析(一) - 学习笔记

朱治龙
2022-04-11 / 0 评论 / 35 阅读 / 正在检测是否收录...
温馨提示:
本文最后更新于2022年05月07日,已超过977天没有更新,若内容或图片失效,请留言反馈。

3D基本元素

2D坐标系

2D坐标系由x轴和y轴构成。其中,笛卡尔坐标系是最常见的2D坐标系。

笛卡尔坐标系

l1uc0iz0.png

规定y轴向上为正方向,x轴向右为正方向

HTML5 Canvas 2D坐标系

原点(0,0)为左上角,向右为x轴的正方向,向下为y轴的正方向
l1uc46jg.png

3D坐标系

在2D坐标系上增加了表示深度的z轴,即3D物体里屏幕的深度
l1uc8lhg.png

HTML5 WebGL 3D坐标系

绘图的可是范围为[-1,+1]
l1uca7yz.png

网格与3D图形

  • 三角形是基础图形
  • 3D图形由一个或多个三角形组成
  • 网格由一个或多个图形组成,网格也叫模型

法线

始终垂直于某平面的虚线。在几何中,法线是指平面上垂直于曲线在某点切线的直线。
l1uchb95.png

3D变换概念

  • 变换原理是对顶点的改变
  • 变换的缩放、平移和旋转

矩阵

  • 逐个顶点的变换方法是复杂乏味的
  • 矩阵是复杂性解决之道

网格表面

纹理与材质

一个网格构成了物体的形状,纹理可以定义网格表面的外观

  • 纹理可以定义网格表面的外观
  • 材质是网格表面的特性

光照原理

  • 光线方向决定物体的明暗度与阴影
  • 在着色过程中,需要考虑光源类型与反射类型

光源类型

  • 平行光
  • 点光源光
  • 环境光

l1ufqa8o.png

反射类型

  • 漫反射:光色由入射光色、表面漆色和入射角决定
    l1ufu4d9.png
  • 环境反射:由入射光色和表面漆色决定

着色器

替代传统的固定渲染管线
可编程性

顶点着色器

顶点着色器用来描述顶点的位置、颜色的程序

l1ufxo0d.png

片元着色器

片元着色器是对网格表面像素的处理程序。

l1ufz6r9.png

3D 世界与立方体

  • 立方体的构成

一个正方形的立方体在WebGL程序中,实际上是绘制了12个三角形,再对它的表面进行着色,最终形成我们看到的效果。

  • 模型文件格式

相机、视口和投影

左下角的眼睛代表相机,相机角度的不同,观察到的场景的景象就是不同的。而它的远近(x)就决定了他到的物体的大小。图中的绿色区域代表视口,通俗的讲就是WebGL Canvas决定的,它的大小也就决定了它可视化的范围。后面红色的区域能够显示场景的最远距离到哪里。红绿块之间的可视化椎体,就决定了可视化世界的范围。最终的影像会在可视化视口中呈现出来。

投影的概念就是物体投影到前方视口上最终形成的影像

通过对相机角度的不同,它的椎体肯定是有变化的,它看到的投影到视口上的影像肯定也是不同的。
l1ug5gqy.png

第一个WebGL程序

开发WebGL程序的基本步骤

  1. 得到canvas标签
  2. 得到绘制上下文对象
  3. 编写着色器
  4. 初始化着色器
  5. 绘制
  6. 代码实现
<!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="./lib.js"></script>
</head>
<body>
    <canvas id="webgl" width="500" height="500"></canvas>
    <script>
        var canvasElement = document.getElementById('webgl')
        const context = canvasElement.getContext('webgl')
        // 绘制顶点着色器
        const vertexShaderSource = `
            attribute vec4 apos;
            void main() {
                gl_PointSize=20.0;
                gl_Position = apos;
            }
        `
        // 绘制片元着色器
        const fragShaderSource = `
        void main(){
            gl_FragColor = vec4(1.0,0.0,0.0,1.0);
        }
        `
        const program = initShader(context, vertexShaderSource, fragShaderSource)
        const aposLocation = context.getAttribLocation(program, 'apos')
        context.clearColor(0.2, 0.3, 0.5, 1.0)
        context.clear(context.COLOR_BUFFER_BIT)
        let x = 0.0
        let y = 0.0
        for(var i = 0, j = 0.1; i< 10; i++){
            x += y
            y += j
            context.vertexAttrib4f(aposLocation, x, y, 0.0, 1.0)
            context.drawArrays(context.POINTS, 0, 1)
        }
    </script>
</body>
</html>
// lib.js
/**
 * 
 * @param {CanvasContext} gl Canvas Context 对象
 * @param {*} vertexShaderSource   顶点着色器源码
 * @param {*} fragmentShaderSource  片元着色器源码
 * @returns 
 */
const initShader = function(gl, vertexShaderSource, fragmentShaderSource) {
    // 创建一个空的顶点着色器对象
    const vertexShader = gl.createShader(gl.VERTEX_SHADER)
    // 创建一个空的片元着色器对象
    const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER)
    // 加入源代码
    gl.shaderSource(vertexShader, vertexShaderSource)
    gl.shaderSource(fragmentShader, fragmentShaderSource)
    // 编译源代码
    gl.compileShader(vertexShader)
    gl.compileShader(fragmentShader)
    // 创建内部调用的应用程序
    var program = gl.createProgram()
    // 将着色器附着在应用程序上
    gl.attachShader(program, vertexShader)
    gl.attachShader(program, fragmentShader)
    // 连接
    gl.linkProgram(program)

    gl.useProgram(program)
    // 输出调试日志
    console.log(gl.getShaderInfoLog(fragmentShader))
    return program
}

WebGL 缓冲区对象

attribute 变量的使用

l2v6wytx.png

  • 在顶点着色器中,声明 attribute 变量
  • 将 attribute 变量赋值给 gl_Position 变量
  • 向 attribute 变量传递数据

缓冲区对象的创建与绑定

缓冲区对象是绘制面所必须的技术,否则只能绘制点,它可以一次性地写入多个顶点数据,这样就可以为我们描绘出一个图形的轮廓,它可以是一块内存区域,这块内存区域通常来说很可能是显卡分配的

创建步骤

1、创建缓冲区的方法 var buffer = gl.createBuffer()
2、根据返回值判断是否创建成功
3、绑定缓冲区方法 gl.bindBuffer(gl.ARRAY_BUFFER,buffer)
l2v7qesv.png

向缓冲区内写入数据

gl.bufferData(gl.ARRAY_BUFFER, data, useMethod)

  • data:是类型化数组
  • useMethod:如何使用内存中的数据,可选值有:

    • gl.STATIC_DRAW 一次性写入,多次绘制
    • gl.STREAM_DRAW 一次性写入,调用几次,比第一次调用的要少,但都是写入一次
    • gl.DYNAMIC_DRAW 多次写入,多次绘制

类型化数组:

  • Int8Array Unit8Array
  • Int16Array Unit16Array
  • Int32Array Unit32Array
  • Float32Array 最常用
  • Float64Array

缓冲区数据导入 attribute变量

vertexAttribPointer(location, size, type, normalized, stride, offset)
l2v9bt7v.png

完整代码示例

<!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>Document</title>
    <script src="./lib.js"></script>
</head>
<body>
    <canvas id="webglcanvas" width="500" height="500"></canvas>
    <script>
        var ctx = document.getElementById('webglcanvas').getContext('webgl')
        var vertexShaderSource = `
        attribute vec4 apos;
        void main(){
            gl_Position = apos;
            gl_PointSize = 15.0;
        }
        `
        var fragmentShaderSource = `
        void main(){
            gl_FragColor = vec4(1.0,0.0,0.0,1.0);
        }
        `
        var buf = ctx.createBuffer()
        ctx.bindBuffer(ctx.ARRAY_BUFFER, buf)
        var program = initShader(ctx,vertexShaderSource,fragmentShaderSource)
        var aposLocation = ctx.getAttribLocation(program, 'apos')
        var data = new Float32Array([
            0.6,0.8,
            0.2,0.5,
            -1.0,1.0,
            1.0,1.0,
            -1.0,-1.0,
            1.0,-1.0,
            0.0,0.0
        ])
        ctx.bufferData(ctx.ARRAY_BUFFER, data, ctx.STATIC_DRAW)
        ctx.vertexAttribPointer(aposLocation, 2, ctx.FLOAT, false, 0, 0)
        ctx.enableVertexAttribArray(aposLocation)
        ctx.clearColor(0.0,0.0,1.0,1.0)
        ctx.clear(ctx.COLOR_BUFFER_BIT)
        // var points = [
        //     {x:0, y:0},
        //     {x:0.6, y:0.8},
        //     {x:0.2, y:0.5}
        // ]
        // for(var i = 0; i< points.length; i++) {
        //     ctx.vertexAttrib4f(aposLocation, points[i].x, points[i].y, 1.0, 1.0)
        //     ctx.drawArrays(ctx.POINTS, 0, 3)
        // }
        ctx.drawArrays(ctx.POINTS, 0, 7)

    </script>
</body>
</html>

WebGL 基本图形绘制

点的绘制

gl.drawArrays(gl.POINTS, 0, 1)

线段的绘制

绘制线

gl.drawArrays(gl.LINES, start, count)

var vertexShaderSource = `
    attribute vec4 aPos;
    void main() {
        gl_Position = aPos;
        gl_PointSize = 20.5;
    }
    `
var fragmentShaderSource = `
    void main() {
        gl_FragColor = vec4(1.0,0.0,0.0,1.0);
    }
`
var gl = document.getElementById('canvas').getContext('webgl')
var program = initShader(gl,vertexShaderSource,fragmentShaderSource)
var posLocation = gl.getAttribLocation(program, 'aPos')
var data = new Float32Array([
    0.0,0.0,0.5,0.5,

    0.3,0.6,-0.3,-0.9
])
var buffer = gl.createBuffer()
gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW)

gl.vertexAttribPointer(posLocation, 2, gl.FLOAT, false, 0, 0)
gl.enableVertexAttribArray(posLocation)

gl.clearColor(0.0, 0.0, 0.0, 1.0)
gl.clear(gl.COLOR_BUFFER_BIT)
gl.drawArrays(gl.LINES, 0, 4)

运行效果:
l2vhn1cw.png

绘制多线段

gl.drawArray(gl.LINE_STRIP, start, count)
绘制Z形线

var vertexShaderSource = `
  attribute vec4 aPos;
  void main() {
      gl_Position = aPos;
      gl_PointSize = 20.5;
  }
  `
var fragmentShaderSource = `
  void main() {
      gl_FragColor = vec4(1.0,0.0,0.0,1.0);
  }
`
var gl = document.getElementById('canvas').getContext('webgl')
var program = initShader(gl,vertexShaderSource,fragmentShaderSource)
var posLocation = gl.getAttribLocation(program, 'aPos')
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)
gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW)

gl.vertexAttribPointer(posLocation, 2, gl.FLOAT, false, 0, 0)
gl.enableVertexAttribArray(posLocation)

gl.clearColor(0.0, 0.0, 0.0, 1.0)
gl.clear(gl.COLOR_BUFFER_BIT)
gl.drawArrays(gl.LINE_STRIP, 0, 4)

l2vi1nr2.png

绘制矩形框

// 绘制矩形框
var vertexShaderSource = `
    attribute vec4 aPos;
    void main() {
        gl_Position = aPos;
        gl_PointSize = 20.5;
    }
    `
var fragmentShaderSource = `
    void main() {
        gl_FragColor = vec4(1.0,0.0,0.0,1.0);
    }
`
var gl = document.getElementById('canvas').getContext('webgl')
var program = initShader(gl,vertexShaderSource,fragmentShaderSource)
var posLocation = gl.getAttribLocation(program, 'aPos')
var data = new Float32Array([
    -0.5,0.5,
    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)
gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW)

gl.vertexAttribPointer(posLocation, 2, gl.FLOAT, false, 0, 0)
gl.enableVertexAttribArray(posLocation)

gl.clearColor(0.0, 0.0, 0.0, 1.0)
gl.clear(gl.COLOR_BUFFER_BIT)
gl.drawArrays(gl.LINE_STRIP, 0, 5)

l2vi9sbp.png

绘制回路线段

gl.drawArray(gl.LING_LOOP,start, count)
可以把最后一个顶点跟第一个顶点连接在一起

// 绘制回路线段
var vertexShaderSource = `
    attribute vec4 aPos;
    void main() {
        gl_Position = aPos;
        gl_PointSize = 20.5;
    }
    `
var fragmentShaderSource = `
    void main() {
        gl_FragColor = vec4(1.0,0.0,0.0,1.0);
    }
`
var gl = document.getElementById('canvas').getContext('webgl')
var program = initShader(gl,vertexShaderSource,fragmentShaderSource)
var posLocation = gl.getAttribLocation(program, 'aPos')
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)
gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW)

gl.vertexAttribPointer(posLocation, 2, gl.FLOAT, false, 0, 0)
gl.enableVertexAttribArray(posLocation)

gl.clearColor(0.0, 0.0, 0.0, 1.0)
gl.clear(gl.COLOR_BUFFER_BIT)
gl.drawArrays(gl.LINE_LOOP, 0, 4)

多边形的绘制

gl.drawArray(gl.TRIANGLES, start, count)

// 绘制三角形
var vertexShaderSource = `
    attribute vec4 aPos;
    void main() {
        gl_Position = aPos;
        gl_PointSize = 20.5;
    }
    `
var fragmentShaderSource = `
    void main() {
        gl_FragColor = vec4(1.0,0.0,0.0,1.0);
    }
`
var gl = document.getElementById('canvas').getContext('webgl')
var program = initShader(gl,vertexShaderSource,fragmentShaderSource)
var posLocation = gl.getAttribLocation(program, 'aPos')
var data = new Float32Array([
    0.0,0.0,
    -0.5,-0.5,
    0.5,-0.5
])
var buffer = gl.createBuffer()
gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW)

gl.vertexAttribPointer(posLocation, 2, gl.FLOAT, false, 0, 0)
gl.enableVertexAttribArray(posLocation)

gl.clearColor(0.0, 0.0, 0.0, 1.0)
gl.clear(gl.COLOR_BUFFER_BIT)
gl.drawArrays(gl.TRIANGLES, 0, 3)

l2vjcx4i.png

绘制三角带

gl.drawArray(gl.TRIANGLE_STRIP,start, count)

// 绘制平行四边形
var vertexShaderSource = `
    attribute vec4 aPos;
    void main() {
        gl_Position = aPos;
        gl_PointSize = 20.5;
    }
    `
var fragmentShaderSource = `
    void main() {
        gl_FragColor = vec4(1.0,0.0,0.0,1.0);
    }
`
var gl = document.getElementById('canvas').getContext('webgl')
var program = initShader(gl,vertexShaderSource,fragmentShaderSource)
var posLocation = gl.getAttribLocation(program, 'aPos')
var data = new Float32Array([
   -0.3,0.3,
   0.5,0.3,
   -0.5,-0.3,
   0.3, -0.3
])
var buffer = gl.createBuffer()
gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW)

gl.vertexAttribPointer(posLocation, 2, gl.FLOAT, false, 0, 0)
gl.enableVertexAttribArray(posLocation)

gl.clearColor(0.0, 0.0, 0.0, 1.0)
gl.clear(gl.COLOR_BUFFER_BIT)
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4)

l2vjgazu.png

绘制三角扇

gl.drawArray(gl.TRIANGLE_FAN, start, count)

// 绘制六边形
var vertexShaderSource = `
    attribute vec4 aPos;
    void main() {
        gl_Position = aPos;
        gl_PointSize = 20.5;
    }
    `
var fragmentShaderSource = `
    void main() {
        gl_FragColor = vec4(1.0,0.0,0.0,1.0);
    }
`
var gl = document.getElementById('canvas').getContext('webgl')
var program = initShader(gl,vertexShaderSource,fragmentShaderSource)
var posLocation = gl.getAttribLocation(program, 'aPos')
var data = new Float32Array([
    0.0,0.0,
    -0.3,0.5,
    -0.6,0.0,
    -0.3,-0.5,
    0.3,-0.5,
    0.6,0.0,
    0.3,0.5,
    -0.3,0.5

])
var buffer = gl.createBuffer()
gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW)

gl.vertexAttribPointer(posLocation, 2, gl.FLOAT, false, 0, 0)
gl.enableVertexAttribArray(posLocation)

gl.clearColor(0.0, 0.0, 0.0, 1.0)
gl.clear(gl.COLOR_BUFFER_BIT)
gl.drawArrays(gl.TRIANGLE_FAN, 0, 8)

l2vkkixb.png

WebGL 图形变换

图形的移动

原理

图形的移动,实际上就是定量改变顶点的位置,在顶点着色器中,顶点修改是逐个进行的,且偏移值一致

平移代码示例

var main = function() {
    // 顶点着色器
    var vertexShaderSource = `
        attribute vec4 pos;
        uniform float a;
        uniform float b;
        void main() {
            gl_Position.x = pos.x + a;
            gl_Position.y = pos.y + b;
            gl_Position.z = 0.0;
            gl_Position.w = 1.0;
        }
    `
    // 片元着色器
    var fragmentShaderSource = `
        void main() {
            gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
        }
    `
    var gl = document.getElementById('canvas').getContext('webgl')
    var buffer = gl.createBuffer()
    gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
    var data = new Float32Array([
        0.0,0.0,
        -0.5,-0.5,
        0.5,-0.5
    ])
    gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW)

    var program = initShader(gl, vertexShaderSource, fragmentShaderSource)
    var a = 0.0, b = 0.0
    var posLocation = gl.getAttribLocation(program, 'pos')
    var aLocation = gl.getUniformLocation(program, 'a')
    var bLocation = gl.getUniformLocation(program, 'b')

    gl.vertexAttribPointer(posLocation, 2, gl.FLOAT, false, 0, 0)
    gl.enableVertexAttribArray(posLocation)
    var run = function() {
        gl.uniform1f(aLocation, a)
        gl.uniform1f(bLocation, b)

        gl.clearColor(0.0, 0.0, 1.0, 1.0)
        gl.clear(gl.COLOR_BUFFER_BIT)
        gl.drawArrays(gl.TRIANGLES, 0, 3)
        a += 0.05
        b += 0.05
        setTimeout(run, 50)
    }
    run()
}
main()

move.gif

图形的缩放

顶点不变的方式缩放

var main = function() {
    // 顶点着色器
    var vertexShaderSource = `
        attribute vec4 pos;
        uniform float zoomRadio;
        void main() {
            gl_Position.x = pos.x * zoomRadio;
            gl_Position.y = pos.y * zoomRadio;
            gl_Position.z = 0.0;
            gl_Position.w = 1.0;
        }
    `
    // 片元着色器
    var fragmentShaderSource = `
        void main() {
            gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
        }
    `
    var gl = document.getElementById('canvas').getContext('webgl')
    var buffer = gl.createBuffer()
    gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
    var data = new Float32Array([
        0.0,0.0,
        -0.5,-0.5,
        0.5,-0.5
    ])
    gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW)

    var program = initShader(gl, vertexShaderSource, fragmentShaderSource)
    var zoomRatio = Math.random()
    var posLocation = gl.getAttribLocation(program, 'pos')
    var zoomLocation = gl.getUniformLocation(program, 'zoomRadio')

    gl.vertexAttribPointer(posLocation, 2, gl.FLOAT, false, 0, 0)
    gl.enableVertexAttribArray(posLocation)
    var run = function() {
        gl.uniform1f(zoomLocation, zoomRatio)
        gl.clearColor(0.0, 0.0, 1.0, 1.0)
        gl.clear(gl.COLOR_BUFFER_BIT)
        gl.drawArrays(gl.TRIANGLES, 0, 3)
        zoomRatio = Math.random()
        setTimeout(run, 500)
    }
    run()
}
main()

zoom.gif

图形的旋转

var main = function() {
    // 顶点着色器
    var vertexShaderSource = `
        attribute vec4 pos;
        uniform float sinB;
        uniform float cosB;
        void main() {
            gl_Position.x = pos.x * cosB - pos.y * sinB;
            gl_Position.y = pos.x * sinB + pos.y * cosB;
            gl_Position.z = 0.0;
            gl_Position.w = 1.0;
        }
    `
    // 片元着色器
    var fragmentShaderSource = `
        void main() {
            gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
        }
    `
    var gl = document.getElementById('canvas').getContext('webgl')
    var data = new Float32Array([
        0.0,0.0,
        -0.5,-0.5
    ])
    gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW)

    var program = initShader(gl, vertexShaderSource, fragmentShaderSource)
    bindAttribute(gl, 'pos', data, program)
    var sinB, sinBLocation,cosB, cosBLocation
    sinBLocation = gl.getUniformLocation(program, 'sinB')
    cosBLocation = gl.getUniformLocation(program, 'cosB')
    var angle = -135;
    setInterval(function() {
        angle -= 1
        var t = Math.PI * angle / 180
        sinB = Math.sin(t)
        cosB = Math.cos(t)
        gl.uniform1f(sinBLocation, sinB)
        gl.uniform1f(cosBLocation,cosB)
        gl.clearColor(0.0, 0.0, 1.0, 1.0)
        gl.clear(gl.COLOR_BUFFER_BIT)
        gl.drawArrays(gl.LINES, 0, 2)
    }, 1000)
}
main()

rotate.gif

0

评论 (0)

取消