webgl中的三维世界

34 阅读6分钟

3D基础

视点,目标点,上方向

视点:眼睛 目标点:要看的物体 上方向:正方向

image.png

辅助函数

归一化函数:归一化到0-1区间 叉积,求两个平面的法向量 点积,求某点在xyz轴上的投影长度 向量差,获取视点到目标点之间的向量

image.png (z轴是面向自己的)

    //归一化函数
    function normalized(arr){
        let sum = 0
        for (let i = 0; i < arr.length; i++) {
            sum = arr[i] * arr[i];         
        }

        const middle += Math.sqrt(sum)

        for (let i = 0; i < arr.length; i++) {
            arr[i] = arr[i] / middle;         
        }
    }

    //叉积 获取法向量
    function cross(a,b){
        return new Float32Array([            a[1]*b[2]-a[2]*b[1],
            a[2]*b[0]-a[0]*b[2],
            a[0]*b[1]-a[1]*b[0],
        ])
    }

    //点积 获取投影长度
    function dot(a,b){
        return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
    }

    //向量差
    function minus(a,b){
        return new Float32Array([            a[0]-b[0],
            a[1]-b[1],
            a[2]-b[2],
        ])
    }

    //获取视图矩阵
    //                   视点x,y,z        目标点                  上方向
    function getViewMatrix(eyex,eyey,eyez,lookAtx,lookAty,lookAtz,upx,upy,upz){
        // 视点
        const eye = new Float32Array([eyex,eyey,eyez])
        // 目标点
        const lookAt = new Float32Array([lookAtx,lookAty,lookAtz])
        // 上方向
        const up = new Float32Array([upx,upy,upz])

        // 确定Z轴(面向观察者)
        const z = minus(eye,lookAt)

        normalized(z)
        normalized(up)

        // 确定X轴
        const x = cross(z,up)
        normalized(x)

        const y = cross(x,z)

        //返回视图矩阵(列主序)
        return new Float32Array([            x[0],       y[0],        z[0],      0,
            x[1],       y[1],        z[1],      0,
            x[2],       y[2],        z[2],      0,
            -dot(x,eye),-dot(y,eye),-dot(z,eye),1
        ])
    }

实际使用

    let eyey = -0.1
    function animation(){
        eyey += 0.01
        if(eyey>1){
            eyey = -0.1
        }
        //获取矩阵
        const matrix = getViewMatrix(0.0,eyey,0.2,0.0,0.0,0.0,0.0,0.6,0.0)

        //给mat4变量赋值uniformMatrix4fv(location,transpose,array)location uniform变量,transpose恒为false,array矩阵
        gl.uniformMatrix4fv(mat,false,matrix);
        gl.drawArrays(gl.TRIANGLES,0,3);
        //用于实现动画效果
        requestAnimationFrame(animation)
    }
    animation()

正射投影

不管物体距离视点有多远,投影后物体的大小和尺寸是不变的

image.png

image.png

image.png

image.png

//获取正射投影矩阵
    function getOrtho(l,r,t,b,n,f){
        return new Float32Array([
            2/(r-l),0,0,0,
            0,2/(t-b),0,0,
            0,0,-2/(f-n),0,
            -(r+l)/(r-l),-(t+b)/(t-b),-(f+n)/(f-n),1,
        ])
    }

透视投影(近大远小)

image.png

首先需要把透视投影的棱台(锥形)映射为长方体,借助正射投影

image.png

image.png这是一个平面

处理过程中,z的坐标是不变动的,即x'=xn y'=yn,得

image.png

如何获取a,b的值呢吗,z的值前后不变即z' = z

image.png

image.png

image.png

    //获取透视投影矩阵
                        // 视角,宽高比
    function getPerspective(fov,aspect,far,near){
        fov = fov*Math.PI/180
        return new Float32Array([
            1/(aspect * Math.tan(fov/2)),0,0,0,
            0,1/(aspect * Math.tan(fov/2)),0,0,
            0,0,-(far+near)/(far-near),(-2*far*near)/(far-near),
            0,0,-1,0,
        ])
    }

本节课代码

    const ctx = document.getElementById('canvas')
    const gl = document.getElementById('canvas').getContext('webgl')
    //创建着色器源码
    const VERTEX_SHADER_SOURCE = `
    attribute vec4 aPosition;
    //使用uniform声明接受矩阵的变量,uniform对所有点生效,平移是平移所有点
    uniform mat4 mat;

    attribute vec4 aColor;
    varying vec4 vColor;

    void main(){
        gl_Position =mat * aPosition;
        vColor = aColor;

    }
    ` //顶点着色器
    const FRAGMENT_SHADER_SOURCE = `
    precision lowp float;
    varying vec4 vColor;
    void main(){
        gl_FragColor = vColor;

    }
    `//片元着色器
    //创建着色器
    const vertexShader = gl.createShader(gl.VERTEX_SHADER);
    const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
    //指定顶点着色器的源码
    gl.shaderSource(vertexShader,VERTEX_SHADER_SOURCE)
    //指定片元着色器的源码
    gl.shaderSource(fragmentShader,FRAGMENT_SHADER_SOURCE)
    //编译着色器
    gl.compileShader(vertexShader)
    gl.compileShader(fragmentShader)
    //使用着色器
    //创建一个程序对象
    const program = gl.createProgram();
    //指定程序对象使用的着色器
    gl.attachShader(program,vertexShader)
    gl.attachShader(program,fragmentShader)
    //连接程序对象
    gl.linkProgram(program)
    //使用程序对象
    gl.useProgram(program)
    
    //获取attribute变量需要在initShader方法后,因为需要profram对象
    //getAttribLocation(program,name) program 程序对象, name 指定想要获取的attribute变量的名称 返回变量的储存地址
    const aPosition = gl.getAttribLocation(program,'aPosition')
    const aColor = gl.getAttribLocation(program,'aColor')
    const mat = gl.getUniformLocation(program,'mat')

    //创建一个获取矩阵的函数


    const points = new Float32Array([
        0.75,1.0,0.6,1.0,0.0,0.0,
        0.25,-1.0,0.6,1.0,0.0,0.0,
        1.0,-1.0,0.6,1.0,0.0,0.0,
        
        0.75,1.0,0.5,0.0,1.0,0.0,
        0.25,-1.0,0.5,0.0,1.0,0.0,
        1.0,-1.0,0.5,0.0,1.0,0.0,

        0.75,1.0,0.4,0.0,0.0,1.0,
        0.25,-1.0,0.4,0.0,0.0,1.0,
        1.0,-1.0,0.4,0.0,0.0,1.0,

        -0.75,1.0,0.6,1.0,0.0,0.0,
        -0.25,-1.0,0.6,1.0,0.0,0.0,
        -1.0,-1.0,0.6,1.0,0.0,0.0,

        -0.75,1.0,0.5,1.0,0.1,0.0,
        -0.25,-1.0,0.5,1.0,1.0,0.0,
        -1.0,-1.0,0.5,1.0,0.1,0.0,

        -0.75,1.0,0.4,0.0,0.0,1.0,
        -0.25,-1.0,0.4,0.0,0.0,1.0,
        -1.0,-1.0,0.4,0.0,0.0,1.0,
    ])

    const buffer = gl.createBuffer()

    gl.bindBuffer(gl.ARRAY_BUFFER,buffer)

    gl.bufferData(gl.ARRAY_BUFFER,points,gl.STATIC_DRAW)

    const BYTES = points.BYTES_PER_ELEMENT

    gl.vertexAttribPointer(aPosition,3,gl.FLOAT,false,BYTES*6,0);

    gl.enableVertexAttribArray(aPosition)

    gl.vertexAttribPointer(aColor,3,gl.FLOAT,false,BYTES*6,BYTES*3);

    gl.enableVertexAttribArray(aColor)

    // gl.drawArrays(gl.TRIANGLES,0,3*6);
    
    let eyex = 0.0
    let eyey = -0.1
    let eyez = 0.2
    function animation(){

        const matrix = getViewMatrix(eyex,eyey,eyez,0.0,0.0,0.0,0.0,0.6,0.0)

        const perspective = getPerspective(150,ctx.width/ctx.height,100,1)

        gl.uniformMatrix4fv(mat,false,mixMatrix(matrix,perspective));
        gl.drawArrays(gl.TRIANGLES,0,3*6);

    }
    animation()

未能成功执行,应该是顶点着色器的问题,但是没有成功执行

立方体绘制(顶点法)


    const ctx = document.getElementById('canvas')
    const gl = document.getElementById('canvas').getContext('webgl')
    //创建着色器源码
    const VERTEX_SHADER_SOURCE = `
    attribute vec4 aPosition;
    attribute vec4 aColor;
    varying vec4 vColor;
    uniform mat4 mat;
    void main(){
        gl_Position =mat * aPosition;
        vColor = aColor;

    }
    ` //顶点着色器
    const FRAGMENT_SHADER_SOURCE = `
    precision lowp float;
    varying vec4 vColor;
    void main(){
        gl_FragColor = vColor;

    }
    `//片元着色器
    //创建着色器
    const vertexShader = gl.createShader(gl.VERTEX_SHADER);
    const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
    //指定顶点着色器的源码
    gl.shaderSource(vertexShader, VERTEX_SHADER_SOURCE)
    //指定片元着色器的源码
    gl.shaderSource(fragmentShader, FRAGMENT_SHADER_SOURCE)
    //编译着色器
    gl.compileShader(vertexShader)
    gl.compileShader(fragmentShader)
    //使用着色器
    //创建一个程序对象
    const program = gl.createProgram();
    //指定程序对象使用的着色器
    gl.attachShader(program, vertexShader)
    gl.attachShader(program, fragmentShader)
    //连接程序对象
    gl.linkProgram(program)
    //使用程序对象
    gl.useProgram(program)

    //获取attribute变量需要在initShader方法后,因为需要profram对象
    //getAttribLocation(program,name) program 程序对象, name 指定想要获取的attribute变量的名称 返回变量的储存地址
    const aPosition = gl.getAttribLocation(program, 'aPosition')
    const aColor = gl.getAttribLocation(program, 'aColor')
    const mat = gl.getUniformLocation(program, 'mat')

    //顶点1
    const v0 = [1, 1, 1]
    //顶点2
    const v1 = [-1, 1, 1]
    //顶点3
    const v2 = [-1, -1, 1]
    //顶点4
    const v3 = [1, -1, 1]
    //顶点5
    const v4 = [1, -1, -1]
    //顶点6
    const v5 = [1, 1, -1]
    //顶点7
    const v6 = [-1, 1, -1]
    //顶点8
    const v7 = [-1, -1, -1]



    const points = new Float32Array([
        ...v0,...v1,...v2,...v0,...v2,...v3,//上
        ...v0,...v3,...v4,...v0,...v4,...v5,//右
        ...v0,...v5,...v6,...v0,...v6,...v1,//前
        ...v1,...v6,...v7,...v1,...v7,...v2,//左
        ...v7,...v4,...v3,...v7,...v3,...v2,//底
        ...v4,...v7,...v6,...v4,...v6,...v5,//后

    ])

    const buffer = gl.createBuffer()

    gl.bindBuffer(gl.ARRAY_BUFFER, buffer)

    gl.bufferData(gl.ARRAY_BUFFER, points, gl.STATIC_DRAW)

    const BYTES = points.BYTES_PER_ELEMENT

    gl.vertexAttribPointer(aPosition, 3, gl.FLOAT, false, 0, 0);

    gl.enableVertexAttribArray(aPosition)

    //给每个面指定纯色
    const color = new Float32Array([
        1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,
        0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,
        0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,
        1,1,0,1,1,0,1,1,0,1,1,0,1,1,0,1,1,0,
        1,0,1,1,0,1,1,0,1,1,0,1,1,0,1,1,0,1,
        0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
    ])
    const colorBuffer = gl.createBuffer()

    gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer)
    gl.bufferData(gl.ARRAY_BUFFER, color, gl.STATIC_DRAW)

    gl.vertexAttribPointer(aColor,3,gl.FLOAT,false,0,0);

    gl.enableVertexAttribArray(aColor)

    // gl.drawArrays(gl.TRIANGLES,0,3*6);

    let eyex = 3
    let eyey = 3
    let eyez = 5

    let deg = 0
    function animation() {
        deg += 0.01
        const rotate = getRotateMatrix(deg)
        const matrix = getViewMatrix(eyex, eyey, eyez, 0.0, 0.0, 0.0, 0.0, 0.6, 0.0)

        const perspective = getPerspective(30, ctx.width / ctx.height, 100, 1)

        gl.uniformMatrix4fv(mat, false, mixMatrix(mixMatrix(perspective, matrix),rotate));
        gl.drawArrays(gl.TRIANGLES, 0, points.length/3);
        requestAnimationFrame(animation)
    }
    animation()

立方体绘制(索引法)

在绑定缓冲区参数,绘制方法,指定每一面的颜色上有所不同

    //顶点1
    const v0 = [1, 1, 1]
    //顶点2
    const v1 = [-1, 1, 1]
    //顶点3
    const v2 = [-1, -1, 1]
    //顶点4
    const v3 = [1, -1, 1]
    //顶点5
    const v4 = [1, -1, -1]
    //顶点6
    const v5 = [1, 1, -1]
    //顶点7
    const v6 = [-1, 1, -1]
    //顶点8
    const v7 = [-1, -1, -1]



    const vertices = new Float32Array([
        1, 1, 1,
        -1, 1, 1,
        -1, -1, 1,
        1, -1, 1,
        1, -1, -1,
        1, 1, -1,
        -1, 1, -1,
        -1, -1, -1
    ])

    const buffer = gl.createBuffer()

    gl.bindBuffer(gl.ARRAY_BUFFER, buffer)

    gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW)

    const BYTES = vertices.BYTES_PER_ELEMENT

    gl.vertexAttribPointer(aPosition, 3, gl.FLOAT, false, 0, 0);

    gl.enableVertexAttribArray(aPosition)

    // 创建索引数据
    const index = new Uint8Array([
        0,1,2,0,2,3,
        0,3,4,0,4,5,
        0,5,6,0,6,1,
        1,6,7,1,7,2,
        7,4,3,7,3,2,
        4,5,7,4,6,5,
    ])
    const indexBuffer = gl.createBuffer()
    //            注意参数变化
    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer)
    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, index, gl.STATIC_DRAW)

指定每一面的颜色

需要重新更改点的数据和索引

    //顶点1
    const v0 = [1, 1, 1]
    //顶点2
    const v1 = [-1, 1, 1]
    //顶点3
    const v2 = [-1, -1, 1]
    //顶点4
    const v3 = [1, -1, 1]
    //顶点5
    const v4 = [1, -1, -1]
    //顶点6
    const v5 = [1, 1, -1]
    //顶点7
    const v6 = [-1, 1, -1]
    //顶点8
    const v7 = [-1, -1, -1]



    const vertices = new Float32Array([
        // 0123
        1, 1, 1,
        -1, 1, 1,
        -1, -1, 1,
        1, -1, 1,
        // 0345
        1, 1, 1,
        1, -1, 1,
        1, -1, -1,
        1, 1, -1,
        // 0156
        1, 1, 1,
        1, 1, -1,
        -1, 1, -1,
        -1, 1, 1,
        // 1267
        -1, 1, 1,
        -1, 1, -1,
        -1, -1, -1,
        -1, -1, 1,
        // 2347
        -1, -1, 1,
        1, -1, 1,
        1, -1, -1,
        -1, -1, -1,
        // 4567
        1, -1, -1,
        1, 1, -1,
        -1, 1, -1,
        -1, -1, -1
    ])

    const buffer = gl.createBuffer()

    gl.bindBuffer(gl.ARRAY_BUFFER, buffer)

    gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW)

    const BYTES = vertices.BYTES_PER_ELEMENT

    gl.vertexAttribPointer(aPosition, 3, gl.FLOAT, false, 0, 0);

    gl.enableVertexAttribArray(aPosition)

    // 创建颜色数据
    const color = new Float32Array([
        0.4,0.4,1.0,0.4,0.4,1.0,0.4,0.4,1.0,0.4,0.4,1.0,//前面
        0.4,1.0,0.4,0.4,1.0,0.4,0.4,1.0,0.4,0.4,1.0,0.4,//右面
        1.0,0.4,0.4,1.0,0.4,0.4,1.0,0.4,0.4,1.0,0.4,0.4,//上面
        1.0,1.0,0.4,1.0,1.0,0.4,1.0,1.0,0.4,1.0,1.0,0.4,//左面
        1.0,0.0,1.0,1.0,0.0,1.0,1.0,0.0,1.0,1.0,0.0,1.0,//下面
        0.0,1.0,1.0,0.0,1.0,1.0,0.0,1.0,1.0,0.0,1.0,1.0,//背面
    ])
    const colorBuffer = gl.createBuffer()

    gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer)
    gl.bufferData(gl.ARRAY_BUFFER, color, gl.STATIC_DRAW)

    gl.vertexAttribPointer(aColor,3,gl.FLOAT,false,0,0);

    gl.enableVertexAttribArray(aColor)

    // 创建索引数据
    const index = new Uint8Array([
        0,1,2,0,2,3,
        4,5,6,4,6,7,
        8,9,10,8,10,11,
        12,13,14,12,14,15,
        16,17,18,16,18,19,
        20,21,22,20,22,23,
    ])
    const indexBuffer = gl.createBuffer()
    //            注意参数变化
    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer)
    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, index, gl.STATIC_DRAW)