纹理矩阵堆栈/深度纹理和阴影的绘制

纹理矩阵堆栈

纹理矩阵堆栈和模型视图矩阵堆栈,投影矩阵堆栈一样是普通的矩阵堆栈,只不过作用于纹理坐标。默认情况下纹理矩阵是单位矩阵,因此显式指定或自动生成的纹理坐标并不会发生变化。但是启用纹理矩阵后,这些纹理坐标就会进行纹理矩阵变换,实现纹理沿着表面滑动,绕表面旋转,收缩放大镜像等效果或者这些表现的组合效果。
纹理矩阵启用为:
glMatrixMode(GL_TEXTURE);
// OGL矩阵变换函数
glMatrixMode(GL_MODELVIEW):
纹理s,t,r,q;q坐标的含义类似顶点的w坐标,纹理变换后需要除以q(除非q为0)得到最后使用的纹理坐标。当将灯光立体效果或透视方式将物体映射到表面时候,就需要进行透视变换需要设置q坐标的值。

深度纹理

深度纹理是在纹理单元存储的是物体顶点的深度值是光栅化后的深度缓存区中得到的深度值,OGL中是[-1,1], DX中是[0,1]。
深度纹理和普通纹理一样,只不过是先创建一个空的来自深度的纹理,然后绘制场景,再从深度缓存中读取到深度纹理中。
glEnable( GL_TEXTURE_2D )
glTexImage2D( GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT,
SHADOW_MAP_WIDTH, SHADOW_MAP_HEIGHT, 0,
GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, NULL );
drawObjects( GL_TRUE );
glCopyTexImage2D( GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, 0, 0,
SHADOW_MAP_WIDTH, SHADOW_MAP_HEIGHT, 0 );


用深度纹理实现阴影的绘制

基本原理:

深度纹理,是捕获深度缓存中的值作为一张纹理;深度纹理实现阴影的原理是:把观察点设置到光源的位置,调整视口大小和深度纹理大小一致,然后绘制物体,启用深度缓存禁止颜色缓存写入,这样从光源观察看到的物体都是光所照亮的,写入的深度缓存也是有效的,将深度缓存中的值通过glCopyTexImage2D函数得到。设置相同的投影和视图变换,用纹理堆栈自动计算纹理贴图的(s,t,r,q)坐标,r值缩放到[0,1]内(物体上的纹理深度值写入在物体纹理坐标中,这样片段着色器就可以处理了)。再设置需要的视口和投影变换,正常绘制物体,在使用深度纹理的地方使用glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_R_TO_TEXTURE );GL_COMPARE_R_TO_TEXTURE指示使片断的r坐标和深度纹理单元值进行比较,小于等于用亮度1,大于用亮度0也就是没有光照的地方用亮度0黑色来绘制。这个阴影方法,场景大需要较多的计算,且有自身阴影和锯齿效果,且在阴影和非阴影之间也会急剧过渡
效果,应该采用物体在特定光源(平行光正交投影,聚光灯点光源透视投影)投影到指定平面上的方法,也就是使用阴影矩阵来绘制物体的阴影。

在视口变换期间会将NDC坐标中的z坐标存入深度缓存中,OGL中在设备坐标[-1,1]中的z值也要经过glDepthRange(near,far),near默认是0,far默认是1,将其变换到[0,1]放入深度缓存中,用于后面的深度检测剔除背面。 因此深度纹理中的坐标值是[0,1],而物体的s,t,r中的r值为[-1,1],因此需要将物体的[-1,1]的值转换到[0,1]中 。深度纹理实现阴影会有自身阴影问题(效果不明显还好),距离观察点远的物体有锯齿问题(高分辨率可以解决),GL_MODULATE模式导致阴影和非阴影区域急剧变化。

深度纹理和阴影矩阵

用深度纹理可以绘制阴影。
用阴影矩阵(平行投影,透视投影物体)也可以绘制物体的阴影,实际是相对于光源的透视投影或正交投影。

绘制深度纹理阴影的纹理状态设置

 glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );
    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
// 纹理比较函数是小于等于为GL_TRUE
    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL );
// GL_LUMINANCE 按照亮度值存储纹理单元;fragment时候取输入片断亮度值作为输出片断颜色。
    glTexParameteri( GL_TEXTURE_2D, GL_DEPTH_TEXTURE_MODE, GL_LUMINANCE );
// GL_COMPARE_R_TO_TEXTURE指示使片断的r坐标和纹理单元值进行比较,小于等于用亮度1,大于用亮度0
    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_R_TO_TEXTURE );

绘制深度纹理阴影感悟

理论上的:
1)OGL深度缓存中的值也是[0,1]的。
2)纹理坐标其实是渲染的物体身上的,纹理中并不需要纹理坐标数据,只有值即可。
计算纹理坐标时候的:
3)计算纹理坐标匹配时候,r,s,r,q都铺满整个屏幕即可,全部相对于观察各平面的距离,不需要按照之前的渲染来缩放纹理坐标,得到相对整个屏幕大小的纹理的坐标系。
4) 纹理坐标的自动计算,应该在视图坐标系后使用纹理矩阵堆栈对其进行平移->旋转->缩放,透视投影的设置顺序,来在OGL代码内部进行右乘运算,才得到正确的纹理坐标值。下面代码看得迷糊且无法运行,根据自己当前水平的分析下面计算应该是有问题的。或者是自己研究得不够透彻?

代码实例

不能运行,感觉有错误,仅有参考作用:

#include 
#include 
#include 
#include 
#pragma  comment(lib, "glew32d.lib")
#include "helpers.h"

#ifdef GL_ARB_shadow
#define GL_TEXTURE_COMPARE_MODE      GL_TEXTURE_COMPARE_MODE_ARB
#define GL_TEXTURE_COMPARE_FUNC      GL_TEXTURE_COMPARE_FUNC_ARB
#define GL_DEPTH_TEXTURE_MODE        GL_DEPTH_TEXTURE_MODE_ARB
#define GL_COMPARE_R_TO_TEXTURE      GL_COMPARE_R_TO_TEXTURE_ARB
#endif

#define SHADOW_MAP_WIDTH      256
#define SHADOW_MAP_HEIGHT     256

#define PI       3.14159265359

GLdouble    fovy      = 60.0;
GLdouble    nearPlane = 10.0;
GLdouble    farPlane  = 100.0;

GLfloat     angle = 0.0;
GLfloat     torusAngle = 0.0;

GLfloat     lightPos[] = { 25.0, 25.0, 25.0, 1.0 };
GLfloat     lookat[] = { 0.0, 0.0, 0.0 };
GLfloat     up[] = { 0.0, 0.0, 1.0 };

GLboolean showShadow = GL_FALSE;

void
init( void )
{
    GLfloat  white[] = { 1.0, 1.0, 1.0, 1.0 };

    glTexImage2D( GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, 
  		  SHADOW_MAP_WIDTH, SHADOW_MAP_HEIGHT, 0,
		  GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, NULL );

    glLightfv( GL_LIGHT0, GL_POSITION, lightPos );
    glLightfv( GL_LIGHT0, GL_SPECULAR, white );
    glLightfv( GL_LIGHT0, GL_DIFFUSE, white );

    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );
    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
	// 纹理比较函数是小于等于为GL_TRUE
    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL );
	// GL_LUMINANCE 按照亮度值存储纹理单元;fragment时候取输入片断亮度值作为输出片断颜色。
    glTexParameteri( GL_TEXTURE_2D, GL_DEPTH_TEXTURE_MODE, GL_LUMINANCE );
	// 使片断的r坐标和纹理单元值进行比较,小于等于用亮度1,大于用亮度0
    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_R_TO_TEXTURE );

	// 纹理坐标生成时线性的
    glTexGeni( GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR );
    glTexGeni( GL_T, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR );
    glTexGeni( GL_R, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR );
    glTexGeni( GL_Q, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR );

    glColorMaterial( GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE );

    glCullFace( GL_BACK );

    glEnable( GL_DEPTH_TEST );
    glEnable( GL_LIGHT0 );
    glEnable( GL_LIGHTING );
    glEnable( GL_TEXTURE_2D );
    glEnable( GL_TEXTURE_GEN_S );
    glEnable( GL_TEXTURE_GEN_T );
    glEnable( GL_TEXTURE_GEN_R );
    glEnable( GL_TEXTURE_GEN_Q );
    glEnable( GL_COLOR_MATERIAL );
    glEnable( GL_CULL_FACE );
}

void
reshape( int width, int height )
{
    glViewport( 0, 0, width, height );

    glMatrixMode( GL_PROJECTION );
    glLoadIdentity();
    gluPerspective( fovy, (GLdouble) width/height, nearPlane, farPlane );
    glMatrixMode( GL_MODELVIEW );
}

void
idle( void )
{
    angle += PI / 10000;
    torusAngle += .1;
    glutPostRedisplay();
}


void
keyboard( unsigned char key, int x, int y )
{
    switch( key ) {
    case 27:  /* Escape */
      exit( 0 );
      break;

    case 't': {
        static GLboolean textureOn = GL_TRUE;
        textureOn = !textureOn;
        if ( textureOn )
	glEnable( GL_TEXTURE_2D );
        else
	glDisable( GL_TEXTURE_2D );
      }
      break;
      
    case 'm': {
        static GLboolean compareMode = GL_TRUE;
        compareMode = !compareMode;
        printf( "Compare mode %s\n", compareMode ? "On" : "Off" );
        glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE,
		         compareMode ? GL_COMPARE_R_TO_TEXTURE : GL_NONE );
      }
      break;

    case 'f': {
        static GLboolean funcMode = GL_TRUE;
        funcMode = !funcMode;
        printf( "Operator %s\n", funcMode ? "GL_LEQUAL" : "GL_GEQUAL" );
        glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC,
		         funcMode ? GL_LEQUAL : GL_GEQUAL );
      }
      break;

    case 's':
      showShadow = !showShadow;
      break;

    case 'p': {
        static GLboolean  animate = GL_TRUE;
        animate = !animate;
        glutIdleFunc( animate ? idle : NULL );
      }
      break;
    }

    glutPostRedisplay();
}


void
transposeMatrix( GLfloat m[16] )
{
    GLfloat  tmp;
#define Swap( a, b )    tmp = a; a = b; b = tmp
    Swap( m[1],  m[4]  );
    Swap( m[2],  m[8]  );
    Swap( m[3],  m[12] );
    Swap( m[6],  m[9]  );
    Swap( m[7],  m[13] );
    Swap( m[11], m[14] );
#undef Swap
}

void
drawObjects( GLboolean shadowRender )
{
    GLboolean textureOn = glIsEnabled( GL_TEXTURE_2D );

    if ( shadowRender )
        glDisable( GL_TEXTURE_2D );

    if ( !shadowRender ) {
        glNormal3f( 0, 0, 1 );
        glColor3f( 1, 1, 1 );
        glRectf( -20.0, -20.0, 20.0, 20.0 );
    }
    
    glPushMatrix();
    glTranslatef( 11, 11, 11 );
    glRotatef( 54.73, -5, 5, 0 );
    glRotatef( torusAngle, 1, 0, 0 );
    glColor3f( 1, 0, 0 );
    glutSolidTorus( 1, 4, 8, 36 );
    glPopMatrix();

    glPushMatrix();
    glTranslatef( 2, 2, 2 );
    glColor3f( 0, 0, 1 );
    glutSolidCube( 4 );
    glPopMatrix();

    glPushMatrix();
    glTranslatef( lightPos[0], lightPos[1], lightPos[2] );
    glColor3f( 1, 1, 1 );
    glutWireSphere( 0.5, 6, 6 );
    glPopMatrix();

    if ( shadowRender && textureOn ) 
        glEnable( GL_TEXTURE_2D );
}


void
generateShadowMap( void )
{
    GLint    viewport[4];
    GLfloat  lightPos[4];

    glGetLightfv( GL_LIGHT0, GL_POSITION, lightPos );
    glGetIntegerv( GL_VIEWPORT, viewport );
	// 调整视口大小,使得和设置生成的纹理大小匹配;也是第二次渲染时候受到影响的片段着色区域
    glViewport( 0, 0, SHADOW_MAP_WIDTH, SHADOW_MAP_HEIGHT );

    glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );

    glMatrixMode( GL_PROJECTION );
    glPushMatrix();
    glLoadIdentity();
	// 透视投影设置,fovy是80相比标准的90偏小了,也就是将80区域的大小映射到整个屏幕其实是将物体放大了。
	// aspect=1,屏幕的宽高比等于1,符合设置的深度纹理大小。zear距离是10,zfar距离是1000.
	// 这样会求得一个透视投影矩阵,会将物体坐标缩放到[-1,1]区域包括OGL的z坐标。
	// 但是:glDepthRange(near,far),near默认是0,far默认是1,将其变换到[0,1]放入深度缓存中。
    gluPerspective( 80.0, 1.0, 10.0, 1000.0 );
    glMatrixMode( GL_MODELVIEW );

    glPushMatrix();
    glLoadIdentity();
	// 模型视图矩阵,设置观察点在灯光处,朝向世界坐标原点,up指向屏幕外
    gluLookAt( lightPos[0], lightPos[1], lightPos[2],
	       lookat[0], lookat[1], lookat[2],
	       up[0], up[1], up[2] );
	// 世界坐标中绘制物体
    drawObjects( GL_TRUE );

	//恢复模型视图矩阵
    glPopMatrix();
	// 恢复投影矩阵
    glMatrixMode( GL_PROJECTION );
    glPopMatrix();
	// 切换到模型视图坐标系
    glMatrixMode( GL_MODELVIEW );

	// 将深度纹理拷贝到了当前纹理内存活动的纹理对象中
    glCopyTexImage2D( GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, 0, 0,
		      SHADOW_MAP_WIDTH, SHADOW_MAP_HEIGHT, 0 );
	// 恢复视口大小
    glViewport( viewport[0], viewport[1], viewport[2], viewport[3] );

	// 将得到的纹理图像绘制出来,只是观察使用的,绘制歧视位置从中下角开始,因为屏幕大小是512x512
    if ( showShadow ) {
      GLfloat depthImage[SHADOW_MAP_WIDTH][SHADOW_MAP_HEIGHT];
      glReadPixels( 0, 0, SHADOW_MAP_WIDTH, SHADOW_MAP_HEIGHT, 
		    GL_DEPTH_COMPONENT, GL_FLOAT, depthImage );
      glWindowPos2f( viewport[2]/2, 0 );

      glDrawPixels( SHADOW_MAP_WIDTH, SHADOW_MAP_HEIGHT, GL_LUMINANCE, 
		    GL_FLOAT, depthImage );
      glutSwapBuffers();
    }
}

void
generateTextureMatrix( void )
{
    GLfloat  tmpMatrix[16];

    /*
     *  Set up projective texture matrix.  We use the GL_MODELVIEW matrix
     *    stack and OpenGL matrix commands to make the matrix.
     */
	// 自动生成物体的纹理坐标,
	// 且将物体在视图坐标系下的z在[-1,1]转换到深度纹理单元值[0,1]之间,用于比较。
    glPushMatrix();
    glLoadIdentity();

	// 连续变换从下面到上,且先平移。且设置纹理矩阵变换,来执行这样的平移旋转缩放+透视投影变换?
	// 模型坐标系下,x,y平移0.5;将所有物体都向右向上偏移0.5。
    glTranslatef( 0.5, 0.5, 0.0 );// 这里个人觉得是: glTranslatef( 1, 1, 1);
	// 模型坐标系下,将所有物体x,y都缩小为原来的1/2; z不变。
    glScalef( 0.5, 0.5, 1.0 ); // 这里个人觉得是: glScalef( 0.5, 0.5, 0.5);除非还有什么问题?
    gluPerspective( 60.0, 1.0, 1.0, 1000.0 );
	
	// 还是从光源位置观察所有物体
    gluLookAt( lightPos[0], lightPos[1], lightPos[2],
	       lookat[0], lookat[1], lookat[2],
	       up[0], up[1], up[2] ); // 这里只是简单的坐标系转换
    glGetFloatv( GL_MODELVIEW_MATRIX, tmpMatrix );
    glPopMatrix();

	// 转置模型视图矩阵,也就是矩阵的逆,用于变换到视口坐标系用
    transposeMatrix( tmpMatrix );

	// 生成的纹理坐标是物体该维度的顶点到该维度的平面的距离 = p1x0 + p2y0 + p3z0 + p4w0
	// p向量是平面系数; x0, y0, z0, w0是物体的顶点坐标;这里的tmpMatrix刚好是视图坐标系的基向量。
	// 自动计算物体的深度坐标系s,t,r,q;s' = s / q; t'= t / q, r = r'/q;
	// s,t是光栅化的位置,在视图坐标系中的距离,还需要进一步乘以纹理矩阵堆栈计算的就是透视和缩放。
    glTexGenfv( GL_S, GL_OBJECT_PLANE, &tmpMatrix[0] );
    glTexGenfv( GL_T, GL_OBJECT_PLANE, &tmpMatrix[4] );
	// r刚好是物体到观察点的距离,在视图坐标系中的值。
    glTexGenfv( GL_R, GL_OBJECT_PLANE, &tmpMatrix[8] );
    glTexGenfv( GL_Q, GL_OBJECT_PLANE, &tmpMatrix[12] );
}

void
display( void )
{
    GLfloat  radius = 30;

	// 第一次绘制生成了深度纹理和纹理坐标
    generateShadowMap();
    generateTextureMatrix();

    if ( showShadow )
      return;

    glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
    // 第二次绘制物体,使用深度纹理和深度纹理设置的纹理采样函数
    glPushMatrix();
	// 从各个位置观察物体
    gluLookAt( radius*cos(angle), radius*sin(angle), 30,
	       lookat[0], lookat[1], lookat[2], 
	       up[0], up[1], up[2] );
    drawObjects( GL_FALSE );
    glPopMatrix();

    glutSwapBuffers();
}

int
main( int argc, char** argv )
{
    glutInit( &argc, argv );
    glutInitDisplayMode( GLUT_RGBA | GLUT_DEPTH | GLUT_DOUBLE );
    glutInitWindowSize( 512, 512 );
    glutInitWindowPosition( 100, 100 );
    glutCreateWindow( argv[0] );

    init();

    glutDisplayFunc( display );
    glutReshapeFunc( reshape );
    glutKeyboardFunc( keyboard );
    glutIdleFunc( idle );

    glutMainLoop();

    return 0;
}

阴影顶点信息的绘制

上面描述的是用深度纹理来绘制阴影,也就是得到阴影的纹理贴图。

但是阴影的顶点信息,需要考虑光源,投射平面;光源类型是平行光则是正交投影,光源是点光源或聚光灯则是透视投影。

顶点信息,通过对需要绘制阴影的物体,进行阴影矩阵变换,来绘制物体的阴影顶点信息。

在DX中可以用:D3DMatrixShadow来获取,需要传入光源类型方向,投影到的平面定义,传出阴影矩阵。在OGL中应该也有相应的矩阵。

绘制了阴影的顶点信息时,可以传入阴影纹理贴图或传入颜色就会得到想要的阴影;更加简单的阴影就是圆底贴图,一般性能承受不起时候采用。


绘制深度缓存的图像

静态的复杂背景的绘制,从已有图像绘制比将复杂背景物体重新绘制要高效。从后台缓存中拷贝得到背景色的贴图和保存背景色的深度缓存。绘制方法是,先绘制前景色,开启深度测试和写入深度缓存,不写入模板缓存,这个时候深度缓存是前景色。开启深度测试和写入深度缓存,同时开启模板测试和写入模板缓存,禁止颜色缓存写入,用glDrawPixels将背景的深度值写入深度缓存,这个时候写入了模板缓存得到模板缓存值。最后绘制背景图像,根据模板缓存写入,得到结果。

动态的图像,需要glPixelTransfer来实现,但是动态背景还是不用深度缓存绘制为好, 一般绘制动的物体,背景用地图来实现。

你可能感兴趣的:(OpenGL图形学)