# eryar

posts - 414, comments - 576, trackbacks - 0, articles - 0

# Surface Normal Averaging

## 一、引言 Introduction

OpenGL中的顶点（Vertex）不是一个值，而由其空间坐标值、法向、颜色坐标、纹理坐标、雾坐标等所组成的一个集合。一个最基本的几何体对象至少需要设置一个合法的顶点数组，并记录顶点数据；如有必要，还可以设置颜色数组、法线数组、纹理坐标数组等多种信息。

Figure 1.1 Lighting on a surface

Figure 1.2 Light is reflected off objects at specific angles

Figure 2.1 A normal vector as cross product of two vectors

//=====================================================================//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];
80);
WriteInteger (aFile, theMesh
->NbTriangles());

int dum=0;
StlMesh_MeshExplorer aMexp (theMesh);

// create progress sentry for domains
Standard_Integer aNbDomains = theMesh->NbDomains();
"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

return !isInterrupted;
}

Figure 2.2 A typical sphere made up of triangles

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

## 三、OpenSceneGraph中面的法向计算 Finding Normal for OpenSceneGraph Mesh

Figure 3.1 Jagged surface with the usual surface normals

Figure 3.2 Averaging the normals will make sharp corners appear softer

Figure 3.3 An approximation with normals perpendicular to each face

Figure 3.4 Each normal is perpendicular to the surface itself

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.

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

## 四、计算正确的法向 Finding the Correct Normal for the Face

Figure 4.1 Derivatives with respect to u and v

Figure 4.1 Tangents on a surface

Figure 4.2 Normal on a surface

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());
1, Precision::Confusion());

theProp.SetParameters(u, v);

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

Figure 4.3 Sphere vertex normals computed by BRepLProp_SLProps

## 五、程序示例 Putting It All Together

/*
*
*           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
*
*/

#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
#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,
#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);
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

theGeode
}

// 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

// 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
theRoot

// 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
theRoot

return theRoot.release();
}

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

viewer.setSceneData(BuildScene());

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

return viewer.run();
}

Figure 5.1 Same sphere triangulation mesh

Figure 5.2 Same sphere mesh with different vertex normals

Figure 5.3 Pipe and equipments with correct vertex normals

## 七、参考资料 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. 王锐，钱学雷，OpenSceneGraph三维渲染引擎设计与实践，清华大学出版社

6. 肖鹏，刘更代，徐明亮，OpenSceneGraph三维渲染引擎编程指南，清华大学出版社