• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            eryar

            PipeCAD - Plant Piping Design Software.
            RvmTranslator - Translate AVEVA RVM to OBJ, glTF, etc.
            posts - 603, comments - 590, trackbacks - 0, articles - 0

            Surface Normal Averaging

            Posted on 2014-02-27 21:47 eryar 閱讀(4476) 評(píng)論(0)  編輯 收藏 引用 所屬分類: 2.OpenCASCADE

            Surface Normal Averaging

            eryar@163.com

            摘要Abstract:正確設(shè)置網(wǎng)格面上點(diǎn)的法向,對(duì)幾何體在光照等情況下顯示得更真實(shí),這樣就可以減少頂點(diǎn)數(shù)量,提高渲染速度。本文通過將OpenCascade中的形狀離散成網(wǎng)格數(shù)據(jù)后在OpenSceneGraph中顯示,及使用OSG的快速法向osgUtil::SmoothingVisitor優(yōu)化與使用OpenCascade來計(jì)算正確的法向的結(jié)果的對(duì)比,說明面法向量的重要性。

            關(guān)鍵字Key Words:OpenCascade, OpenSceneGraph, Normal Averaging, Triangulation Mesh

            一、引言 Introduction

            OpenGL中的頂點(diǎn)(Vertex)不是一個(gè)值,而由其空間坐標(biāo)值、法向、顏色坐標(biāo)、紋理坐標(biāo)、霧坐標(biāo)等所組成的一個(gè)集合。一個(gè)最基本的幾何體對(duì)象至少需要設(shè)置一個(gè)合法的頂點(diǎn)數(shù)組,并記錄頂點(diǎn)數(shù)據(jù);如有必要,還可以設(shè)置顏色數(shù)組、法線數(shù)組、紋理坐標(biāo)數(shù)組等多種信息。

            在很多應(yīng)用中,網(wǎng)格上的各點(diǎn)都需要一個(gè)表面法向量,它的作用非常廣泛。例如可用來計(jì)算光照、背面剔除、模擬粒子系統(tǒng)在表面的“彈跳”效果、通過只需要正面而加速碰撞檢測等。

            wps_clip_image-22656

            Figure 1.1 Lighting on a surface

            wps_clip_image-29877

            Figure 1.2 Light is reflected off objects at specific angles

            如上圖所示,物體在光照情況下的反射光等的計(jì)算是與法向N有關(guān)的。

            二、OpenCascade中面的法向計(jì)算 Finding Normal for OpenCascade Face

            在OpenCascade中可以將拓樸形狀轉(zhuǎn)換成STL格式的文件進(jìn)行模型的數(shù)據(jù)交換。其中STL結(jié)構(gòu)中只保存了三角網(wǎng)格的頂點(diǎn)坐標(biāo)和三角面的法向量。為了將拓樸數(shù)據(jù)轉(zhuǎn)換成STL的網(wǎng)格數(shù)據(jù),先將拓樸形狀進(jìn)行三角剖分,再將剖分的網(wǎng)格保存成STL即可。其中每個(gè)三角面的法向計(jì)算也是直接根據(jù)兩個(gè)向量的叉乘得來。

            wps_clip_image-29679

            Figure 2.1 A normal vector as cross product of two vectors

            實(shí)現(xiàn)文件是RWStl.cxx,其中計(jì)算法向的程序代碼如下所示:

             

            //=====================================================================//function : WriteBinary
            //purpose  : write a binary STL file in Little Endian format
            //=====================================================================
            Standard_Boolean RWStl::WriteBinary (const Handle(StlMesh_Mesh)& theMesh,
                                                
            const OSD_Path& thePath,
                                                
            const Handle(Message_ProgressIndicator)& theProgInd)
            {
              OSD_File aFile (thePath);
              aFile.Build (OSD_WriteOnly, OSD_Protection());

              Standard_Real x1, y1, z1;
              Standard_Real x2, y2, z2;
              Standard_Real x3, y3, z3;

             
            // writing 80 bytes of the trash?
              char sval[80];
              aFile.Write ((Standard_Address)sval,
            80);
              WriteInteger (aFile, theMesh
            ->NbTriangles());

             
            int dum=0;
              StlMesh_MeshExplorer aMexp (theMesh);

             
            // create progress sentry for domains
              Standard_Integer aNbDomains = theMesh->NbDomains();
              Message_ProgressSentry aDPS (theProgInd,
            "Mesh domains", 0, aNbDomains, 1);
             
            for (Standard_Integer nbd = 1; nbd <= aNbDomains && aDPS.More(); nbd++, aDPS.Next())
              {
               
            // create progress sentry for triangles in domain
                Message_ProgressSentry aTPS (theProgInd, "Triangles", 0,
                    theMesh
            ->NbTriangles (nbd), IND_THRESHOLD);
                Standard_Integer aTriangleInd
            = 0;
               
            for (aMexp.InitTriangle (nbd); aMexp.MoreTriangle(); aMexp.NextTriangle())
                {
                  aMexp.TriangleVertices (x1,y1,z1,x2,y2,z2,x3,y3,z3);
                 
            //pgo      aMexp.TriangleOrientation (x,y,z);
                  gp_XYZ Vect12 ((x2-x1), (y2-y1), (z2-z1));
                  gp_XYZ Vect13 ((x3
            -x1), (y3-y1), (z3-z1));
                  gp_XYZ Vnorm
            = Vect12 ^ Vect13;
                  Standard_Real Vmodul
            = Vnorm.Modulus ();
                 
            if (Vmodul > gp::Resolution())
                  {
                    Vnorm.Divide(Vmodul);
                  }
                 
            else
                  {
                   
            // si Vnorm est quasi-nul, on le charge a 0 explicitement
                    Vnorm.SetCoord (0., 0., 0.);
                  }

                  WriteDouble2Float (aFile, Vnorm.X());
                  WriteDouble2Float (aFile, Vnorm.Y());
                  WriteDouble2Float (aFile, Vnorm.Z());

                  WriteDouble2Float (aFile, x1);
                  WriteDouble2Float (aFile, y1);
                  WriteDouble2Float (aFile, z1);

                  WriteDouble2Float (aFile, x2);
                  WriteDouble2Float (aFile, y2);
                  WriteDouble2Float (aFile, z2);

                  WriteDouble2Float (aFile, x3);
                  WriteDouble2Float (aFile, y3);
                  WriteDouble2Float (aFile, z3);

                  aFile.Write (
            &dum, 2);

                 
            // update progress only per 1k triangles
                  if (++aTriangleInd % IND_THRESHOLD == 0)
                  {
                   
            if (!aTPS.More())
                     
            break;
                    aTPS.Next();
                  }
                }
              }
              aFile.Close();
              Standard_Boolean isInterrupted
            = !aDPS.More();
             
            return !isInterrupted;
            }

            這種方式渲染的圖形效果如下圖所示:

            wps_clip_image-11876

            Figure 2.2 A typical sphere made up of triangles

            上面的球面是由三角形組成,由OpenCascade的三角剖分算法生成。如果將每個(gè)三角面的法向作為每個(gè)頂點(diǎn)的法向,則渲染效果如下圖所示:

            wps_clip_image-31623

            Figure 2.3 Specific the triangle face normal as the vertex normal of the trangle

            如上圖所示,在光照效果下每個(gè)三角面界限分明,感覺不是很光滑,面之間的過渡很生硬。

            三、OpenSceneGraph中面的法向計(jì)算 Finding Normal for OpenSceneGraph Mesh

            直接將網(wǎng)格頂點(diǎn)的法向設(shè)置成三角面的法向產(chǎn)生的效果不是很理想,通過改變頂點(diǎn)法向的方向可以讓曲面更滑,這種技術(shù)稱為法向平均(Normal Averaging)。利用法向平均技術(shù)可以產(chǎn)生一些有意思的視覺效果。如果有個(gè)面像下面圖所示:

            wps_clip_image-28615

            Figure 3.1 Jagged surface with the usual surface normals

            當(dāng)我們考慮兩個(gè)相連面的頂點(diǎn)處的法向?yàn)閮上噙B面的法向的平均值時(shí),那么這兩個(gè)相連表面的連接處在OpenGL中渲染時(shí)看上去就不那么棱角分明了,如下圖所示:

            wps_clip_image-19581

            Figure 3.2 Averaging the normals will make sharp corners appear softer

            對(duì)于球面或更一般的自由曲面,法向平均的算法也是適用的。如下圖所示:

            wps_clip_image-25766

            Figure 3.3 An approximation with normals perpendicular to each face

            wps_clip_image-16699

            Figure 3.4 Each normal is perpendicular to the surface itself

            球面的法向計(jì)算還是相當(dāng)簡單的。但是對(duì)于一般的曲面就不是那么容易了。這種情況下需要計(jì)算多邊形面片相連處的頂點(diǎn)的法向,將相連接處的頂點(diǎn)的法向設(shè)置為各相鄰面的平均法向后,視覺效果還是很棒的,光滑。

            The actual normal you assign to that vertex is the average of these normals. The visual effect is a nice, smooth, regular surface, even though it is actually composed of numerous small, flat segments.

            在OpenSceneGraph中生成頂點(diǎn)法向量的類是osgUtil::SmoothingVisitor,它使用了Visitor的模式,通過遍歷場景中的幾何體,生成頂點(diǎn)的法向量。對(duì)于上面同一個(gè)球的網(wǎng)格,使用osgUtil::SmoothingVisitor生成法向后在光照下的顯示效果如下圖所示:

            wps_clip_image-27777

            Figure 3.5 Use osgUtil::SmoothingVisitor to generate normals for the sphere

            四、計(jì)算正確的法向 Finding the Correct Normal for the Face

            不管是STL中三角面的法向還是使用osgUtil::SmoothingVisitor來生成面的法向都是無奈之舉,因?yàn)槎际窃陔x散的三角網(wǎng)格上找出法向,不精確,在光照下渲染效果都不是很理想。但是OpenCascade作為幾何造型內(nèi)核,提供了計(jì)算曲面法向的功能,因此有能力計(jì)算出頂點(diǎn)處的法向的精確值。

            當(dāng)計(jì)算網(wǎng)格曲面頂點(diǎn)的法向時(shí),共享頂點(diǎn)處的法向最好設(shè)置為頂點(diǎn)各相連面的法向的平均值。對(duì)于參數(shù)化的曲面,是可以直接計(jì)算出每個(gè)頂點(diǎn)處的法向,就不需要再求法向平均值了,因?yàn)橐呀?jīng)有了曲面法向數(shù)學(xué)定義的值。所以在OpenCascade中計(jì)算出來曲面中某個(gè)頂點(diǎn)的法向就是數(shù)學(xué)定義上面的法向。計(jì)算方法如下:

            對(duì)頂點(diǎn)處的參數(shù)u,v分別求一階導(dǎo)數(shù),得出頂點(diǎn)處在u,v方向的切向量,如下圖所示:

            wps_clip_image-22287

            Figure 4.1 Derivatives with respect to u and v 

            wps_clip_image-1012

            Figure 4.1 Tangents on a surface

            將u和v方向的切向量叉乘就得到了該頂點(diǎn)處的法向,計(jì)算方法如下所示:

            wps_clip_image-25225

            叉乘后頂點(diǎn)處的法向如下圖所示:

            wps_clip_image-22056

            Figure 4.2 Normal on a surface

            OpenCascade中計(jì)算曲面表面屬性的類是BRepLProp_SLProps,計(jì)算法向部分程序如下所示:

             

            Standard_Boolean LProp_SLProps::IsNormalDefined()
            {

             
            if (normalStatus == LProp_Undefined) {
               
            return Standard_False;
              }
             
            else if (normalStatus >= LProp_Defined) {
               
            return Standard_True;
              }
             
             
            // first try the standard computation of the normal.
              CSLib_DerivativeStatus Status;
              CSLib::Normal(d1U, d1V, linTol, Status, normal);
             
            if (Status  == CSLib_Done ) {
                normalStatus
            = LProp_Computed;
               
            return Standard_True;
              }
             
              normalStatus
            = LProp_Undefined;
             
            return Standard_False;
            }

            此類的使用方法如下所示:

             

            const TopoDS_Face& theFace = TopoDS::Face(faceExp.Current());
            BRepLProp_SLProps theProp(BRepAdaptor_Surface(theFace),
            1, Precision::Confusion());

            theProp.SetParameters(u, v);

            if (theProp.IsNormalDefined())
            {
                gp_Vec theNormal
            = theProp.Normal();
            }

            計(jì)算法向后渲染效果如下圖所示:

            wps_clip_image-20387

            Figure 4.3 Sphere vertex normals computed by BRepLProp_SLProps

            由圖可知,OpenCascade計(jì)算的面的法向在渲染時(shí)效果很好。

            五、程序示例 Putting It All Together

            將這三種情況產(chǎn)生的渲染效果放在一起來比較,程序代碼如下所示:

             

            /*
            *    Copyright (c) 2014 eryar All Rights Reserved.
            *
            *           File : Main.cpp
            *         Author : eryar@163.com
            *           Date : 2014-02-25 17:00
            *        Version : 1.0v
            *
            *    Description : Learn the Normal Averaging from OpenGL SuperBible.
            *
            *      Key Words : OpenCascade, OpenSceneGraph, Normal Averaging
            *                 
            */

            // OpenCascade library.
            #define WNT
            #include
            <Poly_Triangulation.hxx>
            #include
            <TColgp_Array1OfPnt2d.hxx>

            #include
            <TopoDS.hxx>
            #include
            <TopoDS_Face.hxx>
            #include
            <TopoDS_Shape.hxx>
            #include
            <TopExp_Explorer.hxx>

            #include
            <BRep_Tool.hxx>
            #include
            <BRepAdaptor_Surface.hxx>
            #include
            <BRepLProp_SLProps.hxx>

            #include
            <BRepMesh.hxx>

            #include
            <BRepPrimAPI_MakeBox.hxx>
            #include
            <BRepPrimAPI_MakeCone.hxx>
            #include
            <BRepPrimAPI_MakeSphere.hxx>

            #pragma comment(lib,
            "TKernel.lib")
            #pragma comment(lib,
            "TKMath.lib")
            #pragma comment(lib,
            "TKG3d.lib")
            #pragma comment(lib,
            "TKBRep.lib")
            #pragma comment(lib,
            "TKMesh.lib")
            #pragma comment(lib,
            "TKPrim.lib")
            #pragma comment(lib,
            "TKTopAlgo.lib")


            // OpenSceneGraph library.
            #include <osg/MatrixTransform>
            #include
            <osg/Material>

            #include
            <osgGA/StateSetManipulator>

            #include
            <osgViewer/Viewer>
            #include
            <osgViewer/ViewerEventHandlers>

            #include
            <osgUtil/SmoothingVisitor>

            #pragma comment(lib,
            "osgd.lib")
            #pragma comment(lib,
            "osgDBd.lib")
            #pragma comment(lib,
            "osgGAd.lib")
            #pragma comment(lib,
            "osgUtild.lib")
            #pragma comment(lib,
            "osgViewerd.lib")
            #pragma comment(lib,
            "osgManipulatord.lib")

            /**
            * @breif Build the mesh for the OpenCascade TopoDS_Shape.
            * @param [in] TopoDS_Shape theShape OpenCascade TopoDS_Shape.
            * @param [in] Standard_Boolean bSetNormal If set to true, will set the vertex normal correctly
            *             else will set vertex normal by its triangle face normal.
            */
            osg::Geode
            * BuildMesh(const TopoDS_Shape& theShape, Standard_Boolean bSetNormal = Standard_False)
            {
                Standard_Real theDeflection
            = 0.1;
                BRepMesh::Mesh(theShape, theDeflection);

                osg::ref_ptr
            <osg::Geode> theGeode = new osg::Geode();

               
            for (TopExp_Explorer faceExp(theShape, TopAbs_FACE); faceExp.More(); faceExp.Next())
                {
                    TopLoc_Location theLocation;
                   
            const TopoDS_Face& theFace = TopoDS::Face(faceExp.Current());
                   
            const Handle_Poly_Triangulation& theTriangulation = BRep_Tool::Triangulation(theFace, theLocation);
                    BRepLProp_SLProps theProp(BRepAdaptor_Surface(theFace),
            1, Precision::Confusion());

                   
            if (theTriangulation.IsNull())
                    {
                       
            continue;
                    }

                    osg::ref_ptr
            <osg::Geometry> theMesh = new osg::Geometry();
                    osg::ref_ptr
            <osg::Vec3Array> theVertices = new osg::Vec3Array();
                    osg::ref_ptr
            <osg::Vec3Array> theNormals = new osg::Vec3Array();

                   
            for (Standard_Integer t = 1; t <= theTriangulation->NbTriangles(); ++t)
                    {
                       
            const Poly_Triangle& theTriangle = theTriangulation->Triangles().Value(t);
                        gp_Pnt theVertex1
            = theTriangulation->Nodes().Value(theTriangle(1));
                        gp_Pnt theVertex2
            = theTriangulation->Nodes().Value(theTriangle(2));
                        gp_Pnt theVertex3
            = theTriangulation->Nodes().Value(theTriangle(3));

                        gp_Pnt2d theUV1
            = theTriangulation->UVNodes().Value(theTriangle(1));
                        gp_Pnt2d theUV2
            = theTriangulation->UVNodes().Value(theTriangle(2));
                        gp_Pnt2d theUV3
            = theTriangulation->UVNodes().Value(theTriangle(3));

                        theVertex1.Transform(theLocation.Transformation());
                        theVertex2.Transform(theLocation.Transformation());
                        theVertex3.Transform(theLocation.Transformation());

                       
            // find the normal for the triangle mesh.
                        gp_Vec V12(theVertex1, theVertex2);
                        gp_Vec V13(theVertex1, theVertex3);
                        gp_Vec theNormal
            = V12 ^ V13;
                        gp_Vec theNormal1
            = theNormal;
                        gp_Vec theNormal2
            = theNormal;
                        gp_Vec theNormal3
            = theNormal;

                       
            if (theNormal.Magnitude() > Precision::Confusion())
                        {
                            theNormal.Normalize();
                            theNormal1.Normalize();
                            theNormal2.Normalize();
                            theNormal3.Normalize();
                        }

                        theProp.SetParameters(theUV1.X(), theUV1.Y());
                       
            if (theProp.IsNormalDefined())
                        {
                            theNormal1
            = theProp.Normal();
                        }

                        theProp.SetParameters(theUV2.X(), theUV2.Y());
                       
            if (theProp.IsNormalDefined())
                        {
                            theNormal2
            = theProp.Normal();
                        }

                        theProp.SetParameters(theUV3.X(), theUV3.Y());
                       
            if (theProp.IsNormalDefined())
                        {
                            theNormal3
            = theProp.Normal();
                        }

                       
            if (theFace.Orientation() == TopAbs_REVERSED)
                        {
                            theNormal.Reverse();
                            theNormal1.Reverse();
                            theNormal2.Reverse();
                            theNormal3.Reverse();
                        }

                        theVertices
            ->push_back(osg::Vec3(theVertex1.X(), theVertex1.Y(), theVertex1.Z()));
                        theVertices
            ->push_back(osg::Vec3(theVertex2.X(), theVertex2.Y(), theVertex2.Z()));
                        theVertices
            ->push_back(osg::Vec3(theVertex3.X(), theVertex3.Y(), theVertex3.Z()));

                       
            if (bSetNormal)
                        {
                            theNormals
            ->push_back(osg::Vec3(theNormal1.X(), theNormal1.Y(), theNormal1.Z()));
                            theNormals
            ->push_back(osg::Vec3(theNormal2.X(), theNormal2.Y(), theNormal2.Z()));
                            theNormals
            ->push_back(osg::Vec3(theNormal3.X(), theNormal3.Y(), theNormal3.Z()));
                        }
                       
            else
                        {
                            theNormals
            ->push_back(osg::Vec3(theNormal.X(), theNormal.Y(), theNormal.Z()));
                            theNormals
            ->push_back(osg::Vec3(theNormal.X(), theNormal.Y(), theNormal.Z()));
                            theNormals
            ->push_back(osg::Vec3(theNormal.X(), theNormal.Y(), theNormal.Z()));
                        }
                    }

                    theMesh
            ->setVertexArray(theVertices);
                    theMesh
            ->setNormalArray(theNormals);
                    theMesh
            ->setNormalBinding(osg::Geometry::BIND_PER_VERTEX);
                    theMesh
            ->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::TRIANGLES, 0, theVertices->size()));

                    theGeode
            ->addDrawable(theMesh);
                }

               
            // Set material for the mesh.
                osg::ref_ptr<osg::StateSet> theStateSet = theGeode->getOrCreateStateSet();
                osg::ref_ptr
            <osg::Material> theMaterial = new osg::Material();

                theMaterial
            ->setDiffuse(osg::Material::FRONT, osg::Vec4(1.0f, 0.0f, 0.0f, 1.0f));
                theMaterial
            ->setSpecular(osg::Material::FRONT, osg::Vec4(1.0f, 1.0f, 1.0f, 1.0f));
                theMaterial
            ->setShininess(osg::Material::FRONT, 100.0f);

                theStateSet
            ->setAttribute(theMaterial);

               
            return theGeode.release();
            }

            osg::Node
            * BuildScene(void)
            {
                osg::ref_ptr
            <osg::Group> theRoot = new osg::Group();

               
            // 1. Build a sphere without setting vertex normal correctly.
                TopoDS_Shape theSphere = BRepPrimAPI_MakeSphere(1.6);
                osg::ref_ptr
            <osg::Node> theSphereNode = BuildMesh(theSphere);
                theRoot
            ->addChild(theSphereNode);

               
            // 2. Build a sphere without setting vertex normal correctly, but will
               
            // use osgUtil::SmoothingVisitor to find the average normals.
                osg::ref_ptr<osg::MatrixTransform> theSmoothSphere = new osg::MatrixTransform();
                osg::ref_ptr
            <osg::Geode> theSphereGeode = BuildMesh(theSphere);
                theSmoothSphere
            ->setMatrix(osg::Matrix::translate(5.0, 0.0, 0.0));

               
            // Use SmoothingVisitor to find the vertex average normals.
                osgUtil::SmoothingVisitor sv;
                sv.apply(
            *theSphereGeode);

                theSmoothSphere
            ->addChild(theSphereGeode);
                theRoot
            ->addChild(theSmoothSphere);

               
            // 3. Build a sphere with setting vertex normal correctly.
                osg::ref_ptr<osg::MatrixTransform> theBetterSphere = new osg::MatrixTransform();
                osg::ref_ptr
            <osg::Geode> theSphereGeode1 = BuildMesh(theSphere, Standard_True);
                theBetterSphere
            ->setMatrix(osg::Matrix::translate(10.0, 0.0, 0.0));

                theBetterSphere
            ->addChild(theSphereGeode1);
                theRoot
            ->addChild(theBetterSphere);

               
            return theRoot.release();
            }

            int main(int argc, char* argv[])
            {
                osgViewer::Viewer viewer;   

                viewer.setSceneData(BuildScene());

                viewer.addEventHandler(
            new osgViewer::StatsHandler());
                viewer.addEventHandler(
            new osgViewer::WindowSizeHandler());
                viewer.addEventHandler(
            new osgGA::StateSetManipulator(viewer.getCamera()->getOrCreateStateSet()));

               
            return viewer.run();
            }

            生成效果圖如下所示:

            wps_clip_image-7902

            Figure 5.1 Same sphere triangulation mesh

            wps_clip_image-5142

            Figure 5.2 Same sphere mesh with different vertex normals

            由上圖可知,相同的球面網(wǎng)格,當(dāng)頂點(diǎn)的法向?yàn)槿敲娴姆ㄏ驎r(shí),在有光照的情況下,渲染效果最差。使用osgUtil::SmoothingVisitor法向生成算法生成的頂點(diǎn)法向與使用類BRepLProp_SLProps計(jì)算出的法向,在光照情況下顯示效果相當(dāng)。

            wps_clip_image-2529

            Figure 5.3 Pipe and equipments with correct vertex normals

            wps_clip_image-28058

            六、結(jié)論 Conclusion

            正確設(shè)置網(wǎng)格面頂點(diǎn)的法向可以在光照環(huán)境中看上去更光滑真實(shí)。利用法向平均算法或使用曲面的參數(shù)方程求解曲面頂點(diǎn)上法向,可以在滿足顯示效果基本相同的條件下減少網(wǎng)格頂點(diǎn)的數(shù)量,可以提高渲染速度。

            七、參考資料 References

            1. Waite group Press, OpenGL Super Bible(1st), Macmillan Computer Publishing, 1996

            2. Richard S. Wright Jr., Benjamin Lipchak, OpenGL SuperBible(3rd), Sams Publishing, 2004

            3. vsocc.cpp in netgen

            4. Kelly Dempski, Focus on Curves and Surfaces, Premier Press, 2003

            5. 王銳,錢學(xué)雷,OpenSceneGraph三維渲染引擎設(shè)計(jì)與實(shí)踐,清華大學(xué)出版社

            6. 肖鵬,劉更代,徐明亮,OpenSceneGraph三維渲染引擎編程指南,清華大學(xué)出版社

             

            狠狠色婷婷综合天天久久丁香 | 久久婷婷五月综合色奶水99啪| 久久露脸国产精品| 久久精品成人一区二区三区| 狠狠综合久久综合中文88| 国产精品激情综合久久| 久久99国产精品成人欧美| 欧美日韩精品久久久久| 久久亚洲AV无码精品色午夜麻豆| 欧美与黑人午夜性猛交久久久| 香蕉久久夜色精品国产2020 | 国产亚洲精久久久久久无码77777 国产亚洲精品久久久久秋霞 | 亚洲乱亚洲乱淫久久| 久久久WWW成人免费精品| 中文字幕久久精品| 久久久久99精品成人片直播| 久久99国产精品一区二区| 精品久久久久久无码人妻蜜桃| 午夜精品久久久内射近拍高清| 亚洲伊人久久精品影院| 国产精品久久久天天影视| 久久久精品人妻无码专区不卡| 久久夜色精品国产噜噜亚洲a| 欧美牲交A欧牲交aⅴ久久| 国产精品日韩深夜福利久久| 久久亚洲AV成人无码| 久久精品免费观看| 久久久久久伊人高潮影院| 亚洲国产成人久久精品影视| 日韩人妻无码一区二区三区久久99 | 久久99国产精品久久99| 日本高清无卡码一区二区久久| 熟妇人妻久久中文字幕| 久久国产视屏| 久久w5ww成w人免费| 三级片免费观看久久| 国产欧美一区二区久久| 久久精品国产亚洲AV香蕉| 国产精品成人久久久久三级午夜电影 | 亚洲午夜福利精品久久| 久久精品国产99国产电影网|