OpenGL学习随笔(十二)

凹凸纹理让物体看上去更真实,而凹凸效果的本质是光源入射到凹凸不平的物体表面之后产生的反射现象。在图形领域“更真实,更快速”宗旨的指导下,凹凸也表现出多种具体的解决方案。如果要完全真实模拟凹凸,那必然是一个浩大的工程,我理解主要有两点困难:

1、现实物体表面的凹凸数据的收集,包括静态数据和动态数据(物理作用之后产生的各种凹凸)
2、凹凸表面的光照计算,如果要真实模拟,则需要逐点计算。虽然光源照射到平面后,平面中各点的颜色都是经过计算而得出的,但由于我们并没有对凹凸表面进行多边形三维建模,而是通过纹理来生成,所以OpenGL 1.x并不能得到凹凸表面各点的法向量信息。也就是说在OpenGL 1.x的框架下,如果要对凹凸表面进行真实的光照计算,必须要对凹凸表面进行三维建模。即使是在可编程的OpenGL 2.x,能在不建模的情况下对凹凸表面进行逐点光照计算,但由此带来的GPU资源消耗也是很可观的。

早在十年前的TNT时代,针对固定渲染管线,前辈们使用什么样的方式来模拟凹凸表面呢?

首先想到的是把凹凸的样子做在纹理图片中,用贴图的方式来解决。但简单的贴图很明显无法解决视角和光源位置改变之后的问题——真实的凹凸主要还是因为光从不同角度照射过来,或者人从不同角度看过出,阴影是不太一样的。基于此的一个改进方法是先贴一张“凹”图,然后沿光照方向偏移一段距离再贴一张“凸”图。由于第二张图的位置计算参考了光源位置,因此比原始的那种“做死的”贴图效果要好。但很明显这种方案是假设平面上各种凸起的高度是近似相等的,同时把光照模型想得十分简单。但无论怎么样,总算是一种进步。

NeHe教程上对此方法有具体的实现,可以参考。当然该教程也是参考了Nvidia Micheal I. Gold写的一个胶片《Emboss Bump Mapping》。由于NeHe中文教程在翻译上的问题,直接看中文不是很容易理解。这里总结一下之前在理解所走过的弯路。

1.《Emboss Bump Mapping》中反复提及Bump mapping和Emboss bump mapping,我理解作者把Bump mapping定义为真实的凹凸模拟,而把他自己提出这种方法称为“Emboss bump mapping(浮雕式的凹凸纹理映射)”。因此,他说"Bump mapping changes N per pixel", 而"Emboss bump mapping approximates (L.N)",意思是真实的凹凸模拟是计算每个点的N(法向量),而他的这种方法是对(整个平面)(L.N)(光源与法向量的偏移值)的一个近似。那如何进行近似呢?原文胶片第7页画了个图,基本原理就如上所述,贴两次图。
2. 在一个表面上连续贴两个纹理最好是GPU能支持多个纹理通道(多纹理通道在OpenGL中是一个扩展),这样就避免通过多次调用OpenGL渲染来达到多次纹理贴图的目的,而NeHe教程中是用这两种方式各实现了凹凸效果。

最后结合NeHe代码来理解一下整个过程,其实只要围绕doMesh2TexelUnits这个函数来看就可以了。

函数一上来先把光源变换到当前视点空间中,VMatMult是一个矩阵乘法,l是光源的位置,Minv是当前视点模型矩阵。如果不能理解这个动作的几何意义,可以先思考顶点坐标乘以模型矩阵是什么意思。

    glGetFloatv(GL_MODELVIEW_MATRIX,Minv);

    l[0]=LightPosition[0];
    l[1]=LightPosition[1];
    l[2]=LightPosition[2];
    l[3]=1.0f;                                            // Homogenous Coordinate
    VMatMult(Minv,l);
   
接下来启用多个纹理通道,分别绑定“凹”图和“凸”图。

    glActiveTextureARB(GL_TEXTURE0_ARB);
    glEnable(GL_TEXTURE_2D);
    glBindTexture(GL_TEXTURE_2D, bump[filter]);
   
    glActiveTextureARB(GL_TEXTURE1_ARB);
    glEnable(GL_TEXTURE_2D);
    glBindTexture(GL_TEXTURE_2D, invbump[filter]);

“凹”图和“凸”图是如何生成的?可以看下LoadGLTextures函数,可以看到“凹”图来自原始的bump.bmp,是做好的。而“凸”图则是由“凹”图“取反”得来。事实上凹凸的几何意义不也正是如此吗,呵呵。

        for (int i=0; i<3*Image->sizeX*Image->sizeY; i++)        // Invert The Bumpmap
            Image->data[i]=255-Image->data[i];

接下来是最关键的一步,根据光源照射方向计算出“凸”图相对“凹”图坐标的偏移量。c是当前的顶点坐标,n是当前顶点的法向量,l是光源位置, s,t是当前顶点的纹理坐标。

        n[0]=0.0f;        n[1]=0.0f;        n[2]=1.0f;
        s[0]=1.0f;        s[1]=0.0f;        s[2]=0.0f;
        t[0]=0.0f;        t[1]=1.0f;        t[2]=0.0f;


            c[0]=data[5*i+2];
            c[1]=data[5*i+3];
            c[2]=data[5*i+4];

            SetUpBumps(n,c,l,s,t);
            glMultiTexCoord2fARB(GL_TEXTURE0_ARB,data[5*i]     , data[5*i+1]);
            glMultiTexCoord2fARB(GL_TEXTURE1_ARB,data[5*i]+c[0], data[5*i+1]+c[1]);
            glVertex3f(data[5*i+2], data[5*i+3], data[5*i+4]);

可以很明显的看到c经过SetUpBumps计算之后变成了“凸”图坐标偏移量。

    // Calculate v From Current Vector c To Lightposition And Normalize v
    v[0]=l[0]-c[0];
    v[1]=l[1]-c[1];
    v[2]=l[2]-c[2];
    lenQ=(GLfloat) sqrt(v[0]*v[0]+v[1]*v[1]+v[2]*v[2]);
    v[0]/=lenQ;        v[1]/=lenQ;        v[2]/=lenQ;

    // Project v Such That We Get Two Values Along Each Texture-Coordinat Axis.
    c[0]=(s[0]*v[0]+s[1]*v[1]+s[2]*v[2])*MAX_EMBOSS;
    c[1]=(t[0]*v[0]+t[1]*v[1]+t[2]*v[2])*MAX_EMBOSS;

MAX_EMBOSS就是《Emboss Bump Mapping》所指的(H1-H0), 是个固定值. 计算v是为了找出凹凸的增长方向(与光照方向一致), (s,t)与v做的这一堆计算是投影的意思, 把纹理所在平面的坐标投影至光照平面, 得到的就是阴影长度,也就是“凸”图坐标偏移量。

最后,把原图,也就是有颜色的图贴上。也就是说,事实上我们需要三张图来完成凹凸效果,当然其中一张是可以动态生成出来的。

    glActiveTextureARB(GL_TEXTURE1_ARB);
    glDisable(GL_TEXTURE_2D);
    glActiveTextureARB(GL_TEXTURE0_ARB);
    if (!emboss) {
        glTexEnvf (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
        glBindTexture(GL_TEXTURE_2D,texture[filter]);
        glBlendFunc(GL_DST_COLOR,GL_SRC_COLOR);
        glEnable(GL_BLEND);
        glEnable(GL_LIGHTING);
        doCube();
    }

猜你喜欢

转载自blog.csdn.net/ison81/article/details/3990866