亚洲视频二区_亚洲欧洲日本天天堂在线观看_日韩一区二区在线观看_中文字幕不卡一区

公告:魔扣目錄網(wǎng)為廣大站長(zhǎng)提供免費(fèi)收錄網(wǎng)站服務(wù),提交前請(qǐng)做好本站友鏈:【 網(wǎng)站目錄:http://www.430618.com 】, 免友鏈快審服務(wù)(50元/站),

點(diǎn)擊這里在線咨詢客服
新站提交
  • 網(wǎng)站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會(huì)員:747

Subsurfer 是使用 html5 Canvas 控件和 WebGL 用 Ja?vaScript 編寫的 3D 建模應(yīng)用程序。它實(shí)現(xiàn)了 Catmull-Clark 細(xì)分曲面算法。該程序的一個(gè)獨(dú)特功能是編輯窗口使用自定義 JAVAScript 代碼在 2D 畫布上下文中實(shí)現(xiàn) 3D 投影。視圖窗口使用 WebGL 的 3D 畫布上下文。Subsurfer 是用 Notepad++ 編寫并在 Chrome 中調(diào)試的,源代碼可以從這里下載。

1、介紹

Subsurfer 中的建模基于立方體,每個(gè)模型都以立方體開始。頂部的按鈕選擇當(dāng)前工具。使用實(shí)體工具,你可以右鍵單擊實(shí)體并更改其某些屬性,例如顏色。使用滑塊工具完成模型的平移、縮放和旋轉(zhuǎn)。上下文菜單和顏色選擇器在Canvas控件中實(shí)現(xiàn)。此 3D 投影和所有模型編輯均在 2D 環(huán)境中完成。

JavaScript實(shí)戰(zhàn)3D建模軟件開發(fā)

 

模型是通過(guò)將連續(xù)的細(xì)分曲面應(yīng)用于實(shí)體,結(jié)合擠出和切面的分裂來(lái)開發(fā)的。該界面是按鍵命令和使用實(shí)體、小平面、邊和頂點(diǎn)工具的右鍵單擊菜單的組合。在這里,我們看到了立方體表面細(xì)分的連續(xù)應(yīng)用。

JavaScript實(shí)戰(zhàn)3D建模軟件開發(fā)

 


復(fù)選框控制查看選項(xiàng)。在這里,我們看到選中了清除輪廓選項(xiàng)的相同模型。

JavaScript實(shí)戰(zhàn)3D建模軟件開發(fā)

 

在這里,我們看到一個(gè)被擠壓的刻面。擠壓是一個(gè)右鍵菜單項(xiàng)和一個(gè)擊鍵命令。使用 Facet 工具選擇 Facet。你可以單擊一個(gè)構(gòu)面,單擊并滾動(dòng)以選擇多個(gè)構(gòu)面,或拖動(dòng)一個(gè)框以凈選構(gòu)面。

擠出刻面時(shí)的一件重要事情是避免有共同的內(nèi)壁。當(dāng)擠壓法線指向同一方向的多個(gè)相鄰面時(shí),可能會(huì)發(fā)生這種情況。共享內(nèi)墻會(huì)混淆 Catmull-Clark 算法,結(jié)果看起來(lái)不正確。為避免這種情況,在拉伸相鄰面時(shí),除非它們的法線朝向不同的方向,否則最好使用“擠出組”命令。

JavaScript實(shí)戰(zhàn)3D建模軟件開發(fā)

 

邊循環(huán)影響曲面細(xì)分將如何塑造模型。可以使用 Bevel 命令(Facet 工具)或使用 Split 命令(Edge 工具)添加循環(huán)邊。可以使用邊緣工具的右鍵單擊菜單選項(xiàng)來(lái)選擇邊緣循環(huán)。

Subsurfer 中的每個(gè)面都是四邊形。Catmull-Clark 算法可以很好地處理四邊形,并且它們可以更容易地實(shí)現(xiàn)可以遍歷模型以查找邊緣循環(huán)和小平面循環(huán)的算法。

JavaScript實(shí)戰(zhàn)3D建模軟件開發(fā)

 

頂點(diǎn)工具可以用來(lái)拖動(dòng)頂點(diǎn),就像面工具可以拖動(dòng)面,邊工具可以拖動(dòng)邊一樣。拖動(dòng)模型元素時(shí),顯示網(wǎng)格(網(wǎng)格復(fù)選框選項(xiàng))很重要,這樣您就會(huì)知道您正在拖動(dòng)哪個(gè) 2 維。否則,結(jié)果可能是意外和不受歡迎的。

JavaScript實(shí)戰(zhàn)3D建模軟件開發(fā)

 

Subsurfer 有一個(gè)編輯窗口(2D 畫布上下文)和一個(gè)查看窗口(3D 畫布上下文)。它們由“編輯”和“查看”復(fù)選框控制。在這里,我們?cè)?ldquo;編輯”窗口中看到了一個(gè)模型,在“視圖”窗口中看到了它的 WebGL 等效模型。

JavaScript實(shí)戰(zhàn)3D建模軟件開發(fā)

 

細(xì)分曲面建模生成具有平滑圓角曲線的形狀。通過(guò)仔細(xì)的規(guī)劃和耐心的編輯,可以通過(guò)小平面的擠壓、分割、縮放和傾斜、邊緣和頂點(diǎn)的平移以及平滑算法的連續(xù)應(yīng)用來(lái)生成復(fù)雜的模型。

JavaScript實(shí)戰(zhàn)3D建模軟件開發(fā)

 

這是編輯窗口中 spacepig 模型的網(wǎng)格視圖。像所有 Subsurfer 模型一樣,它最初是一個(gè)立方體。

JavaScript實(shí)戰(zhàn)3D建模軟件開發(fā)

 

Subsurfer 支持一些內(nèi)置紋理,例如木紋(如下所示)。名為textures.png的圖像文件包含所有紋理。

如果要從文件系統(tǒng)運(yùn)行程序,瀏覽器安全設(shè)置將不允許網(wǎng)頁(yè)加載紋理圖像。HTML 頁(yè)面和 PNG 圖像都必須托管在同一臺(tái)服務(wù)器上。如果你有合適的軟件來(lái)設(shè)置它,您可以從 localhost 運(yùn)行該程序。或者,你可以使用特殊的命令行選項(xiàng)運(yùn)行Chrome.exe,以允許從文件系統(tǒng)加載紋理。需要執(zhí)行的命令是“ chrome.exe
--allow-file-access-from-files”。在執(zhí)行此操作之前,你必須關(guān)閉所有 Chrome 實(shí)例。

JavaScript實(shí)戰(zhàn)3D建模軟件開發(fā)

 

包括各種紋理,包括下面看到的 mod 佩斯利。有一個(gè)擠壓系列命令可以自動(dòng)連續(xù)擠壓刻面,這有助于創(chuàng)造幻覺的洛夫克拉夫特式噩夢(mèng)。

JavaScript實(shí)戰(zhàn)3D建模軟件開發(fā)

 

源命令(左側(cè)按鈕)打開一個(gè)新選項(xiàng)卡,顯示當(dāng)前模型網(wǎng)格的文本表示。

JavaScript實(shí)戰(zhàn)3D建模軟件開發(fā)

 


SaveOpenDelete按鈕是使用 AJAX 調(diào)用實(shí)現(xiàn)和測(cè)試的,以將模型存儲(chǔ)在服務(wù)器上并按名稱檢索它們。但是,出于本文的目的,我不想在我的服務(wù)器上進(jìn)行任何點(diǎn)擊,因此我更改了路徑和名稱,因此按鈕不會(huì)做任何事情。你仍然可以使用提供的 AJAX 代碼,但你必須實(shí)現(xiàn)自己的 SOAP Web 服務(wù)并更改客戶端代碼以匹配。

但是,你仍然可以通過(guò)復(fù)制 Source 命令中的文本將模型保存在本地文件中,如上所示。如果想將本地保存的模型輸入到 Subsurfer 中,請(qǐng)使用輸入按鈕。這是左側(cè)的命令之一,但未在這些圖片中顯示。輸入命令會(huì)彈出一個(gè)表單,你只需將網(wǎng)格文本粘貼到字段中,如下所示。即使對(duì)于大型模型,這似乎也很有效。你可能會(huì)遇到瀏覽器安全設(shè)置的問(wèn)題,但對(duì)我來(lái)說(shuō)效果很好。

JavaScript實(shí)戰(zhàn)3D建模軟件開發(fā)

 

包括各種 WebGL 著色器,可以從右上角的下拉菜單中選擇。WebGL 中的著色器是使用 GLSL 實(shí)現(xiàn)的。具有可選鏡面反射的平面著色和 Phong(平滑)著色是最有用的。邊緣銳利的物體應(yīng)使用平面陰影。帶有 Phong 陰影的立方體看起來(lái)很有趣。我還實(shí)現(xiàn)了一些不真實(shí)的自定義著色器,包括下圖的節(jié)日彩虹著色器(這不是紋理,它是自定義著色器)。這個(gè)著色器對(duì)物體在空間中的位置很敏感,所以當(dāng)物體旋轉(zhuǎn)時(shí)顏色會(huì)以一種非常詭異的方式變化。

程序中內(nèi)置了一個(gè)幫助文件和一個(gè)擊鍵列表(左側(cè)的最后兩個(gè)按鈕),但是開始使用 Subsurfer 的最快方法是使用擊鍵命令通過(guò)擠出刻面和平滑實(shí)體來(lái)進(jìn)行實(shí)驗(yàn),看看什么您可以制作各種奇怪而有趣的模型。擠出小平面的擊鍵命令是' e',平滑實(shí)體的擊鍵命令是's'。您將希望選擇 Facet 工具,以便您可以選擇 facet。您可以使用 Facet 工具(和大多數(shù)其他工具)通過(guò)在窗口中單擊鼠標(biāo)右鍵并拖動(dòng)來(lái)旋轉(zhuǎn)模型。加號(hào)和減號(hào)鍵將放大或縮小。單擊一個(gè)構(gòu)面以將其選中。您還可以凈選擇構(gòu)面并單擊+拖動(dòng)以選擇區(qū)域。可以同時(shí)擠出多個(gè)面。但是,如果進(jìn)行多次拉伸,請(qǐng)確保小平面不朝向完全相同的方向,否則您最終會(huì)得到共享的內(nèi)壁,這會(huì)影響細(xì)分算法。如果擠壓面向相同方向的相鄰面,最好使用擠壓組(擊鍵' g')。

2、使用代碼

你可以從本地文件系統(tǒng)運(yùn)行 HTML 文件。如上所述,如果在本地運(yùn)行,則會(huì)遇到安全問(wèn)題,并且紋理不會(huì)在 WebGL 中顯示。

要解決此問(wèn)題,請(qǐng)關(guān)閉所有 Chrome 實(shí)例并使用以下命令啟動(dòng) Chrome:“ chrome.exe
--allow-file-access-from-files”。

此外,SaveOpenDelete按鈕被有效禁用。要保存模型,請(qǐng)使用Source命令(左側(cè)按鈕)復(fù)制網(wǎng)格規(guī)范。要將保存的模型輸入到 Subsurfer,請(qǐng)使用輸入命令并將網(wǎng)格文本粘貼到提供的表格中。

擠出刻面時(shí)的一件重要事情是避免有共同的內(nèi)壁。當(dāng)擠壓法線指向同一方向的多個(gè)相鄰面時(shí),可能會(huì)發(fā)生這種情況。內(nèi)墻會(huì)弄亂 Catmull-Clark 算法的結(jié)果。為避免這種情況,在拉伸相鄰面時(shí),除非它們的法線朝向不同的方向,否則最好使用“擠出組”命令。

3、構(gòu)建編輯視圖

應(yīng)用程序中有大約 14000 行代碼。WebGL 部分使用 James Coglan 的 Sylvester 矩陣數(shù)學(xué)庫(kù),根據(jù)許可協(xié)議使用。在本文中,我將介紹使該程序正常工作的一些基本元素。我可能會(huì)在以后的文章中更深入地介紹一些主題。

本節(jié)介紹如何在 2D 繪圖環(huán)境中生成編輯視圖的 3D 投影。

該程序使用 HTML5 Canvas 控件,該控件具有兩個(gè)上下文。這是初始化程序 UI 的函數(shù)。它添加了兩個(gè)Canvas控件并為一個(gè)獲取 2D 上下文,為另一個(gè)獲取 webgl (3D) 上下文。如果 webgl 不可用,它會(huì)回退到實(shí)驗(yàn)性 webgl。WebGL 功能似乎在所有主要瀏覽器上都得到了很好的支持。其余代碼為用戶輸入設(shè)置偵聽器并處理其他事務(wù),例如將可用的著色器選項(xiàng)添加到listbox.

function startModel()
{
    alertUser("");
    
    filename = "";
    
    setInterval(timerEvent, 10);

    makeCube();
                     
    canvas = document.createElement('canvas');
    canvas2 = document.createElement('canvas');

    document.body.AppendChild(canvas);
    document.body.appendChild(canvas2);

    canvas.style.position = 'fixed';
    canvas2.style.position = 'fixed';

    ctx = canvas.getContext('2d');
    gl = canvas2.getContext("webgl") || canvas2.getContext("experimental-webgl");
    
    pos = new Point(0, 0); // last known position
        
    lastClickPos = new Point(0, 0); // last click position

    window.addEventListener('resize', resize);
    window.addEventListener('keydown', keyDown);
    window.addEventListener('keyup', keyRelease);
    
    canvas.addEventListener('mousemove', mouseMove);
    canvas.addEventListener('mousedown', mouseDown);
    canvas.addEventListener('mouseup', mouseUp);
    canvas.addEventListener('mouseenter', setPosition);
    canvas.addEventListener('click', click);
    
    canvas2.addEventListener('mousemove', mouseMoveGL);
    canvas2.addEventListener('mousedown', mouseDownGL);
    canvas2.addEventListener('mouseup', mouseUpGL);    
    
    canvas.style.backgroundColor = colorString(canvasBackgroundColor, false);

    canvas.style.position = "absolute";

    canvas.style.border = '1px solid black';    
    
    canvas2.style.position = "absolute";

    canvas2.style.border = '1px solid black';    
    resize();
    
    document.getElementById("checkboxoutlines").checked = false;
    document.getElementById("checkboxsolid").checked = true;
    document.getElementById("checkboxgrid").checked = false;
    document.getElementById("toolslider").checked = true;
    document.getElementById("checkboxtwosided").checked = true;
    document.getElementById("checkboxwebgl").checked = false;
    document.getElementById("checkbox2DWindow").checked = true;
    document.getElementById("checkboxtransparent").checked = false;
    
    if (gl != null)
    {    
      gl.clearColor(canvasBackgroundColor.R / 255.0, 
      canvasBackgroundColor.G / 255.0, canvasBackgroundColor.B / 255.0, 1.0);
      gl.enable(gl.DEPTH_TEST);
      gl.depthFunc(gl.LEQUAL);
      gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
    }
    
    addShaderToList("Phong");
    addShaderToList("Rainbow 1");
    addShaderToList("Rainbow 2");
    addShaderToList("Stripes");
    addShaderToList("Chrome");
    addShaderToList("Smear");
    addShaderToList("Flat");
    addShaderToList("T-Map");
    addShaderToList("Comic");
    addShaderToList("Comic 2");
    addShaderToList("Topo");
    addShaderToList("Paint By Numbers");
    
    var rect = canvas.getBoundingClientRect();
    origin = new Point(-(rect.width / 2), -(rect.height / 2));
    
    setEditViewOptions();
    
    hideInputForm();
}

由于各種原因,程序中的所有編輯都是在 2D 上下文中完成的,因?yàn)槲宜坪醺菀捉鉀Q與 2D 上下文中的命中檢測(cè)和用戶交互相關(guān)的問(wèn)題。在 2D 上下文中繪圖也比在 WebGL 中繪圖簡(jiǎn)單得多。

為了在 2D 中創(chuàng)建 3D 投影,只需要發(fā)生一些事情。這是將 3D 點(diǎn)映射到二維的投影代碼。為了實(shí)現(xiàn)這一點(diǎn),只需要想象一個(gè)位于模型和觀察者眼睛之間的沿 Z 軸的 X/Y 平面。然后計(jì)算從眼睛到每個(gè) 3D 模型頂點(diǎn)的光線與該平面相交的位置。

function To2D(p3d) // gives a 3D->2D perspective projection
{
    var point3d = new Point3D(p3d.x, p3d.y, p3d.z);
    
    RotateXYZ(point3d, myCenter, radiansX, radiansY, radiansZ);

    var xRise = point3d.x - myCenter.x;
    var yRise = point3d.y - myCenter.y;

    var zRunEye = zEyePlane - point3d.z;
    var zRunView = zViewingPlane - point3d.z;

    var factor = (zRunEye - zRunView) / zRunEye;

    var x = (myCenter.x + (factor * xRise));
    var y = (myCenter.y + (factor * yRise));

    x *= ctx.canvas.width;
    x /= docSize;
    y *= ctx.canvas.width;
    y /= docSize;
    
    var p = new Point(Math.floor(x), -Math.floor(y)); 
    // have to flip sign of Y coordinate, this makes it match the GL side
    
    p.x -= origin.x;
    p.y -= origin.y;
    
    return p;
}

請(qǐng)注意,上述函數(shù)所做的第一件事是將點(diǎn)從其實(shí)際位置旋轉(zhuǎn)到當(dāng)前查看位置。這是為用戶提供一種旋轉(zhuǎn)作品并從各個(gè)角度查看的方式。這也是一件小事,如下所示。每當(dāng)用戶輸入鼠標(biāo)輸入來(lái)旋轉(zhuǎn)視圖時(shí),變量radiansX、 radiansY和radiansZ都會(huì)更新并重新繪制投影。

function RotateXYZ(p, rotation_point, radiansX, radiansY, radiansZ)
{
    if (radiansZ != 0.0) // rotate about Z axis
    {
        radiansZ = normalize_radians(radiansZ);

        if (radiansZ != 0)
        {
            var ydiff = (p.y) - (rotation_point.y);
            var xdiff = (p.x) - (rotation_point.x);

            var xd = (xdiff * Math.cos(radiansZ)) - (ydiff * Math.sin(radiansZ));
            xd = Math.round(xd, 0);

            var yd = (xdiff * Math.sin(radiansZ)) + (ydiff * Math.cos(radiansZ));
            yd = Math.round(yd, 0);

            p.x = rotation_point.x + (xd);
            p.y = rotation_point.y + (yd);
        }
    }
    
    if (radiansY != 0.0) // rotate about the Y axis
    {
        radiansY = normalize_radians(radiansY);

        if (radiansY != 0)
        {
            var zdiff = (p.z) - (rotation_point.z);
            var xdiff = (p.x) - (rotation_point.x);

            var xd = (xdiff * Math.cos(radiansY)) - (zdiff * Math.sin(radiansY));
            xd = Math.round(xd, 0);

            var zd = (xdiff * Math.sin(radiansY)) + (zdiff * Math.cos(radiansY));
            zd = Math.round(zd, 0);

            p.x = rotation_point.x + (xd);
            p.z = rotation_point.z + (zd);
        }
    }
    
    if (radiansX != 0.0) // rotate about the X axis
    {
        radiansX = normalize_radians(radiansX);

        if (radiansX != 0)
        {
            var ydiff = (p.y) - (rotation_point.y);
            var zdiff = (p.z) - (rotation_point.z);

            var zd = (zdiff * Math.cos(radiansX)) - (ydiff * Math.sin(radiansX));
            zd = Math.round(zd, 0);

        var yd = (zdiff * Math.sin(radiansX)) + (ydiff * Math.cos(radiansX));
            yd = Math.round(yd, 0);

            p.z = rotation_point.z + (zd);
            p.y = rotation_point.y + (yd);
        }
    }    
}

模型由方面組成。面由邊組成,邊由點(diǎn)組成。以下是保存模型的基本數(shù)據(jù)結(jié)構(gòu)。請(qǐng)注意,就本程序而言,立方體仍然是立方體,無(wú)論它有多少面。每個(gè)模型都以具有 6 個(gè)面的立方體開始,但隨著擠壓、分割和平滑算法的應(yīng)用,將向立方體添加更多面。

function cube(left, right, top, bottom, front, back)
{
    if (left == undefined)
    {
        left = 0;
    }
    if (right == undefined)
    {
        right = 0;
    }
    if (top == undefined)
    {
        top = 0;
    }
    if (bottom == undefined)
    {
        bottom = 0;
    }
    if (front == undefined)
    {
        front = 0;
    }
    if (back == undefined)
    {
        back = 0;
    }
    
    this.color = new Color(190, 180, 190); // default solid color
    this.outlineColor = new Color(0, 0, 0); // default solid outline color
    this.textureName = "";
    
    this.nSubdivide = 0;
    
    this.left = left;
    this.right = right;
    this.top = top;
    this.bottom = bottom;
    this.front = front;
    this.back = back;

    this.previousFacetLists = [];
    this.facets = [];
    
    var lefttopback = new Point3D(left, top, back);
    var lefttopfront = new Point3D(left, top, front);
    var righttopfront = new Point3D(right, top, front);
    var righttopback = new Point3D(right, top, back);
    
    var leftbottomback = new Point3D(left, bottom, back);
    var leftbottomfront = new Point3D(left, bottom, front);
    var rightbottomfront = new Point3D(right, bottom, front);
    var rightbottomback = new Point3D(right, bottom, back);
    
    var topPoints = [];
    
    topPoints.push(clonePoint3D(lefttopback));
    topPoints.push(clonePoint3D(righttopback));
    topPoints.push(clonePoint3D(righttopfront));
    topPoints.push(clonePoint3D(lefttopfront));
    topPoints.reverse();
    
    var bottomPoints = [];
    
    bottomPoints.push(clonePoint3D(leftbottomfront));
    bottomPoints.push(clonePoint3D(rightbottomfront));
    bottomPoints.push(clonePoint3D(rightbottomback));
    bottomPoints.push(clonePoint3D(leftbottomback));
    bottomPoints.reverse();
    
    var frontPoints = [];
    
    frontPoints.push(clonePoint3D(lefttopfront));
    frontPoints.push(clonePoint3D(righttopfront));
    frontPoints.push(clonePoint3D(rightbottomfront));
    frontPoints.push(clonePoint3D(leftbottomfront));
    frontPoints.reverse();

    var backPoints = [];
    
    backPoints.push(clonePoint3D(righttopback));
    backPoints.push(clonePoint3D(lefttopback));
    backPoints.push(clonePoint3D(leftbottomback));
    backPoints.push(clonePoint3D(rightbottomback));
    backPoints.reverse();
    
    var leftPoints = [];
    
    leftPoints.push(clonePoint3D(lefttopback));
    leftPoints.push(clonePoint3D(lefttopfront));
    leftPoints.push(clonePoint3D(leftbottomfront));
    leftPoints.push(clonePoint3D(leftbottomback));
    leftPoints.reverse();

    var rightPoints = [];
    
    rightPoints.push(clonePoint3D(righttopfront));
    rightPoints.push(clonePoint3D(righttopback));
    rightPoints.push(clonePoint3D(rightbottomback));
    rightPoints.push(clonePoint3D(rightbottomfront));
    rightPoints.reverse();
    
    var id = 1;
    
    var s1 = new Facet();
    s1.ID = id++;
    s1.points = topPoints;    
    this.facets.push(s1);
    
    var s2 = new Facet();
    s2.ID = id++;
    s2.points = bottomPoints;    
    this.facets.push(s2);
    
    var s3 = new Facet();
    s3.ID = id++;
    s3.points = backPoints;    
    this.facets.push(s3);
    
    var s4 = new Facet();
    s4.ID = id++;
    s4.points = frontPoints;    
    this.facets.push(s4);
    
    var s5 = new Facet();
    s5.ID = id++;
    s5.points = leftPoints;    
    this.facets.push(s5);
    
    var s6 = new Facet();
    s6.ID = id++;
    s6.points = rightPoints;    
    this.facets.push(s6);    
    
    for (var n = 0; n < this.facets.length; n++)
    {
        this.facets[n].cube = this;
    }
}

function Facet()
{
    this.cube = -1;
    this.ID = -1;
    this.points = [];
    this.point1 = new Point(0, 0);
    this.point2 = new Point(0, 0);
    this.closed = false;
    this.fill = false;
    this.averagePoint3D = new Point3D(0, 0, 0);
    this.normal = -1;    
    
    this.edges = [];    
    this.neighbors = [];
    this.greatestRotatedZ = 0;
    this.greatestLeastRotatedZ = 0;
    this.averageRotatedZ = 0;
    
    this.boundsMin = new Point3D(0, 0, 0);
    this.boundsMax = new Point3D(0, 0, 0);
}

function Point3D(x, y, z)
{
    this.x = x;
    this.y = y;
    this.z = z;
}

要在 2D 中繪制模型,只需將每個(gè) facet 描述的多邊形從 3D 映射到 2D,然后填充生成的 2D 多邊形。只有兩個(gè)并發(fā)癥。第一個(gè)是每個(gè)面必須根據(jù)其相對(duì)于表示光源的矢量的角度進(jìn)行著色。第二個(gè)是在給定當(dāng)前視圖旋轉(zhuǎn)的情況下,必須根據(jù)它們沿 Z 軸的位置從后到前對(duì)刻面進(jìn)行排序。這樣,首先繪制背面的刻面,而前面的刻面將它們遮住,這就是你想要的。

需要注意的是,這種通過(guò)沿 Z 軸對(duì)多邊形進(jìn)行排序來(lái)描繪實(shí)體對(duì)象的方法是一種近似。它不考慮構(gòu)面之間的交叉點(diǎn)。此外,當(dāng)對(duì)象包含凹面時(shí),Z 排序會(huì)給出看起來(lái)不正確的結(jié)果。然而,當(dāng)對(duì)象沒有凹面并且表面之間沒有相交時(shí),該方法會(huì)產(chǎn)生足夠好的結(jié)果。當(dāng)小平面相對(duì)于模型的大小較小時(shí),異常的發(fā)生會(huì)大大減少,就像應(yīng)用了平滑時(shí)一樣。如果存在不規(guī)則性,你始終可以在編輯期間通過(guò)旋轉(zhuǎn)模型和/或使用“清除”和“輪廓”查看選項(xiàng)并將模型視為具有透明表面的線框來(lái)解決它們。任何此類異常都不會(huì)出現(xiàn)在“查看”窗口中,

要對(duì)多邊形著色,必須獲取其法線。這是一個(gè)垂直于刻面表面的向量(使用叉積計(jì)算)。計(jì)算此法線和光源矢量之間的角度(使用點(diǎn)積),這用于使刻面顏色變亮或變暗。如果角度更接近 0,則刻面顏色變亮。如果角度接近 180,則刻面顏色變暗。這是計(jì)算刻面法線并對(duì)刻面進(jìn)行著色的代碼。

function CalculateNormal(facet)
{
    var normal = -1;
    
    if (facet.points.length > 2)
    {
        var p0 = facet.points[0];
        var p1 = facet.points[1];
        var p2 = facet.points[2];
        
        var a = timesPoint(minusPoints(p1, p0), 8);
        var b = timesPoint(minusPoints(p2, p0), 8);

        normal = new line(clonePoint3D(p0), 
                                new Point3D((a.y * b.z) - (a.z * b.y), // cross product
                                          -((a.x * b.z) - (a.z * b.x)),
                                            (a.x * b.y) - (a.y * b.x))
                );

        normal.end = LengthPoint(normal, cubeSize * 2);
        
        var avg =  averageFacetPoint(facet.points);
        
        normal.end.x += avg.x - normal.start.x;
        normal.end.y += avg.y - normal.start.y;
        normal.end.z += avg.z - normal.start.z;
        normal.start = avg;        
    }
    
    return normal;
}

function getLightSourceAngle(normal)
{
    var angle = 0;
    
    if (normal != -1)
    {
        angle = normalize_radians(vectorAngle
                (lightSource, minusPoints(ToRotated(normal.end), ToRotated(normal.start))));
    }
    
    return angle;
}      

function vectorAngle(vector1, vector2)
{
    var angle = 0.0;

    var length1 = Math.sqrt((vector1.x * vector1.x) + (vector1.y * vector1.y) + 
                            (vector1.z * vector1.z));

    var length2 = Math.sqrt((vector2.x * vector2.x) + (vector2.y * vector2.y) + 
                           (vector2.z * vector2.z));

    var dot_product = (vector1.x * vector2.x + vector1.y * vector2.y + vector1.z * vector2.z);

    var cosine_of_angle = dot_product / (length1 * length2);

    angle = Math.acos(cosine_of_angle);

    return angle;
}               

function ShadeFacet(color, angle)
{
    var darken_range = 0.75;
    var lighten_range = 0.75;
    
    var result = new Color(color.R, color.G, color.B);

    if (angle > 180)
    {
        angle = 360 - angle;
    }

    if (angle > 90) // darken
    {
        var darken_amount = (angle - 90) / 90;
        darken_amount *= darken_range;

        var r = color.R - (color.R * darken_amount);
        var g = color.G - (color.G * darken_amount);
        var b = color.B - (color.B * darken_amount);

        r = Math.min(255, Math.max(0, r));
        g = Math.min(255, Math.max(0, g));
        b = Math.min(255, Math.max(0, b));
        
        result = new Color(r, g, b);
    }
    else // lighten
    {
        var lighten_amount = (90 - angle) / 90;
        lighten_amount *= lighten_range;

        var r = color.R + ((255 - color.R) * lighten_amount);
        var g = color.G + ((255 - color.G) * lighten_amount);
        var b = color.B + ((255 - color.B) * lighten_amount);

        r = Math.max(0, Math.min(255, r));
        g = Math.max(0, Math.min(255, g));
        b = Math.max(0, Math.min(255, b));
        
        result = new Color(r, g, b);
    }

    return result;
}

一旦刻面被著色,就必須將它們從后到前排序,這樣當(dāng)你按順序繪制它們時(shí),最近的刻面將覆蓋它們后面的刻面。

function sortFacets()
{
    allFacets = [];

    for (var w = 0; w < cubes.length; w++)
    {
        var cube = cubes[w];
        
        for (var i = 0; i < cube.facets.length; i++)
        {
            allFacets.push(cube.facets[i]);
        }        
    }

    sortFacetsOnZ(allFacets);
}

function sortFacetsOnZ(facets)
{
    for (var i = 0; i < facets.length; i++)
    {
        setAverageAndGreatestRotatedZ(facets[i]);
    }

    facets.sort(
                    function(a, b)
                    {
                        if (a.greatestRotatedZ == b.greatestRotatedZ)
                        {
                            if (a.leastRotatedZ == b.leastRotatedZ)
                            {
                                return a.averageRotatedZ - b.averageRotatedZ;
                            }
                            else
                            {
                                return a.leastRotatedZ - b.leastRotatedZ;
                            }
                        }
                        else
                        {
                            return a.greatestRotatedZ - b.greatestRotatedZ
                        }
                    }
    );
}

下面是一些在 2D 上下文中使用 3D 投影繪制編輯顯示的代碼。這里發(fā)生的基本事情是sortFacets()和drawCubes()。這就是產(chǎn)生立體形狀錯(cuò)覺的 3D 投影的原因。此處的其他代碼與更新 WebGL 視圖和編輯 UI 的繪圖元素有關(guān)。編輯 UI 元素包括矩形方向網(wǎng)格和上下文菜單,以及模型元素(面、邊、頂點(diǎn)),這些元素會(huì)受到翻轉(zhuǎn)行為和高亮行為的影響,必須根據(jù)當(dāng)前工具和鼠標(biāo)位置重新繪制不同的顏色。

function updateModel()
{
    for (var c = 0; c < cubes.length; c++)
    {
        updateCube(cubes[c]);
    }

    sortFacets();

    reloadSceneGL();

    draw();
}

function draw()
{
    if (isGL && gl != null)
    {
        drawSceneGL();
    }

    if (is2dWindow || !isGL)
    {
        ctx.clearRect(0, 0, canvas.width, canvas.height);

        findGridOrientation();

        if (gridChosen())
        {       
            drawGridXY();
        }

        lineColor = lineColorShape;

        drawCubes();

        if (mouseIsDown && draggingShape)
        {
            draw3DRectangleFrom2DPoints(mouseDownPos, pos, false, "white");       
        }

        if (hitLine != -1)
        {
            var pts = [];
            pts.push(To2D(hitLine.start));
            pts.push(To2D(hitLine.end));
            drawPolygonHighlighted(pts);
        }

        if (hitFacet != -1 && toolChosen() == "facet")
        {
            drawPolygon3d(hitFacet.points, true, true, "yellow", true);
        }

        for (var g = 0; g < selectedLines.length; g++)
        {
            var pts = [];
            pts.push(To2D(selectedLines[g].start));
            pts.push(To2D(selectedLines[g].end));
            drawPolygonSelected(pts);
        }

        if (hitVertex != -1)
        {
            drawVertex(hitVertex, false);
        }

        for (var qq = 0; qq < selectedVertexes.length; qq++)
        {
            drawVertex(selectedVertexes[qq], true);
        }

        if (lineDiv != -1 &&
            lineDiv2 != -1)
        {
            drawLine2D(lineDiv, "blue");
            drawLine2D(lineDiv2, "blue");
        }

        if (draggingRect)
        {
            draw2DRectangleFrom2DPoints(mouseDownPos, pos, "black");       
        }

        if (colorPickMode.length > 0)
        {
            drawColors(0, 0, colorPickHeight);
        }

        drawMenu();
    }
}

function drawCubes()
{
    var drawlines = isOutline || !isShade;

    var drawNormals = isNormals;

    var shadeSolids = isShade;

    var dual = isDualSided;

    for (var i = 0; i < allFacets.length; i++)
    {
        var facet = allFacets[i];

        if (facet.normal == -1)
        {
            facet.normal = CalculateNormal(facet);
        }

        var c = facet.cube.color;

        if (colorPickMode.length == 0)
        {
            if (facet.cube == hitSolid)
            {
                c = new Color(23, 100, 123);
            }    

            if (listHas(selectedSolids, facet.cube))
            {
                c = new Color(200, 30, 144);
            }

            if (listHas(selectedFacets, facet))
            {
                c = new Color(0, 255, 255);
            }
        }

        c = ShadeFacet(c, degrees_from_radians(getLightSourceAngle(facet.normal)));
        var show = true;

        if (!dual)
        {
            show = ShowFacet(degrees_from_radians(getFrontSourceAngle(facet.normal)));
        }

        var colorFillStyle = colorString(c, isTransparent);

        var colorOutlineStyle = colorString(facet.cube.outlineColor, isTransparent);    

        if (listHas(selectedSolids, facet.cube))
        {
            drawlines = true;
            colorOutlineStyle = "red";
        }

        if (show)
        {
            drawPolygon3d(facet.points, true, shadeSolids || listHas(selectedFacets, facet),
                colorFillStyle, drawlines, colorOutlineStyle);

            if (drawNormals)
            {
                drawLine3D(facet.normal, "magenta");
            }
        }
    }
}

function drawPolygon3d(points, isClosed, isFill, fillColor, isOutline, outlineColor)
{
    var result = [];
    
    if (points.length > 0)
    {
        for (var i = 0; i < points.length; i++)
        {
            result.push(To2D(points[i]));
        }
    
        drawPolygon(result, isClosed, isFill, fillColor, isOutline, outlineColor);
    }
}

function drawPolygon
(points, isClosed, isFill, fillColor, isOutline, outlineColor, l.NEThickness)
{
    if (points.length > 0)
    {
        isClosed = isClosed ? isClosed : false;
        isFill = isFill ? isFill : false;
        if (isOutline === undefined)
        {
            isOutline = true;
        }
        if (lineThickness === undefined)
        {
            lineThickness = 1;
        }
        if (outlineColor === undefined)
        {
            outlineColor = lineColor;
        }
        
        ctx.beginPath();

        ctx.lineWidth = lineThickness;
        ctx.lineCap = 'round';
        ctx.strokeStyle = outlineColor;
        if (isFill)
        {
            ctx.fillStyle = fillColor;
        }

        ctx.moveTo(points[0].x, points[0].y);
        for (var i = 1; i < points.length; i++)
        {        
            ctx.lineTo(points[i].x, points[i].y);
        }
        
        if (isClosed)
        {
            ctx.lineTo(points[0].x, points[0].y);
        }

        if (isFill)
        {
            ctx.fill();    
        }
        
        if (isOutline)
        {
            ctx.stroke();
        }
    }
}

4、構(gòu)建 WebGL 模型

因此 2D 編輯視圖的制作相當(dāng)簡(jiǎn)單。WebGL 視圖的制作有點(diǎn)困難,將在以后的文章中更深入地討論。我只會(huì)展示一些將我們的 JavaScript 數(shù)據(jù)結(jié)構(gòu)綁定到我們模型的 WebGL 表示的代碼。有五個(gè)基本元素必須被緩沖并綁定到 WebGL。這是完成這項(xiàng)工作的主要功能。

function bindModelGL()
{
    bindVerticesGL();

    bindColorsGL();

    bindVertexIndicesGL();

    bindTextureCoordinatesGL();

    bindNormalsGL();
}

將顏色綁定到我們的模型。每個(gè)立方體只能是一種顏色。每個(gè)方面都有一個(gè)指向其父多維數(shù)據(jù)集的指針。請(qǐng)注意,就我們的目的而言,多維數(shù)據(jù)集只是一個(gè)分面列表,它可能是也可能不是實(shí)際的多維數(shù)據(jù)集。所有面的列表將為我們提供每個(gè)頂點(diǎn)的正確顏色。每個(gè)頂點(diǎn)需要 4 個(gè)元素:R、G、B 和 A(表示透明度的 alpha 通道)。我們?yōu)?A 使用 1.0,因此我們的 WebGL 模型將始終是不透明的。

function bindColorsGL()
{
    if (isGL && gl != null)
    {
        var generatedColors = [];

        for (var i = 0; i < allFacets.length; i++)
        {   
            var f = allFacets[i];

            var c = color2FromColor(f.cube.color);
            
            var b = [];
            
            b.push(c.R);
            b.push(c.G);
            b.push(c.B);
            b.push(1.0);
                
            // repeat each color 4 times for the 4 vertices of each facet
            for (var s = 0; s < 4; s++) 
            {            
                generatedColors.push(b[0]);
                generatedColors.push(b[1]);
                generatedColors.push(b[2]);
                generatedColors.push(b[3]);                
            }
        }
      
        cubeVerticesColorBuffer = gl.createBuffer();
        gl.bindBuffer(gl.ARRAY_BUFFER, cubeVerticesColorBuffer);
        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(generatedColors), gl.STATIC_DRAW);
    }
}

我們必須綁定刻面法線,以便 WebGL 可以對(duì)模型進(jìn)行著色。請(qǐng)注意,對(duì)于每個(gè)面法線,我們只需要 3 個(gè)數(shù)字。這是因?yàn)?WebGL 只關(guān)心法線的方向,而不關(guān)心它在空間中的位置。

這里的一個(gè)具體問(wèn)題是 Subsurfer 支持 Phong 著色,這需要頂點(diǎn)法線。如果你認(rèn)為每個(gè)小平面法線垂直于小平面表面,那么頂點(diǎn)法線是包含該頂點(diǎn)的所有小平面的法線的平均值。因此,當(dāng) Phong 著色生效時(shí),必須計(jì)算頂點(diǎn)法線。我們不在 2D 投影中使用這些,因?yàn)槲覀冎蛔銎矫嬷晕覀冎恍枰∑矫娣ň€。但是 WebGL 中的 Phong 著色需要頂點(diǎn)法線。如果我們?cè)?WebGL 中進(jìn)行平面著色,則不必計(jì)算頂點(diǎn)法線。在平面著色的情況下,我們只是使用面法線作為每個(gè)頂點(diǎn)的法線。

function bindNormalsGL()
{              
    if (isGL && gl != null)
    {    
        cubeVerticesNormalBuffer = gl.createBuffer();
        gl.bindBuffer(gl.ARRAY_BUFFER, cubeVerticesNormalBuffer);

        var vertexNormals = [];

        for (q = 0; q < allFacets.length; q++) 
        {
            var f = allFacets[q];

            if (f.normal == -1)
            {
                f.normal = CalculateNormal(f);
            }
        }

        if (fastVertexNormalMethod)
        {
            if (isSmoothShading())
            {
                allSortedPoints = getFacetPointsAndSetUpBackPointers(allFacets);
                sortPointsByXYZ(allSortedPoints);
                stageVertexNeighborFacets(allSortedPoints);
            }
        }

        if (isSmoothShading())
        {        
            for (q = 0; q < allFacets.length; q++) 
            {
                var f = allFacets[q];

                for (var j = 0; j < f.points.length; j++)
                {
                    var p = f.points[j];
                    var vn = p.vertexNormal;
                    if (vn == undefined)
                    {
                        vn = calculateVertexNormal(p, allFacets);
                        p.vertexNormal = vn;
                    }
                    vertexNormals.push((vn.end.x / reductionFactor) - 
                                       (vn.start.x / reductionFactor));
                    vertexNormals.push((vn.end.y / reductionFactor) - 
                                       (vn.start.y / reductionFactor));
                    vertexNormals.push((vn.end.z / reductionFactor) - 
                                       (vn.start.z / reductionFactor));
                }
            }        
        }
        else
        {
            for (q = 0; q < allFacets.length; q++) 
            {
                var f = allFacets[q];

                for (var i = 0; i < 4; i++) 
                {
                    vertexNormals.push((f.normal.end.x / reductionFactor) - 
                                       (f.normal.start.x / reductionFactor));
                    vertexNormals.push((f.normal.end.y / reductionFactor) - 
                                       (f.normal.start.y / reductionFactor));
                    vertexNormals.push((f.normal.end.z / reductionFactor) - 
                                       (f.normal.start.z / reductionFactor));
                }
            }        
        }        

        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertexNormals), gl.STATIC_DRAW);
    }
}

我們必須綁定模型中的每個(gè)頂點(diǎn)。盡管 WebGL 需要三角形而不是四邊形才能正常工作,但不必復(fù)制頂點(diǎn),因?yàn)槲覀儗⒃陧旤c(diǎn)緩沖區(qū)中提供索引列表。一些索引將被重復(fù),這給了我們?nèi)切巍?/p>

function bindVerticesGL()
{
    cubeVerticesBuffer = gl.createBuffer();

    gl.bindBuffer(gl.ARRAY_BUFFER, cubeVerticesBuffer);

    var vertices = [];
  
    for (var i = 0; i < allFacets.length; i++)
    {    
        var f = allFacets[i];
        for (var j = 0; j < f.points.length; j++)
        {
            var point3d = f.points[j];
        
            vertices.push(point3d.x / reductionFactor);
            vertices.push(point3d.y / reductionFactor);
            vertices.push((point3d.z / reductionFactor));
        }
    }
  
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
}

在這里,我們構(gòu)建頂點(diǎn)索引緩沖區(qū)并將其綁定到 WebGL。索引模式 0、1、2 后跟 0、2、3 將我們的四個(gè)面頂點(diǎn)劃分為兩個(gè)三角形。

function bindVertexIndicesGL()
{
    cubeVerticesIndexBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, cubeVerticesIndexBuffer);

    var cubeVertexIndices = [];
 
    var t = 0;
    
    for (var i = 0; i < allFacets.length; i++)
    {
        cubeVertexIndices.push(t + 0);
        cubeVertexIndices.push(t + 1);
        cubeVertexIndices.push(t + 2);
        cubeVertexIndices.push(t + 0);
        cubeVertexIndices.push(t + 2);
        cubeVertexIndices.push(t + 3);
        
        t += 4;
    }

    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(cubeVertexIndices), gl.STATIC_DRAW);
}

我們模型中的每個(gè)頂點(diǎn)都有 X、Y、Z 表示空間位置,加上另外兩個(gè)坐標(biāo) U 和 V,它們是紋理圖像的偏移量。U 和 V 值介于 0 和 1 之間。對(duì)于復(fù)雜的形狀,我們會(huì)自動(dòng)分配 U 和 V 坐標(biāo),就好像紋理被包裹在圖像周圍一樣。這是由assignPolarUV_2()函數(shù)完成的。

function bindTextureCoordinatesGL()
{
    for (var i = 0; i < cubes.length; i++)
    {
        assignPolarUV_2(cubes[i], i);
    }

    cubeVerticesTextureCoordBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, cubeVerticesTextureCoordBuffer);
  
    var textureCoordinates = [];
    for (var i = 0; i < allFacets.length; i++)
    {
        if (isPolarUV)
        {
            var f = allFacets[i];
            
            textureCoordinates.push(f.points[0].u);  
            textureCoordinates.push(f.points[0].v);  
            
            textureCoordinates.push(f.points[1].u);  
            textureCoordinates.push(f.points[1].v);  

            textureCoordinates.push(f.points[2].u);  
            textureCoordinates.push(f.points[2].v);  

            textureCoordinates.push(f.points[3].u);  
            textureCoordinates.push(f.points[3].v);  
        }
        else
        {
            textureCoordinates.push(0.0);  
            textureCoordinates.push(0.0);
            textureCoordinates.push(1.0);  
            textureCoordinates.push(0.0);
            textureCoordinates.push(1.0);  
            textureCoordinates.push(1.0);
            textureCoordinates.push(0.0);  
            textureCoordinates.push(1.0);
        }
    }  

    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(textureCoordinates),
              gl.STATIC_DRAW);
}

5、平面著色器

當(dāng)直接處理 WebGL 時(shí),有必要編寫自己的著色器。這些是用稱為 GLSL 的語(yǔ)言編寫的。每個(gè)著色器都必須有一個(gè)main()過(guò)程。著色器是一個(gè)在計(jì)算機(jī)圖形芯片上編譯和加載的小程序。

著色器包含在scriptHTML 文件的標(biāo)簽中,可以按名稱尋址。如果您為模型使用紋理而不是純色,則需要不同的著色器。Subsurfer 具有用于顏色和紋理的平面著色器,以及用于顏色和紋理的 Phong(平滑)著色器。還包括幾個(gè)古怪的自定義著色器。在這里,我將只提到純色情況下的平面著色器和 Phong 著色器。

這是純色的平面著色器。您必須提供頂點(diǎn)著色器和片段著色器。這適用于您實(shí)現(xiàn)的每種類型的著色。頂點(diǎn)著色器為您提供每個(gè)頂點(diǎn)的顏色。片段著色器可以在頂點(diǎn)之間進(jìn)行插值以創(chuàng)建更平滑的外觀。它基本上為每個(gè)單獨(dú)的像素著色。

下面的平面著色器的外觀與我們?cè)?JavaScript 中構(gòu)建的 3D 投影非常相似,它顯示在編輯窗口中。如果你看一下這個(gè)頂點(diǎn)著色器在做什么,它實(shí)際上和我們之前在 JavaScript 函數(shù)中看到的計(jì)算是一樣的shadeFacet()。它采用頂點(diǎn)法線(在這種情況下,與小平面法線相同)和光源方向矢量之間的角度(點(diǎn)積),并使用它來(lái)使小平面顏色變亮或變暗。但是著色器可以更快地完成它,因?yàn)樗诖笠?guī)模并行設(shè)備上運(yùn)行。此外,它還考慮了光的顏色,以及定向光和環(huán)境光。請(qǐng)注意,在此著色器中,燈光顏色和方向是硬編碼的。

這里的片段著色器并沒有做太多,它只是一個(gè)傳遞。這是因?yàn)槠矫嬷鳑]有插值或平滑處理,因此平面上的所有像素都可以被著色為相同的顏色。

<script id="vertex-shader-color-flat" type="x-shader/x-vertex">
  // VERTEX SHADER COLOR (FLAT)
  attribute highp vec3 aVertexNormal;
  attribute highp vec3 aVertexPosition;
  attribute vec4 aVertexColor;

  uniform highp mat4 uNormalMatrix;
  uniform highp mat4 uMVMatrix;
  uniform highp mat4 uPMatrix;
  
  varying highp vec3 vLighting;
  varying lowp vec4 vColor;

  void main(void) {
    gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0);
       
    highp vec3 ambientLight = vec3(0.5, 0.5, 0.5);
    highp vec3 directionalLightColor = vec3(0.5, 0.5, 0.5);
    highp vec3 directionalVector = vec3(0.85, 0.8, 0.75);
    
    highp vec4 transformedNormal = uNormalMatrix * vec4(aVertexNormal, 1.0);
    
    highp float directional = max(dot(transformedNormal.xyz, directionalVector), 0.0);
    vLighting = ambientLight + (directionalLightColor * directional);
    
    vColor = aVertexColor;
  }
</script>

<script id="fragment-shader-color-flat" type="x-shader/x-fragment">
   // FRAGMENT SHADER COLOR (FLAT)
    varying lowp vec4 vColor;
    varying highp vec3 vLighting;
  
  uniform sampler2D uSampler;
  
  void main(void) {
    gl_FragColor = vec4(vColor.rgb * vLighting, 1.0);
  }
</script>

6、Phong 著色器

Phong 著色提供更平滑的外觀,因?yàn)樗陧旤c(diǎn)之間進(jìn)行插值以單獨(dú)著色每個(gè)像素。顏色 Phong 著色器如下所示。

請(qǐng)注意,頂點(diǎn)著色器并沒有發(fā)生太多事情。大多數(shù)動(dòng)作都發(fā)生在片段著色器中,因?yàn)槲覀円?jì)算每個(gè)單獨(dú)的像素。關(guān)于頂點(diǎn)著色器最有趣的一點(diǎn)是,轉(zhuǎn)換后的頂點(diǎn)法線被聲明為“可變的”。這將導(dǎo)致它為片段著色器中的每個(gè)像素進(jìn)行平滑插值。

所以這個(gè)片段著色器實(shí)際上對(duì)每個(gè)像素使用不同的法線。您看不到任何明確的代碼來(lái)執(zhí)行此操作,因?yàn)樗鼉?nèi)置于 GLSL 語(yǔ)言和“可變”類型中。與平面著色器一樣,環(huán)境光和定向光的顏色是硬編碼的,光的方向也是如此。此外,使用光方向向量與頂點(diǎn)法線之間的角度計(jì)算顏色與平面著色器非常相似。這里的不同之處在于,計(jì)算發(fā)生在片段著色器中,對(duì)每個(gè)像素使用不同的插值法線值。這就是賦予光滑外觀的原因。Phong 著色器比平面著色器更慢,因?yàn)樗仨氝M(jìn)行更多的計(jì)算。

關(guān)于 Phong 著色器的最后一件事是我已經(jīng)實(shí)現(xiàn)了鏡面反射。如果選中 UI 上的“鏡面反射”復(fù)選框,則統(tǒng)一值specularUniform將設(shè)置為1. 如果發(fā)生這種情況,只要光源和頂點(diǎn)法線之間的角度足夠小,該像素的顏色就會(huì)自動(dòng)設(shè)置為白色。這會(huì)產(chǎn)生使模型看起來(lái)有光澤的鏡面高光。

<script id="shader-vs-normals-notexture-phong" type="x-shader/x-vertex">
  // VERTEX SHADER COLOR (PHONG)
  attribute highp vec3 aVertexNormal;
  attribute highp vec3 aVertexPosition;
  attribute vec4 aVertexColor;

  uniform highp mat4 uNormalMatrix;
  uniform highp mat4 uMVMatrix;
  uniform highp mat4 uPMatrix;

  varying vec3 vTransformedNormal;
  varying vec4 vPosition;
  varying lowp vec4 vColor;

  void main(void) 
  {
     vPosition = uMVMatrix * vec4(aVertexPosition, 1.0);
     gl_Position = uPMatrix * vPosition;
     vTransformedNormal = vec3(uNormalMatrix * vec4(aVertexNormal, 1.0));
     
      vColor = aVertexColor;
  }
</script>

<script id="shader-fs-normals-notexture-phong" type="x-shader/x-fragment">
  // FRAGMENT SHADER COLOR (PHONG)
  precision mediump float;

  uniform int specularUniform;    
  
  varying vec3 vTransformedNormal;
  varying vec4 vPosition;
 
  varying lowp vec4 vColor;

  void main(void) {
    vec3 pointLightingLocation;
    pointLightingLocation = vec3(0, 13.5, 13.5);

    vec3 ambientColor;
    ambientColor = vec3(0.5, 0.5, 0.5);

    vec3 pointLightingColor;
    pointLightingColor = vec3(0.5, 0.5, 0.5);
    
    vec3 lightWeighting;
    vec3 lightDirection = normalize(pointLightingLocation - vPosition.xyz);

    float directionalLightWeighting = max(dot(normalize(vTransformedNormal), 
                                      lightDirection), 0.0);
        
    lightWeighting = ambientColor + pointLightingColor * directionalLightWeighting;

    vec4 fragmentColor;
    
    fragmentColor = vColor;
                         
    gl_FragColor = vec4(fragmentColor.rgb * lightWeighting, fragmentColor.a);

    if (specularUniform == 1)
    {
        if (dot(normalize(vTransformedNormal), lightDirection) > 0.99) // specular
        {
            gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
        }
    }
  }
  
</script>

6、細(xì)分曲面算法

我本來(lái)打算多說(shuō)一些關(guān)于 Catmull-Clark 細(xì)分曲面算法以及我在 JavaScript 中的實(shí)現(xiàn),但是這篇文章已經(jīng)太長(zhǎng)了,所以我將把它留到以后的文章中。但是,如果查看代碼,你可以看到發(fā)生了什么。我只想說(shuō)大部分動(dòng)作發(fā)生在函數(shù)
subdivisionSurfaceProcessFacet() 內(nèi),它通過(guò)計(jì)算稱為重心的加權(quán)平均值來(lái)細(xì)分單個(gè)面。使用計(jì)時(shí)器在三個(gè)函數(shù)中實(shí)現(xiàn)該算法的原因是,我可以在屏幕底部繪制一個(gè)進(jìn)度指示計(jì)。我不得不這樣做,因?yàn)?JavaScript 中沒有真正的線程。該算法采用一個(gè)分面列表并將其替換為一個(gè)列表,其中每個(gè)分面都已被四個(gè)新分面替換。請(qǐng)注意,當(dāng)模型中有孔時(shí)必須小心。位于此類孔邊界上的刻面被視為特殊情況。

function startSubdivision(solid)
{
    informUser("Subdividing, please wait...");
    subdivSurfaceLoopCounter = 0;
    var facets = solid.facets;
    solidToSubdivide = solid;
    isSubdividing = true;

    if (solid.nSubdivide == 0)
    {
        solid.previousFacetLists.push(solid.facets);
    }
    
    for (var i = 0; i < facets.length; i++)
    {
        facets[i].edges = getFacetLines(facets[i]);
        facets[i].averagePoint3D = averageFacetPoint(facets[i].points);
    }
    
    findFacetNeighborsAndAdjacents(facets);
    
    for (var i = 0; i < facets.length; i++)
    {
        var facet = facets[i];
        
        for (var j = 0; j < facet.edges.length; j++)
        {
            var edge = facet.edges[j];
            
            var list = [];

            list.push(edge.start);
            list.push(edge.end);

            if (edge.parentFacet != -1 && edge.adjacentFacet != -1)
            {
                list.push(edge.parentFacet.averagePoint3D);
                list.push(edge.adjacentFacet.averagePoint3D);
            }

            edge.edgePoint = averageFacetPoint(list);
        }
    }
    
    subdivTimerId = setTimeout(subdivisionSurfaceProcessFacet, 0);
    
    newSubdivFacets = [];
}    

function subdivisionSurfaceProcessFacet()
{
    var facet = solidToSubdivide.facets[subdivSurfaceLoopCounter];
    var nEdge = 0;

    var neighborsAndCorners = facetNeighborsPlusFacet(facet);

    for (var j = 0; j < facet.points.length; j++)
    {
        var p = facet.points[j];
    
        var facepoints = [];
        var edgepoints = [];

        var facetsTouchingPoint = findFacetsTouchingPoint(p, neighborsAndCorners);

        for (var n = 0; n < facetsTouchingPoint.length; n++)
        {
            var f = facetsTouchingPoint[n];
            
            facepoints.push(averageFacetPoint(f.points));
        }

        var edgesTouchingPoint = findEdgesTouchingPoint(p, facetsTouchingPoint);

        for (var m = 0; m < edgesTouchingPoint.length; m++)
        {
            var l = edgesTouchingPoint[m];
            
            edgepoints.push(midPoint3D(l.start, l.end));
        }

        var onBorder = false;
        if (facepoints.length != edgepoints.length)
        {                
            onBorder = true; // vertex is on a border
        }

        var F = averageFacetPoint(facepoints);
        var R = averageFacetPoint(edgepoints);

        var n = facepoints.length;
        
        var barycenter = roundPoint(divPoint(plusPoints
                         (plusPoints(F, timesPoint(R, 2)), timesPoint(p, n - 3)), n));

        var n1 = nEdge;

        if (n1 > facet.edges.length - 1)
        {
            n1 = 0;
        }

        var n2 = n1 - 1;

        if (n2 < 0)
        {
            n2 = facet.edges.length - 1;
        }

        if (onBorder)
        {
            var borderAverage = [];
            
            var etp = edgesTouchingPoint;
            
            for (var q = 0; q < etp.length; q++)
            {
                var l = etp[q];
                
                if (lineIsOnBorder(l))
                {
                    borderAverage.push(midPoint3D(l.start, l.end));
                }
            }
            
            borderAverage.push(clonePoint3D(p));
            
            barycenter = averageFacetPoint(borderAverage);
        }

        var newFacet = new Facet();
        newFacet.points.push(clonePoint3D(facet.edges[n2].edgePoint));
        newFacet.points.push(clonePoint3D(barycenter));
        newFacet.points.push(clonePoint3D(facet.edges[n1].edgePoint));
        newFacet.points.push(clonePoint3D(facet.averagePoint3D));                                     

        newSubdivFacets.push(newFacet);
        newFacet.cube = solidToSubdivide;

        nEdge++;                        
    }        

    drawThermometer(solidToSubdivide.facets.length, subdivSurfaceLoopCounter);
    subdivSurfaceLoopCounter++;
    if (subdivSurfaceLoopCounter >= solidToSubdivide.facets.length)
    {
        clearInterval(subdivTimerId);
        finishSubdivision(solidToSubdivide);
    }
    else
    {
        subdivTimerId = setTimeout(subdivisionSurfaceProcessFacet, 0);
    }
}

function finishSubdivision(parentShape)
{
    parentShape.nSubdivide++;

    parentShape.facets = newSubdivFacets;

    fuseFaster(parentShape);
    
    selectedFacets = [];
    selectedLines = [];
    selectedVertexes = [];

    sortFacets();
    
    setFacetCount(parentShape);

    isSubdividing = false;
    
    alertUser("");
    
    reloadSceneGL();
    
    draw();
}

原文鏈接:
http://www.bimant.com/blog/js-3d-modeler-dev-tutorial/

分享到:
標(biāo)簽:建模
用戶無(wú)頭像

網(wǎng)友整理

注冊(cè)時(shí)間:

網(wǎng)站:5 個(gè)   小程序:0 個(gè)  文章:12 篇

  • 51998

    網(wǎng)站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會(huì)員

趕快注冊(cè)賬號(hào),推廣您的網(wǎng)站吧!
最新入駐小程序

數(shù)獨(dú)大挑戰(zhàn)2018-06-03

數(shù)獨(dú)一種數(shù)學(xué)游戲,玩家需要根據(jù)9

答題星2018-06-03

您可以通過(guò)答題星輕松地創(chuàng)建試卷

全階人生考試2018-06-03

各種考試題,題庫(kù),初中,高中,大學(xué)四六

運(yùn)動(dòng)步數(shù)有氧達(dá)人2018-06-03

記錄運(yùn)動(dòng)步數(shù),積累氧氣值。還可偷

每日養(yǎng)生app2018-06-03

每日養(yǎng)生,天天健康

體育訓(xùn)練成績(jī)?cè)u(píng)定2018-06-03

通用課目體育訓(xùn)練成績(jī)?cè)u(píng)定