说明

【跟月影学可视化】学习笔记。

如何用 WebGL 绘制三维立方体

我们知道立方体有8个顶点,6个面,在 WebGL 中,需要用 12 个三角形来绘制它。把每个面的顶点分开,需要 24 个顶点。


绘制 3D 图形与绘制 2D 图形有一点不一样,必须要开启深度检测和启用深度缓冲区。

在 WebGL 中,可以通过 gl.enable(gl.DEPTH_TEST),来开启深度检测。

在清空画布的时候,也要用 gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT),来同时清空颜色缓冲区和深度缓冲区。

<!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>如何用 WebGL 绘制三维立方体</title><style>canvas {border: 1px dashed salmon;}</style></head><body><canvas width="512" height="512"></canvas><script src="./common/lib/gl-renderer.js"></script><script type="module">const vertex = `attribute vec3 a_vertexPosition;attribute vec4 color;varying vec4 vColor;void main() {gl_PointSize = 1.0;vColor = color;gl_Position = vec4(a_vertexPosition, 1);}`;const fragment = `#ifdef GL_ESprecision highp float;#endifvarying vec4 vColor;void main() {gl_FragColor = vColor;}`;const canvas = document.querySelector("canvas");// 开启深度检测const renderer = new GlRenderer(canvas, {depth: true});const program = renderer.compileSync(fragment, vertex);renderer.useProgram(program);// 用来生成立方体 6 个面的 24 个顶点,以及 12 个三角形的索引function cube(size = 1.0, colors = [[1, 0, 0, 1]]) {const h = 0.5 * size;// 立方体的顶点const vertices = [[-h, -h, -h],[-h, h, -h],[h, h, -h],[h, -h, -h],[-h, -h, h],[-h, h, h],[h, h, h],[h, -h, h],];const positions = [];const color = [];const cells = [];let colorIdx = 0;let cellsIdx = 0;const colorLen = colors.length;function quad(a, b, c, d) {[a, b, c, d].forEach((i) => {positions.push(vertices[i]);color.push(colors[colorIdx % colorLen]);});cells.push([0, 1, 2].map(i => i + cellsIdx),[0, 2, 3].map(i => i + cellsIdx),);colorIdx++;cellsIdx += 4;}// 立方体的六个面quad(1, 0, 3, 2);quad(4, 5, 6, 7);quad(2, 3, 7, 6);quad(5, 4, 0, 1);quad(3, 0, 4, 7);quad(6, 5, 1, 2);return { positions, color, cells };}const geometry = cube(1.0, [[250/255, 128/255, 114/255, 1], // salmon rgb(250 128 114)[218/255, 165/255, 32/255, 1],// goldenrod rgb(218, 165, 32)[46/255, 139/255, 87/255, 1], // seagreen rgb(46 139 87)[255/255, 192/255, 203/255, 1], // pink rgb(255, 192, 203)[135/255, 206/255, 235/255, 1],// skyblue rgb(135, 206, 235)[106/255, 90/255, 205/255, 1], // slateblue rgb(106, 90, 205)]);renderer.setMeshData([{positions: geometry.positions,attributes: {color: geometry.color,},cells: geometry.cells,},]);renderer.render();</script></body>
</html>

投影矩阵:变换 WebGL 坐标系

上面朝向我们的面应该是 goldenrod 颜色, WebGL 默认的剪裁坐标的 z 轴方向,的确是朝内的。WebGL 坐标系就是一个左手系而不是右手系。下面我们需要将 WebGL 的坐标系从左手系转换为右手系。

实际上就是将 z 轴坐标方向反转,对应的齐次矩阵如下:

[1, 0, 0, 0,0, 1, 0, 0,0, 0, -1, 0,0, 0, 0, 1
]
<!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>投影矩阵:变换 WebGL 坐标系</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">const vertex = `attribute vec3 a_vertexPosition;attribute vec4 color;varying vec4 vColor;uniform mat4 projectionMatrix;void main() {gl_PointSize = 1.0;vColor = color;gl_Position = projectionMatrix * vec4(a_vertexPosition, 1);}`;const fragment = `#ifdef GL_ESprecision highp float;#endifvarying vec4 vColor;void main() {gl_FragColor = vColor;}`;const canvas = document.querySelector("canvas");// 开启深度检测const renderer = new GlRenderer(canvas, {depth: true});const program = renderer.compileSync(fragment, vertex);renderer.useProgram(program);// 用来生成立方体 6 个面的 24 个顶点,以及 12 个三角形的索引function cube(size = 1.0, colors = [[1, 0, 0, 1]]) {const h = 0.5 * size;// 立方体的顶点const vertices = [[-h, -h, -h],[-h, h, -h],[h, h, -h],[h, -h, -h],[-h, -h, h],[-h, h, h],[h, h, h],[h, -h, h],];const positions = [];const color = [];const cells = [];let colorIdx = 0;let cellsIdx = 0;const colorLen = colors.length;function quad(a, b, c, d) {[a, b, c, d].forEach((i) => {positions.push(vertices[i]);color.push(colors[colorIdx % colorLen]);});cells.push([0, 1, 2].map(i => i + cellsIdx),[0, 2, 3].map(i => i + cellsIdx),);colorIdx++;cellsIdx += 4;}// 立方体的六个面quad(1, 0, 3, 2);quad(4, 5, 6, 7);quad(2, 3, 7, 6);quad(5, 4, 0, 1);quad(3, 0, 4, 7);quad(6, 5, 1, 2);return { positions, color, cells };}const geometry = cube(1.0, [[250/255, 128/255, 114/255, 1], // salmon rgb(250 128 114)[218/255, 165/255, 32/255, 1],// goldenrod rgb(218, 165, 32)[46/255, 139/255, 87/255, 1], // seagreen rgb(46 139 87)[255/255, 192/255, 203/255, 1], // pink rgb(255, 192, 203)[135/255, 206/255, 235/255, 1],// skyblue rgb(135, 206, 235)[106/255, 90/255, 205/255, 1], // slateblue rgb(106, 90, 205)]);// 将 z 轴坐标方向反转,对应的齐次矩阵如下,转换坐标的齐次矩阵,又被称为投影矩阵(ProjectionMatrix)renderer.uniforms.projectionMatrix = [1, 0, 0, 0,0, 1, 0, 0,0, 0, -1, 0,0, 0, 0, 1,];renderer.setMeshData([{positions: geometry.positions,attributes: {color: geometry.color,},cells: geometry.cells,},]);renderer.render();</script></body>
</html>

模型矩阵:让立方体旋转起来

用立方体沿 x、y、z 轴的旋转来生成模型矩阵。以 x、y、z 三个方向的旋转得到三个齐次矩阵,然后将它们相乘,就能得到最终的模型矩阵。

<!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';const vertex = `attribute vec3 a_vertexPosition;attribute vec4 color;varying vec4 vColor;uniform mat4 projectionMatrix;uniform mat4 modelMatrix;void main() {gl_PointSize = 1.0;vColor = color;gl_Position = projectionMatrix * modelMatrix * vec4(a_vertexPosition, 1);}`;const fragment = `#ifdef GL_ESprecision highp float;#endifvarying vec4 vColor;void main() {gl_FragColor = vColor;}`;const canvas = document.querySelector("canvas");// 开启深度检测const renderer = new GlRenderer(canvas, {depth: true});const program = renderer.compileSync(fragment, vertex);renderer.useProgram(program);// 用来生成立方体 6 个面的 24 个顶点,以及 12 个三角形的索引function cube(size = 1.0, colors = [[1, 0, 0, 1]]) {const h = 0.5 * size;// 立方体的顶点const vertices = [[-h, -h, -h],[-h, h, -h],[h, h, -h],[h, -h, -h],[-h, -h, h],[-h, h, h],[h, h, h],[h, -h, h],];const positions = [];const color = [];const cells = [];let colorIdx = 0;let cellsIdx = 0;const colorLen = colors.length;function quad(a, b, c, d) {[a, b, c, d].forEach((i) => {positions.push(vertices[i]);color.push(colors[colorIdx % colorLen]);});cells.push([0, 1, 2].map(i => i + cellsIdx),[0, 2, 3].map(i => i + cellsIdx),);colorIdx++;cellsIdx += 4;}// 立方体的六个面quad(1, 0, 3, 2);quad(4, 5, 6, 7);quad(2, 3, 7, 6);quad(5, 4, 0, 1);quad(3, 0, 4, 7);quad(6, 5, 1, 2);return { positions, color, cells };}const geometry = cube(1.0, [[250/255, 128/255, 114/255, 1], // salmon rgb(250 128 114)[218/255, 165/255, 32/255, 1],// goldenrod rgb(218, 165, 32)[46/255, 139/255, 87/255, 1], // seagreen rgb(46 139 87)[255/255, 192/255, 203/255, 1], // pink rgb(255, 192, 203)[135/255, 206/255, 235/255, 1],// skyblue rgb(135, 206, 235)[106/255, 90/255, 205/255, 1], // slateblue rgb(106, 90, 205)]);// 将 z 轴坐标方向反转,对应的齐次矩阵如下,转换坐标的齐次矩阵,又被称为投影矩阵(ProjectionMatrix)renderer.uniforms.projectionMatrix = [1, 0, 0, 0,0, 1, 0, 0,0, 0, -1, 0,0, 0, 0, 1,];function fromRotation(rotationX, rotationY, rotationZ) {let c = Math.cos(rotationX);let s = Math.sin(rotationX);const rx = [1, 0, 0, 0,0, c, s, 0,0, -s, c, 0,0, 0, 0, 1,];c = Math.cos(rotationY);s = Math.sin(rotationY);const ry = [c, 0, s, 0,0, 1, 0, 0,-s, 0, c, 0,0, 0, 0, 1,];c = Math.cos(rotationZ);s = Math.sin(rotationZ);const rz = [c, s, 0, 0,-s, c, 0, 0,0, 0, 1, 0,0, 0, 0, 1,];const ret = [];multiply(ret, rx, ry);multiply(ret, ret, rz);return ret;}renderer.setMeshData([{positions: geometry.positions,attributes: {color: geometry.color,},cells: geometry.cells,},]);let rotationX = 0;let rotationY = 0;let rotationZ = 0;function update() {rotationX += 0.003;rotationY += 0.005;rotationZ += 0.007;renderer.uniforms.modelMatrix = fromRotation(rotationX, rotationY, rotationZ);requestAnimationFrame(update);}update();renderer.render();</script></body>
</html>

如何用 WebGL 绘制圆柱体

圆柱体的两个底面都是圆,可以用割圆的方式对圆进行简单的三角剖分,然后把圆柱的侧面用上下两个圆上的顶点进行三角剖分。

<!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>如何用 WebGL 绘制圆柱体</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';const vertex = `attribute vec3 a_vertexPosition;attribute vec4 color;varying vec4 vColor;uniform mat4 projectionMatrix;uniform mat4 modelMatrix;void main() {gl_PointSize = 1.0;vColor = color;gl_Position = projectionMatrix * modelMatrix * vec4(a_vertexPosition, 1.0);}`;const fragment = `#ifdef GL_ESprecision highp float;#endifvarying vec4 vColor;void main() {gl_FragColor = vColor;}`;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;// 顶和底的圆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]));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]));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));// 侧面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);color.push(colorSide, colorSide, colorSide, colorSide);cells.push([offset, offset + 1, offset + 2], [offset, offset + 2, offset + 3]);offset += 4;}return { positions, cells, color };}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,];function fromRotation(rotationX, rotationY, rotationZ) {let c = Math.cos(rotationX);let s = Math.sin(rotationX);const rx = [1, 0, 0, 0,0, c, s, 0,0, -s, c, 0,0, 0, 0, 1,];c = Math.cos(rotationY);s = Math.sin(rotationY);const ry = [c, 0, s, 0,0, 1, 0, 0,-s, 0, c, 0,0, 0, 0, 1,];c = Math.cos(rotationZ);s = Math.sin(rotationZ);const rz = [c, s, 0, 0,-s, c, 0, 0,0, 0, 1, 0,0, 0, 0, 1,];const ret = [];multiply(ret, rx, ry);multiply(ret, ret, rz);return ret;}renderer.setMeshData([{positions: geometry.positions,attributes: {color: geometry.color,},cells: geometry.cells,},]);let rotationX = 0;let rotationY = 0;let rotationZ = 0;function update() {rotationX += 0.003;rotationY += 0.005;rotationZ += 0.007;renderer.uniforms.modelMatrix = fromRotation(rotationX, rotationY, rotationZ);requestAnimationFrame(update);}update();renderer.render();</script></body>
</html>

上面是个六棱柱,我们可以修改 cylinder 函数里的 segments 参数,比如:400,就可以得到近似的圆柱体了

使用法向量和法向量矩阵来实现点光源光照效果

对于圆柱体来说,底面和顶面法线分别是 (0, 0, -1)(0, 0, 1),侧面的法向量可以通过三角网格来计算。

因为几何体是由三角网格构成的,而法线是垂直于三角网格的线,如果要计算法线,我们可以借助三角形的顶点,使用向量的叉积定理来求。假设在一个平面内,有向量 a 和 b,n 是它们的法向量,那我们可以得到公式:n = a X b

在片元着色器中,拿到的是变换后的顶点坐标,需要对法向量也进行变换,可以通过一个矩阵来实现,这个矩阵叫做法向量矩阵(NormalMatrix)

在顶点着色器中,计算位于(1,0,0)坐标处的点光源与几何体法线的夹角余弦。根据物体漫反射模型,光照强度等于光线与法向量夹角的余弦,就能在片元着色器叠加光照。

<!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);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_ESprecision highp float;#endifuniform 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 fromRotation(rotationX, rotationY, rotationZ) {let c = Math.cos(rotationX);let s = Math.sin(rotationX);const rx = [1, 0, 0, 0,0, c, s, 0,0, -s, c, 0,0, 0, 0, 1,];c = Math.cos(rotationY);s = Math.sin(rotationY);const ry = [c, 0, s, 0,0, 1, 0, 0,-s, 0, c, 0,0, 0, 0, 1,];c = Math.cos(rotationZ);s = Math.sin(rotationZ);const rz = [c, s, 0, 0,-s, c, 0, 0,0, 0, 1, 0,0, 0, 0, 1,];const ret = [];multiply(ret, rx, ry);multiply(ret, ret, rz);return ret;}console.log(geometry);renderer.setMeshData([{positions: geometry.positions,attributes: {color: geometry.color,normal: geometry.normal},cells: geometry.cells,},]);let rotationX = 0;let rotationY = 0;let rotationZ = 0;function update() {rotationX += 0.003;rotationY += 0.005;rotationZ += 0.007;const modelMatrix = fromRotation(rotationX, rotationY, rotationZ);renderer.uniforms.modelMatrix = modelMatrix;renderer.uniforms.normalMatrix = normalFromMat4([], modelMatrix);requestAnimationFrame(update);}update();renderer.render();</script></body>
</html>

【视觉高级篇】20 # 如何用WebGL绘制3D物体?相关推荐

  1. 【图形基础篇】04 # GPU与渲染管线:如何用WebGL绘制最简单的几何图形?

    说明 [跟月影学可视化]学习笔记. 图形系统是如何绘图的? 一个通用计算机图形系统主要包括 6 个部分,分别是: 输入设备 中央处理单元:首先,数据经过 CPU 处理,成为具有特定结构的几何信息. 图 ...

  2. 【视觉高级篇】27 # 如何实现简单的3D可视化图表:GitHub贡献图表的3D可视化?

    说明 [跟月影学可视化]学习笔记. 第一步:准备要展现的数据 可以使用这个生成数据:https://github.com/sallar/github-contributions-api 这里直接使用月 ...

  3. 用python绘制柱状图标题-如何用Python绘制3D柱形图

    本文主要讲解如何使用python绘制三维的柱形图,如下图 源代码如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 2 ...

  4. python三维柱形图_如何用Python绘制3D柱形图

    本文主要讲解如何使用python绘制三维的柱形图,如下图 源代码如下: import numpy as np import matplotlib.pyplot as plt from mpl_tool ...

  5. WebGL绘制3D文字(非中文)

    本教程是我在贴吧为吧友写的,搬到博客以备份. demo地址:cool.js--3D文字 源码地址:cool.js 此教程介绍一种简单粗暴的绘制3D文字的方案. 本教程以数字为例.核心思想: 制作或下载 ...

  6. 【视觉高级篇】19 # 如何用着色器实现像素动画?

    说明 [跟月影学可视化]学习笔记. 如何用着色器实现固定帧动画 <!DOCTYPE html> <html lang="en"><head>&l ...

  7. 【视觉高级篇】25 # 如何用法线贴图模拟真实物体表面

    说明 [跟月影学可视化]学习笔记. 什么是法线贴图? 法线贴图就是在原物体的凹凸表面的每个点上均作法线,通过RGB颜色通道来标记法线的方向,你可以把它理解成与原凹凸表面平行的另一个不同的表面,但实际上 ...

  8. 【视觉高级篇】22 # 如何用仿射变换来移动和旋转3D物体?

    说明 [跟月影学可视化]学习笔记. 三维仿射变换:平移 对于平移变换来说,如果向量 P( x 0 ​ x_0​ x0​​, y 0 y_0 y0​​, z 0 ​ z_0​ z0​​) 沿着向量 Q( ...

  9. 【视觉高级篇】21 # 如何添加相机,用透视原理对物体进行投影?

    说明 [跟月影学可视化]学习笔记. 如何理解相机和视图矩阵? 用一个三维坐标(Position)和一个三维向量方向(LookAt Target)来表示 WebGL 的三维世界的一个相机.要绘制以相机为 ...

最新文章

  1. BackTrack5 安装中文输入法
  2. 爬虫实战学习笔记_3 网络请求urllib模块:设置IP代理+处理请求异常+解析URL+解码+编码+组合URL+URL连接
  3. springMVC 控制层添加异步线程
  4. java nextgaussian(),java.util.Random.nextGaussian()
  5. 美团配送事业部尹兵兵加入货拉拉任运力副总裁
  6. 固态硬盘怎么看出厂日期_固态到底怎么选?雷克沙NM610和西部数据SN500固态硬盘实测对比...
  7. pycharm: connot find declaration to go to
  8. fireworks8序列号:
  9. ExtJS 前端 日期数据格式转化
  10. 同一个表单form,两个按钮button,调用同一个submit(),给action附不同的参数
  11. 劫持域名,劫持是什么意思?seo教程
  12. 【Linux】git clone报错fatal: unable to access ‘https://github.com/xxx.git/‘: Encountered end of file
  13. Pygame从0实战9(泡泡小游戏碰撞检测)
  14. 韵达上半年营收228亿:同比增25% 丰科与韵科减持套现8亿
  15. 象形文字--中文自然语言理解的突破
  16. 大数据基础之Hadoop(三)—— MapReduce
  17. k线分析中的量化测试方法_k线分析中如何使用量化思维案例分析
  18. 总包 50W,4 轮拿下阿里 Offer !(真题分享)
  19. 存储解决方案公司招聘
  20. Linux学习笔记-段错误与内存转储

热门文章

  1. bagging通过bootstrap构建集成分类器
  2. 有了抖音账号,应该这样做
  3. 关于十七届恩智浦杯安徽赛区基础组参赛分享
  4. APP切换到后台时的运行规则以及如何实现后台运行
  5. AI芯片:寒武纪NPU设计分析(DianNao)
  6. a different object with the same identifier value was already associated whith
  7. Aurix TC3xx系列MCU ADC模块简介(一)
  8. 在js中Date对象用getDay方法get到的是个啥玩意儿?
  9. Android Apk签名修改V1,V2,V3,V4
  10. php手机网页下载文件,php 手机下载 POST 类