日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 前端技术 > HTML >内容正文

HTML

前端对div连线_《前端图形学从入门到放弃》003 三维世界

發布時間:2023/12/4 HTML 30 豆豆
生活随笔 收集整理的這篇文章主要介紹了 前端对div连线_《前端图形学从入门到放弃》003 三维世界 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

從本篇起,我們將正式進入webgl的3D世界

本篇涵蓋的內容包括:

  • webgl它在干啥?
  • 如何畫一個正方體?
  • 如何成為一個“有深度”的正方體?
  • 正方體要離家出走了!
  • webgl它在干啥?

    首先我們需要知道webgl的世界其實是一個x[-1,1],y[-1,1],z[-1,1]的小世界,所有在這個長度2的世界中的物體,才能被顯示。如圖所示:

    黃色區域是webgl的[-1,1]區域,上方的球和下方的長方體都完全在這個區域內可以完全被顯示,而中間的立方體這是部分顯示

    從正面看就會是

    段棒子和球完全顯示,常棒子超出區域被截掉

    簡單說來,這個過程中webgl把三緯空間xyz[-1,1]壓扁成xy[1,-1]再根據實際畫布的尺寸,繪制到canvas上,這個過程就是光柵化。不過如果三緯空間xyz[-1,1]中的物體如圖所示,而canvas是2:1的尺寸的話實際得到的圖片會是這樣的:

    那么為啥我平常用的canvas不會變形?請耐心看到文末你會有答案的!

    如何畫一個正方體?

    本篇我們并不會在作色器上大動手腳,所以按照老方法,做一遍就可以了,不懂得可以看看《前端圖形學從入門到放棄》第一篇。我們對作色器的唯一改造就是,開放了標示深度的z值:

    <!-- 一個頂點著色器提供裁剪空間坐標值 --> <script id="vertex-shader-2d" type="notjs">attribute vec3 a_position;uniform mat4 u_matrix;// 這個是后續空間變化需要的,這一步void main() {vec4 position = u_matrix * vec4(a_position,1.0);gl_Position = vec4(position.xyz,1.0);} </script> <!-- 一個片斷著色器提供顏色值 --> <script id="fragment-shader-2d" type="notjs">precision mediump float;void main() {// 輸出顏色固定即可,我選的是(1.0,0.1,.45),rgb都除以255gl_FragColor = vec4(vec3(1.0,0.1,.45), 1.0); // }</script>

    我們當然可以使用gl.drawArrays方法繼續繪制,但對于數量更多的圖形,這個方法其實不夠高效,想象一下有如圖ABCD四個點組成了兩個三角形:

    對于gl.drawArrays我們需要向buffer中傳入ABC后再傳入BCD繪制兩個三角形,但buffer就會重復存儲點BC,目前還只有位置坐標,如果是復雜的場景一個點上除了位置信息外,還會包含顏色,法線等一些列數據,這就相當浪費存儲空間了。所以我們采用gl.drawElements方法。

    頂點數據還是如前,往buffer中丟,只不過每個點只傳入一次,例如我們需要繪制一個以[0,0,0]為中心,邊長是0.4的正方形,他的的八個頂點組成的數據是:

    var data = new Float32Array([0.2, 0.2, 0.2,-0.2, 0.2, 0.2,-0.2, -0.2, 0.2,0.2, -0.2, 0.2,0.2, 0.2, -0.2,-0.2, 0.2, -0.2,-0.2, -0.2, -0.2,0.2, -0.2, -0.2, ]);

    這時我們還需要一個索引數組來標識,用哪些點圍城圖形。

    //頂點索引數組 var indexes = new Uint8Array([//右側四個點0, 1, 2, 3,//左側四個頂點4, 5, 6, 7,//左右對應關系0, 4,1, 5,2, 6,3, 7 ]);

    然后把這兩個數據丟給buffer:

    var indexesBuffer = gl.createBuffer();gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexesBuffer);gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indexes, gl.STATIC_DRAW);var vBuffer = gl.createBuffer();// 頂點需要是 ARRAY_BUFFERgl.bindBuffer(gl.ARRAY_BUFFER, vBuffer);gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW);

    最后就是繪制:

    //LINE_LOOP模式繪制右側四個點 gl.drawElements(gl.LINE_LOOP, 4, gl.UNSIGNED_BYTE, 0); //LINE_LOOP模式從第五個點開始繪制左側四個點 gl.drawElements(gl.LINE_LOOP, 4, gl.UNSIGNED_BYTE, 4); //LINES模式繪制連線左右點連線 gl.drawElements(gl.LINES, 8, gl.UNSIGNED_BYTE, 8);

    這樣運行一遍會得到如下結果:

    奇怪?為什么只有一個正方形?說好的3D?

    這是因為我們現在還是正交視圖,后面的點完全被前面的點擋住了,所以我們需要移動這個立方體。讓后面的點被看到

    所以,接下來我們創建6個滑塊分別控制正方形向x,y,z移動和繞x,y,z旋轉。你怎么實現都可以:

    <div id="control"><div class="item item-x"><span>x:</span><input type="range" id="item-x" value="0" min="-1" max="1" step="0.01" ></div><div class="item item-y"><span>y:</span><input type="range" id="item-y" value="0" min="-1" max="1" step="0.01" ></div><div class="item"><span>z:</span><input type="range" id="item-z" value="0" min="-1" max="1" step="0.01" ></div><div class="item item-x"><span>x軸角度:</span><input type="range" id="item-r-x" value="0" min="-3.14" max="3.14" step="0.001" ></div><div class="item item-y"><span>y軸角度:</span><input type="range" id="item-r-y" value="0" min="-3.14" max="3.14" step="0.001" ></div><div class="item"><span>z軸角度:</span><input type="range" id="item-r-z" value="0" min="-3.14" max="3.14" step="0.001" ></div></div>

    而通過這些數據我們可以組成一個矩陣對立方體的點進行變換。只是從《前端圖形學從入門到放棄》002第二篇的2D(支持齊次變換的3維矩陣)變成了3D(支持齊次變換的4維矩陣)。

    他們分別是一個平移矩陣和3個旋轉矩陣:

    var m4 = { // 用來存放三位變換方法的對象transform: function (x, y, z) {return [1, 0, 0, 0,0, 1, 0, 0,0, 0, 1, 0,x, y, z, 1,];},rotateX: function (deg) {var c = Math.cos(deg);var s = Math.sin(deg);return [1, 0, 0, 0,0, c, s, 0,0, -s, c, 0,0, 0, 0, 1,]},rotateY: function (deg) {var c = Math.cos(deg);var s = Math.sin(deg);return [c, 0, -s, 0,0, 1, 0, 0,s, 0, c, 0,0, 0, 0, 1,]},rotateZ: function (deg) {var c = Math.cos(deg);var s = Math.sin(deg);return [c, s, 0, 0,-s, c, 0, 0,0, 0, 1, 0,0, 0, 0, 1,]}, }

    當然我們還需要一個矩陣的乘法方法multiply,以及可以支持多個矩陣連乘的multiplys方法:

    var m4 = { // .......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,];},multiplys: function (list) {var that = this;return list.reduce(function (a, b) {return that.multiply(a, b);})}, // ....... }

    要完成對立方體的控制,我們需要把渲染寫入一個循環中:

    var xNode = document.querySelector('#item-x');var yNode = document.querySelector('#item-y');var zNode = document.querySelector('#item-z');var rXNode = document.querySelector('#item-r-x');var rYNode = document.querySelector('#item-r-y');var rZNode = document.querySelector('#item-r-z');// 。。。。。loop();function loop() {var mx = m4.rotateX(rXNode.value); var my = m4.rotateY(rYNode.value);var mz = m4.rotateZ(rZNode.value);var mTransform = m4.transform(xNode.value,yNode.value,zNode.value);var matrix = m4.multiplys([mTransform,mz,my,mx]);gl.uniformMatrix4fv(u_matrix, false, matrix);// 把矩陣傳入頂點作色器gl.clearColor(0.75, 0.85, 0.8, 1.0);gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);//LINE_LOOP模式繪制右側四個點gl.drawElements(gl.LINE_LOOP, 4, gl.UNSIGNED_BYTE, 0);//LINE_LOOP模式從第五個點開始繪制左側四個點gl.drawElements(gl.LINE_LOOP, 4, gl.UNSIGNED_BYTE, 4);//LINES模式繪制連線左右點連線gl.drawElements(gl.LINES, 8, gl.UNSIGNED_BYTE, 8);requestAnimationFrame(loop)}

    這樣一來我們就能看到立方體的屁股了:

    知乎視頻?www.zhihu.com

    如何成為一個“有深度”的正方體?

    雖然我們實現了3D效果,但這種正交效果并不會有日常我們見到的透視中的近大遠小。

    我們要如何實現呢?

    我們先來想想近大遠小是什么,近大遠小不就是近處的看起來大一點遠處的看起來小一點么。那么最直接的近大遠小就是根據z值大小去縮放點的位置。

    這里我們添加一個控制點來縮放透視程度:

    <div class="item"><span>z透視參數:</span><input type="range" id="item-z-factor" value="0" min="0" max="2" step="0.001" ></div> // ....... <script> //........ var zFactorNode = document.querySelector("#item-z-factor") // ........ </script>

    然后我們把這個變換寫成一個矩陣的形式:

    var zFactorMatrix = [1,0,0,0,0,1,0,0,0,0,1,zFactorNode.value,0,0,0,1 ]; var matrix = m4.multiplys([zFactorMatrix,mTransform,mz,my,mx]);

    這樣通過調節z透視參數,我們的立方體就有了縱深感:

    知乎視頻?www.zhihu.com

    需要注意的是,xyz經過zFactorMatrix處理后沒有變化:

    out_x = inx+0+0+0;out_y = in_y+0+0+0;out_z = in_z+0+0+0;

    但w會變成

    out_w = in_w+1

    給 gl_Position 的 x,y,z,w 值自動除以 w,所以所有點的x,y才會受到z的控制。

    但實際開發中,我們不會使用zFactor來控制深度。而是用透視矩陣:

    不過在此之前我們先來解決,物體超出xyz[-1,1]空間的問題。

    正方體要離家出走了!

    例如我把立方體變成一個邊長20,中心在(0,0,70)的立方體:

    var data = new Float32Array([10, 10, 80,-10, 10, 80,-10, -10, 80,10, -10, 80,10, 10, 60,-10, 10, 60,-10, -10, 60,10, -10, 60, ]);

    它立刻消失在屏幕上。

    所以我們現在要做的是指定一個區域(假設是以(0,0,70)為中心,變成是50的空間,圖中黑色區域)將其矩陣變換到空間[-1,1]之中。

    第一步我們自然是要把這個空間移動到(0,0,0),第二部就是把這個空間縮小到[-1,1]

    這里我們在m4對象中再創建一個方法orthographic,它接受6個參數(t, b, l, r, n, f)分別所需要呈現空間的y,x,z的最小和最大值,對于以(0,0,70)為中心,變成是50的空間,這6個參數是(25,-25,25,-25,45,95):

    var m4 = { // ......orthographic: function (t, b, l, r, n, f) {//先把空間移動到0,0,0var trans = [1, 0, 0, 0,0, 1, 0, 0,0, 0, 1, 0,-(r + l) / 2, -(t + b) / 2, -(n + f) / 2, 1,]//在把空間縮放到[-1,1]var scale = [2 / (r - l), 0, 0, 0,0, 2 / (t - b), 0, 0,0, 0, 2 / (n - f), 0,0, 0, 0, 1,]return this.multiply(scale, trans);}, //...... }

    大家可以用原始空間的頂點帶入矩陣計算看看是不是都被移動到了[-1,1]的八個頂點上。

    這樣我們在loop中再創建一個矩陣:

    var Ortho = m4.orthographic(25,-25,25,-25,45,95);

    而此時傳入作色器的matrix矩陣將變成:

    var matrix = m4.multiplys([Ortho, mTransform, mx, my, mz]);

    而運行后通過調整參數我們又得到立方體,這樣我們就實現了將空間中任意區域的幾何體畫到canvas上了:

    雖然如此但我們的透視又消失了回到了正交的狀態,而且旋轉的時候,物體好像并不是繞著自己的中心在轉?這又是怎么回事?由于篇幅有限,這些事我們留到下回再說吧!

    下回我們將解決:

  • 如何加入透視?
  • 如何讓物體繞著自己旋轉
  • 如何讓相機移動與旋轉
  • 如何盯住物體
  • 參考資料:

    https://webglfundamentals.org/webgl/lessons/zh_cn/webgl-3d-perspective.html?webglfundamentals.org

    WebGL 三維透視投影

    WebGL 三維透視投影?webglfundamentals.org

    Marschner, S., & Shirley, P. (2018). Fundamentals of computer graphics. Place of publication not identified: A K Peters/CRC Press.

    總結

    以上是生活随笔為你收集整理的前端对div连线_《前端图形学从入门到放弃》003 三维世界的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。