3D基础
视点,目标点,上方向
视点:眼睛 目标点:要看的物体 上方向:正方向
辅助函数
归一化函数:归一化到0-1区间 叉积,求两个平面的法向量 点积,求某点在xyz轴上的投影长度 向量差,获取视点到目标点之间的向量
(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()
正射投影
不管物体距离视点有多远,投影后物体的大小和尺寸是不变的
//获取正射投影矩阵
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,
])
}
透视投影(近大远小)
首先需要把透视投影的棱台(锥形)映射为长方体,借助正射投影
这是一个平面
处理过程中,z的坐标是不变动的,即x'=xn y'=yn,得
如何获取a,b的值呢吗,z的值前后不变即z' = z
//获取透视投影矩阵
// 视角,宽高比
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)