NURBS

贝塞尔曲线的缺点是当我们增加很多控制点的时候,曲线变得不可控,其连续性会变差差。如果控制点很多(高阶曲线),当我们调整一个控制点的位置,对整个曲线的影响是很大的。要获得更高级的控制,可以使用GLU库提供的NURBS(非均匀有理B样条)。通过这些函数我们可以在求值器中调整控制点的影响力,在有大量控制点的情况下,依然可以产生平滑的曲线。

从贝塞尔到B样条

贝塞尔曲线由起点、终点和其他控制点来影响曲线的形状。在二次贝塞尔曲线和三次贝塞尔曲线中,可以通过调整控制点的位置而得到很好的平滑性(C2级连续性 曲率级)的曲线。当增加更多的控制点的时候,这种平滑性就被破坏了。如下图所示,前两个曲线很平滑(曲率级的连续性),第三个曲线在增加了一个控制点之后,曲线被拉伸了,其平滑性遭到了破坏。

B样条的工作方式类似于贝塞尔曲线,但不同的是曲线被分成很多段。每段曲线的形状只受到最近的四个控制点的影响,这样曲线就像是4阶的贝塞尔曲线拼接起来的。这样很长的有很多控制点的曲线就会有固定的连续性,平滑性(每一段都是c2级的连续性)。

结点

NURBS(非均匀有理B样条)的真正威力在于,可以调整任意一段曲线中的四个控制点的影响力,来产生较好的平滑性。这是通过一系列结点来控制的。每个控制点都定义了两个结点的值。结点的取值范围是u或v的定义域,而且必须是非递减的。

结点的值决定了落在u、v参数定义域内的控制点的影响力。下图的曲线表示控制点对一条在u参数定义域内的具有四个单位的曲线的影响。下图表示中间点对曲线的影响更大,而且只有在[0,3]范围内的控制点才会对曲线产生影响。

在u、v参数定义域内的控制点对曲线的形状会有有影响,而且我们可以通过结点来控制控制点的影响力。非均匀性就是指一个控制点的影响力的范围是可以改变的。

节点 ( Knot ) 是一个 ( 阶数 + N - 1 ) 的数字列表,N 代表控制点数目。有时候这个列表上的数字也称为节点矢量 ( Knot Vector ),这里的矢量并不是指 3D 方向。

节点列表上的数字必须符合几个条件,确定条件是否符合的标准方式是在列表上往下时,数字必需维持不变或变大,而且数字重复的次数不可以比阶数大。例如,阶数 3 有 15 个控制点的 NURBS 曲线,列表数字为 0,0,0,1,2,2,2,3,7,7,9,9,9 是一个符合条件的节点列表。列表数字为 0,0,0,1,2,2,2,2,7,7,9,9,9 则不符合,因为此列表中有四个 2,而四比阶数大 ( 阶数为 3 )。

节点值重复的次数称为节点的重数 ( Multiplicity ),在上面例子中符合条件的节点列表中,节点值 0 的重数值为三;节点值 1 的重数值为一;节点值 2 的重数为三;节点值 7 的重数值为二;节点值 9 的重数值为三。如果节点值重复的次数和阶数一样,该节点值称为全复节点 ( Full-Multiplicity Knot )。在上面的例子中,节点值 0、2、9 有完整的重数,只出现一次的节点值称为单纯节点 ( Simple Knot ),节点值 1 和 3 为单纯节点。

如果在节点列表中是以全复节点开始,接下来是单纯节点,再以全复节点结束,而且节点值为等差,称为均匀 ( Uniform )。例如,如果阶数为 3 有 7 个控制点的 NURBS 曲线,其节点值为 0,0,0,1,2,3,4,4,4,那么该曲线有均匀的节点。如果节点值是 0,0,0,1,2,5,6,6,6 不是均匀的,称为非均匀 ( Non-Uniform )。在 NURBS 的 NU 代表“非均匀”,意味着在一条 NURBS 曲线中节点可以是非均匀的。

在节点值列表中段有重复节点值的 NURBS 曲线比较不平滑,最不平滑的情形是节点列表中段出现全复节点,代表曲线有锐角。因此,有些设计师喜欢在曲线插入或移除节点,然后调整控制点,使曲线的造型变得平滑或尖锐。因为节点数等于 ( N + 阶数 - 1 ),N 代表控制点的数量,所以插入一个节点会增加一个控制点,移除一个节点也会减少一个控制点。插入节点时可以不改变 NURBS 曲线的形状,但通常移除节点必定会改变 NURBS 曲线的形状。

节点(Knot)与控制点

控制点和节点是一对一成对的是常见的错误概念,这种情形只发生在 1 阶的 NURBS ( 多重直线 )。较高阶数的 NURBS 的每 ( 2 x 阶数 ) 个节点是一个群组,每 ( 阶数 + 1 ) 个控制点是一个群组。例如,一条 3 阶 7 个控制点的 NURBS 曲线,节点是 0,0,0,1,2,5,8,8,8,前四个控制点是对应至前六个节点;第二至第五个控制点是对应至第二至第七个节点 0,0,1,2,5,8;第三至第六个控制点是对应至第三至第八个节点 0,1,2,5,8,8;最后四个控制点是对应至最后六个节点

创建NURBS表面

GLU库中提供了易用高级的绘制NURBS表面的函数。我们不需要显示地调用求值函数或建立网格。渲染一个NURBS表面的步骤如下:

创建一个NURBS渲染对象 gluNewNurbsRenderer()

调用相关的NURBS函数来修改曲线或曲面的外观 gluNurbsProperty

定义表面,渲染NURBS gluBeginSurface gluNurbsSurface gluEndSurface

销毁NURBS渲染对象 gluDeleteNurbsRenderer();

我们通过gluNewNurbsRenderer函数来为NURBS创建一个渲染器对象,在最后使用gluDeleteNurbsRenderer销毁它。代码如下:

//

NURBS 对象指针

GLUnurbsObj

*

pNurb

=

NULL;

...

...

//

创建NURBS对象

pNurb

=

gluNewNurbsRenderer();

...

if

(pNurb)

gluDeleteNurbsRenderer(pNurb);

NURBS属性

在创建了NURBS渲染器之后,我们需要设置NURBS的属性。

//设置采样容差

gluNurbsProperty(pNurb, GLU_SAMPLING_TOLERANCE, 25.0f);

//填充一个实体的表面

gluNurbsProperty(pNurb, GLU_DISPLAY_MODE, (GLfloat)GLU_FILL);

GLU_SAMPLING_TOLERANCE决定了网格的精细程度。GLU_FILL表示使用填充模式,相应的GLU_OUTLINE_POLYGON是线框模式。

定义表面

表面通过一组控制点和一个结点序列来定义。使用gluNurbsSurface函数来定义表面,这个函数要在gluBeginSurface和gluEndSurface中间:

//

渲染NURB

//

开始NURB表面的定义

gluBeginSurface(pNurb);

gluNurbsSurface(pNurb,

//

指针指向NURBS渲染器

8

, Knots,

//

u定义域内的结点个数,以及结点序列

8

, Knots,

//

v定义域内的结点个数,以及结点序列

4

*

3

,

//

u方向上控制点的间隔

3

,

//

v方向上控制点的间隔

&

ctrlPoints[

0

][

0

][

0

],

//

控制点数组

4

,

4

,

//

u,v 的次数

GL_MAP2_VERTEX_3);

//

表面的类型

//

完成定义

gluEndSurface(pNurb);

我们可以通过gluNurbsSurface来定义多个NURBS表面,但NURBS渲染器的属性不会改变。一般情况下我们连续画两个不同的属性的表面,比如很少需要相邻的两个曲面一个是填充型的一个是线框性的。控制点和结点的序列如下:

GLint nNumPoints = 4; // 4 X 4

// u v (x,y,z)

GLfloat ctrlPoints[4][4][3]= {{{ -6.0f, -6.0f, 0.0f}, // u = 0, v = 0

{ -6.0f, -2.0f, 0.0f}, // v = 1

{ -6.0f, 2.0f, 0.0f}, // v = 2

{ -6.0f, 6.0f, 0.0f}}, // v = 3

{{ -2.0f, -6.0f, 0.0f}, // u = 1 v = 0

{ -2.0f, -2.0f, 8.0f}, // v = 1

{ -2.0f, 2.0f, 8.0f}, // v = 2

{ -2.0f, 6.0f, 0.0f}}, // v = 3

{{ 2.0f, -6.0f, 0.0f }, // u =2 v = 0

{ 2.0f, -2.0f, 8.0f }, // v = 1

{ 2.0f, 2.0f, 8.0f }, // v = 2

{ 2.0f, 6.0f, 0.0f }},// v = 3

{{ 6.0f, -6.0f, 0.0f}, // u = 3 v = 0

{ 6.0f, -2.0f, 0.0f}, // v = 1

{ 6.0f, 2.0f, 0.0f}, // v = 2

{ 6.0f, 6.0f, 0.0f}}};// v = 3效果图:

修剪

修剪的功能常用语消减NURBS表面的锐利的边缘。我们也可以使用修剪的功能在表面上剪一个洞。下面的示例在表面剪一个三角形的洞:

static GLUnurbsObj *pNurb = NULL;

GLint nNumPoints = 4; // 4 X 4

// u v (x,y,z)

GLfloat ctrlPoints[4][4][3]= {{{ -6.0f, -6.0f, 0.0f}, // u = 0, v = 0

{ -6.0f, -2.0f, 0.0f}, // v = 1

{ -6.0f, 2.0f, 0.0f}, // v = 2

{ -6.0f, 6.0f, 0.0f}}, // v = 3

{{ -2.0f, -6.0f, 0.0f}, // u = 1 v = 0

{ -2.0f, -2.0f, 8.0f}, // v = 1

{ -2.0f, 2.0f, 8.0f}, // v = 2

{ -2.0f, 6.0f, 0.0f}}, // v = 3

{{ 2.0f, -6.0f, 0.0f }, // u =2 v = 0

{ 2.0f, -2.0f, 8.0f }, // v = 1

{ 2.0f, 2.0f, 8.0f }, // v = 2

{ 2.0f, 6.0f, 0.0f }},// v = 3

{{ 6.0f, -6.0f, 0.0f}, // u = 3 v = 0

{ 6.0f, -2.0f, 0.0f}, // v = 1

{ 6.0f, 2.0f, 0.0f}, // v = 2

{ 6.0f, 6.0f, 0.0f}}};// v = 3

// Knot sequence for the NURB

GLfloat Knots[8] = {0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f};

void DrawPoints(void)

{

glColor3ub(255, 0, 0);

glPointSize(5.0f);

glBegin(GL_POINTS);

for (int i = 0; i < 4; ++i)

{

for (int j = 0; j < 4; ++j)

{

glVertex3fv(ctrlPoints[i][j]);

}

}

glEnd();

}

// NURBS 出错时的回调函数

void CALLBACK NurbsErrorHandler(GLenum nErrorCode)

{

char cMessage[100] = {0,};

strcpy(cMessage, "NURBS error : ");

strcat(cMessage, (char*)gluErrorString(nErrorCode));

glutSetWindowTitle(cMessage);

}

void RenderScene(void)

{

glColor3ub(0,0,220);

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

glMatrixMode(GL_MODELVIEW);

glPushMatrix();

glRotatef(330.0f, 1.0f, 0.0f, 0.0f);

//修剪框是一个闭合的环

//外修剪框

GLfloat outSidePts[5][2] =

{{0.0f, 0.0f}, {1.0f, 0.0f}, {1.0f, 1.0f}, {0.0f, 1.0f}, {0.0f, 0.0f}};

//内修剪框

GLfloat inSidePts[4][2] =

{{0.25f, 0.25f}, { 0.5f, 0.5f}, {0.75f, 0.25f},{0.25f, 0.25f} };

gluBeginSurface(pNurb);

//定义NURBS表面

gluNurbsSurface(pNurb,

8, Knots, //u定义域内的结点个数,以及结点序列

8, Knots,//v定义域内的结点个数,以及结点序列

4 * 3, //u方向上的控制点间隔

3, //v方向上的控制点间隔

&ctrlPoints[0][0][0], //控制点数组

4, 4, //u v的次数

GL_MAP2_VERTEX_3);//产生的类型

//开始修剪

gluBeginTrim(pNurb);

gluPwlCurve(pNurb,

5, //修剪点的个数

&outSidePts[0][0], //修剪点数组

2, //点之间的间隔

GLU_MAP1_TRIM_2);//修剪的类型

gluEndTrim(pNurb);

gluBeginTrim(pNurb);

gluPwlCurve(pNurb, 4, &inSidePts[0][0], 2, GLU_MAP1_TRIM_2);

gluEndTrim(pNurb);

gluEndSurface(pNurb);

DrawPoints();

glPopMatrix();

glutSwapBuffers();

}

在一对gluBeginSurface/gluEndSurface调用内部,调用gluBeginTrim函数开始修剪,调用gluPwlCurve函数指定一条修剪曲线,然后调用gluEndTrim完成曲线的修剪。这些修剪曲线必须根据单位参数方程u和v空间指定。这意味着u/v定义域被缩放到0.0到1.0之间。

gluPwlCurve函数定义一条由片段拼接成的线性曲线,实质上就是一些首尾相连的点。对曲线进行修剪时,顺时针方向修剪的曲线将会丢弃它的内部。一般情况下,应该指定一条外部修剪曲线,它包围了整个NURBS参数空间,然后在这个区域内部指定一个较小的修剪区域(顺时针围绕)。

曲面细分(Tesselation)

为了使OpenGl的速度尽可能快,所有的几何图元都必须是凸的。如果我们遇到复杂的几何图形(如下图),要把它手工切分为多个凸多边形工作量较大。OpenGL的GLU库提供了曲面细分的特性,帮助我们处理复杂的图形。

对上面的两个几何图形我们可以进行如下图的细分(这种分法不是唯一的)

镶嵌器(Tessellatror)

曲面细分通过镶嵌器对象来工作。镶嵌器对象类似于二次方程状态对象需要创建以及销毁。

GLUtesselator *pTess = gluNewTes(); //注意不是GLUtessellator 少了个l

gluDeleteTess(pTess);

所有的曲面细分函数的第一个参数都是镶嵌器对象。这样就允许我们构造多个镶嵌器,并在需要的时候很方便的进行切换,这也使得镶嵌器只影响当前的工作对象。

镶嵌器分解多边形并渲染的步骤如下:

创建镶嵌器对象

设置镶嵌器的回调函数和镶嵌器的状态

开始一个多边形

开始一个轮廓

把轮廓的顶点发送给镶嵌器

结束一个轮廓

如果有更多的轮廓回到步骤4

结束多边形

每个多边形由一个或多个轮廓组成。上图左边的多边形就只有一个轮廓,右边的有个洞所以有两个轮廓。曲线细分的工作是到步骤8才进行的。曲线细分会带来一定性能的开销。如果图形是静态的最好是把函数调用放到显示列表中。

镶嵌器的回调函数

在曲面细分的过程中,镶嵌器会调用一系列你提供的回调函数。这些回调函数指定了顶点的信息以及开始和结束图元。注册回调函数的原型如下:

void gluTessCallback(GLUTesselator *tobj, GLenum which, void (*fn)());

第一个参数是镶嵌器对象,第二个指定回调函数的类型,第三个是回调函数指针。

例如:

typedef GLvoid (_stdcall *CallBack)();

gluTessCallback(pTess, GLU_TESS_BEGIN, (CallBack)glBegin);

gluTessCallback(pTess, GLU_TESS_VERTEX, (CallBack)glVertex3dv);

gluTessCallback(pTess, GLU_TESS_END, (CallBack)glEnd);

上面三个函数分别指定了,新图元开始的回调函数,为每一个顶点调用glVertex3dv,以及结束的回调函数。上面只是简单的指定了OpenGL的函数,你也可以自定一个回调函数,里面实现你想要的功能。还可以注册出错时的回调函数:

void tessError(GLenum code)

{

const char *str = (const char*)gluErrorString(code);

glutSetWindowTitle(str);

}

gluTessCallback(pTess, GLU_TESS_ERROR, (CallBack)tessError);

指定顶点数据

void gluTessBeginPolygon(GLUTesselator *tobj, void *data);

第一个参数为镶嵌器对象指针,第二个参数是指向曲面细分处理相关联的用户自定义数据,这个数据可以用回调函数在细分的过程中发送回来。一般情况下,这个参数常常设置为NULL. 完成一个多边形后调用下面的函数开始细分。

void gluTessEndPolygon(GLUTesslator *tobj);

下面两个函数开始和结束轮廓,对应于步骤4和6:

void gluTessBeginContour(GLUTesselator *tobj);

void gluTessEndContour(GLUTesselator *tobj);

在这两个函数中,添加轮廓的顶点:

void gluTessVertex(GLUTesselator *tobj, GLdouble v[3], void *data);

v参数包含了用于镶嵌器计算的真实的顶点数据,data参数是指向顶点数据的指针,传给指定的GLU_VERTEX的回调函数。第二个参数可以包含除了顶点之外的一些信息如颜色,法线等。如果我们自己定义了GLU_VERTEX的回调函数,那么就可以使用data的数据了。

示例

一个佛罗里达州的简单轮廓,这个州里面还有个奥基乔比湖的轮廓。通过右键菜单,我们可以在简单的画线环绕模式,外围轮廓曲线细分模式,和复杂模式之间切换。

这里面有个新函数gluTessProperty(pTess, GLU_TESS_WINDING_RULE, GLU_TESS_WINDING_ODD);这个指定了轮廓线为奇数的是填充的,轮廓线是偶数的是镂空的。我们的湖的轮廓线在里面,是第二个轮廓线,所以是镂空的。

代码如下:

#include "gltools.h"

#include

//外围轮廓线

#define COAST_POINTS 24

GLdouble vCoast[COAST_POINTS][3] = {{-70.0, 30.0, 0.0 },

{-50.0, 30.0, 0.0 },

{-50.0, 27.0, 0.0 },

{ -5.0, 27.0, 0.0 },

{ 0.0, 20.0, 0.0 },

{ 8.0, 10.0, 0.0 },

{ 12.0, 5.0, 0.0 },

{ 10.0, 0.0, 0.0 },

{ 15.0,-10.0, 0.0 },

{ 20.0,-20.0, 0.0 },

{ 20.0,-35.0, 0.0 },

{ 10.0,-40.0, 0.0 },

{ 0.0,-30.0, 0.0 },

{ -5.0,-20.0, 0.0 },

{-12.0,-10.0, 0.0 },

{-13.0, -5.0, 0.0 },

{-12.0, 5.0, 0.0 },

{-20.0, 10.0, 0.0 },

{-30.0, 20.0, 0.0 },

{-40.0, 15.0, 0.0 },

{-50.0, 15.0, 0.0 },

{-55.0, 20.0, 0.0 },

{-60.0, 25.0, 0.0 },

{-70.0, 25.0, 0.0 }};

//湖的轮廓线

#define LAKE_POINTS 4

GLdouble vLake[LAKE_POINTS][3] = {{ 10.0, -20.0, 0.0 },

{ 15.0, -25.0, 0.0 },

{ 10.0, -30.0, 0.0 },

{ 5.0, -25.0, 0.0 }};

#define LINE_LOOP 1

#define TESS 2

#define COMPLEX 3

static int iMode = LINE_LOOP;

void SetupRC()

{

glClearColor(0.0f, 0.0f, 0.0f, 1.0f);

}

void tessError(GLenum code)

{

const char *str = (const char*)gluErrorString(code);

glutSetWindowTitle(str);

}

void RenderScene()

{

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

glColor3f(1.0f, 0.0f, 1.0f);

glPushMatrix();

glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);

switch(iMode)

{

case LINE_LOOP:

{

glBegin(GL_LINE_LOOP);

for (int i = 0; i < COAST_POINTS; ++i)

{

glVertex3dv(vCoast[i]);

}

glEnd();

}

break;

case TESS:

{

//创建镶嵌器对象

GLUtesselator *pTess = gluNewTess();

//设置回调函数

gluTessCallback(pTess, GLU_TESS_BEGIN, (CallBack)glBegin);

gluTessCallback(pTess, GLU_TESS_END, (CallBack)glEnd);

gluTessCallback(pTess, GLU_TESS_VERTEX, (CallBack)glVertex3dv);

gluTessCallback(pTess, GLU_TESS_ERROR, (CallBack)tessError);

//开始一个多边形

gluTessBeginPolygon(pTess, NULL);

//开始一个轮廓

gluTessBeginContour(pTess);

//设置轮廓的顶点

for (int i = 0; i < COAST_POINTS; ++i)

{

gluTessVertex(pTess, vCoast[i], vCoast[i]);

}

gluTessEndContour(pTess);

gluTessEndPolygon(pTess);

gluDeleteTess(pTess);

}

break;

case COMPLEX:

{

GLUtesselator *pTess = gluNewTess();

gluTessCallback(pTess, GLU_TESS_BEGIN, (CallBack)glBegin);

gluTessCallback(pTess, GLU_TESS_END, (CallBack)glEnd);

gluTessCallback(pTess, GLU_TESS_VERTEX, (CallBack)glVertex3dv);

gluTessCallback(pTess, GLU_TESS_ERROR, (CallBack)tessError);

//指定奇数的轮廓为填充,偶数的轮廓是镂空的。这也是默认的设置

gluTessProperty(pTess, GLU_TESS_WINDING_RULE, GLU_TESS_WINDING_ODD);

gluTessBeginPolygon(pTess, NULL);

gluTessBeginContour(pTess);

for (int i = 0; i < COAST_POINTS; ++i)

{

gluTessVertex(pTess, vCoast[i], vCoast[i]);

}

gluTessEndContour(pTess);

gluTessBeginContour(pTess);

for (int i = 0; i < LAKE_POINTS; ++i)

{

gluTessVertex(pTess, vLake[i], vLake[i]);

}

gluTessEndContour(pTess);

gluTessEndPolygon(pTess);

gluDeleteTess(pTess);

}

break;

default:

break;

}

glPopMatrix();

glutSwapBuffers();

}

void ChangeSize(GLsizei w, GLsizei h)

{

if (h == 0)

{

h = 1;

}

glViewport(0, 0, w, h);

GLfloat aspect = (GLfloat)w/(GLfloat)h;

glMatrixMode(GL_PROJECTION);

glLoadIdentity();

gluOrtho2D(-100.0, 100.0, -100.0, 100.0);

glMatrixMode(GL_MODELVIEW);

glLoadIdentity();

}

void ProcessMenu(int value)

{

iMode = value;

glutPostRedisplay();

}

int main(int args, char *argv[])

{

glutInit(&args, argv);

glutInitWindowSize(800, 600);

glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH);

glutCreateWindow("florida");

glutDisplayFunc(RenderScene);

glutReshapeFunc(ChangeSize);

glutCreateMenu(ProcessMenu);

glutAddMenuEntry("LINE_LOOP", LINE_LOOP);

glutAddMenuEntry("Tess", TESS);

glutAddMenuEntry("Complex", COMPLEX);

glutAttachMenu(GLUT_RIGHT_BUTTON);

SetupRC();

glutMainLoop();

return 0;

}用线环绕的模式:

单轮廓模式:

包含湖的轮廓

java nurbs几何库_OpenGL超级宝典笔记——NURBS与曲面细分相关推荐

  1. java nurbs几何库_NURBS曲线与曲面

    B样条方法在表示与设计自由型曲线曲面形状时显示了强大的威力,然而 在表示与设计初等曲线曲面时时却遇到了麻烦.因为B样条曲线包括其特例的 Bezier曲线都不能精确表示出抛物线外的二次曲线,B样条曲面包 ...

  2. 【转】OpenGL超级宝典笔记——纹理映射Mipmap

    原文地址 http://my.oschina.net/sweetdark/blog/177812 , 感谢作者,若非法转载请联系本人. 目录[-] Mipmapping Mipmap过滤 构建Mip层 ...

  3. OpenGL超级宝典笔记——累积缓冲区与其他颜色操作

    2019独角兽企业重金招聘Python工程师标准>>> 累积缓冲区 OpenGL除了颜色缓冲区.深度缓冲区.模板缓冲区之外,还有累积缓冲区.累积缓冲区允许你把渲染到颜色缓冲区的值,拷 ...

  4. Opengl超级宝典笔记——空间绘图画点

    2019独角兽企业重金招聘Python工程师标准>>> <h2>3D概念</h2> <ol> <li>像素,计算机显示器中的最小元素. ...

  5. OpenGL 超级宝典笔记 —— 纹理高级(一)

    辅助颜色 一般情况下,我们设置纹理的环境为 GL_MODULATE 模式,在这种情况下,受到光照的几何图形会和纹理的颜色进行结合.正常情况下,OpenGL 进行光照计算,并根据标准的光照模型进行单个片 ...

  6. OpenGL 超级宝典笔记 —— 雾

    应用雾 雾是 OpenGL 支持的一种易于使用的特殊效果.在使用雾时,OpenGL 把雾的颜色与完成所有其他颜色计算的几何图元进行混合.雾与几何图元的混合程度取决于几何图元离观察者的距离.雾可以使物体 ...

  7. OpenGL超级宝典笔记——遮挡查询 [转]

    目录[-] 遮挡查询之前 包围体 遮挡查询 在一个场景中,如果有有些物体被其他物体遮住了不可见.那么我们就不需要绘制它.在复杂的场景中,这可以减少大量的顶点和像素的处理,大幅度的提高帧率.遮挡查询就是 ...

  8. OpenGL超级宝典笔记——光照参数与材料属性

    2019独角兽企业重金招聘Python工程师标准>>> <h3>添加光照</h3> <p>glEnable(GL_LIGHTING);</p ...

  9. 超级宝典编程指南(红蓝宝书)-读书笔记

    学习中and持续更新中-- 超级宝典= 客户端:指我们需要执行,存储在CPU中的指令(OpenGLAPI,C和C++代码),客户端会把渲染命令发送给服务器执行. 服务器:接收客户端的指令后调用GPU芯 ...

最新文章

  1. 魅族员工跳槽OPPO后感慨,公司高层各种反思,不会骂员工废材
  2. 苹果连接电脑只能充电_苹果 iPhone 12 曝充电 Bug:多口充电器无一幸免 只能用单独充电头 - 手机 - IT商业网...
  3. 腾讯大佬教我的工作方法(非常有效!)
  4. 添加按钮图标并且当点击或者悬浮上面出现不同效果的代码
  5. Cassandra Dev 1: Cassandra 入门
  6. Anaconda简介:它是什么,以及如何安装
  7. mysql外键约束脚本_使用SQL脚本创建数据库,操作主键、外键与各种约束(MS SQL Server)...
  8. 浅谈C++物理设计:实用宏
  9. php 双向队列,php实现的双向队列类实例
  10. 用于无人驾驶技术的车道线_自动驾驶汽车可用于查找车道的4种技术
  11. 国内主流大数据平台对比
  12. linux md5 大文件慢,【我的Linux,我做主!】浅谈MD5校验文件完整一致性
  13. 电容笔能否替代Apple pencil?高性价比电容笔排行
  14. word标题级别与编号不关联的处理办法
  15. 视频工具箱android,小熊视频工具箱
  16. 拯救行动 OpenJ_Bailian - 4116
  17. 弘兵金融学院 站在山顶 看不见山
  18. java for二重循环_java什么是二重循环
  19. 在公司用手机通过4G网络上网,上网内容可能被公司监控吗?
  20. hadoop的fs shell命令

热门文章

  1. 【高等数学】下册 第十二章 第二节 常数项级数的审敛法
  2. 【题目回顾】广工大2020年10月ACM第一次月赛B题--Dio的面包工坊
  3. mysql怎么集合查询_MySql集合查询
  4. 解决使用ssh工具远程连接到服务器上因为网络波动而需要重连的问题
  5. revo加密_使用Revo Uninstaller完全卸载程序以及更多其他功能
  6. RISC 和CISC
  7. (转)当android调试遇到ADB server didn't ACK以及顽固的sjk_daemon进程 .
  8. python3发新浪微博
  9. Win10截图快捷键教程
  10. C/C++基础题045.PUM