频道栏目
首页 > 资讯 > 其他 > 正文

[OpenGL] Simple Bump mapping(简单凹凸纹理映射)

16-08-22        来源:[db:作者]  
收藏   我要投稿

注:这份代码年代比较久远了,直接跑会提示各种未定义标识符,经过查阅发现这些都是glew中的。如果想要通过编译,首先需要配置并include glew.h。我用网上下的glew.h库编译这个代码,发现总是链接错误,所以后来我从原作者的shadow mapping中拷贝了一份glee.h通过了编译(这是一个简化版的glew库),最终通过了编译。此外还会有一个映像的bug,只要在网上搜一下就能解决。

在过去的几周,我看到许多人在论坛里讨论在应用程序中使用凹凸纹理的帖子。尽管如此,并没有简单的教程来介绍这个效果是如何实现的。所以我就尝试来解释一下这一技术。

这是我第一次写教程,欢迎你随时告诉我你的想法。

我们将会使用一些OpenGL扩展来完成这一功能,它们并不仅仅适用于特定的供应商,而是能在任何NVidia Geforce、ATI Radeon以及其它系列卡上工作。如果你之前没有使用过OpengGL扩展,我将会解释有哪些必要的设置。不过,这篇教程的主要面向对象是已经掌握多重纹理,最好是立方体贴图相关的知识的人。

我将会使用glut来管理窗口,以保证这个教程足够的简单。

以下是我们将完成的。左边的图片显示了使用凹凸纹理的圆环,右边的图片显示了使用标准OpenGL顶点光照的相同圆环。你可以在这个页面的底部下载这个demo。 

OPenGL 光照方程

红宝书中这样定义Opengl的光照方程:

顶点颜色 = 自发射光+全局环境光+ sum(衰减系数*点光源*(环境光+ max{L.N,0}*漫反射光)+(max{H.N,0} ^镜面反射系数)*镜面反射光

其中:


自发射光(emission) 物体本身发射的颜色

全局环境光(globalAmbient) 环境的颜色乘以全局环境亮度

衰减(attenuation) 描述光随着距离而变暗的术语

点光源(spotlight) 引起点光源效果的术语

环境光(lightAmbient) 光的环境颜色乘以亮度

漫反射(diffuse) 光的漫反射颜色乘以材料的漫反射颜色

镜面反射系数(shininess) 镜面反射系数,告诉我们物体表面的光泽程度

镜面反射光(specular) 光的镜面反射颜色乘以材料的镜面反射颜色

L 标准化的(单位长度)向量:从光照到的顶点到光源的连线

N 顶点的单位法线

H 标准化的“半角”向量,在L向量和视线向量的中间



我们的目标是逐像素点评估光照方程。然后,通过改变普通映射,我们实现一个凹凸的效果我们没有办法去评估以上所有的内容,尤其是在不使用任何片段程序,寄存混合等的情况下。所以,我们将简化这个方程。

 

简化

 

没有环境光

没有自发射光

只有一个光源

没有衰减或者聚光灯效果

没有镜面反射

 

因此,我们的每像素光照方程被简化为:

Color = max{l.n,0}*diffuse

 

坐标空间

 

为了了解凹凸纹理映射,接下来你需要分清坐标空间。这个教程里用到了很多坐标空间。下面的图描述了这些坐标空间以及它们之间的转换关系。

 

 

这里是我们将使用的五个坐标空间。我假设你已经非常熟悉标准的OpenGL模型视图矩阵以及投影矩阵。从上面的图片,你可以看到模型视图矩阵把物体空间的坐标转换到了视图空间,然后投影矩阵将视图空间的坐标转换到裁剪空间。在裁剪空间之后,你的坐标将进行透视变换,然后是视口变换,最终得到了可以绘制在屏幕上的窗口坐标。

当你指定顶点的时候,你实际上是在物体空间做这件事。因此,模型视图矩阵和投影矩阵将一起从物体空间变换到裁剪空间。如果你想要从视图空间转换回物体空间,你可以通过乘以模型矩阵的逆矩阵来变换你的顶点。

那么,这个切线空间(tangent space)是什么?TBN矩阵又是什么?

好的。这可能是最难理解的一部分了。切线空间是一个我们模型表面的一个局部空间,让我们来考虑一个单一的四方体:

 

现在你们应该都知道什么是法线了吧。它是一个从四方形里指出的向量(在这个例子中它是标准化的,也就是单位长度的)每个顶点的法线都指向屏幕外。
现在,我们需要找到一个顶点的两个切线。“S切线”指向s纹理坐标增加的方向,’T切线”指向T纹理坐标增加的方向。
这两个切线以及法线形成了这个顶点的基底。它们定义了一个坐标空间——切线空间。把s切线、t切线以及法线分别想象成这个坐标空间的x,y,z坐标轴。TBN矩阵就是从物体空间转换到切线空间的矩阵,它的组成像这样:
( Sx Sy Sz )
( Tx Ty Tz )
( Nx Ny Nz )
其中Sx,Sy,Sz是S切线的元素,Tx,Ty,Tz是T切线的元素,Nx,Ny,Nz是法线的元素。
顺便说一句,TBN矩阵的名字源于“切线(Tangent),次法线(Binormal),法线(Normal)”
“Binormal”是切线的另一个叫法。我相信切线t这个名字能更清晰地表达这个向量的含义。

 

普通的映射

 

所以,我们该如何在每像素基底上改变法线呢?和你使用纹理映射改变一个模型的每个像素颜色基本一样。但是,法线映射需要保存标准化向量,而不是颜色。

我们的法线看起来是这样的:

(x)
(y)
(z)

由于它们是单位长度的,(x*x)+(y*y)+(z*z),因而x,y,z的取值范围在[-1,1]。我们可以通过用x,y,z分量代替rgb分量在一个纹理映射中表达它们。我们用来表达向量的颜色分量必须分布在0,1之间,所以,我们设置:

r = (x+1)/2;
g = (y+1)/2;
b = (z+1)/2;

花几分钟来让自己确信:每一个可能的单位法线都可以通过这种方式存储在颜色里。

 

我们放在纹理映射中的法线将用在切线空间。在切线空间中,法线通常在z方向上。因此,直法线的RGB颜色为(0.5,0.5,1.0),这就是为什么我们的法线映射图是蓝色的。

这是我们的例子将会用到的一张法线映射。

 

 

立方体映射

 

 

回顾一下,一个2D的纹理是一个平坦的四方形,一个颜色的二维数组。想象六个正方形的二维纹理,把它们放在一起组成立方体的六个面。这就是一个立方体映射。为了访问立方体映射中纹理颜色,我们使用了三维纹理坐标,即使用glTexCoord3f,而不是glTexCoord2f,或者说在我们的例子中,相应的顶点数组结构。
想象中心在原点的立方体。给定一个三维纹理坐标,我们可以从原点到我们的坐标画一条线。直线与立方体交点的纹理颜色就是纹理映射的颜色。这很简单吧?
这就意味着向量(2,1,2)和(8,4,8),事实上,对于任意k,(2k,k,2k)都会返回相同的结果。
这是一个例子。如果我们把绿色的X的坐标转换到glTexCoord3f,OpenGL将会使用红色点的纹理坐标,它是通过绘制绿线并计算它在何处与立方体相交得到的。

 

 

标准化立方体映射

 

 

在这个demo中,我们将会使用一个标准化立方体映射。这意味着,我们将在立方体映射中存储向量,而不是颜色,就和法线映射一样。使用纹理下标(ka,kb,kc)。我们将会存储向量:
normalize(a, b, c)
所以,任何输入到我们的立方体映射中的向量都会得到一个单位向量的返回结果。我们将使用这个来保证每个像素的光线向量都是标准化的。
作为提醒,这是我们的目标:
颜色 = max{l.n,0}*漫反射光
我们知道我们的灯光在世界坐标系中的位置。我们将使用模型矩阵的逆矩阵(看上面的图)来把它转换到物体空间。然后,我们将从我们正在考虑的向量计算出到光线的向量。接下来,我们可以使用TBN矩阵把我们的光线位置转换到切线空间,并把它保存到我们的向量结构中。然后我们就可以绘制我们的圆管了。这个光线向量将会使用法线立方体映射进行标准化。因为我们的法线映射中的法线以及光线向量是在切线空间中定义的,它们之间的点乘是有意义的。然后,我们简单地将它们的结果与漫反射材料颜色相乘。
为了完成这一点,我们需要使用OpenGL的ARB扩展:
ARB_multitexture
ARB_texture_cube_map
ARB_texture_env_combine
ARB_texture_env_dot3

能理解这一切吗?如果不能理解,不要担心,尝试再读一遍,然后试着阅读下面代码的笔记。如果这些都失败了,你依然无法掌握其中的要领,请联系我。
好了,接下来来看代码。

 

 

设置扩展库

 

OpenGL的最大优点之一就是它的可扩展性,扩展可以由硬件供应商书写,以便更好利用硬件功能。我们在这里使用的扩展都是ARB扩展,这意味着它们是由OpenGL Architecture Review Board(OpenGL架构审查委员会,制定OpenGL标准的人)批准的。这些扩展是OpenGL最新1.4版本的所有核心特点。
然而,微软已经决定不再将OpenGL1.1以后的任何标准收录进windows。OpenGL1.4已经可以下载了,但是我们仍使用OpenGL1.1的扩展来访问我们需要的函数。
教程里那些叫做a...cpp的文件是我们将使用的扩展。这是ARB_multitexture的文件。
首先,一个布尔变量来判断这个扩展是否被支持:
bool ARB_multitexture_supported=false;
这个函数测试ARB_multitexture是否被支持,如果支持的话,那么就初始化它。所有你的OpenGL支持的扩展名都存储在扩展字符串中。我们分析这些字符串试图找到“GL_ARB_multitextrue”

 

 

bool SetUpARB_multitexture()
{

 

得到扩展的字符串,遍历它寻找”GL_ARB_multitexture”。如果找到了,令ARB_multitexture_supported为真。

 

    char * extensionString=(char *)glGetString(GL_EXTENSIONS);
    char * extensionName="GL_ARB_multitexture";

    char * endOfString; //store pointer to end of string
    unsigned int distanceToSpace; //distance to next space

    endOfString=extensionString+strlen(extensionString);

    //loop through string
    while(extensionString

 

如果我们找不到GL_ARB_multitexture,输出错误信息,并返回假。

 

    if(!ARB_multitexture_supported)
    {
        printf("ARB_multitexture unsupported!\n");
        return false;
    }

    printf("ARB_multitexture supported!\n");


 

如果我们运行到了这里,ARB_multitexture已经被支持了。为了使用ARB_multitexture函数,我们需要为每个函数创建函数指针。接下来,我们使用wglGetProcAddress来初始化函数指针:

 

    PFNGLACTIVETEXTUREARBPROC glActiveTextureARB =NULL;

    glActiveTextureARB = (PFNGLACTIVETEXTUREARBPROC)
    wglGetProcAddress("glActiveTextureARB");


 

函数指针是全局声明的,并且在头文件中extern声明过。通过include正确的头文件,并且在我们程序的开头调用SetUpARB_multitexture,我们现在就可以使用这些函数了。

其它的三个扩展使用相同的方式设置的,但是它们会更简单,因为我们不需要函数指针。

 

创建标准化立方体映射

 

函数“GenerateNormalisationCubeMap”所做的正如它的名字。

 

bool GenerateNormalisationCubeMap()
{

 

首先我们创建空间来存储每个面的数据。每个面的大小是32X32,然后我们需要存储每个点的RGB分量。

 

    unsigned char * data=new unsigned char[32*32*3];
    if(!data)
    {
        printf("Unable to allocate memory for texture data for cube map\n");
        return false;
    }


 

声明一些有用的变量。

 

    //some useful variables
    int size=32;
    float offset=0.5f;
    float halfSize=16.0f;
    VECTOR3D tempVector;
    unsigned char * bytePtr;

 

接下来,我们将为每个面执行一次这样的操作。我将展示正的x面是怎样做的,其它的面非常类似。你可以在源代码中看到更多细节。

 

    //positive x
    bytePtr=data;

遍历面中的每个像素:

 

 

    for(int j=0; j

    计算从正方体的中心到纹理的向量:

 

 

            tempVector.SetX(halfSize);
            tempVector.SetY(-(j+offset-halfSize));
            tempVector.SetZ(-(i+offset-halfSize));

我们标准化这一向量,把它转换到[0,1]之间,这样它就能被存在颜色中 。我们把它存为纹理数据:

 

 

            tempVector.Normalize();
            tempVector.PackTo01();

            bytePtr[0]=(unsigned char)(tempVector.GetX()*255);
            bytePtr[1]=(unsigned char)(tempVector.GetY()*255);
            bytePtr[2]=(unsigned char)(tempVector.GetZ()*255);

            bytePtr+=3;
        }
    }

现在我们把立方体的这个面交给OpenGL。我们告诉它这是当前立方体映射的x方向的正面,还告诉它可以在哪里找到数据。

 

 

    glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB, 0, GL_RGBA8,
                 32, 32, 0, GL_RGB, GL_UNSIGNED_BYTE, data);

为立方体的每个面重复这一过程后,我们终于完成了。不要忘记删除临时数据存储空间。

 

 

    delete [] data;

    return true;
}

 

绘制圆环

 

 

我们将圆环的信息存储在一个类中。首先,我们有一个存储一个顶点的简单类。我们存储了位置,纹理坐标,即切线和法线,最后,我们存储每个顶点的切线空间的光线向量。这是切线空间中从顶点指向光源的向量。

 

class TORUS_VERTEX
{
public:
    VECTOR3D position;
    float s, t;
    VECTOR3D sTangent, tTangent;
    VECTOR3D normal;
    VECTOR3D tangentSpaceLight;
};

现在,我们有了主要的圆环类了。它仅仅存储了一系列顶点和一系列索引,以及它们的大小。它同样包含了填充这些列表元素的函数:InitTorus。

 

 

class TORUS
{
public:
    TORUS();
    ~TORUS();

    bool InitTorus();

    int numVertices;
    int numIndices;

    unsigned int * indices;
    TORUS_VERTEX * vertices;
};

我们定义我们的圆环细分度为48。这也就意味着我们创建它时,每一环有48个顶点。

 

 

const int torusPrecision=48;

圆环的构造函数调用了初始化函数。

 

 

TORUS::TORUS()
{
    InitTorus();
}

圆环的析构函数删除了索引、顶点列表来释放内存。

 

 

TORUS::~TORUS()
{
    if(indices)
        delete [] indices;
    indices=NULL;

    if(vertices)
        delete [] vertices;
    vertices=NULL;
}

这是我们圆环的初始化函数。

 

 

bool TORUS::InitTorus()
{

我们计算顶点以及索引的个数,然后为顶点索引列表申请空间。

 

 

    numVertices=(torusPrecision+1)*(torusPrecision+1);
    numIndices=2*torusPrecision*torusPrecision*3;

    vertices=new TORUS_VERTEX[numVertices];
    if(!vertices)
    {
        printf("Unable to allocate memory for torus vertices\n");
        return false;
    }

    indices=new unsigned int[numIndices];
    if(!indices)
    {
        printf("Unable to allocate memory for torus indices\n");
        return false;
    }

现在,我们计算圆环的顶点位置,法线等等。

 

首先,我们在xy平面产生48个顶点形成的环,以右边为原点。

这些顶点的法线正如它们所显示的那样。我们同样设置T纹理坐标随着i线性增长,S纹理坐标为0。因此,T切线指向i增长的方向。S切线指向屏幕里,我们接下来会介绍原因。

 

    //calculate the first ring - inner radius 4, outer radius 1.5
    for(int i=0; i

接下来,我们绕着Y轴以2*pi/48的步长旋转整个圆环。这将会产生另一个环形成的环面。所有的法线、切线都旋转了,T纹理坐标和初始圆环相同,S纹理坐标随着一个圆环到下一个而增加。这就是第一个圆环S切线指向屏幕内的原因。下面这张照片是一张俯视图。

 

    //rotate the first ring to get the other rings
    for(int ring=1; ring

    现在,我们计算我们刚刚放置好的顶点的三角形面片的索引。

 

 

    //calculate the indices
    for(ring=0; ring

   好了,现在圆环画好了。

 

 

    return true;
}


 

最终,在我们的main文件中

首先,我们需要包含必要的头文件。

“WIN32_LEAN_AND_MEAN” 是为了告诉windows.h不要包含大量我们用不到的晦涩的东西。

然后,我们包含“glut.h”,它自身包含了"gl.h"和“glu.h”

“glext.h”包含了我们即将使用的扩展所需要的东西。

接下来,我们包含一些扩展的头文件。IMAGE.h是一个加载法线映射和纹理映射的图像类。“Maths.h”包含了我们将使用到的顶点和矩阵类。“TORUS.h”包含了我们即将绘制的圆环的细节,“Normalisation cube map.h”包含了……嗯,这个我将留给你自己去分辨。

 

#define WIN32_LEAN_AND_MEAN
#include 
#include 
#include 
#include 
#include "Extensions/ARB_multitexture_extension.h"
#include "Extensions/ARB_texture_cube_map_extension.h"
#include "Extensions/ARB_texture_env_combine_extension.h"
#include "Extensions/ARB_texture_env_dot3_extension.h"
#include "Image/IMAGE.h"
#include "Maths/Maths.h"
#include "TORUS.h"
#include "Normalisation Cube Map.h"

现在我们要声明一些全局对象,包括我们是否要加载凹凸纹理以及颜色纹理的布尔量。

 

 

//Our torus
TORUS torus;

//Normal map
GLuint normalMap;

//Decal texture
GLuint decalTexture;

//Normalisation cube map
GLuint normalisationCubeMap;

//Light position in world space
VECTOR3D worldLightPosition=VECTOR3D(10.0f, 10.0f, 10.0f);

bool drawBumps=true;
bool drawColor=true;

现在,我们创建一个叫Init的函数。这个在程序一开始时调用,不过应该在窗体被创建之后。

 

 

//Called for initiation
void Init(void)
{

首先,我们设置我们将要用到的扩展。如果扩展设置返回假,那么这个扩展就不被支持,我们就没有办法运行这个demo,输出错误并退出。

 

 

    //Check for and set up extensions
    if( !SetUpARB_multitexture() || !SetUpARB_texture_cube_map() ||
        !SetUpARB_texture_env_combine() || !SetUpARB_texture_env_dot3())
    {
        printf("Required Extension Unsupported\n");
        exit(0);
    }

接下来我们设置OpenGL状态。如果你看过NeHe‘s的教程,接下来的代码你可能会很眼熟。我们加载单位模型视图矩阵,设置颜色以及深度状态,然后允许背面剔除。

 

 

    //Load identity modelview
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    //Shading states
    glShadeModel(GL_SMOOTH);
    glClearColor(0.2f, 0.4f, 0.2f, 0.0f);
    glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
    glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);

    //Depth states
    glClearDepth(1.0f);
    glDepthFunc(GL_LEQUAL);
    glEnable(GL_DEPTH_TEST);

    glEnable(GL_CULL_FACE);

现在,我们加载我们的纹理。首先我们使用IMAGE类来加载映射。然后,既然我们的纹理映射使用的是调色板色(每个像素8位),我们在发送给OpenGL前,将其扩展到24位真彩色。我们使用glTexImage2D来发送数据,然后设置纹理参数。事实上,我们做的和普通纹理映射一样。

 

 

    //Load decal texture
    IMAGE decalImage;
    decalImage.Load("decal.bmp");
    decalImage.ExpandPalette();

    //Convert normal map to texture
    glGenTextures(1, &decalTexture);
    glBindTexture(GL_TEXTURE_2D, decalTexture);
    glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA8, decalImage.width, decalImage.height,
        0, decalImage.format, GL_UNSIGNED_BYTE, decalImage.data);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);

    //Load normal map
    IMAGE normalMapImage;
    normalMapImage.Load("Normal map.bmp");
    normalMapImage.ExpandPalette();

    //Convert normal map to texture
    glGenTextures(1, &normalMap);
    glBindTexture(GL_TEXTURE_2D, normalMap);
    glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA8, normalMapImage.width, normalMapImage.height,
        0, normalMapImage.format, GL_UNSIGNED_BYTE, normalMapImage.data);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);

接下来,我们创建我们的标准化立方体映射。我们就像之前一样创建一个纹理,但是我们不再使用GL_TEXTURE_2D,而是使用GL_TEXTURE_CUBE_MAP_ARB。末尾的ARB示意这是ARB扩展的一部分。我们不再使用LoadTexture,而是创建立方体映射并通过前面提到的GenerateNormalisationCubeMap函数发送给OpenGL。

 

 

    //Create normalisation cube map
    glGenTextures(1, &normalisationCubeMap);
    glBindTexture(GL_TEXTURE_CUBE_MAP_ARB, normalisationCubeMap);
    GenerateNormalisationCubeMap();
    glTexParameteri(GL_TEXTURE_CUBE_MAP_ARB, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_CUBE_MAP_ARB, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_CUBE_MAP_ARB, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_CUBE_MAP_ARB, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_CUBE_MAP_ARB, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
}

好了,初始化到这里就结束了。接下来我们可以画点什么了。

 

 

//Called to draw scene
void Display(void)
{

 

首先,我们清除颜色和深度缓存,并且设置模型视图矩阵为单位矩阵。

 

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glLoadIdentity();

 

现在设置模型视图矩阵。

首先,我们执行视区变换,也就是上面图表中的视图矩阵。这将我们的摄像机摆放到了场景中。我们使用gluLookAt来将相机放置到(0.0,10.0,10.0)位置,望向原点。

然后,我们设置模型矩阵。记得模型视图矩阵也就是模型矩阵紧跟着视图矩阵。但是,OpenGL要求我们以相反的顺序来应用变换。我们想要让圆环绕着Y轴旋转。我们使用一个静态变量来维护这个旋转的过程,我们要做的仅仅是增加它的值,然后把旋转交给OpenGL。

 

    //use gluLookAt to look at torus
    gluLookAt(0.0f,10.0f,10.0f,
              0.0f, 0.0f, 0.0f,
              0.0f, 1.0f, 0.0f);

    //rotate torus
    static float angle=0.0f;
    angle+=0.1f;
    glRotatef(angle, 0.0f, 1.0f, 0.0f);

我们现在计算模型矩阵的逆,正如你所知道的,这将把我们从世界空间转换到物体空间。

 

首先,我们保存当前的模型视图矩阵,然后我们把它重设为单位矩阵。旋转a度角的逆就是绕着同样的坐标轴旋转-a度角。我们把这个旋转交给OpenGL,然后使用GetFloatv来得到当前矩阵。然后我们再恢复我们存储的模型视图矩阵。

 

    //Get the inverse model matrix
    MATRIX4X4 inverseModelMatrix;
    glPushMatrix();
    glLoadIdentity();
    glRotatef(-angle, 0.0f, 1.0f, 0.0f);
    glGetFloatv(GL_MODELVIEW_MATRIX, inverseModelMatrix);
    glPopMatrix();

现在,我们使用刚刚计算出来的矩阵将光的位置转换到物体空间。

 

 

    //Get the object space light vector
    VECTOR3D objectLightPosition=inverseModelMatrix*worldLightPosition;

 

现在,我们需要计算切线空间每个顶点的光向量。我们遍历所有顶点,并且填充向量。

观察上面的图表(再一次),我们看到TSB矩阵将物体空间的点转换到切线空间。

我们知道光源在物体空间的位置。我们将其与顶点位置坐标作差,来得到从顶点到光源的向量。我们使用TSB矩阵将其转换到切线空间。TSB矩阵的形式如下:

( Sx Sy Sz )
( Tx Ty Tz )
( Nx Ny Nz )

所以,切线空间光向量的第一个分量是(Sx,Sy,Sz).(Lx,ly,Lz)中,L是从顶点到光源的向量。类似的,其它两个分量也是简单的点乘。

 

    //Loop through vertices
    for(int i=0; i

 

现在,我们可以绘制圆环了。我们将绘制两遍来完成这个凹凸纹理圆环。第一遍,我们仅仅用颜色纹理来绘制。我们可以开启或者关闭这个绘制,来得到不同的效果。在一开始,如果有必要,我们将绘制普通纹理。

 

 

    //Draw bump pass
    if(drawBumps)
    {

首先,我们混合我们即将用到的纹理。我们将普通纹理设置为第0层,然后开启二维纹理。然后我们将法线立方体映射设置为第1层,然后开启立方体纹理映射。接下来我们重设当前单元为0.

 

 

        //Bind normal map to texture unit 0
        glBindTexture(GL_TEXTURE_2D, normalMap);
        glEnable(GL_TEXTURE_2D);

        //Bind normalisation cube map to texture unit 1
        glActiveTextureARB(GL_TEXTURE1_ARB);
        glBindTexture(GL_TEXTURE_CUBE_MAP_ARB, normalisationCubeMap);
        glEnable(GL_TEXTURE_CUBE_MAP_ARB);
        glActiveTextureARB(GL_TEXTURE0_ARB);


现在,我们设置我们即将用到的顶点数组。

我们设置顶点指针指向位置,并且开启顶点数组。

纹理坐标数组将为每个纹理单元设置。

第0层纹理坐标仅仅包含s和t纹理坐标。所以,普通的映射将和一个标准的纹理映射一样应用到圆环上。

第1层纹理坐标仅仅是切线空间的光源向量。由于我们把纹理1设置为法线立方体映射,纹理1将会包含每个像素的标准化的切线空间光线向量。

 

 

        //Set vertex arrays for torus
        glVertexPointer(3, GL_FLOAT, sizeof(TORUS_VERTEX), &torus.vertices[0].position);
        glEnableClientState(GL_VERTEX_ARRAY);

        //Send texture coords for normal map to unit 0
        glTexCoordPointer(2, GL_FLOAT, sizeof(TORUS_VERTEX), &torus.vertices[0].s);
        glEnableClientState(GL_TEXTURE_COORD_ARRAY);

        //Send tangent space light vectors for normalisation to unit 1
        glClientActiveTextureARB(GL_TEXTURE1_ARB);
        glTexCoordPointer(3, GL_FLOAT, sizeof(TORUS_VERTEX), &torus.vertices[0].tangentSpaceLight);
        glEnableClientState(GL_TEXTURE_COORD_ARRAY);
        glClientActiveTextureARB(GL_TEXTURE0_ARB);

我们在纹理0中存储普通映射,在纹理1中存储标准化切线空间光线向量。记得我们要计算的目标:

color = max{l.n, 0}*diffuse

我们将在第二次计算过程中乘以漫反射光,所以我们需要求出tex0点乘tex1的值。所以,我们使用ARB_texture_env_combine和ARB_texture_env_dot3扩展来完成这一点。第一个纹理单元将会纹理颜色替换面片颜色,例如,普通的映射。

第二个纹理单元将会将其与texture1点乘,例如,光线向量。因为它们都处在切线空间,点乘是有意义的。同样,由于输出是一个颜色,它将被限制到[0,1]范围内。所以,我们可以开始着手于我们的等式的第一部分了。在这个demo中按下2,你将会看到这一部分长成什么样子。

 

        //Set up texture environment to do (tex0 dot tex1)*color
        glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_ARB);
        glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_TEXTURE);
        glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_REPLACE);

        glActiveTextureARB(GL_TEXTURE1_ARB);

        glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_ARB);
        glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_TEXTURE);
        glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_DOT3_RGB_ARB);
        glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB_ARB, GL_PREVIOUS_ARB);

        glActiveTextureARB(GL_TEXTURE0_ARB);

如果你留意到的话,你可能会为向量在纹理中转换到了[0,1],但却没有转回[-1,1]感到困惑。不用担心,DOT3_RGB_ARG纹理环境将会自动为我们完成这件事情。

现在,我们可以绘制圆环了。

 

        //Draw torus
        glDrawElements(GL_TRIANGLES, torus.numIndices, GL_UNSIGNED_INT, torus.indices);

我们已经在这个设置中绘制了我们需要绘制的东西,所以我们可以禁止纹理和顶点数组了。我们同样将纹理环境重设为标准模型。

 

        //Disable textures
        glDisable(GL_TEXTURE_2D);

        glActiveTextureARB(GL_TEXTURE1_ARB);
        glDisable(GL_TEXTURE_CUBE_MAP_ARB);
        glActiveTextureARB(GL_TEXTURE0_ARB);

        //disable vertex arrays
        glDisableClientState(GL_VERTEX_ARRAY);

        glDisableClientState(GL_TEXTURE_COORD_ARRAY);

        glClientActiveTextureARB(GL_TEXTURE1_ARB);
        glDisableClientState(GL_TEXTURE_COORD_ARRAY);
        glClientActiveTextureARB(GL_TEXTURE0_ARB);

        //Return to standard modulate texenv
        glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
    }

下一步是绘制漫反射颜色,我们的纹理圆环。如果我们绘制了凹凸纹理以及纹理,我们可以通过相乘将它们合并起来。我们开启相乘混合模式:

 

    //If we are drawing both passes, enable blending to multiply them together
    if(drawBumps && drawColor)
    {
        //Enable multiplicative blending
        glBlendFunc(GL_DST_COLOR, GL_ZERO);
        glEnable(GL_BLEND);
    }

 

我们可以开始绘制纹理部分了。

 

    //Perform a second pass to color the torus
    if(drawColor)
    {

 

如果我们不绘制凹凸纹理部分,我们可以允许标准OpenGL光源来实现无凹凸纹理映射的圆环与有凹凸纹理映射的圆环的比较。你可以点击3来观察标准照射下的圆环。

 

        if(!drawBumps)
        {
            glLightfv(GL_LIGHT1, GL_POSITION, VECTOR4D(objectLightPosition));
            glLightfv(GL_LIGHT1, GL_DIFFUSE, white);
            glLightfv(GL_LIGHT1, GL_AMBIENT, black);
            glLightModelfv(GL_LIGHT_MODEL_AMBIENT, black);
            glEnable(GL_LIGHT1);
            glEnable(GL_LIGHTING);

            glMaterialfv(GL_FRONT, GL_DIFFUSE, white);
        }

 

我们绑定贴图纹理到纹理0上,并开启二维纹理。

 

 

        //Bind decal texture
        glBindTexture(GL_TEXTURE_2D, decalTexture);
        glEnable(GL_TEXTURE_2D);

现在,我们和上面一样开始顶点数组和纹理坐标数组。这一次,我们同样为标准OpenGL光线开启法线数组。

 

        //Set vertex arrays for torus
        glVertexPointer(3, GL_FLOAT, sizeof(TORUS_VERTEX), &torus.vertices[0].position);
        glEnableClientState(GL_VERTEX_ARRAY);

        glNormalPointer(GL_FLOAT, sizeof(TORUS_VERTEX), &torus.vertices[0].normal);
        glEnableClientState(GL_NORMAL_ARRAY);

        glTexCoordPointer(2, GL_FLOAT, sizeof(TORUS_VERTEX), &torus.vertices[0].s);
        glEnableClientState(GL_TEXTURE_COORD_ARRAY);

现在,我们绘制圆环并重设Opengl状态(灯光,纹理等)

 

 

        //Draw torus
        glDrawElements(GL_TRIANGLES, torus.numIndices, GL_UNSIGNED_INT, torus.indices);

        if(!drawBumps)
            glDisable(GL_LIGHTING);

        //Disable texture
        glDisable(GL_TEXTURE_2D);

        //disable vertex arrays
        glDisableClientState(GL_VERTEX_ARRAY);
        glDisableClientState(GL_NORMAL_ARRAY);
        glDisableClientState(GL_TEXTURE_COORD_ARRAY);
    }

    //Disable blending if it is enabled
    if(drawBumps && drawColor)
    glDisable(GL_BLEND);

glFinish告诉OpenGL结束绘制。

glutSwapBuffers交换前后两个颜色矩阵。

glutPostRedisplay告诉glut我们想要尽快的绘制下一帧。

 

 

    glFinish();
    glutSwapBuffers();
    glutPostRedisplay();
}

这就是绘制的全部内容!

现在我们需要为glut创建多一些函数。如果你之前使用过glut,你应该对它们非常熟悉。

Reshape()在窗口大小被改变时调用。它重设视口为整个屏幕大小,并且重设投影矩阵到正确的视角。

 

//Called on window resize
void Reshape(int w, int h)
{
    //Set viewport
    if(h==0)
        h=1;

    glViewport(0, 0, w, h);

    //Set up projection matrix
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective( 45.0f, (GLfloat)w/(GLfloat)h, 1.0f, 100.0f);

    glMatrixMode(GL_MODELVIEW);
}


 

Keyboard() 在按键后被调用。

 

//Called when a key is pressed
void Keyboard(unsigned char key, int x, int y)
{

如果用户点击esp,那么退出。

 

    //If escape is pressed, exit
    if(key==27)
    exit(0);

 

如果点击1,我们将会绘制凹凸映射和普通颜色。所以,将两个布尔量都设为true。

 

 

    //'1' draws both passes
    if(key=='1')
    {
        drawBumps=true;
        drawColor=true;
    }

 

如果点击2,我们只绘制凹凸纹理,将它设置为true,另一个为false。

 

 

 

    //'2' draws only bumps
    if(key=='2')
    {
        drawBumps=true;
        drawColor=false;
    }

 

如果点击3,我们只绘制普通颜色,所以,要禁止凹凸纹理。

 

 

    //'3' draws only color
    if(key=='3')
    {
        drawBumps=false;
        drawColor=true;
    }

如果点击w,我们设置绘制模式为现线框。

如果点击f,我们设置绘制模式为实体。

 

 

    //'W' draws in wireframe
    if(key=='W' || key=='w')
        glPolygonMode(GL_FRONT, GL_LINE);

    //'F' return to fill
    if(key=='F' || key=='f')
        glPolygonMode(GL_FRONT, GL_FILL);
}

最终,在我们的main函数中,这就是一个标准的glut事务了。我们初始化glut,创建一个双缓冲,RGB模式,支持深度缓存的窗口。然后,我们调用我们的前面的初始化函数。接下来,我们告诉glut哪里调用显示,重绘以及键盘响应函数。这些就是注册回调。之后,我们告诉glut开始绘制。Glut从这里开始接管我们的程序。

 

 

int main(int argc, char** argv)
{
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
    glutInitWindowSize(640, 480);
    glutCreateWindow("Simple Bumpmapping");

    Init();

    glutDisplayFunc(Display);
    glutReshapeFunc(Reshape);
    glutKeyboardFunc(Keyboard);
    glutMainLoop();
    return 0;
}

以上就是全部内容了,我希望你能理解大部分内容,并且期待着你能自己使用它们!
相关TAG标签
上一篇:嵌入式开发第31、32天(项目2:用线程池实现大批量复制文件)
下一篇:STL_算法(22)_ STL_算法_替换算法
相关文章
图文推荐

关于我们 | 联系我们 | 广告服务 | 投资合作 | 版权申明 | 在线帮助 | 网站地图 | 作品发布 | Vip技术培训 | 举报中心

版权所有: 红黑联盟--致力于做实用的IT技术学习网站