之前我们介绍过简单的把物体压平到投影平面来制造阴影。但这种阴影方式有其局限性(如投影平面须是平面)。在OpenGL1.4引入了一种新的方法阴影贴图来产生阴影。

阴影贴图背后的原理是简单的。我们先把光源的位置当作照相机的位置,我们从这个位置观察物体,我们就知道哪些物体的表面是被照射到(被光源看到) 的,哪些是没有被照射到(被遮挡住)的(在某个方向上离光源最近的表面是被照射的,后面的表面则没有被照射到)。我们开启深度测试,这样我们就可以得到一 个有用的深度缓冲区数据(每一个像素在深度缓冲区中的结果),然后我们从深度缓冲区中读取数据作为一个阴影纹理,投影回场景中,然后我们在使用照相机的视 角,来渲染物体。

光源视角

首先我们把视角移到光源的位置。我们可以通过glu库的辅助函数:

gluLookAt(lightPos[0], lightPos[1], lightPos[2], 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f);

把光源的位置设置为观察的位置。

为了以最佳的方式利用空间来产生阴影贴图。从光源的角度看过去的透视可视区域要适应窗口的比例,且透视的最近平面位置是里光源最近的物体的平面,最 远的平面位置是离光源最远的物体的平面。这样我们就可以充分的利用场景的信息来填充深度缓冲区,来制造阴影贴图。我们估计恰好包好整个场景的视野。

//场景的半径大小
GLfloat sceneBoundingRadius = 95.0f; //光的距离
lightToSceneDistance = sqrt(lightPos[0] * lightPos[0] +
lightPos[1] * lightPos[1] +
lightPos[2] * lightPos[2]); //近裁剪平面
nearPlane = lightToSceneDistance - sceneBoundingRadius;
//让场景充满整个深度纹理
fieldOfView = (GLfloat)m3dRadToDeg(2.0f * atan(sceneBoundingRadius/lightToSceneDistance)); glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(fieldOfView, 1.0f, nearPlane, nearPlane + (2.0f * sceneBoundingRadius));

在上面的代码中,场景的中心位于原点,场景中所有的物体,在以原点为中心,半径为sceneBoundingRadius的圆中。这是我们对场景的粗略估计。大致如下图:

因为我们只需要得到像素经过深度测试后,深度缓冲区的结果。所以我们可以去掉一切不必要的的细节,不往颜色缓冲区中写数据因为不需要显示。

glShadeModel(GL_FLAT);
glDisable(GL_LIGHTING);
glDisable(GL_COLOR_MATERIAL);
glDisable(GL_NORMALIZE);
glColorMask(0,0,0,0);
...

如果我们可以看到深度缓冲区,深度缓冲区的灰度图大概是这样子的。

新型的纹理

我们需要拷贝深度的数据到纹理中作为阴影贴图。在OpenGL1.4之后,glCopyTexImage2D允许我们从深度缓冲区中拷贝数据。纹理 数据多了一种深度纹理的类型,其内部格式包括 GL_DEPTH_COMPONENT16,GL_DEPTH_COMPONENT24,GL_DEPTH_COMPONENT32,数字代表每个纹理单 元包含的位数。一般情况下,我们希望其内部格式与深度缓冲区的精度相匹配。OpenGL允许你指定通用的GL_DEPTH_COMPONENT格式来匹配 你的深度缓冲区。在以光源的视角绘制后,我们把深度缓冲区的数据拷贝出来作为深度纹理:

glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, 0, 0, shadowWidth, shadowHeight, 0);

只有在物体移动或者光源移动时,才需要重新产生深度纹理。如果仅仅是照相机移动,我们并不需要重新产生深度纹理,因为以光源的角度来看,深度纹理没有变化。当窗口的大小改变时,我们也需要产生一个更大的深度纹理。

深度纹理的大小

在OpenGL2.0之前,在不支持非二次幂的纹理(GL_ARB_texture_non_power_of_two)的扩展的情况下,我们需要调整深度纹理的大小,使其恰好为二次幂。例如在1024x768的分辨率下,最大的二次幂纹理大小是1024x512.

void ChangeSize(int w, int h)
{
windowWidth = shadowWidth = w;
windowHeight = shadowHeight  = h;
//不支持非二次幂纹理大小
if(!nptTextureAvailable)
{
int i = 0;
int j = 0;
//获得二次幂的宽度
while((1 << i) <= shadowWidth )
i++; shadowWidth = (1 << (i-1));
//二次幂的高度
while((1 << j) <= shadowHeight )
j++; shadowHeight = (1 << (j-1));
} }

首先绘制阴影

如果阴影被定义为完全没有光照的,那么我们不需要绘制它。例如只有单一的聚光灯作为光源,那让阴影是全黑色的就足以满足我们的要求了。如果我们不希 望阴影是全黑的,而且需要阴影区域中的一些细节,那么我们需要在场景中模拟一些环境光。同时,我们还添加一些散射光,帮助传递形状的信息。

GLfloat lowAmbient[4] = {0.1f, 0.1f, 0.1f, 1.0f};
GLfloat lowDiffuse[4] = {0.35f, 0.35f, 0.35f, 1.0f}; glLightfv(GL_LIGHT0, GL_AMBIENT, lowAmbient);
glLightfv(GL_LIGHT0, GL_DIFFUSE, lowDiffuse); //在场景中绘制物体
DrawModels()

PS:此时我们并不需要交换缓冲区(swapbuffers).

如果显示出来是这样子的。

有些OpenGL实现支持一种GL_ARB_shadow_ambient扩展,它可以使我们不必进行第一遍的阴影绘图。

然后是光照

目前我们有了一个很昏暗的场景,要制造阴影,我需要一个明亮的光照区域,来与阴影区形成对比。如何决定这个接受更强光照的区域是阴影贴图的关键。在这个明亮的区域,我们用两倍于阴影的光照强度进行绘制。

GLfloat ambientLight[] = { 0.2f, 0.2f, 0.2f, 1.0f};
GLfloat diffuseLight[] = { 0.7f, 0.7f, 0.7f, 1.0f};
...
glLightfv(GL_LIGHT0, GL_AMBIENT, ambientLight);
glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuseLight);
 
这样得到的阴影不全是黑色的。

如果去掉前面的绘制阴影的结果是:

投影阴影贴图

我们的目的是需要把阴影贴图投影到场景中(从照相机的位置看)。投影这些代表着光源到被光照射到的第一个物体的距离的深度值。把纹理坐标重定向到正确的坐标空间需要一些数学知识。之前我们解释了把顶点从物体空间变换到视觉空间,再变换到裁剪空间,然后变换到规格化的设备坐标,最后变换到窗口空间的过程。在这里有两组不同的变换矩阵,一组用于变换到照相机的视觉空间,一组用于变换到光源的视觉空间。通过这两组矩阵变换得到两个从不同角度观察的场景。

上面的箭头表示了我们需要应用到视觉线性纹理坐标的变换过程。纹理的投影通常是从视觉线性坐标的产生开始的。这个过程是自动产生纹理坐标的。不同于物体线性纹理坐标的生成,视觉线性坐标的生成并不固定到任何几何图形之上。反之,它好像是一台投影仪把纹理投影到场景中,想象一下你在投影仪前走动的时候,屏幕上会出现不规则的身体形状。

投影纹理映射:

现在我们获得在照相机的视觉空间下顶点对应的纹理坐标。那我们需要进行一些变换来得到顶点的纹理坐标。当前我们在照相机机的视觉空间,首先我们通过视图矩阵的逆变换回到世界坐标系,然后再变换到光源的视觉空间,然后到光源的裁剪空间。这一系列的变换可以通过下面的矩阵相乘得到:

M = Plight * MVlight * MVcamara-1

裁剪空间规格化后的x,y,z的坐标范围在[-1, 1]之间,然而我们的纹理坐标范围为[0,1],所以我们还需要把[-1,1]变换到[0,1]的范围,这个变换很简单,我们只需要把[-1,1]缩放一半(S),然后偏移0.5就可以得到[0,1]了(B)。

M = B * S * Plight * MVlight * MVcamara-1

所以我们可以得到顶点经过变换后的纹理坐标。T1 = M * T;

图解过程如下:

PS: 当前模型视图矩阵的逆矩阵的乘法操作已经包含在了视觉平面方程式中。

即在OpenGL的纹理自动生成模式GL_EYE_LINEAR中,每一个觉平面方程式(eye plane equation)会自动乘以MVcamara-1 

实现上面的步骤一种方式是手动的通过glTranslatef, glScalef, glMultMatrixf 来一步步的实现。另一个方式是在纹理自动生成中,我们可以通过设置一个纹理矩阵来实现上面的变换,把这个纹理矩阵作为视觉线性坐标的视觉平面方程GL_EYE_PLANE即可。

M = B * S * Plight * MVlight 大致代码如下:

M3DMatrix44f tempMatrix;
m3dLoadIdentity44(tempMatrix);
//偏移0.5
m3dTranslateMatrix44(tempMatrix, 0.5f, 0.5f, 0.5f);
//缩放0.5
m3dScaleMatrix44(tempMatrix, 0.5f, 0.5f, 0.5f);
//乘以光源投影矩阵
m3dMatrixMultiply44(textureMatrix, tempMatrix, lightProjection);
//乘以光源视图矩阵
m3dMatrixMultiply44(tempMatrix, textureMatrix, lightModelView);
//矩阵转置,获得平面方程的s,t,r和q行
m3dTransposeMatrix44(textureMatrix, tempMatrix);

应用到视觉平面中:

//因为在当前模型视图矩阵的逆矩阵的乘法操作已经包含在了视觉平面方程式中
//确保在glTexGenfv前已经设置好照相机的模型视图矩阵。
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
gluLookAt(cameraPos[0], cameraPos[1], cameraPos[2],
0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f);
... //为阴影贴图的投影设置视觉平面
glEnable(GL_TEXTURE_GEN_S);
glEnable(GL_TEXTURE_GEN_T);
glEnable(GL_TEXTURE_GEN_R);
glEnable(GL_TEXTURE_GEN_Q);
glTexGenfv(GL_S, GL_EYE_PLANE, &textureMatrix[0]);
glTexGenfv(GL_T, GL_EYE_PLANE, &textureMatrix[4]);
glTexGenfv(GL_R, GL_EYE_PLANE, &textureMatrix[8]);
glTexGenfv(GL_Q, GL_EYE_PLANE, &textureMatrix[12]);
...
glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
glTexGeni(GL_R, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
glTexGeni(GL_Q, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);

阴影比较

现在我们如何知道从照相机视角看到的点是否在阴影中呢。从上面的那些步骤来看,我们已知顶点的深度纹理坐标,那么这个深度纹理坐标对应的在深度纹理的值我们可以知道即texture[s/q, t/q],这个深度纹理记录了在光的角度看过去离光源最近的点的深度值,我们是设置的深度比较函数是glDepthFunc(GL_LEQUAL);。,同时我们知道(r/q)是顶点在真实光源中深度值,已经通过缩放和偏移变换到了[0,1]的范围。然后我们比较texture[s/q, t/q]和(r/q)如果texture[s/q, t/q] < r/q那么就表示这个点在阴影中。如下图:

深度纹理只包含了一个值代表深度。但在纹理环境的纹理查询中,我们需要返回四个成分的值(RGBA)。OpenGL提供了几种方式把这单个深度值扩展到其他的通道中,其中包含GL_ALPHA(0,0,0,D),GL_LUMINANCE(D,D,D,1)和GL_INTENSITY(D,D,D,D)。在这里我们把深度值扩展到所有的深度通道。

glTexParameteri(GL_TEXTURE_2D, GL_DEPTH_TEXTURE_MODE, GL_INSTENSITY);

在OpenGL中开启阴影比较,来产生阴影效果。我们把深度值与纹理坐标的R成分进行比较。

//设置阴影比较

glEnable(GL_TEXTURE_2D);

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_R_TO_TEXTURE);

效果:

书中部分的代码示例:

// Called to regenerate the shadow map
void RegenerateShadowMap(void)
{
  GLfloat lightToSceneDistance, nearPlane, fieldOfView;
  GLfloat lightModelview[16], lightProjection[16];
  GLfloat sceneBoundingRadius = 95.0f; // based on objects in scene   // Save the depth precision for where it's useful
  lightToSceneDistance = sqrt(lightPos[0] * lightPos[0] + 
    lightPos[1] * lightPos[1] + 
    lightPos[2] * lightPos[2]);
  nearPlane = lightToSceneDistance - sceneBoundingRadius;
  // Keep the scene filling the depth texture
  fieldOfView = (GLfloat)m3dRadToDeg(2.0f * atan(sceneBoundingRadius / lightToSceneDistance));   glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  gluPerspective(fieldOfView, 1.0f, nearPlane, nearPlane + (2.0f * sceneBoundingRadius));
  glGetFloatv(GL_PROJECTION_MATRIX, lightProjection);
  // Switch to light's point of view
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
  gluLookAt(lightPos[0], lightPos[1], lightPos[2], 
    0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f);
  glGetFloatv(GL_MODELVIEW_MATRIX, lightModelview);
  glViewport(0, 0, shadowWidth, shadowHeight);   // Clear the depth buffer only
  glClear(GL_DEPTH_BUFFER_BIT);   // All we care about here is resulting depth values
  glShadeModel(GL_FLAT);
  glDisable(GL_LIGHTING);
  glDisable(GL_COLOR_MATERIAL);
  glDisable(GL_NORMALIZE);
  glColorMask(0, 0, 0, 0);   // Overcome imprecision
  glEnable(GL_POLYGON_OFFSET_FILL);   // Draw objects in the scene except base plane
  // which never shadows anything
  DrawModels(GL_FALSE);   // Copy depth values into depth texture
  glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, 
    0, 0, shadowWidth, shadowHeight, 0);   // Restore normal drawing state
  glShadeModel(GL_SMOOTH);
  glEnable(GL_LIGHTING);
  glEnable(GL_COLOR_MATERIAL);
  glEnable(GL_NORMALIZE);
  glColorMask(1, 1, 1, 1);
  glDisable(GL_POLYGON_OFFSET_FILL);   // Set up texture matrix for shadow map projection,
  // which will be rolled into the eye linear
  // texture coordinate generation plane equations
  M3DMatrix44f tempMatrix;
  m3dLoadIdentity44(tempMatrix);
  m3dTranslateMatrix44(tempMatrix, 0.5f, 0.5f, 0.5f);
  m3dScaleMatrix44(tempMatrix, 0.5f, 0.5f, 0.5f);
  m3dMatrixMultiply44(textureMatrix, tempMatrix, lightProjection);
  m3dMatrixMultiply44(tempMatrix, textureMatrix, lightModelview);
  // transpose to get the s, t, r, and q rows for plane equations
  m3dTransposeMatrix44(textureMatrix, tempMatrix);
} // Called to draw scene
void RenderScene(void)
{
  // Track camera angle
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  if (windowWidth > windowHeight)
  {
    GLdouble ar = (GLdouble)windowWidth / (GLdouble)windowHeight;
    glFrustum(-ar * cameraZoom, ar * cameraZoom, -cameraZoom, cameraZoom, 1.0, 1000.0);
  }
  else
  {
    GLdouble ar = (GLdouble)windowHeight / (GLdouble)windowWidth;
    glFrustum(-cameraZoom, cameraZoom, -ar * cameraZoom, ar * cameraZoom, 1.0, 1000.0);
  }   glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
  gluLookAt(cameraPos[0], cameraPos[1], cameraPos[2], 
    0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f);   glViewport(0, 0, windowWidth, windowHeight);   // Track light position
  glLightfv(GL_LIGHT0, GL_POSITION, lightPos);   // Clear the window with current clearing color
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);   if (showShadowMap)
  {
    // Display shadow map for educational purposes
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glMatrixMode(GL_TEXTURE);
    glPushMatrix();
    glLoadIdentity();
    glEnable(GL_TEXTURE_2D);
    glDisable(GL_LIGHTING);
    glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_NONE);
    // Show the shadowMap at its actual size relative to window
    glBegin(GL_QUADS);
    glTexCoord2f(0.0f, 0.0f);
    glVertex2f(-1.0f, -1.0f);
    glTexCoord2f(1.0f, 0.0f);
    glVertex2f(((GLfloat)shadowWidth/(GLfloat)windowWidth)*2.0f-1.0f, 
      -1.0f);
    glTexCoord2f(1.0f, 1.0f);
    glVertex2f(((GLfloat)shadowWidth/(GLfloat)windowWidth)*2.0f-1.0f, 
      ((GLfloat)shadowHeight/(GLfloat)windowHeight)*2.0f-1.0f);
    glTexCoord2f(0.0f, 1.0f);
    glVertex2f(-1.0f, 
      ((GLfloat)shadowHeight/(GLfloat)windowHeight)*2.0f-1.0f);
    glEnd();
    glDisable(GL_TEXTURE_2D);
    glEnable(GL_LIGHTING);
    glPopMatrix();
    glMatrixMode(GL_PROJECTION);
    gluPerspective(45.0f, 1.0f, 1.0f, 1000.0f);
    glMatrixMode(GL_MODELVIEW);
  }
  else if (noShadows)
  {
    // Set up some simple lighting
    glLightfv(GL_LIGHT0, GL_AMBIENT, ambientLight);
    glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuseLight);     // Draw objects in the scene including base plane
    DrawModels(GL_TRUE);
  }
  else
  {
    if (!ambientShadowAvailable)
    {
      GLfloat lowAmbient[4] = {0.1f, 0.1f, 0.1f, 1.0f};
      GLfloat lowDiffuse[4] = {0.35f, 0.35f, 0.35f, 1.0f};       // Because there is no support for an "ambient"
      // shadow compare fail value, we'll have to
      // draw an ambient pass first...
      glLightfv(GL_LIGHT0, GL_AMBIENT, lowAmbient);
      glLightfv(GL_LIGHT0, GL_DIFFUSE, lowDiffuse);       // Draw objects in the scene, including base plane
      DrawModels(GL_TRUE);       // Enable alpha test so that shadowed fragments are discarded
      glAlphaFunc(GL_GREATER, 0.9f);
      glEnable(GL_ALPHA_TEST);
    }     glLightfv(GL_LIGHT0, GL_AMBIENT, ambientLight);
    glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuseLight);     // Set up shadow comparison
    glEnable(GL_TEXTURE_2D);
    glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, 
      GL_COMPARE_R_TO_TEXTURE);     // Set up the eye plane for projecting the shadow map on the scene
    glEnable(GL_TEXTURE_GEN_S);
    glEnable(GL_TEXTURE_GEN_T);
    glEnable(GL_TEXTURE_GEN_R);
    glEnable(GL_TEXTURE_GEN_Q);
    glTexGenfv(GL_S, GL_EYE_PLANE, &textureMatrix[0]);
    glTexGenfv(GL_T, GL_EYE_PLANE, &textureMatrix[4]);
    glTexGenfv(GL_R, GL_EYE_PLANE, &textureMatrix[8]);
    glTexGenfv(GL_Q, GL_EYE_PLANE, &textureMatrix[12]);     // Draw objects in the scene, including base plane
    DrawModels(GL_TRUE);     glDisable(GL_ALPHA_TEST);
    glDisable(GL_TEXTURE_2D);
    glDisable(GL_TEXTURE_GEN_S);
    glDisable(GL_TEXTURE_GEN_T);
    glDisable(GL_TEXTURE_GEN_R);
    glDisable(GL_TEXTURE_GEN_Q);
  }   if (glGetError() != GL_NO_ERROR)
    fprintf(stderr, "GL Error!\n");   // Flush drawing commands
  glutSwapBuffers();
}

完整代码地址https://github.com/sweetdark/openglex/tree/master/shadowmap

表述能力有限。如果错误,请指正不胜感激。详细的请参考下面的链接。

投影映射纹理GL_EYE_LINEAR的参考:

http://blog.csdn.net/liu_lin_xm/article/details/4850526

http://blog.csdn.net/xukunn1226/article/details/775644

英文http://www.nvidia.com/object/Projective_Texture_Mapping.html

阴影贴图的参考:

http://www.eng.utah.edu/~cs5610/lectures/ShadowMapping%20OpenGL%202009.pdf

ftp://download.nvidia.com/developer/presentations/2004/GPU_Jackpot/Shadow_Mapping.pdf

OpenGL超级宝典笔记——深度纹理和阴影 【转】的更多相关文章

  1. OpenGL超级宝典笔记----框架搭建

    自从工作后,总是或多或少的会接触到客户端3d图形渲染,正好自己对于3d图形的渲染也很感兴趣,所以最近打算从学习OpenGL的图形API出发,进而了解3d图形的渲染技术.到网上查了一些资料,OpenGL ...

  2. 【转载】OpenGL超级宝典笔记——GLSL语言基础

    变量 GLSL的变量命名方式与C语言类似.变量的名称可以使用字母,数字以及下划线,但变量名不能以数字开头,还有变量名不能以gl_作为前缀,这个是GLSL保留的前缀,用于GLSL的内部变量.当然还有一些 ...

  3. OpenGL超级宝典笔记----渲染管线

    在OpenGL中任何事物都在3D空间中,但是屏幕和窗口是一个2D像素阵列,所以OpenGL的大部分工作都是关于如何把3D坐标转变为适应你屏幕的2D像素.3D坐标转为2D坐标的处理过程是由OpenGL的 ...

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

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

  5. OpenGL超级宝典笔记——画三角形(转)

    http://my.oschina.net/sweetdark/blog/161002 学习了画线的知识,我们可以使用GL_LINE_LOOP来画闭合的多边形.但是使用这种方式画出来的只有线框,多边形 ...

  6. OpenGL超级宝典笔记——贝塞尔曲线和曲面(转)

    http://my.oschina.net/sweetdark/blog/183721 参数方程表现形式 在中学的时候,我们都学习过直线的参数方程:y = kx + b;其中k表示斜率,b表示截距(即 ...

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

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

  8. win8+VS2012搭建OpenGL超级宝典的环境

    版权声明:本文为博主原创文章,未经博主同意不得转载. https://blog.csdn.net/booirror/article/details/36957799 自从公司搬到腾讯附近,每天上班都迟 ...

  9. OpenGL超级宝典visual studio 2013开发环境配置,GLTools

    做三维重建需要用到OpenGL,开始看<OpenGL超级宝典>,新手第一步配置环境就折腾了一天,记录下环境的配置过程. <超级宝典>中的例子使用了GLEW,freeglut以及 ...

随机推荐

  1. socket中send和recv函数

    Socket一次Recv接受的字节有限制么? 从套接字接收数据. 返回值是表示接收数据的字符串. 一次接收的最大数据量由bufsize指定.它默认为零. 注意为了最好地匹配硬件和网络现实,bufsiz ...

  2. 《小团团团队》第九次团队作业:Beta冲刺与验收准备

    项目 内容 这个作业属于哪个课程 任课教师博客主页链接 这个作业的要求在哪里 实验十三 团队作业9:Beta冲刺与团队项目验收 团队名称 小团团团队 作业学习目标 (1)掌握软件黑盒测试技术:(2)学 ...

  3. 关于MySQL建表对DML的影响【转】

    本文来自这里 今天一位同学问到线上曾经碰到过连续建表,导致阻塞普通的insert.update等.不过也没有保留现场.因此有疑问为什么建表会影响DML? 分析          首先这个现象不是在所有 ...

  4. GCC内嵌汇编一些限制字符串

    /******************/ “b”将输入变量放入ebx “c”将输入变量放入ecx “d”将输入变量放入edx “s”将输入变量放入esi “d”将输入变量放入edi “q”将输入变量放 ...

  5. LiveScript 流程控制、循环以及列表推导式

    The LiveScript Book     The LiveScript Book Generators and Yield 你可以在你的 LiveScript 代码中使用 Ecmascript ...

  6. linux下 export只能设定临时变量

    今天在调用ABBYY API的时候,需要传递APPID和APPPASSWD给系统环境才能够执行相应的python调用代码. 设置之后,因为写代码自己关掉了terminal,后面直接运行报错,访问权限不 ...

  7. 网络编程之IO复用:select or epoll

    对于服务器的并发处理能力,我们需要的是:每一毫秒服务器都能及时处理这一毫秒内收到的数百个不同TCP连接上的报文,与此同时,可能服务器上还有数以十万计的最近几秒没有收发任何报文的相对不活跃连接.同时处理 ...

  8. C++单例模式实例

    定义:在某些情况下,我们设计中的对象只需要一个,比方说:线程池(threadpool).缓存(cache).对话框.处理偏好设置和注册表对象.日志对象.充当打印机.显卡等设备的驱动程序的对象等.事实上 ...

  9. jenkins发送html测试报告

    jenkins发送html测试报告  https://blog.csdn.net/galen2016/article/details/77975965/ <!DOCTYPE html> & ...

  10. django获取前端有multiple属性的select的多选项

    author_list = request.POST.getlist('author_list') ###