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)
2 将rbga颜色设置为webgl颜色
使用three.js的color
const color = new Color('rgba(255,1,2,1)')
3 webgl坐标系
坐标中心位于原点,y轴方向朝上,其余和正常坐标系一致
4 webgl绘图步骤
- 建立canvas画图
- 获取canvas画布
- 使用canvas获取wegbl绘图上下文
- 在script中建立顶点着色器和片元着色器
<script id="vertexShader" type="x-shader/x-vertex">
<script id="fragmentShader" type="x-shader/x-fragment">
- 初始化着色器
//获取着色器文本
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
}
5 着色器
着色器语言是GLSL ES
语言
- 顶点着色器:描述顶点的特征,如位置、颜色
- 片元着色器:进行逐片元处理,如光照
着色器编程:
void main(){
gl_Position = vec4(x,y,z,1.0);
gl_PointSize = 100.0;
gl_FragColor = vec4(r,g,b,a)
}
着色器初始化:
//建立程序对象
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
}
attribute变量及设置:
const position = gl.getAttribLocation(gl.program,'z_position')
//设置前3各值
gl.vertexAttrib3f(position,0,0,0)
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)
})
}
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)
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;
}
}
9 设置颜色透明度
需要开启片元的颜色合成功能
gl.enable(gl.BLEND)
//设置片元的合成方式
gl.blendFunc(gl.SRC_ALPHA,gl.ONE_MINUS_SRC_ALPHA)
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)
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
}
13 旋转
无论绕那个轴,从正半轴向正半轴逆时针旋转都是正
(1)先移动后旋转
移动到相应位置后,按原来的中心点进行旋转
(2)先旋转后移动
先按中心点旋转,然后再位移
绕z轴逆时针旋转角度:[[cosθ,-sinθ],[sinθ,cosθ]]
14 视图矩阵
- 视点:相机的位置
- 视线方向:相机所看的方向
- 上方向:相机绕视线转动的方向
三维向量叉乘:
x={ax,ay,az}
y = {bx,by,bz}
cross(x,y)=(ay*bz-az*by,)
生成视图矩阵:
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
}
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
)
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)
}
纹理混合:mix(m,n,a):m+(n-m)*a
跨域贴图:设置image.setAttribute(‘crossOrigin’,‘Anonymous’)
17 GLSL ES语言
- 结构体: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;
}
- 数组
vec4 vs[2];
vs[0]=vec4();
const int n=2;
vec4 vs[n];
- for循环
for循环的循环变量必须是int或float
for(int i=0;i<3;i++){}
for(float i=0.0;i<4.0;i++){}
- 函数
//不会影响原始数据,如果想修改原始数据要使用out
void setLum(in vec3 color){
color.y = 255.0
}
- 设置精度
精度有:highp、mediump、lowp,float是没有默认精度的
//设置某个变量的精度
mediump float size;
//设置某种数据类型的精度
precision mediump float;
- 设置颜色渐变
//获取片元的投影长度
//以投影长度比例乘以颜色差值获得该点的颜色
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)
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)
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
)
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
)
原理:
//位移矩阵
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
)
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)
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()
}
(2)设置相机旋转轨道
(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()
}