WebGL-三維透視投影
透視投影? 通俗的講就是 近大遠小。
在上方的示例中,遠處的物體會變小,想要實現例子中近大遠小的效果, 簡單的做法就是將裁減空間中的 X 和 Y 值除以 Z 值。
你可以這麼想:如果一個線段是 (10, 15) 到 (20,15), 它長度為十個單位,在當前的程式碼中它就是 10 個畫素長, 但是如果我們將它除以 Z ,且 Z 值 為 1
10 / 1 = 10
20 / 1 = 20
abs(10-20) = 10
它將是 10 個畫素長,如果 Z 值為 2
10 / 2 = 5
20 / 2 = 10
abs(5 – 10) = 5
就是 5 畫素了,當 Z 值為 3 時
10 / 3 = 3.333
20 / 3 = 6.666
abs(3.333 – 6.666) = 3.333
你可以看出隨著 Z 變大距離就變遠了,畫的也會小一點。 如果我們除以裁剪空間中的 Z ,值可能會變大,因為 Z 是一個較小的值(-1 到 +1)。但是我們可以提供一個 fudgeFactor 因子和 Z 相乘,這樣就可以調整縮放的程度。
讓我們來試試,首先修改頂點著色器,除以 Z 再乘以我們的 “fudgeFactor” 因子。
<script id="3d-vertex-shader" type="x-shader/x-vertex"> ... uniform float u_fudgeFactor; ... void main() { // 將位置和矩陣相乘 vec4 position = u_matrix * a_position; // 調整除數 float zToDivideBy = 1.0 + position.z * u_fudgeFactor; // x 和 y 除以調整後的除數 gl_Position = vec4(position.xy / zToDivideBy, position.zw); } </script>
注意,由於裁減空間中的 Z 值是 -1 到 +1 的,所以 +1 是為了讓 zToDivideBy 變成 0 到 +2 * fudgeFactor
還需要更新程式碼以設定 fudgeFactor。
... var fudgeLocation = gl.getUniformLocation(program, "u_fudgeFactor"); ... var fudgeFactor = 1; ... function drawScene() { ... // 設定 fudgeFactor gl.uniform1f(fudgeLocation, fudgeFactor); // 繪製幾何體 var primitiveType = gl.TRIANGLES; var offset = 0; var count = 16 * 6; gl.drawArrays(primitiveType, offset, count);
執行下面的程式碼:
"use strict"; function main() { // Get A WebGL context /** @type {HTMLCanvasElement} */ var canvas = document.getElementById("canvas"); var gl = canvas.getContext("webgl"); if (!gl) { return; } // setup GLSL program var program = webglUtils.createProgramFromScripts(gl, ["3d-vertex-shader", "3d-fragment-shader"]); // look up where the vertex data needs to go. var positionLocation = gl.getAttribLocation(program, "a_position"); var colorLocation = gl.getAttribLocation(program, "a_color"); // lookup uniforms var matrixLocation = gl.getUniformLocation(program, "u_matrix"); var fudgeLocation = gl.getUniformLocation(program, "u_fudgeFactor"); // Create a buffer to put positions in var positionBuffer = gl.createBuffer(); // Bind it to ARRAY_BUFFER (think of it as ARRAY_BUFFER = positionBuffer) gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer); // Put geometry data into buffer setGeometry(gl); // Create a buffer to put colors in var colorBuffer = gl.createBuffer(); // Bind it to ARRAY_BUFFER (think of it as ARRAY_BUFFER = colorBuffer) gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer); // Put geometry data into buffer setColors(gl); function radToDeg(r) { return r * 180 / Math.PI; } function degToRad(d) { return d * Math.PI / 180; } var translation = [45, 150, 0]; var rotation = [degToRad(40), degToRad(25), degToRad(325)]; var scale = [1, 1, 1]; var fudgeFactor = 1; drawScene(); // Setup a ui. webglLessonsUI.setupSlider("#fudgeFactor", {value: fudgeFactor, slide: updateFudgeFactor, max: 2, step: 0.001, precision: 3 }); webglLessonsUI.setupSlider("#x", {value: translation[0], slide: updatePosition(0), max: gl.canvas.width }); webglLessonsUI.setupSlider("#y", {value: translation[1], slide: updatePosition(1), max: gl.canvas.height}); webglLessonsUI.setupSlider("#z", {value: translation[2], slide: updatePosition(2), max: gl.canvas.height, min: -gl.canvas.height}); webglLessonsUI.setupSlider("#angleX", {value: radToDeg(rotation[0]), slide: updateRotation(0), max: 360}); webglLessonsUI.setupSlider("#angleY", {value: radToDeg(rotation[1]), slide: updateRotation(1), max: 360}); webglLessonsUI.setupSlider("#angleZ", {value: radToDeg(rotation[2]), slide: updateRotation(2), max: 360}); webglLessonsUI.setupSlider("#scaleX", {value: scale[0], slide: updateScale(0), min: -5, max: 5, step: 0.01, precision: 2}); webglLessonsUI.setupSlider("#scaleY", {value: scale[1], slide: updateScale(1), min: -5, max: 5, step: 0.01, precision: 2}); webglLessonsUI.setupSlider("#scaleZ", {value: scale[2], slide: updateScale(2), min: -5, max: 5, step: 0.01, precision: 2}); function updateFudgeFactor(event, ui) { fudgeFactor = ui.value; drawScene(); } function updatePosition(index) { return function(event, ui) { translation[index] = ui.value; drawScene(); } } function updateRotation(index) { return function(event, ui) { var angleInDegrees = ui.value; var angleInRadians = angleInDegrees * Math.PI / 180; rotation[index] = angleInRadians; drawScene(); } } function updateScale(index) { return function(event, ui) { scale[index] = ui.value; drawScene(); } } // Draw the scene. function drawScene() { webglUtils.resizeCanvasToDisplaySize(gl.canvas); // Tell WebGL how to convert from clip space to pixels gl.viewport(0, 0, gl.canvas.width, gl.canvas.height); // Clear the canvas AND the depth buffer. gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); // Turn on culling. By default backfacing triangles // will be culled. gl.enable(gl.CULL_FACE); // Enable the depth buffer gl.enable(gl.DEPTH_TEST); // Tell it to use our program (pair of shaders) gl.useProgram(program); // Turn on the position attribute gl.enableVertexAttribArray(positionLocation); // Bind the position buffer. gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer); // Tell the position attribute how to get data out of positionBuffer (ARRAY_BUFFER) var size = 3; // 3 components per iteration var type = gl.FLOAT; // the data is 32bit floats var normalize = false; // don't normalize the data var stride = 0; // 0 = move forward size * sizeof(type) each iteration to get the next position var offset = 0; // start at the beginning of the buffer gl.vertexAttribPointer( positionLocation, size, type, normalize, stride, offset) // Turn on the color attribute gl.enableVertexAttribArray(colorLocation); // Bind the color buffer. gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer); // Tell the attribute how to get data out of colorBuffer (ARRAY_BUFFER) var size = 3; // 3 components per iteration var type = gl.UNSIGNED_BYTE; // the data is 8bit unsigned values var normalize = true; // normalize the data (convert from 0-255 to 0-1) var stride = 0; // 0 = move forward size * sizeof(type) each iteration to get the next position var offset = 0; // start at the beginning of the buffer gl.vertexAttribPointer( colorLocation, size, type, normalize, stride, offset) // Compute the matrices var matrix = m4.projection(gl.canvas.clientWidth, gl.canvas.clientHeight, 400); matrix = m4.translate(matrix, translation[0], translation[1], translation[2]); matrix = m4.xRotate(matrix, rotation[0]); matrix = m4.yRotate(matrix, rotation[1]); matrix = m4.zRotate(matrix, rotation[2]); matrix = m4.scale(matrix, scale[0], scale[1], scale[2]); // Set the matrix. gl.uniformMatrix4fv(matrixLocation, false, matrix); // Set the fudgeFactor gl.uniform1f(fudgeLocation, fudgeFactor); // Draw the geometry. var primitiveType = gl.TRIANGLES; var offset = 0; var count = 16 * 6; gl.drawArrays(primitiveType, offset, count); } } var m4 = { projection: function(width, height, depth) { // Note: This matrix flips the Y axis so 0 is at the top. return [ 2 / width, 0, 0, 0, 0, -2 / height, 0, 0, 0, 0, 2 / depth, 0, -1, 1, 0, 1, ]; }, multiply: function(a, b) { var a00 = a[0 * 4 + 0]; var a01 = a[0 * 4 + 1]; var a02 = a[0 * 4 + 2]; var a03 = a[0 * 4 + 3]; var a10 = a[1 * 4 + 0]; var a11 = a[1 * 4 + 1]; var a12 = a[1 * 4 + 2]; var a13 = a[1 * 4 + 3]; var a20 = a[2 * 4 + 0]; var a21 = a[2 * 4 + 1]; var a22 = a[2 * 4 + 2]; var a23 = a[2 * 4 + 3]; var a30 = a[3 * 4 + 0]; var a31 = a[3 * 4 + 1]; var a32 = a[3 * 4 + 2]; var a33 = a[3 * 4 + 3]; var b00 = b[0 * 4 + 0]; var b01 = b[0 * 4 + 1]; var b02 = b[0 * 4 + 2]; var b03 = b[0 * 4 + 3]; var b10 = b[1 * 4 + 0]; var b11 = b[1 * 4 + 1]; var b12 = b[1 * 4 + 2]; var b13 = b[1 * 4 + 3]; var b20 = b[2 * 4 + 0]; var b21 = b[2 * 4 + 1]; var b22 = b[2 * 4 + 2]; var b23 = b[2 * 4 + 3]; var b30 = b[3 * 4 + 0]; var b31 = b[3 * 4 + 1]; var b32 = b[3 * 4 + 2]; var b33 = b[3 * 4 + 3]; return [ b00 * a00 + b01 * a10 + b02 * a20 + b03 * a30, b00 * a01 + b01 * a11 + b02 * a21 + b03 * a31, b00 * a02 + b01 * a12 + b02 * a22 + b03 * a32, b00 * a03 + b01 * a13 + b02 * a23 + b03 * a33, b10 * a00 + b11 * a10 + b12 * a20 + b13 * a30, b10 * a01 + b11 * a11 + b12 * a21 + b13 * a31, b10 * a02 + b11 * a12 + b12 * a22 + b13 * a32, b10 * a03 + b11 * a13 + b12 * a23 + b13 * a33, b20 * a00 + b21 * a10 + b22 * a20 + b23 * a30, b20 * a01 + b21 * a11 + b22 * a21 + b23 * a31, b20 * a02 + b21 * a12 + b22 * a22 + b23 * a32, b20 * a03 + b21 * a13 + b22 * a23 + b23 * a33, b30 * a00 + b31 * a10 + b32 * a20 + b33 * a30, b30 * a01 + b31 * a11 + b32 * a21 + b33 * a31, b30 * a02 + b31 * a12 + b32 * a22 + b33 * a32, b30 * a03 + b31 * a13 + b32 * a23 + b33 * a33, ]; }, translation: function(tx, ty, tz) { return [ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, tx, ty, tz, 1, ]; }, xRotation: function(angleInRadians) { var c = Math.cos(angleInRadians); var s = Math.sin(angleInRadians); return [ 1, 0, 0, 0, 0, c, s, 0, 0, -s, c, 0, 0, 0, 0, 1, ]; }, yRotation: function(angleInRadians) { var c = Math.cos(angleInRadians); var s = Math.sin(angleInRadians); return [ c, 0, -s, 0, 0, 1, 0, 0, s, 0, c, 0, 0, 0, 0, 1, ]; }, zRotation: function(angleInRadians) { var c = Math.cos(angleInRadians); var s = Math.sin(angleInRadians); return [ c, s, 0, 0, -s, c, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, ]; }, scaling: function(sx, sy, sz) { return [ sx, 0, 0, 0, 0, sy, 0, 0, 0, 0, sz, 0, 0, 0, 0, 1, ]; }, translate: function(m, tx, ty, tz) { return m4.multiply(m, m4.translation(tx, ty, tz)); }, xRotate: function(m, angleInRadians) { return m4.multiply(m, m4.xRotation(angleInRadians)); }, yRotate: function(m, angleInRadians) { return m4.multiply(m, m4.yRotation(angleInRadians)); }, zRotate: function(m, angleInRadians) { return m4.multiply(m, m4.zRotation(angleInRadians)); }, scale: function(m, sx, sy, sz) { return m4.multiply(m, m4.scaling(sx, sy, sz)); }, }; // Fill the buffer with the values that define a letter 'F'. function setGeometry(gl) { gl.bufferData( gl.ARRAY_BUFFER, new Float32Array([ // left column front 0, 0, 0, 0, 150, 0, 30, 0, 0, 0, 150, 0, 30, 150, 0, 30, 0, 0, // top rung front 30, 0, 0, 30, 30, 0, 100, 0, 0, 30, 30, 0, 100, 30, 0, 100, 0, 0, // middle rung front 30, 60, 0, 30, 90, 0, 67, 60, 0, 30, 90, 0, 67, 90, 0, 67, 60, 0, // left column back 0, 0, 30, 30, 0, 30, 0, 150, 30, 0, 150, 30, 30, 0, 30, 30, 150, 30, // top rung back 30, 0, 30, 100, 0, 30, 30, 30, 30, 30, 30, 30, 100, 0, 30, 100, 30, 30, // middle rung back 30, 60, 30, 67, 60, 30, 30, 90, 30, 30, 90, 30, 67, 60, 30, 67, 90, 30, // top 0, 0, 0, 100, 0, 0, 100, 0, 30, 0, 0, 0, 100, 0, 30, 0, 0, 30, // top rung right 100, 0, 0, 100, 30, 0, 100, 30, 30, 100, 0, 0, 100, 30, 30, 100, 0, 30, // under top rung 30, 30, 0, 30, 30, 30, 100, 30, 30, 30, 30, 0, 100, 30, 30, 100, 30, 0, // between top rung and middle 30, 30, 0, 30, 60, 30, 30, 30, 30, 30, 30, 0, 30, 60, 0, 30, 60, 30, // top of middle rung 30, 60, 0, 67, 60, 30, 30, 60, 30, 30, 60, 0, 67, 60, 0, 67, 60, 30, // right of middle rung 67, 60, 0, 67, 90, 30, 67, 60, 30, 67, 60, 0, 67, 90, 0, 67, 90, 30, // bottom of middle rung. 30, 90, 0, 30, 90, 30, 67, 90, 30, 30, 90, 0, 67, 90, 30, 67, 90, 0, // right of bottom 30, 90, 0, 30, 150, 30, 30, 90, 30, 30, 90, 0, 30, 150, 0, 30, 150, 30, // bottom 0, 150, 0, 0, 150, 30, 30, 150, 30, 0, 150, 0, 30, 150, 30, 30, 150, 0, // left side 0, 0, 0, 0, 0, 30, 0, 150, 30, 0, 0, 0, 0, 150, 30, 0, 150, 0]), gl.STATIC_DRAW); } // Fill the buffer with colors for the 'F'. function setColors(gl) { gl.bufferData( gl.ARRAY_BUFFER, new Uint8Array([ // left column front 200, 70, 120, 200, 70, 120, 200, 70, 120, 200, 70, 120, 200, 70, 120, 200, 70, 120, // top rung front 200, 70, 120, 200, 70, 120, 200, 70, 120, 200, 70, 120, 200, 70, 120, 200, 70, 120, // middle rung front 200, 70, 120, 200, 70, 120, 200, 70, 120, 200, 70, 120, 200, 70, 120, 200, 70, 120, // left column back 80, 70, 200, 80, 70, 200, 80, 70, 200, 80, 70, 200, 80, 70, 200, 80, 70, 200, // top rung back 80, 70, 200, 80, 70, 200, 80, 70, 200, 80, 70, 200, 80, 70, 200, 80, 70, 200, // middle rung back 80, 70, 200, 80, 70, 200, 80, 70, 200, 80, 70, 200, 80, 70, 200, 80, 70, 200, // top 70, 200, 210, 70, 200, 210, 70, 200, 210, 70, 200, 210, 70, 200, 210, 70, 200, 210, // top rung right 200, 200, 70, 200, 200, 70, 200, 200, 70, 200, 200, 70, 200, 200, 70, 200, 200, 70, // under top rung 210, 100, 70, 210, 100, 70, 210, 100, 70, 210, 100, 70, 210, 100, 70, 210, 100, 70, // between top rung and middle 210, 160, 70, 210, 160, 70, 210, 160, 70, 210, 160, 70, 210, 160, 70, 210, 160, 70, // top of middle rung 70, 180, 210, 70, 180, 210, 70, 180, 210, 70, 180, 210, 70, 180, 210, 70, 180, 210, // right of middle rung 100, 70, 210, 100, 70, 210, 100, 70, 210, 100, 70, 210, 100, 70, 210, 100, 70, 210, // bottom of middle rung. 76, 210, 100, 76, 210, 100, 76, 210, 100, 76, 210, 100, 76, 210, 100, 76, 210, 100, // right of bottom 140, 210, 80, 140, 210, 80, 140, 210, 80, 140, 210, 80, 140, 210, 80, 140, 210, 80, // bottom 90, 130, 110, 90, 130, 110, 90, 130, 110, 90, 130, 110, 90, 130, 110, 90, 130, 110, // left side 160, 160, 220, 160, 160, 220, 160, 160, 220, 160, 160, 220, 160, 160, 220, 160, 160, 220]), gl.STATIC_DRAW); } main();
事實上WebGL會將我們提供給 gl_Position 的 x,y,z,w 值自動除以 w 。
我們可以透過修改著色器來證明,用 zToDivideBy 代替 gl_Position.w
<script id="2d-vertex-shader" type="x-shader/x-vertex"> ... uniform float u_fudgeFactor; ... void main() { // 將位置和矩陣相乘 vec4 position = u_matrix * a_position; // 調整除數 float zToDivideBy = 1.0 + position.z * u_fudgeFactor; // 將 x y z 除以 zToDivideBy gl_Position = vec4(position.xyz, zToDivideBy); // 傳遞顏色到給片斷著色器 v_color = a_color; } </script>
為什麼WebGL會自動除以 W ?因為使用矩陣的魔力,可以用把值從 z 傳值到 w 。
一個這樣的矩陣
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 1,
0, 0, 0, 0,
將會把 z 的值複製給 w , 你可以把每列看作
x_out = x_in * 1 +
y_in * 0 +
z_in * 0 +
w_in * 0 ;
y_out = x_in * 0 +
y_in * 1 +
z_in * 0 +
w_in * 0 ;
z_out = x_in * 0 +
y_in * 0 +
z_in * 1 +
w_in * 0 ;
w_out = x_in * 0 +
y_in * 0 +
z_in * 1 +
w_in * 0 ;
簡化後得到
x_out = x_in;
y_out = y_in;
z_out = z_in;
w_out = z_in;
如果 w 原來就是 1.0 就會加 1
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 1,
0, 0, 0, 1,
他會將 W 的運算變為
w_out = x_in * 0 +
y_in * 0 +
z_in * 1 +
w_in * 1 ;
因為 w_in = 1.0 是已知的
w_out = z_in + 1;
最後可以將 fudgeFactor 像這樣放入矩陣中
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, fudgeFactor,
0, 0, 0, 1,
相當於
w_out = x_in * 0 +
y_in * 0 +
z_in * fudgeFactor +
w_in * 1 ;
簡化後為
w_out = z_in * fudgeFactor + 1;
我們來修改程式碼,使用這個矩陣。
首先將頂點著色器還原,又變成簡單的樣子
<script id="2d-vertex-shader" type="x-shader/x-vertex"> uniform mat4 u_matrix; void main() { // 位置和矩陣相乘 gl_Position = u_matrix * a_position; ... } </script>
接下來定義一個方法實現 Z → W 的矩陣
function makeZToWMatrix(fudgeFactor) { return [ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, fudgeFactor, 0, 0, 0, 1, ]; }
然後使用它:
... // 計算矩陣 var matrix = makeZToWMatrix(fudgeFactor); matrix = m4.multiply(matrix, m4.projection(gl.canvas.clientWidth, gl.canvas.clientHeight, 400)); matrix = m4.translate(matrix, translation[0], translation[1], translation[2]); matrix = m4.xRotate(matrix, rotation[0]); matrix = m4.yRotate(matrix, rotation[1]); matrix = m4.zRotate(matrix, rotation[2]); matrix = m4.scale(matrix, scale[0], scale[1], scale[2]); ...
這只是展示了除以 Z 值獲可以實現透視投影,以及在WebGL中簡單實現。
但還有一些問題需要解決,比如將 Z 值設定為 -100 左右的時候會遇到下面的情形
為什麼會這樣?為什麼 F 提前消失了?WebGL裁剪空間中的 X 和 Y 會被 +1 和 -1 裁剪, Z也一樣。我們看到的是 Z < -1 的情況。
我可以從數學方法深入探討並尋找解決辦法,但是你可以 聯想 二維中的的解決方法。我們需要獲取 Z 值,然後加上一些量, 縮放一些量,就可以將任意範圍對映到 -1 到 +1 的範圍內。
最有意思的是這件事可以在一個矩陣中完成,更方便的是, 我們可以定義一個 fieldOfView 代替 fudgeFactor , 計算出更合適的值。
這是建立矩陣的方法。
var m4 = { perspective: function(fieldOfViewInRadians, aspect, near, far) { var f = Math.tan(Math.PI * 0.5 - 0.5 * fieldOfViewInRadians); var rangeInv = 1.0 / (near - far); return [ f / aspect, 0, 0, 0, 0, f, 0, 0, 0, 0, (near + far) * rangeInv, -1, 0, 0, near * far * rangeInv * 2, 0 ]; }, ...
這個矩陣會為我們完成所有轉換。它可以調整單位以適應裁剪空間, 它可以自定義視場角,選擇 Z-裁剪面。假設有一個眼睛或者攝像機 在原點(0, 0, 0),根據 zNear 和 fieldOfView 可以將 zNear 對應到 Z = -1 ,在 zNear 平面上一半的 fieldOfView 長度 對應畫布中心到 Y = -1 或 Y = 1 的距離,X 的值透過乘以 aspect 獲取,最後透過設定 zFar 對應 Z = 1 ,控制縮放的程度。
這是矩陣的圖解。
正方體所在的有四個側面的椎體叫做“視錐”,矩陣將視錐中的空間轉換到裁剪空間中, zNear 決定了被正面切割的位置,zFar 決定被背面切割的位置。 將 zNear 設定為 23 就會看到正方體正面被切割, 將 zFar 設定為 24 就會看到正方體背面被切割。
還有一個問題,矩陣假定觀察位置為 0,0,0 並且看向 Z 軸負方向, Y 軸為上方向。這和我們目前為止做法不同, 為了解決這個問題我們需要將物體放到檢視範圍內。
我們在 (45, 150, 0) 繪製的 F,可以將它移動到 (-150, 0, -360)
使用 m4.projection 方法代替之前的投影方法,可以呼叫 m4.perspective
var aspect = gl.canvas.clientWidth / gl.canvas.clientHeight; var zNear = 1; var zFar = 2000; var matrix = m4.perspective(fieldOfViewRadians, aspect, zNear, zFar); matrix = m4.translate(matrix, translation[0], translation[1], translation[2]); matrix = m4.xRotate(matrix, rotation[0]); matrix = m4.yRotate(matrix, rotation[1]); matrix = m4.zRotate(matrix, rotation[2]); matrix = m4.scale(matrix, scale[0], scale[1], scale[2]);
我們講了矩陣乘法,視角和自定義 Z 範圍。還有很多沒講完, 但這篇文章已經很長了,所以接下來繼續講相機。
為什麼將 F 移動到那麼遠的距離(Z = -360)?
在其他的例子中 F 都在 (45, 150, 0) ,但在最後一個例子中它被移動到了 (-150, 0, -360)。為什麼它被移動到那麼遠的地方?
原因是在最後一個例子中用 m4.projection 方法將 畫素移動到裁減空間,我們的顯示範圍是 400×300 畫素, “畫素”在三維中無法解釋。所以新投影建立了一個視錐,它在 zNear 的距離時是 2 個單位高和 2 * aspect 個單位寬。由於 ‘F’ 的大小是 150 個單位, 在近平面的時候只能看到 2 個單位的高度, 所以我們將它移到足夠遠的地方才能看到完整的它。
同樣的將 ‘X’ 從 45 移動到 -150 。過去檢視表示的範圍是 0 到 400 個單位, 現在它表示的 -1 到 +1 個單位。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69979777/viewspace-2708589/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 視訊投影(二維視訊投影到三維模型上)模型
- 3D圖形:透視投影3D
- 13 Three.js照相機的正交投影和透視投影JS
- 計算機圖形:三維觀察之投影變換計算機
- matlab練習程式(透視投影,把lena貼到billboard上)Matlab
- manim 中的三維視角
- 基於結構光投影三維重建:格雷碼編碼與解碼
- 彈簧系統三維視覺化視覺化
- PostgreSQLHybridDBforPG毫秒級多維資料透視案例分享SQL
- 帶你理解transform:rotate(含三維檢視)ORM
- 透視不同的架構思維,賞析架構之美架構
- 透過視覺化運維配置,實現故障秒級自愈視覺化運維
- 松下電視智慧投影Mirror for Panasonic TV
- w10系統如何投影到電視_w10系統投影到電視的操作步驟
- 透視“消費中國”——“網際網路+”下的觀察新維度
- 多視角三維模型紋理對映 01模型
- 智慧黨建雲展廳三維視覺化視覺化
- OpenGL模型檢視變換、投影變換、視口變換模型
- 透視表excel透視表怎麼做 excel的資料透視表怎麼弄Excel
- 常見的三種沉浸式投影型別型別
- 奔跑吧,大屏-時間+空間實時四維資料透視
- 透過谷歌當機事故看儲存運維三大重要趨勢谷歌運維
- ps透視裁剪扣取透視面上的相框內容
- 中國遊戲公司透視:誰是第三個百億俱樂部成員?遊戲
- Roku智慧電視投影Mirror for Roku Mac版軟體Mac
- Sovit3D智慧海上風電場三維視覺化運維繫統3D視覺化運維
- 數維圖智慧工業園三維視覺化安全管控雲平臺視覺化
- win10 怎麼把螢幕投到電視上_win10系統怎麼透過無線方式將螢幕投影到電視Win10
- Halcon6:三維重建和光度立體視覺視覺
- 三維視覺化,助設計與管理高效對接視覺化
- 使用 ECharts GL 實現三維視覺化 - 入門款Echarts視覺化
- 【數字孿生】智慧機房三維視覺化運維繫統解決方案視覺化運維
- 大屏視覺化解決方案公司,三維視覺化地圖怎麼做?視覺化地圖
- YouLoox全息投影 比VR更刺激的視覺體驗VR視覺
- 雙十一買智慧電視還是買智慧投影儀?
- 淺談沉浸式投影的三大技術特點
- Excel資料透視表怎麼做 Excel資料透視表技巧Excel
- 海事監管新模式 | 智慧艦船三維視覺化管理模式視覺化