基础理论:
透视投影下,产生的三维场景看上去更是有深度感,更加自然,因为我们平时观察真实世界用的也是透视投影。在大多数情况下,比如三维射击类游戏中,我们都应当采用透视投影。正射投影的好处是用户可以方便地比较场景中物体( 比如两个原子的模型)的大小,这是因为物体看上去的大小与其所在的位置没有关系。在建筑平面图等技术绘图的相关场合,应当使用这种投影。
效果:
正射投影
透视投影
MPV变换
源码:
正射投影
<!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="gl-matrix.js"></script>
<script>
let vertexstring = `
attribute vec4 a_position;
uniform mat4 u_formMatrix;
uniform mat4 proj;
attribute vec4 a_color;
varying vec4 color;
void main(void){
gl_Position = u_formMatrix * a_position;
color = a_color;
}
`;
let fragmentstring = `
precision mediump float;
varying vec4 color;
void main(void){
gl_FragColor =color;
}
`;
var webgl;
var near=40;
var far= 0;
function init() {
initWebgl();
initShader();
initBuffer();
draw();
inittext(near,far)
initEvent();
}
function inittext(near,far){
document.getElementById("text").innerHTML = "near:"+near +"<br/>"+ "far:"+far;
}
function initEvent() {
document.onkeydown = handleKeyDown;
}
function handleKeyDown(event) {
if (String.fromCharCode(event.keyCode) == 'W') {
near += 1;
}
else if (String.fromCharCode(event.keyCode) == 'S') {
near -=1;
}
else if (String.fromCharCode(event.keyCode) == 'A') {
far -= 1;
}
else if (String.fromCharCode(event.keyCode) == 'D') {
far += 1;
}
inittext(near,far)
initBuffer();
draw();
}
function initWebgl() {
let webglDiv = document.getElementById('myCanvas');
webgl = webglDiv.getContext("webgl");
webgl.viewport(0, 0, webglDiv.clientWidth, webglDiv.clientHeight);
}
function initShader() {
let vsshader = webgl.createShader(webgl.VERTEX_SHADER);
let fsshader = webgl.createShader(webgl.FRAGMENT_SHADER);
webgl.shaderSource(vsshader, vertexstring);
webgl.shaderSource(fsshader, fragmentstring);
webgl.compileShader(vsshader);
webgl.compileShader(fsshader);
if (!webgl.getShaderParameter(vsshader, webgl.COMPILE_STATUS)) {
var err = webgl.getShaderInfoLog(vsshader);
alert(err);
return;
}
if (!webgl.getShaderParameter(fsshader, webgl.COMPILE_STATUS)) {
var err = webgl.getShaderInfoLog(fsshader);
alert(err);
return;
}
let program = webgl.createProgram();
webgl.attachShader(program, vsshader);
webgl.attachShader(program, fsshader)
webgl.linkProgram(program);
webgl.useProgram(program);
webgl.program = program
}
// 初始化一个投影矩阵,并将其设置为单位矩阵
function initBuffer() {
let ProjMatrix = glMatrix.mat4.create(); // 创建一个新的4x4矩阵
glMatrix.mat4.identity(ProjMatrix); // 将该矩阵设置为单位矩阵
// 使用正交投影设置投影矩阵的视野范围
// 参数分别是:矩阵、左、右、下、上、近、远
glMatrix.mat4.ortho(ProjMatrix, -50, 50, -100, 100, near, far); // 修改可视域范围
// 定义顶点数据,包括位置和颜色
let arr = [
// 位置坐标,颜色值
0.0, 70, -40, 1.0, 1, 0, 0, 1, // 红色
-50, -30, -40, 1.0, 1, 0, 0, 1, // 绿色
50, -30, -40, 1.0, 1, 0, 0, 1,
50, 40, -20, 1, 1.0, 1.0, 0.4, 1, // 紫色
-50, 40, -20, 1, 1.0, 1.0, 0.4, 1, // 紫色
0.0, -60,-20, 1, 1.0, 1.0, 0.4, 1, // 紫色
0.0, 50, 0.0, 1, 0.4, 0.4, 1.0, 1, // 青色
-50, -50, 0.0, 1, 0.4, 0.4, 1.0, 1, // 青色
50, -50, 0.0, 1, 0.4, 0.4, 1.0, 1, // 青色
]
// 创建一个Float32Array类型的数组来存储顶点数据
let pointPosition = new Float32Array(arr);
// 获取顶点着色器中位置属性的位置
let aPsotion = webgl.getAttribLocation(webgl.program, "a_position");
// 创建一个缓冲区对象
let triangleBuffer = webgl.createBuffer();
// 将缓冲区绑定到ARRAY_BUFFER目标上
webgl.bindBuffer(webgl.ARRAY_BUFFER, triangleBuffer);
// 将顶点数据存储到缓冲区对象中
webgl.bufferData(webgl.ARRAY_BUFFER, pointPosition, webgl.STATIC_DRAW);
// 启用顶点位置属性
webgl.enableVertexAttribArray(aPsotion);
// 将缓冲区中的数据解释为顶点位置,并指定步长和偏移量
webgl.vertexAttribPointer(aPsotion, 4, webgl.FLOAT, false, 8 * 4, 0);
// 获取顶点着色器中颜色属性的位置
let aColor = webgl.getAttribLocation(webgl.program, "a_color");
// 启用顶点颜色属性
webgl.enableVertexAttribArray(aColor);
// 将缓冲区中的数据解释为顶点颜色,并指定步长和偏移量
webgl.vertexAttribPointer(aColor, 4, webgl.FLOAT, false, 8 * 4, 4 * 4);
// 获取uniform变量u_formMatrix的位置
let uniformMatrix1 = webgl.getUniformLocation(webgl.program, "u_formMatrix");
// 将投影矩阵传递给着色器
webgl.uniformMatrix4fv(uniformMatrix1, false, ProjMatrix);
}
function draw() {
webgl.clearColor(0.0, 1.0, 0.0, 1.0);
webgl.clear(webgl.COLOR_BUFFER_BIT);
webgl.drawArrays(webgl.TRIANGLES, 0, 9);
}
</script>
</head>
<body onload="init()">
<canvas id='myCanvas' width="1024" height='768'></canvas>
<div id="text"></div>
</body>
</html>
透视投影
//角度小,看到的物体大,角度大,看到的物体小。
glMatrix.mat4.perspective(ProjMatrix,angle * Math.PI / 180,
webglDiv.clientWidth/webglDiv.clientHeight,1,100) //修改可视域范围
MPV变换
// 初始化缓冲区并设置矩阵
function initBuffer() {
// 定义顶点数据,包括位置和颜色
let arr = [
// 位置坐标,颜色值
0.0, 100, -80, 1, 1, 0, 0, 1, // 红色
-50, -100, -80, 1, 1, 0, 0, 1,
50, -100, -80, 1, 1, 0, 0, 1,
0, 100, -60, 1, 1.0, 1.0, 0.4, 1, // 黄色
-50, -100, -60, 1, 1.0, 1.0, 0.4, 1,
50, -100,-60, 1, 1.0, 1.0, 0.4, 1,
0.0, 100, -35.0, 1, 0.4, 0.4, 1.0, 1, // 蓝色
-50, -100, -35.0, 1, 0.4, 0.4, 1.0, 1,
50, -100, -35.0, 1, 0.4, 0.4, 1.0, 1,
]
// 创建一个Float32Array类型的数组来存储顶点数据
let pointPosition = new Float32Array(arr);
// 获取顶点着色器中位置属性的位置
let aPosition = webgl.getAttribLocation(webgl.program, "a_position");
// 创建一个缓冲区对象
let triangleBuffer = webgl.createBuffer();
webgl.bindBuffer(webgl.ARRAY_BUFFER, triangleBuffer);
webgl.bufferData(webgl.ARRAY_BUFFER, pointPosition, webgl.STATIC_DRAW);
webgl.enableVertexAttribArray(aPosition);
webgl.vertexAttribPointer(aPosition, 4, webgl.FLOAT, false, 8 * 4, 0);
// 获取顶点着色器中颜色属性的位置
let aColor = webgl.getAttribLocation(webgl.program, "a_color");
webgl.enableVertexAttribArray(aColor);
webgl.vertexAttribPointer(aColor, 4, webgl.FLOAT, false, 8 * 4, 4 * 4);
// 初始化投影矩阵
let ProjMatrix = glMatrix.mat4.create();
glMatrix.mat4.identity(ProjMatrix);
// 设置透视投影矩阵,参数为:矩阵、角度(转换为弧度)、宽高比、近剪裁面、远剪裁面
glMatrix.mat4.perspective(ProjMatrix, angle * Math.PI / 180, webglDiv.clientWidth / webglDiv.clientHeight, 1, 1000);
// 获取uniform变量u_formMatrix的位置
let uniformMatrix1 = webgl.getUniformLocation(webgl.program, "u_formMatrix");
// 初始化模型矩阵
let ModelMatrix = glMatrix.mat4.create();
glMatrix.mat4.identity(ModelMatrix);
// 沿x轴平移模型
glMatrix.mat4.translate(ModelMatrix, ModelMatrix, [180, 0, 0]);
// 初始化视图矩阵
let ViewMatrix = glMatrix.mat4.create();
glMatrix.mat4.identity(ViewMatrix);
// 设置视图矩阵,参数为:矩阵、相机位置、目标位置、上方向
glMatrix.mat4.lookAt(ViewMatrix, [0, 0, 300], [0, 0, -90], [0, 1, 0]);
// 计算模型视图矩阵
let mvMatrix = glMatrix.mat4.create();
glMatrix.mat4.multiply(mvMatrix, ViewMatrix, ModelMatrix);
// 计算模型视图投影矩阵
let mvpMatrix = glMatrix.mat4.create();
glMatrix.mat4.identity(mvpMatrix);
glMatrix.mat4.multiply(mvpMatrix, ProjMatrix, mvMatrix);
// 将模型视图投影矩阵传递给着色器
webgl.uniformMatrix4fv(uniformMatrix1, false, mvpMatrix);
// 设置清除颜色为绿色,并清除颜色缓冲区
webgl.clearColor(0.0, 1.0, 0.0, 1.0);
webgl.clear(webgl.COLOR_BUFFER_BIT);
// 绘制三角形
webgl.drawArrays(webgl.TRIANGLES, 0, 9);
// 重置模型矩阵并沿x轴负方向平移
glMatrix.mat4.identity(ModelMatrix);
glMatrix.mat4.translate(ModelMatrix, ModelMatrix, [-180, 0, 0]);
// 重置视图矩阵
glMatrix.mat4.identity(ViewMatrix);
glMatrix.mat4.lookAt(ViewMatrix, [0, 0, 300], [0, 0, -90], [0, 1, 0]);
// 重新计算模型视图矩阵
glMatrix.mat4.multiply(mvMatrix, ViewMatrix, ModelMatrix);
// 重新计算模型视图投影矩阵
glMatrix.mat4.identity(mvpMatrix);
glMatrix.mat4.multiply(mvpMatrix, ProjMatrix, mvMatrix);
// 再次将模型视图投影矩阵传递给着色器
webgl.uniformMatrix4fv(uniformMatrix1, false, mvpMatrix);
// 再次绘制三角形
webgl.drawArrays(webgl.TRIANGLES, 0, 9);
}