视觉高级篇21 # 如何添加相机,用透视原理对物体进行投影?
Posted 凯小默
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了视觉高级篇21 # 如何添加相机,用透视原理对物体进行投影?相关的知识,希望对你有一定的参考价值。
说明
【跟月影学可视化】学习笔记。
如何理解相机和视图矩阵?
用一个三维坐标(Position)和一个三维向量方向(LookAt Target)来表示 WebGL 的三维世界的一个相机。要绘制以相机为观察者的图形,需要用一个变换,将世界坐标转换为相机坐标。这个变换的矩阵就是视图矩阵(ViewMatrix)。
怎么计算视图矩阵?
- 先计算相机的模型矩阵
- 然后对矩阵使用 lookAt 函数,得到的矩阵就是视图矩阵的逆矩阵。
- 最后再对这个逆矩阵求一次逆,就可以得到视图矩阵。
用代码的方式表示:
function updateCamera(eye, target = [0, 0, 0])
const [x, y, z] = eye;
// 设置相机初始位置矩阵 m
const m = new Mat4(
1, 0,0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
x, y, z, 1,
);
const up = [0, 1, 0];
m.lookAt(eye, target, up).inverse();
renderer.uniforms.viewMatrix = m;
<!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>在绘制圆柱体里加入相机</title>
<style>
canvas
border: 1px dashed rgb(250, 128, 114);
</style>
</head>
<body>
<canvas width="512" height="512"></canvas>
<script src="./common/lib/gl-renderer.js"></script>
<script type="module">
import Mat4 from './common/lib/math/Mat4.js';
import multiply from './common/lib/math/functions/Mat4Func.js';
import cross, subtract, normalize from './common/lib/math/functions/Vec3Func.js';
import normalFromMat4 from './common/lib/math/functions/Mat3Func.js';
const vertex = `
attribute vec3 a_vertexPosition;
attribute vec4 color;
attribute vec3 normal;
varying vec4 vColor;
varying float vCos;
uniform mat4 projectionMatrix;
uniform mat4 modelMatrix;
uniform mat4 viewMatrix;
uniform mat3 normalMatrix;
const vec3 lightPosition = vec3(1, 0, 0);
void main()
gl_PointSize = 1.0;
vColor = color;
vec4 pos = viewMatrix * modelMatrix * vec4(a_vertexPosition, 1.0);
vec4 lp = viewMatrix * vec4(lightPosition, 1.0);
vec3 invLight = lightPosition - pos.xyz;
vec3 norm = normalize(normalMatrix * normal);
vCos = max(dot(normalize(invLight), norm), 0.0);
gl_Position = projectionMatrix * pos;
`;
const fragment = `
#ifdef GL_ES
precision highp float;
#endif
uniform vec4 lightColor;
varying vec4 vColor;
varying float vCos;
void main()
gl_FragColor.rgb = vColor.rgb + vCos * lightColor.a * lightColor.rgb;
gl_FragColor.a = vColor.a;
`;
const canvas = document.querySelector("canvas");
// 开启深度检测
const renderer = new GlRenderer(canvas,
depth: true
);
const program = renderer.compileSync(fragment, vertex);
renderer.useProgram(program);
function cylinder(radius = 1.0, height = 1.0, segments = 30, colorCap = [0, 0, 1, 1], colorSide = [1, 0, 0, 1])
const positions = [];
const cells = [];
const color = [];
const cap = [[0, 0]];
const h = 0.5 * height;
const normal = [];
// 顶和底的圆
for(let i = 0; i <= segments; i++)
const theta = Math.PI * 2 * i / segments;
const p = [radius * Math.cos(theta), radius * Math.sin(theta)];
cap.push(p);
positions.push(...cap.map(([x, y]) => [x, y, -h]));
normal.push(...cap.map(() => [0, 0, -1]));
for(let i = 1; i < cap.length - 1; i++)
cells.push([0, i, i + 1]);
cells.push([0, cap.length - 1, 1]);
let offset = positions.length;
positions.push(...cap.map(([x, y]) => [x, y, h]));
normal.push(...cap.map(() => [0, 0, 1]));
for(let i = 1; i < cap.length - 1; i++)
cells.push([offset, offset + i, offset + i + 1]);
cells.push([offset, offset + cap.length - 1, offset + 1]);
color.push(...positions.map(() => colorCap));
const tmp1 = [];
const tmp2 = [];
// 侧面,这里需要求出侧面的法向量
offset = positions.length;
for(let i = 1; i < cap.length; i++)
const a = [...cap[i], h];
const b = [...cap[i], -h];
const nextIdx = i < cap.length - 1 ? i + 1 : 1;
const c = [...cap[nextIdx], -h];
const d = [...cap[nextIdx], h];
positions.push(a, b, c, d);
const norm = [];
cross(norm, subtract(tmp1, b, a), subtract(tmp2, c, a));
normalize(norm, norm);
normal.push(norm, norm, norm, norm); // abcd四个点共面,它们的法向量相同
color.push(colorSide, colorSide, colorSide, colorSide);
cells.push([offset, offset + 1, offset + 2], [offset, offset + 2, offset + 3]);
offset += 4;
return positions, cells, color, normal ;
const geometry = cylinder(0.2, 1.0, 400,
[250/255, 128/255, 114/255, 1], // salmon rgb(250 128 114)
[46/255, 139/255, 87/255, 1], // seagreen rgb(46 139 87)
);
// 将 z 轴坐标方向反转,对应的齐次矩阵如下,转换坐标的齐次矩阵,又被称为投影矩阵(ProjectionMatrix)
renderer.uniforms.projectionMatrix = [
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, -1, 0,
0, 0, 0, 1,
];
renderer.uniforms.lightColor = [218/255, 165/255, 32/255, 0.6];// goldenrod rgb(218, 165, 32)
function updateCamera(eye, target = [0, 0, 0])
const [x, y, z] = eye;
// 设置相机初始位置矩阵 m
const m = new Mat4(
1, 0,0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
x, y, z, 1,
);
const up = [0, 1, 0];
m.lookAt(eye, target, up).inverse();
renderer.uniforms.viewMatrix = m;
// 设置相机位置
updateCamera([0.5, 0, 0.5]);
renderer.setMeshData([
positions: geometry.positions,
attributes:
color: geometry.color,
normal: geometry.normal
,
cells: geometry.cells,
,
]);
renderer.uniforms.modelMatrix = new Mat4(
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1,
);
function update()
const modelViewMatrix = multiply([], renderer.uniforms.viewMatrix, renderer.uniforms.modelMatrix);
renderer.uniforms.modelViewMatrix = modelViewMatrix;
renderer.uniforms.normalMatrix = normalFromMat4([], modelViewMatrix);
requestAnimationFrame(update);
update();
renderer.render();
</script>
</body>
</html>
剪裁空间和投影对 3D 图像的影响
WebGL 的默认坐标范围是从 -1 到 1 的。只有当图像的 x、y、z 的值在 -1 到 1 区间内才会被显示在画布上,而在其他位置上的图像都会被剪裁掉。
给下面图形分别给 x、y、z 轴增加 0.5 的平移
<!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>圆柱体被剪裁效果</title>
<style>
canvas
border: 1px dashed rgb(250, 128, 114);
</style>
</head>
<body>
<canvas width="512" height="512"></canvas>
<script src="./common/lib/gl-renderer.js"></script>
<script type="module">
import multiply from './common/lib/math/functions/Mat4Func.js';
import cross, subtract, normalize from './common/lib/math/functions/Vec3Func.js';
import normalFromMat4 from './common/lib/math/functions/Mat3Func.js';
const vertex = `
attribute vec3 a_vertexPosition;
attribute vec4 color;
attribute vec3 normal;
varying vec4 vColor;
varying float vCos;
uniform mat4 projectionMatrix;
uniform mat4 modelMatrix;
uniform mat3 normalMatrix;
const vec3 lightPosition = vec3(1, 0, 0);
void main()
gl_PointSize = 1.0;
vColor = color;
vec4 pos = modelMatrix * vec4(a_vertexPosition, 1.0);
vec4 lp = vec4(lightPosition, 1.0);
vec3 invLight = lightPosition - pos.xyz;
vec3 norm = normalize(normalMatrix * normal);
vCos = max(dot(normalize(invLight), norm), 0.0);
gl_Position = projectionMatrix * pos;
`;
const fragment = `
#ifdef GL_ES
precision highp float;
#endif
uniform vec4 lightColor;
varying vec4 vColor;
varying float vCos;
void main()
gl_FragColor.rgb = vColor.rgb + vCos * lightColor.a * lightColor.rgb;
gl_FragColor.a = vColor.a;
`;
const canvas = document.querySelector("canvas");
// 开启深度检测
const renderer = new GlRenderer(canvas,
depth: true
);
const program = renderer.compileSync(fragment, vertex);
renderer.useProgram(program);
function cylinder(radius = 1.0, height = 1.0, segments = 30, colorCap = [0, 0, 1, 1], colorSide = [1, 0, 0, 1])
const positions = [];
const cells = [];
const color = [];
const cap = [[0, 0]];
const h = 0.5 * height;
const normal = [];
// 顶和底的圆
for(let i = 0; i <= segments; i++)
const theta = Math.PI * 2 * i / segments;
const p = 以上是关于视觉高级篇21 # 如何添加相机,用透视原理对物体进行投影?的主要内容,如果未能解决你的问题,请参考以下文章