• webGL学习


    1 初试webGL

    const canvas = document.getElementById('canvas')
    //获取webgl上下文对象 相当于设置画笔
    const gl = canvas.getContext('webgl')
    //声明颜色
    gl.clearColor(0,0,0,1)
    
    gl.clear(gl.COLOR_BUFFER_BIT)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    2 将rbga颜色设置为webgl颜色

    使用three.js的color

    const color = new Color('rgba(255,1,2,1)')
    
    • 1

    3 webgl坐标系

    坐标中心位于原点,y轴方向朝上,其余和正常坐标系一致

    4 webgl绘图步骤

    1. 建立canvas画图
    2. 获取canvas画布
    3. 使用canvas获取wegbl绘图上下文
    4. 在script中建立顶点着色器和片元着色器
    <script id="vertexShader" type="x-shader/x-vertex">
    <script id="fragmentShader" type="x-shader/x-fragment">
    
    • 1
    • 2
    1. 初始化着色器
    
          //获取着色器文本
          const vsSource = document.querySelector("#vertexShader").innerHTML;
          const fsSource = document.querySelector("#fragmentShader").innerHTML;
          //初始化着色器
          initShaders(gl, vsSource, fsSource);
    
          function initShaders(gl, vsSource, fsSource) {
            //创建程序对象
            const program = gl.createProgram();
            //建立着色对象
            const vertexShader = loadShader(gl, gl.VERTEX_SHADER, vsSource);
            const fragmentShader = loadShader(gl, gl.FRAGMENT_SHADER, fsSource);
            //把顶点着色对象装进程序对象中
            gl.attachShader(program,vertexShader)
            gl.attachShader(program,fragmentShader)
            //连接webgl上下文对象和程序对象
            gl.linkProgram(program)
            //启动程序对象
            gl.useProgram(program)
            //将程序对象挂载到上下文对象中
            gl.program = program
            return true
          }
    
          function loadShader(gl,type,source){
            //根据着色类型,建立着色器对象
            const shader = gl.createShader(type)
            //将着色器源文件传入着色器对象中
            gl.shaderSource(shader,source)
            //编译着色器对象
            gl.compileShader(shader)
            return shader
          }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    5 着色器

    着色器语言是GLSL ES语言

    • 顶点着色器:描述顶点的特征,如位置、颜色
    • 片元着色器:进行逐片元处理,如光照

    着色器编程

    void main(){
    	gl_Position = vec4(x,y,z,1.0);
    	gl_PointSize = 100.0;
    	gl_FragColor = vec4(r,g,b,a)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    着色器初始化

    //建立程序对象
    const shader = gl.createProgram()
    
    //建立着色对象
    const vertexShader = loadShader(gl,gl.VERTEX_SHADER,vsSource)
    const fragShader = loadShader(gl,gl.FRAGMENT_SHADER,fsSource)
    
    //将着色器对象装入程序对象中
    gl.attachShader(program,vertexShader)
    gl.attachShader(program,fragmentShader)
    
    //连接webgl上下文对象和程序对象
    gl.linkProgram(program)
    
    //启动程序对象
    gl.useProgram(program)
    
    gl.program = program
    
    
    function loadShader(gl,type,source){
    	//根据着色器类型,建立着色器对象
    	const shader = gl.createShader(type)
    	//将着色器对象放入着色器源文件中
    	gl.shaderSource(shader,source)
    	//编译着色器对象
    	gl.compileShader(shader)
    	return shader
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    attribute变量及设置:

    const position = gl.getAttribLocation(gl.program,'z_position')
    
    //设置前3各值
    gl.vertexAttrib3f(position,0,0,0)
    
    • 1
    • 2
    • 3
    • 4

    6 获取鼠标点在webgl坐标系中的位置

    webgl是同步绘图是由颜色缓冲区导致的,颜色缓冲区中存储的图像只在当前线程有效;在执行异步线程时,颜色缓冲区会被重置

    canvas.addEventListener('click',(e)=>{
    	const { clientX,clientY } = e
    	const {left,top,width,height} = gl.getBoundingClientRect()
    	const [ cssX,cssY ] = [clientX-left,clientY-top]
    	const [halfWidth,halfHeight] = [width/2,height/2]
    	const [ x,y ] =  [(cssX-halfWidth)/halfWidth,(halfHeight-cssY)/halfHeight]
    	a.points.push({x,y})
    	render()
    })
    
    function render(){
    	gl.clear(gl.COLOR_BUFFER_BIT)
    	a.points.forEach(({x,y})=>{
    		gl.vertexAttrib2f(a_position,x,y)
    		gl.drawArrays(gl.POINTS,0,1)
    	})
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    7 修改顶点颜色

    使用uniform限定符

    const u_fragColor= gl.getUniformLocation(gl.program,'u_fragColor')
    gl.uniform4f(u_fragColor,0,0,0,0)
    const arr = new Float32Array([r,g,b,a])
    gl.uniform4fv(u_fragColor,arr)
    
    • 1
    • 2
    • 3
    • 4

    8 鼠标绘制星空

    设置切片范围

    precision mediump float;
    uniform rec4 u_fragcolor;
    void main(){
    	float dist = distance(gl_PointCoord,rec2(0.5,0.5));
    	if(dist<0.5){
    		gl_FragColor = u_fragcolor;	
    	}else {
    		discard;
    	}
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    9 设置颜色透明度

    需要开启片元的颜色合成功能

    gl.enable(gl.BLEND)
    //设置片元的合成方式
    gl.blendFunc(gl.SRC_ALPHA,gl.ONE_MINUS_SRC_ALPHA)
    
    • 1
    • 2
    • 3

    10 绘制多个点

    //设置顶点坐标
    const vertices = new Float32Array([0,0,0.1,0.1,0.2,0.2])
    //建立顶点缓冲区
    const vertexBuffer = gl.createBuffer()
    //绑定缓冲区对象
    gl.bindBuffer(gl.ARRAY_BUFFER,vertexBuffer)
    //写入顶点数据
    gl.bufferData(gl.ARRAY_BUFFER,vertices,gl.STATIC_DRAW)
    //获取attribute变量
    const a_position = gl.getAttribLocation(gl.program,'a_position')
    //设置attribute变量
    gl.vertexAttribPointer(a_position,2,gl.FLOAT,false,0,0)
    gl.clearColor(0,0,1,1)
    gl.clear(gl.COLOR_BUFFER_BIT)
    gl.drawArrays(gl.POINTS,0,3)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    11 绘制线

    • LINES:每两点连线
    • LINE_STRIP:连接所有点成线
    • LINE_LOOP:形成闭环

    12 判断三角形的第三个顶点是否处于正半边

    有A(x1,y1)、B(x2,y2)、C(x3,y3)

    const abX = x2-x1
    const abY = y2-y1
    const acX = x3-x1
    const acY = y3-y1
    
    const dist = abX*acY - abY*acX
    
    if(dist>0){
    	return true
    } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    13 旋转

    无论绕那个轴,从正半轴向正半轴逆时针旋转都是正

    (1)先移动后旋转
    移动到相应位置后,按原来的中心点进行旋转

    (2)先旋转后移动
    先按中心点旋转,然后再位移

    绕z轴逆时针旋转角度:[[cosθ,-sinθ],[sinθ,cosθ]]

    14 视图矩阵

    • 视点:相机的位置
    • 视线方向:相机所看的方向
    • 上方向:相机绕视线转动的方向

    三维向量叉乘

    x={ax,ay,az}
    y = {bx,by,bz}
    
    cross(x,y)=(ay*bz-az*by,)
    
    • 1
    • 2
    • 3
    • 4

    生成视图矩阵

    function getViewMatrix(e, t, u){
    	const c = new Vector3().subVectors(e, t).normalize()
    	const a = new Vector3().corssVectors(u, c).normalize()
    	const b = new Vector3().crossVectors(c, a).normalize()
    	const mr = new Matrix4().set(
    		...a,0,
    		...b,0,
    		-c.x,-c.y,-c.z,0,
    		0,0,0,1
    	)
    	const mt = new Matrix4().set(
    		1,0,0,-e.x,
    		0,1,0,-e.y,
    		0,0,1,-e.z,
    		0,0,0,1	
    	)
    	return mr.multiple(mt).elements
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    15 多attribute数据合一

    //按列拼接
    const source = new Float32Array([])
    //元素字节数
    const elementBytes = source.BYTES_PRE_ELEMENT
    //变量长度
    const verticeSize = 3
    const colorSize = 4
    //总长度
    const category = verticeSize + colorSize
    //总字节数
    const categoryBytes = catefory*elementBtyes
    //变量索引
    const verticeIndex = 0
    const colorIndex = verticeSize * elementBytes
    //顶点总数
    const sourceSize = source.length/categorySize
    
    //设置某个attribute变量
    //gl.vertexArrtibPointer(index,size,type,normalized,stride,offset)
    const a_color = gl.getAttribLocation(gl.program,'a_color')
    gl.vertexAttribPointer(
    	a_color,
    	colorSize,
    	gl.FLOAT,
    	false,
    	categoryBytes,
    	colorIndex
    )
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    16 纹理

    //将图片上下对称翻转坐标轴
    gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL)
    
    //激活纹理单元
    gl.activeTexture(gl.TEXTURE0)
    //创建纹理对象
    const texture = gl.createTexture()
    //将纹理对象装进纹理单元中
    gl.bindTexture(gl.TEXTURE_2D,texture)
    //创建图像
    const image = new Image()
    img.src = ''
    img.onlaod = function(){
    gl.texImage2D(gl.TEXTURE_2D,0,gl.RGB,gl.RGB,gl.UNSIGNED_BYTE,image)
    gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_MIN_FILTER,gl.LINEAR)
    //防止非2的n次幂图像无法显示
    gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_WRAP_S,gl.CLAMP_TO_EDGE)
    
    gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_WRAP_T,gl.CLAMP_TO_EDGE)
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    纹理混合:mix(m,n,a):m+(n-m)*a

    跨域贴图:设置image.setAttribute(‘crossOrigin’,‘Anonymous’)

    17 GLSL ES语言

    1. 结构体:struct
    struct Light{
    	vec4 color;
    	vec3 pos;
    };
    
    void main(){
    	Light l1 = Light(
    		vec4(255,255,0,255),
    		vec3(1,2,3)
    	)
    	gl_FragColor = l1.color/255.0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    1. 数组
    vec4 vs[2];
    vs[0]=vec4();
    
    
    const int n=2;
    vec4 vs[n];
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    1. for循环
      for循环的循环变量必须是int或float
    for(int i=0;i<3;i++){}
    
    for(float i=0.0;i<4.0;i++){}
    
    • 1
    • 2
    • 3
    1. 函数
    //不会影响原始数据,如果想修改原始数据要使用out
    void setLum(in vec3 color){
    	color.y = 255.0
    }
    
    • 1
    • 2
    • 3
    • 4
    1. 设置精度
      精度有:highp、mediump、lowp,float是没有默认精度的
    //设置某个变量的精度
    mediump float size;
    
    //设置某种数据类型的精度
    precision mediump float;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    1. 设置颜色渐变
    //获取片元的投影长度
    //以投影长度比例乘以颜色差值获得该点的颜色
    
    • 1
    • 2

    18 四元数

    在这里插入图片描述
    例如有一点p需要绕OC2轴旋转ang度,那么可以让c2旋转到z轴对齐,然后点p绕z轴旋转ang度,然后再旋转回来

    在这里插入图片描述
    如何旋转到Z轴?C2旋转B2OB1度到C3,然后C3旋转C3OB1度到Z轴

    //three.js中采用四元数旋转
    const quaternion = new Quaternion()
    quaternion.setFromAxisAngle(oc2,ang)
    const m = new Matrix4()
    m.makeRotationFromQuaternion(quaternion)
    p1.clone().applyMatrix4(m)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    19 正交投影矩阵

    顶点在空间中的位置投影矩阵\*视图矩阵\*模型矩阵*顶点的初始位置

    正交投影矩阵=缩放矩阵*位移矩阵

    const projectMatrix = new Matrix4()
    //定义相机世界高度的一半
    const halfH = 2
    //计算画布的宽高比
    const ratio = canvas.width/canvas.height
    //计算相机的宽度
    const halfW = halfH*ratio
    //定义相机的6个边界
    const [left,right,top,bottom,near,far]=[
    	-halfW,halfW,halfH,-halfH,0,4
    ]
    
    //获取正交投影矩阵
    projectionMatrix.makeOrthographic(left,right,top,bottom,near,far)
     
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    20 视图位移

    const camera = new OrthographicCamera(left,right,top,bottom,near,far)
    
    camera.position.set(0,0,3)
    camera.updateWorldMatrix(true)
    
    const pvMatrix = new Matrix4().multiplyMatrices(
    	camera.projectionMatrix,
    	camera.matrixWorldInverse
    )
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    21 视图旋转

    const eye = new Vector3(1,2,3)
    const target = new Vector3(0,0,0)
    const up = new Vector3(1,1,1)
    
    const camera = new OrthographicCamera(left,right,top,bottom,near,far)
    camera.position.set(eye)
    
    camera.lookat(target)
    camera.updateWorldMatrix(true)
    
    //计算投影矩阵
    const pvMatrix = new Matrix4().multiplyMatrices(
    	camera.projectionMatrix,
    	camera.matrixWorldInverse
    )
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    原理:

    //位移矩阵
    const positionMatrix = new Matrix4().setPosition(eye)
    
    //旋转矩阵
    const rotationMatrix = new Matrix4().lookAt(eye,target,up)
    
    //计算相机的视图矩阵
    const viewMatrix = new Matrix4().multiplyMatrices(
    	positionMatrix,rotationMatrix
    )
    
    //投影视图矩阵
    const pvMatrix = new Matrix4().multiplyMatrices(
    	camera.projectionMatrix,viewMatrix
    )
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    22 透视投影矩阵

    在这里插入图片描述
    两种数据间的转换关系为y=k*x+b
    其中k=(maxM-minM)/(maxN-minN)以及b=minM-k*minN
    在这里插入图片描述

    在这里插入图片描述

    • fov:相机视锥体垂直视野角度
    • aspect:摄像机视锥体宽高比
    • near:近裁剪面到视点的距离
    • far:远裁剪面到视点的距离

    在这里插入图片描述

    //在three.js中使用透视投影矩阵
    //建立透视相机
    const [fov,aspect,near,far] = [45,canvas.width/canvas.height,1,20]
    
    const camera = new PerspectiveCamera(fov,aspect,near,far)
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    23 对投影矩阵、视图矩阵、模型矩阵的理解

    • 模型矩阵:相当于更改物体的位置
    • 视图矩阵:相当于改变相机位置
    • 投影矩阵:将顶点坐标变换到裁剪坐标

    24 相机轨道控制器

    (1)设置相机位移轨道

    //鼠标事件
    const mouseButtons = new Map([
    	[2, 'pan']
    ])
    
    //轨道控制器状态,'pan'代表位移
    let state = 'none'
    
    //鼠标拖拽的起始和结束位置
    const dragStart = new Vector2()
    const dragEnd = new Vector2()
    
    //鼠标移动的位移量
    const panoffset = new Vector3()
    
    //鼠标垂直拖拽,是基于y轴还是z轴
    // true:y false:z
    const screenSpacePanning = true
    
    //取消右击菜单显示
    canvas.addEventListener('contextmenu',event=>{
    	event.preventDefault()
    })
    
    //指针按下时,设置拖拽起始位,获取轨道控制器状态
    canvas.addEventListener('pointerdown',({clientX,clientY,button})=>{
    	dragStart.set(clientX,clientY)
    	state = mouseButtons.get(button)
    })
    
    //指针平移时,如果控制器处于移动状态,平移相机
    canvas.addEventListener('pointermove',(event)=>{
    	switch(case){
    		case 'pan':
    			handleMouseMovePan(event)	
    	}
    })
    
    //指针抬起,清楚控制器状态
    canvas.addEventListener('pointerup',(event)=>{
    	state = 'none'
    })
    
    const handleMouseMovePan = ({clientX,clientY,button})=>{
    	dragEnd.set(clientX,clientY)
    	//基于拖拽距离拖拽相机
    	pan(dragEnd.clone().sub(dragStart))
    	//重置起始点位置
    	dragStart.clone(dragEnd)
    }	
    
    //相机移动是基于鼠标在近裁剪面上的位移量来移动的
    const pan = (delta)=>{
    	//相机近裁剪面尺寸
    	const cameraWidth = camera.right - camera.left
    	const cameraHeight = camera.top - camera.bottom
    	//指针拖拽量在画布中的比值
    	const ratioX = delta.x / canvas.clientWidth
    	const ratioY = delta.y / canvas.clientHeight
    	//将像素单位的位移量转换为近裁剪面上的位移量
    	const distanceLeft = ratioX * cameraWidth
    	const distanceUp = ratioY * cameraHeight
    	//相机本地坐标系的x轴 取相机本地坐标系第一列作为x轴
    	const mx = new Vector3().setFromMatrixColumn(camera.matrix,0)
    	//相机x轴位移量
    	const vx = mx.clone().multiplyScalar(-distanceLeft)
    	//相机z/y轴平移量
    	const vy = new Vector3()
    	if (screenSpacePanning){
    		vy.setFromMatrixColumn(camera.matrix,1)
    	} else {
    	//-z向
    	//相机的上方向叉乘x轴会得到-z轴 x轴叉乘上方向得到z轴
    		vy.crossVectors(camera.up,mx)
    	}
    	vy.multiplyScalar(distanceUp)
    	//整合平移量
    	panoffset.copy(vx.add(vy))
    	update()
    }
    
    const update = ()=>{
    	//移动相机的目标点
    	target.add(panoffset)
    	//移动相机视点
    	camera.position.add(panoffset)
    	//看向目标点
    	camera.lookAt(target)
    	//更新相机世界坐标系
    	camera.updateWorldMatrix(true)
    	//计算投影视图矩阵
    	pvMatrix.multiplyMatrix(
    		camera.projectionMatrix,
    		camera.matrixWorldInverse,
    	)
    	render()	
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97

    (2)设置相机旋转轨道

    
    
    • 1

    (3)设置相机缩放轨道
    主要是让在同一深度上的东西更多或者更少

    //定义缩放系数
    const zoomScale = 0.95
    
    //添加滚动事件
    canvas.addEventListener('wheel',handleMouseWheel)
    
    const handleMouseWheel = ({deltaY})=>{
    	if(deltaY<0){
    		dolly(1/zoomScale)	
    	} else if (deltaY>0) {
    		dolly(zoomScale)
    	}
    	update()
    }
    
    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    25 BufferGeometry和Geometry的区别

    • BufferGeometry的数据以连续的方式存储,能够节省传递数据到gpu的时间,在处理大规模数据或复杂模型时更具优势,能减少向gpu传输数据所需的开销
    • Geometry的数据存储方式是非缓冲的,即每个顶点的属性都会保存在一个单独的数组中,这种存储方式使Geometry更容易读写,但是运行效果不如BufferGeometry
  • 相关阅读:
    BIO、NIO、AIO三者的区别及其应用场景(结合生活例子,简单易懂)
    python基于django的校园公寓宿舍报修管理系统设计与实现
    ros2移植Apollo和autoware规控算法可跑工程
    Android---RecyclerView替代ListView
    go协程的栈
    SSM框架-SpringMVC(二)
    软件设计模式系列之十二——外观模式
    如何去阅读开源的第三方库的源码
    分布式队列celery学习
    Apache Doris 基础 -- 数据表设计(数据模型)
  • 原文地址:https://blog.csdn.net/weixin_44208404/article/details/127829525