Ogre 3D与材质 -----OGRE 3D 1.7 Beginner‘s Guide中文版 第七章

文章转自:http://www.cnblogs.com/oneDouble/articles/2676489.html

没有材质,我们不能给场景添加细节,而这章将对材质的广泛的应用给以介绍。

  材质是一个很重要的概念,而且对产生漂亮的场景来说,非常有必要去理解。材质对尚在进行的研究来说也是个有趣的话题。 

  这一章,我们将会:

  1. 学习如何创建我们自己的材质。
  2. 应用纹理到我们的四边形。
  3. 更好的理解渲染线管是如何工作的。
  4. 使用着色器来创建其他技术很难实现的效果。

  现在,让我们开始吧….

 

【 创建一个白色的四边形 】

 

  在之前的章节中,我们用代码创建了自己的3D模型。现在,我们将会使用这种办法来创建一可做实验的四边形例子。 

  我们将以一个空的程序作为开始,并插入创建四边形的代码到createScene() 函数:

 

  1. 开始创建manual object:

Ogre::ManualObject*  manual = mSceneMgr->createManualObject("Quad");

manual->begin("BaseWhiteNoLighting", RenderOperation::OT_TRIANGLE_LIST);

   2.为四边形创建点:

manual->position(5.0, 0.0, 0.0);

manual->textureCoord(0,1);

manual->position(-5.0, 10.0, 0.0);

manual->textureCoord(1,0);

manual->position(-5.0, 0.0, 0.0);

manual->textureCoord(1,1);

manual->position(5.0, 10.0, 0.0);

manual->textureCoord(0,0);


  3. 使用索引来描述四边形:

manual->index(0);

manual->index(1);

manual->index(2);

manual->index(0);

manual->index(3);

manual->index(1);


  4. 完成manual object 并转换它们为 mesh:

manual->end();manual->convertToMesh("Quad");

  5. 创建实体的实例,并使用场景结点到场景:

Ogre::Entity * ent = mSceneMgr->createEntity("Quad");

Ogre::SceneNode* node = mSceneMgr->getRootSceneNode()->createChildSceneNode("Node1");

node->attachObject(ent);

  6. 编译运行程序。你将会看到一个白色的四边形。

  我们使用之前章节的知识创建了一个四边形并与它关联一个白色的材质。下一步我们要做的是就是创建自己的材质。  

 

 

【 创建我们自己的材质 】

 

  总是渲染所有的东西为白色并不是多么有趣的事情,所以让我们创建我们自己的第一个材质。 

 

  现在,我们将会使用创建的白色四边形来创建自己的材质。

 

 

 

  1. 在程序中把材质的名称BaseWhiteNoLighting改变为MyMaterial1:

 

manual->begin("MyMaterial1", RenderOperation::OT_TRIANGLE_LIST);

  2. 在Ogre 3D SDK 的mediamaterialsscripts文件夹创建一个新的文件名字为Ogre3DBeginnersGuide.material。

 

  3. 写入如下代码到刚才的文件中:

 

material MyMaterial1
{
  technique
  {
      pass
      {
          texture_unit
          {
              texture leaf.png
          }
      }
  }
}



4. 编译运行程序。你将会看到在白色的四边形上有一片植物。

  我们创建了自己的第一个材质文件。在Ogre 3D中,材质可以在material文件中定义。为找到我们的材质文件,我们需要在resources.cfg中给出其材质文件的目录列表。我们也直接在代码中使用ResourceManager给出它们在文件中的路径,就好像我们在之前章节中介绍的加载地址的方式相似。为使用在材质文件中定义的材质,我们需要在开始调用manual object中使用材质的名称。

  真正有趣的部分是材质文件。

  材质 :

  每个材质是以材质的关键字作为开始,也就是材质的名字,然后就是”{ “。在材质的结尾使用了”}”,这种技术目前对你来说应十分熟悉了。每个材质由一种或多种技术(technique)组成;每种技术描述了实现预先效果的方式。因为不同的显卡有着不同的特性,我们可以定义多种技术,Ogre 3D从开始到最后的遍历文件一遍,选择第一种用户显卡支持的技术。在一种技术里面,你可以有多种pass。一个pass是独立几何的渲染。对于我们将要创建的大部分材质,只需要一个pass。然而,对于创建的更复杂的一些材质需要两个或三个的pass,所以Ogre 3D 允许我们每种技术定义几种pass。在这个pass里,我们只定义了一个纹理单位。一个纹理单位定义了一个纹理和它的属性。这次我们定义的唯一属性就是纹理可以被使用。我们使用leaf.png 作为使用的纹理图像。这个纹理是Ogre SDK自带的,并且是resources.cfg可以所以到得文件,这样我们可以不遗余力的使用它了。

 

【 让英雄动起来 —— 创建另一个材质 】

  使用Water02.jpg创建一个称为MyMaterial2 的新纹理

  在之前的章节中,我们讨论了当纹理坐标超出0到1的范围时,各个方式的使用策略。现在,让我们创建一些材质在看看他们的实际效果。

  我们将会使用之前有叶子纹理的四边形那个例子: 

 

  1. 把四边形的纹理坐标范围从[0,1]变为[0,2]。这个四边形的代码将会如下所示:

manual->position(5.0, 0.0, 0.0);

manual->textureCoord(0,2);

manual->position(-5.0, 10.0, 0.0);

manual->textureCoord(2,0);

manual->position(-5.0, 0.0, 0.0);

manual->textureCoord(2,2);

manual->position(5.0, 10.0, 0.0);

manual->textureCoord(0,0);


  2. 现在编译运行程序。如往常一般,我们将会看到在四边形上有一片叶子的纹理,但是这次我们会看到四个纹理。 

 

  刚刚我们简单的改变了我们的四边形,使得纹理坐标的范围为[0,2]。这意味着Ogre 3D 需要使用它本身的策略以渲染大于1的纹理坐标。默认的模式为重复寻址模式(wrap)。这意味着每个超过1的值将会覆盖为0和1范围之间。下面的图示显示了纹理坐标是如何被覆盖的。在边角外部,我们可以看到纹理坐标的原点在边角的内部,我们看到了覆盖之后的纹理坐标的值。同样,为了更好的理解,我们可以看到四个重复的纹理,它们显式的坐标表示。

  我们已经看到默认的纹理wrapping模式是如何覆盖我们的纹理的。我们的平面纹理显示的效果很好,但是它并没有显示这个技术的用处。让我们使用另一个纹理来看下wrapping模式下的好处。以另一个纹理使用wrapping模式。

 

 

【 添加一个岩石纹理 】

 

  对于这个例子,我们将会使用另一个纹理。否则,我们将不会看到这个纹理模式的效果。

 

  1. 创建同上一材质相似的新材质,但是这个把材质变为:terr_rock6.jpg :

 

material MyMaterial3
{
    technique
    {
        pass
        {

            texture_unit

            {
                texture terr_rock6.jpg
            }
        }
    }
}


  2. 从MyMaterial1改变我们使用的材质为MyMaterial3:

 

manual->begin("MyMaterial3", RenderOperation::OT_TRIANGLE_LIST)

  3. 编译运行程序。你将会四边形会有岩石的纹理:

 

  这次,这个四边形看起来好像是一个单独的纹理。我们看不到什么明显的重复,效果如平面纹理一般。原因如我们所知的一样,这是wrapping 模式重复的结果。在一块纹理的最左边,纹理又以其右边重新开始重复,纹理下方的重复同理。这种纹理被称为无缝纹理。我们使用的纹理使左边,右边和上,下边完美的拼接起来。如果不是这种情况,我们就可以看到纹理明显重复的痕迹。

 

 

【 使用另一个纹理模式 】

  我们已经看到wrapping 模式的效果和用处。现在,让我们看一下另一种称为clamping的纹理模型

  我们将会使用相同的项目,并仅创建一个新的纹理:

  1. 创建一个新的成为MyMaterial4的纹理,纹理的内部和之前的纹理一样:

material MyMaterial4
{
    technique
    {
        pass
        {
            texture_unit
            {
                texture terr_rock6.jpg
            }
        }
    }
}


  2. 在纹理单位段中,添加一行代码告诉Ogre 3D 使用clamp 模式:

tex_address_mode clamp

  3. 把我们的四边形材质的名字从MyMaterial3改变为MyMaterial4:

manual->begin("MyMaterial4", RenderOperation::OT_TRIANGLE_LIST);

  4. 编译运行程序。你应会看到四边形纹理左上的石头纹理。四边形的另外三块是由不同颜色的线组成的。

  我们改变纹理模式为clamp。这个模式使用纹理的边缘像素来填充所有大于1的纹理坐标、在实践中,这意味着一个图片的边缘在模型上得到了拉伸;我们可以之前的图片中看到这种效果。

 

【 使用mirror模式 】

  让进入下一个我们可使用的纹理模式。

  1. 使用之前的材质作为模板,创建一个新的称为MyMaterial5的材质。

  改变材质模式为mirror:

tex_address_mode mirror

  3. 改变纹理为我们之前使用过的叶子纹理:

texture leaf.png

  4. 编译运行程序,你将会看到看到叶子被反射了四次:

  我们又一次的改变了纹理模式——这次是mirroring。当用于渲染一块像石墙那样的大面积区域时,使用mirror模式就显得比较简单而有效了。每次纹理坐标值大于1时,纹理就得到翻转,然后就如使用wrap模式一般了。我们可以在下面的图示中看到mirror模式显示的效果。

 

【 使用border模式 】

  最后一个需要尝试的模式,即为,border模式。

 

  1. 创建一个成为MyMaterial6的材质,就如前面五次一样,也是基于之前已使用过的材质。

  2. 改变纹理模式为 border模式:

tex_address_mode border

  3. 同样记住改变在代码在我们使用的材质名称:

manual->begin("MyMaterial6", RenderOperation::OT_TRIANGLE_LIST);

  4. 编译运行程序。令人惊讶的是,这次我们只看到一片叶子。

  别的叶子都去哪里了?border模式并不是如mirro模设计或wrap模式一般创建了纹理的多份拷贝。当纹理坐标大于1的时候,这个模式画任何东西为边框颜色——默认的颜色显然是黑色的,黑色的RGB颜色值为(0,0,0)。

【 改变边框的颜色 】

  如果我们只使用黑色作为边框颜色,这个特性就没有什么用处了。让我们看下如果改变边框的颜色。

 

  1. 复制最后一个材质,并命名为MyMaterial7。

  2. 在设置完材质的模式之后,添加下面的代码以设置边框的颜色为蓝色:

tex_border_color 0.0 0.0 1.0


{ 译者注:tex_border_color应为tex_border_colour 否则不能出效果 }

  3. 编译运行程序。这次,我们也只看到一片叶子的纹理,但是四边形的剩余部分为蓝色。

  我们从黑色改变边框的颜色为蓝色。类似的,我们可以使用任意颜色作为边框颜色,边框颜色可以用RGB值来表示。这个纹理模式可以用于放置logo到像赛车这样的对象。我们只需要设置边框的颜色为车的颜色,然后添加材质。如果纹理坐标存在错误或不准确,它们就不会显示出来不对的地方,因为车和边框颜色都是一样的。

 

 

【 简单测试 —— 纹理模式 】

  四种纹理模式——wrap, clamp, mirror, 和border之间的有什么不同?

  1. 处于0和1之间的纹理坐标是如何使用的?
  2. 高于1或小于0的场景坐标是如何处理的?
  3. 纹理的颜色是如何渲染的?

 

 

【 让英雄动起来 —— 使用纹理模式 】

  尝试使用大于2或小于0的纹理坐标。 

 

【 滚动一个纹理 】

  我们已经看到过几种纹理模式,但是这只是一材质文件的一个属性。现在,我们将会使用另一种相当有用的属性。

  这次,我们将会改变我们的四边形来观察新材质的效果:

  1. 改变使用过的材质为MyMaterial8 并同时改变纹理从2到0.2:

manual->begin("MyMaterial8", RenderOperation::OT_TRIANGLE_LIST);

manual->position(5.0, 0.0, 0.0);

manual->textureCoord(0.0,0.2);

manual->position(-5.0, 10.0, 0.0);

manual->textureCoord(0.2,0.0);

manual->position(-5.0, 0.0, 0.0);

manual->textureCoord(0.2,0.2);

manual->position(5.0, 10.0, 0.0);

manual->textureCoord(0.0,0.0);


  2. 现在创建在材质文件中创建新的材质MyMaterial8。这次,我们不需要任何纹理模式;仅使用纹理terr_rock6.jpg 就可以了:

material MyMaterial8
{
    technique
    {
        pass
        {
            texture_unit
            {
                texture terr_rock6.jpg
            }
        }
    }
}


  3. 编译运行程序。你将会看到我们之前看到的石头纹理的一部分.

 

      我们只能看到纹理的一部分,那是因为我们的四边形的纹理坐标最大到0.2;这意味着五分之四的纹理不能渲染到我们的四边形。这这次“实践时刻”中发生的所有事理解起来应很简单,因为它只是我们目前这章学到东西的重复。如果你不太理解,请重新复习下这章。

 

【滚动一个纹理 】

  现在我们准备好四边形,开始滚动纹理:

  1. 添加下面一行代码到材质文件的纹理设置代码段以滚动纹理:

scroll 0.8 0.8


   2. 编译运行程序。这次,你应看到纹理的不同部分。

  滚动的属性改变了纹理坐标的给定偏移量。下面的图示显示了滚动的效果。右上角是我们所渲染的纹理的第一部分,左下角是滚动属性应用到渲染纹理的一部分。

我们可以在不需要修改模型本身UV坐标系的情况下,来改变纹理坐标的属性。

 

【 动画滚动】

  能够在材质中滚动纹理并不十分惊人。但是它较完整渲染模型来说,可以帮助我们节省一些渲染时间。让我们添加一些动态的滚动。

  我们也使纹理的滚动时动态的。让我们开始实现吧。

 

  1. 创建一个新的材质并改变滚动的属性来使动画滚动:

scroll_anim 0.01 0.01


  2. 记住同样改变使用的manual object的材质;否则,你将看不到任何的改变。 

  3. 编译运行程序。当仔细观察的时,你应发纹理从右上移动到左下角。我不能显示这张图片,因为书上的图片是不能动的(可能未来有可能实现下)。

 

  刚刚我们使用了另一个属性使纹理滚动。除了名字,这个属性同滚动属性的名字几乎相同,但虽不起眼,但重要之区别处在于我们现在设置的偏移量是每秒。

  还有更多我们可操纵的纹理属性。可在http://www.ogre3d.org/docs/manual/manual_17.html#SEC9 找到一个完整的列表。

 

 

【 继承材质 】

  在我们接触像着色器般的复杂话题之前,我们将会尝试从继承材质 

  我们将会创建两个新的材质。我们同样会改变四边形的定义:

 

  1. 对于这个例子,我们需要一个四边形来显示一个纹理。改变四边形的定义使用0到1之间的纹理坐标,并记住改变使用的材质为接下来创建的MyMaterial11:

manual->begin("MyMaterial11", RenderOperation::OT_TRIANGLE_LIST);

manual->position(5.0, 0.0, 0.0);

manual->textureCoord(0.0,1.0);

manual->position(-5.0, 10.0, 0.0);

manual->textureCoord(1.0,0.0);

manual->position(-5.0, 0.0, 0.0);

manual->textureCoord(1.0,1.0);

manual->position(5.0, 10.0, 0.0);

manual->textureCoord(0.0,0.0);

 

manual->index(0);

manual->index(1);

manual->index(2);

manual->index(0);

manual->index(3);

manual->index(1);

manual->end();


  2. 新的材质将会使用岩石材质,并使用rotate_anim 的属性,材质将会以给定的速度旋转。但是最重要的事情是命名纹理单元texture1: 

material MyMaterial11
{
    technique
    {
        pass
        {
            texture_unit texture1
            {
                          texture terr_rock6.jpg
                  rotate_anim 0.1

            }
        }
    }
}


  3. 现在创建第二个四边形,并沿X轴移动15个单位,这样它就不会与第一个四边形相交。同样使用setMaterialName() 函数来改变被实体使用的材质为MyMaterial12: 

ent = mSceneMgr->createEntity("Quad");

ent->setMaterialName("MyMaterial12");

node = mSceneMgr->getRootSceneNode()->createChildSceneNode("Node2",Ogre::Vect

or3(15,0,0));

node->attachObject(ent);


  4. 最后要做的是创建MyMaterial12。我们将会从MyMaterial11继承并设置纹理别名(texture alias)为另一个可使用的纹理:

material MyMaterial12 : MyMaterial11

{

  set_texture_alias texture1 Water02.jpg

}


  5. 编译运行程序,然后你将会看到两个一直旋转的四边形——一个是岩石纹理,另一个是水纹纹理。 

   刚刚我们创建了两个四边形,每个都有其自己的材质。在第一步和第二步中,仅修改四边形的纹理坐标,使其在[0,1]的范围之内。在第二步中,我们创建了四边形的材质并使用新属性rotate_anim x ,这个属性每秒旋转纹理x轴的方向——没有什么复杂。同样我们给纹理单位命名为texture1;稍后我们将使用这个名字。在第三步中,我们创建另一个四边形的实例并使用setMaterialName() 函数改变被实体使用的材质。第四步是重要的一步。这里使用继承创建了一新材质,继承的概念我们应比较熟悉,和C++中的语法是一致的,NewName : ParentName 。在这种情况下,MyMaterial12继承MyMaterial11。然后我们使用了set_texture_alias 属性以绑定纹理Water02.jpg到纹理单元texture1。这种情况下,我们用Water02.jpg代替terr_rock6.jpg。因为这是创建新材质代码中唯一的改变,我们现在可以在这里停止了。 

  纹理别名的使用使我们可以创建很多材质,我们不需要对每个材质从顶层从头写起,只需要改变纹理之中的不同就可以了,而且我们都知道在尽可能的情况下尽量避免重复。

  我们已经了解了关于材质很多的事情,但是我们还需要做很多东西。我们所学已经涵盖了纹理的基础概念,在文档的帮助下,现在应可以理解可在材质中使用的别的大部分属性了。请参考这里http://www.ogre3d.org/docs/manual/manual_14.html#SEC23 。我们将会进一步深入,并学习如何用所谓的shaders编写我们自己的显卡程序。

 

 

【 固定线管和着色器 】

  在这一章,我们一直使用一种称为固定线管的技术。这是一种可在显卡上渲染出很好效果图像的渲染线管技术。正如名字中固定所暗示的那样,并没有为操作者提供太大的操控固定线管的自由。我们可以使用材质文件微调一些参数,但是这没什么意思。这时着色器可以帮助填补这个空白。着色器是可被显卡加载的小程序,而且函数可以作为渲染处理的一部分。这些着色器可被看做以C语言风格,书写虽小,但是很给力的函数程序集。用着色器,我们可以几乎完全控制场景的渲染,而且可以添加仅用固定线管实现不了的新特效。

 

 

【 渲染线管 】

 

  为理解着色器,我们需要对渲染的整个过程原理有个首先的理解。当渲染的时候,我们模型的每个顶点坐标从局部空间变为摄像机空间坐标,然后每个三角形光栅化。这意味着,显卡会计算如何在图像中代替模型。这些图片被称为片段。每个片段随后被处理和操作。我们可以应用纹理的一个特殊部分到这个片段来为模型贴图或者当以一种颜色渲染模型时,我们需要简单赋给它一个颜色。在这种处理过后,显卡测试片段是否被另一更接近摄像机的片段所覆盖,或者它是否是接近摄像机的最近片段。如果是这种情况,这个片段就可以显示在屏幕上。在较新的硬件中,这一步可以在处理片段之前发生。如果在最后的结果中,大部分的场景是不可见的,这种方法可以节省很多计算时间。下面一个简单的图示显示出了线管的步骤:

   对于几乎所有的新一代显卡,新的着色器类型就会被引入。开始的时候是 顶点,像素/片段着色器。顶点着色器的任务是变换顶点到摄像机空间,如果需要的话,可以用任何方式来修改,就像在GPU上做完整的动画。像素和片段着色获得器光栅片段,而且一种别的方式应用纹理或操作它们,比如,对于有像素精度的光源模型。同样存在别的着色器方案,比如几何着色器,但是我们将不会再这本书中做讨论,因为他们是比较新的技术,并不是广泛支持的,而且也超出了这本书的讲述范围。

 

  让我们开始写我们第一个顶点和片段着色器

 

 

   1. 在我们的程序中,我们仅需要改变使用过的材质。改变它为MyMaterial13。同样移除第二个四边形:

 

manual->begin("MyMaterial13", RenderOperation::OT_TRIANGLE_LIST);

  2. 现在我们需要在材质文件中创建这个材质。首先,我们将会定义着色器。Ogre 3D 需要有关着色器的五条信息:

 

l         着色器的名称。

 

l         用哪种语言写的。

 

l         在哪个文件中储存。

 

l         着色器的main函数是如何调用的。

 

l         我们需要编译着色器的哪些文件。

 

 

 

  3. 所有这些信息应该出现在材质文件中。

fragment_program MyFragmentShader1 cg 
{

  source Ogre3DBeginnersGuideShaders.cg   

  entry_point MyFragmentShader1  

  profiles ps_1_1  arbfp1
}


   4. 顶点着色器需要同样的参数,但是我们也需要定义一个从Ogre 3D到着色器的参数。这包含了用于变换四边形到摄像机空间的矩阵。 

 

vertex_program MyVertexShader1 cg
 {

  source Ogre3DBeginnerGuideShaders.cg 

  entry_point MyVertexShader1  

  profiles vs_1_1 arbvp1                    

  default_params                   

  {       
    param_named_auto worldViewMatrix worldviewproj_matrix       
  }
}


  5. 材质本身仅使用顶点和片段着色器的名称来声明它们:

material MyMaterial13
{
    technique
    {
        pass
        { 
                 vertex_program_ref MyVertexShader1   
            {

            }

            fragment_program_ref MyFragmentShader1   
            {

            }
        }
    }
}


  6. 现在我们需要写着色器了。在你Ogre 3D SDK的mediamaterialsprograms文件夹下创建一个名为Ogre3DBeginnersGuideShaders.cg的文件。

 

  7. 每个着色器看起来像一个函数。其中一点不同的是我们可以使用out关键字标识一个参数为传出参数来替代一个默认的传入参数。传出参数是在下一步中渲染线管使用。顶点着色器的传出参数被处理,然后传入一个像素着色器作为一个参数。像素着色器的传出参数是被用于创建最终的渲染结果。记住正确使用函数的名字;否则,Ogre 3D 就找不到它。让我们以片段着色器作为开始,因为它比较简单。

void MyFragmentShader1(out float4 color: COLOR)


  8. 片段着色器将会返回蓝色作为每个像素渲染的颜色。

{
    color = float4(0,0,1,0);
}


  9. 这就是片段着色器;现在我们来到顶点着色器。顶点着色器有三个参数 —— 顶点的位置,顶点变换的位置作为我们的传出变量,并且作为我们用于变换矩阵uniform变量。 

 

void MyVertexShader1(


        float4 position        : POSITION,     


        out float4 oPosition    : POSITION,     


        uniform float4x4 worldViewMatrix)


  10 在着色器内部,我们使用了矩阵和传入的位置来计算传出位置:

 

{
    oPosition = mul(worldViewMatrix, position);
}

  11 编译运行程序。你应看到一四边形,这次它被渲染为蓝色。

 

  在这节发生了很多东西;我们将以第二步作为开始。这里,我们定义了将会使用的片段着色器。正如之前讨论过的,Ogre 3D 需要关于着色器的五条信息。我们用fragment_program关键字作为片段着色器的关键字,紧跟着就是我们给片段程序的函数名称,然后是一个空格,在这行最后,接着是写入所使用的着色器言语。对于程序而言,着色器是用汇编来写的,而且在早期,程序员必须用汇编写着色器代码,因为除了汇编没别的语言可用了。而且,对于一般的编程语言,高级语言的出现使写着色器代码变得容易了。目前,有三种不同的语言可以写入着色器:HLSL,GLSL和CG。着色语言HLSL被DirectX所使用,GLSL是被OpenGL使用的语言。CG语言是Nvidia和微软合作开发,并且它是我们将要使用的语言.这种语言在程序启动时被编译进各自的汇编代码。所以用HLSL写的着色器只能被DirectX支持,并且用GLSL编写的着色器只能被OpenGL所支持。但是CG可以编入DirectX和OpenGL的汇编代码。我们使用它的原因就是想跨平台。着色器的五条信息的两条是Ogre 3D所需要的,另外在大括号给出的三条,他的语法就好像一个属性文件——第一个是主键,然后是值。我们使用的主键后面跟着的是着色器文件的存贮位置。我们不需要给出完整的路径,仅需要文件名,因为Ogre 3D 会扫描我们的目录并且根据需要的文件名来找到文件。

  另一个我们使用的主键是entry_point,紧随其后的是我们将要为着色器使用的函数名称。在代码文件中,我们创建了一个称为MyFragmentShader1的函数,并且我们把这个名称给交给Ogre 3D作为我们片段着色器的入口点。这意味着,每次我们需要片段着色器,这个函数就得到调用。这个函数只有一个传出参数out float4 color : COLOR。这个前缀out表示这个参数是一个传出参数,意思是我们将会写入一个值,这个值将稍后被渲染线管所使用。这个参数的类型被称为float4,表示有四个float值的数组。对于颜色,我们可以认为它是一四元组(r,g,b,a),r代表红色,g代表绿色,b代表蓝色和a代表alpha值:典型的描述颜色的元组。在参数类型之后,我们获得了一个: COLOR。在CG中,这被称作渲染线管上下文中被参数使用的语意描述。参数:COLOR通知渲染线管此为一个颜色。在out关键字的组合下,这成为了一个片段着色器,从渲染线管可以推断出这是此片段着色器的颜色。

     最后一条信息我们提供使用关键字profiles 和它的值ps_1_1 和arbfp1。为便于理解,我们需要谈一些关于着色器历史。每一代的显卡,新一代的着色器被引入进来。它们开始时使用相当简单的没有if条件C语言风格的编程,但现在却用十分复杂而且强大的编程语言。并且现在存在有着色器的不同版本,而且每一个版本都有独特的函数集。Ogre 3D需要知道它使用的是哪个版本。ps_1_1 表示像素着色器的版本是1.1,arbfp1表示片段着色器的版本是1。我们需要两个档案资料,因为ps_1_1是DirectX特有的函数集,而arbfp1 是OpenGL的子函数集。所有的子集可以在http://www.ogre3d.org/docs/manual/manual_18.html 找到。这都需要在材质文件中定义片段着色器。在第三步,我们定义了我们的顶点着色器。这部分和定义的片段着色器代码非常相似;最主要的不同就是default_params那段代码。这段定义了在执行期给着色器的参数。param_named_auto 定义了一个被Ogre 3D自动传给着色器的参数。在这个关键字之后,我们需要给参数一个名称,而且在此之后,我们想要它拥有关键字的值。我们命名参数为worldViewMatrix;别的名字也会同样起作用,并且我们想要它拥有关键字worldviewproj_matrix.这个关键字告诉Ogre 3D我们想要我们的参数拥有WorldViewProjection矩阵的值。这个矩阵的作用是把局部空间的顶点变为摄像机空间的顶点。所有关键字值得列表可以在http://www.ogre3d.org/docs/manual/manual_23.html#SEC128找到。我们如何使用这些值你将会稍后看到。

  在第四步中使用了之前写过的代码。一如往常,我们用一种技术和一个pass定义了我们的材质,我们没有定义一个纹理单元,但是使用了关键字vertex_program_ref。在这个关键字之后,我们需要给它一个定义好的顶点程序的名称,在我们的案例中,这个名称是MyVertexShader1。如果需要,我们可以把更多的参数放进定义中,但是我们并不需要,所以我们仅用大括号来开始和关闭代码段。这个规则对于fragment_program_ref也是适用的。

 

【 写出一个着色器 】

  现在我们定义好了材质文件的所有必要的东西,让我们自己写一个着色器程序。在第六步定义了我们之前讨论过的函数的参数表,所以我们不再深入讲解。第七步定义了函数体;对于片段着色器,函数体很简单。我们创建了一个新的float4元组(0,0,1,0),表示了蓝色并把颜色赋值到参数中。这个效果就是所有用此材质渲染的东西都是蓝色的。所有片段着色器要讲的就这些了,让我们把注意力放到顶点着色器上。在第八步定义了函数的头部。顶点着色器有3个参数——两个使用CG语法标记位置,另一参数是使用float4作为worldViewMatrix值的一个4*4的矩阵。在参数类型定义之前,有uniform的关键字。 �q�;�8����kerning:1.0pt;mso-ansi-language:EN-US;mso-fareast-language:ZH-CN; mso-bidi-language:AR-SA'>r代表红色,g代表绿色,b代表蓝色和a代表alpha值:典型的描述颜色的元组。在参数类型之后,我们获得了一个: COLOR。在CG中,这被称作渲染线管上下文中被参数使用的语意描述。参数:COLOR通知渲染线管此为一个颜色。在out关键字的组合下,这成为了一个片段着色器,从渲染线管可以推断出这是此片段着色器的颜色。

  每次调用我们的顶点着色器,它获得一个新顶点作为输入的位置参数,计算新顶点的位置,保存它到oPosition 参数。这表示每次调用函数,参数都会改变。而worldViewMatrix却不改变。关键字uniform表示参数在每次绘图调用是恒定的。当我们渲染四边形时,worldViewMatrix不改变,而其他参数会因每次顶点着色器所处理顶点的不同而不同。在第九步,创建了顶点着色器的函数体。在函数体中,我们乘以从世界矩阵获得的顶点来得到摄像机空间中的顶点。这个变换的顶点保存在由渲染线管处理的传出参数中。在我们做更多有关着色器实验后,我们会更近一步的了解渲染线管。

 

【 纹理着色 】

  我们用蓝色作为四边形的输出,但我们更愿意用之前的纹理。

  1. 创建一个称为MyMaterial14的新材质。同样创建两个新的称为MyFragmentShader2和MyVertexShader2的着色器。记住复制在材质文件中的定义过的片段和定点着色程序。添加一个岩石纹理单元到材质文件:】

texture_unit
{
  texture terr_rock6.jpg
}


  2. 我们需要添加两个新的参数到我们的片段着色器中。第一个是一关于纹理坐标的float类型的二元组。因此,我们同样使用语意来标志此参数为我们使用的第一个纹理坐标,另一个新的参数的类型是sampler2D,这是纹理的另一个名称。因为纹理不改变的基础上,每一个片段,我们标志为uniform。这个关键字表示参数值来自于外部的CG程序,并且它是由渲染的环境设置的,在当前情况下,是由Ogre 3D设置的。

void MyFragmentShader2(float2 uv       : TEXCOORD0,

            out float4 color : COLOR,

            uniform sampler2D texture)


  3. 在片段着色器中,把颜色赋值用下面一行代码替代。

color = tex2D(texture, uv);


  4. 在顶点着色器中同样需要一些新的参数——一个float2来接受传入的纹理坐标,一个float2作为传出纹理坐标。两个纹理坐标都是TEXCOORD0,因为传入的坐标和另一个传出的坐标都为TEXCOORD0:

void MyVertexShader2(

      float4 position        : POSITION,

      out float4 oPosition    : POSITION,

      float2 uv       : TEXCOORD0,

      out float2 oUv       : TEXCOORD0,

      uniform float4x4 worldViewMatrix)


  5. 在函数体中,我们计算顶点的传出坐标:

oPosition = mul(worldViewMatrix, position);


   6. 对于纹理的坐标,我们赋值传入的值到传出:

 oUv = uv;

  7. 记住改变在程序中材质的代码,然后编译运行它。你将会看到四边形有一个岩石的纹理。 

  在第一步中仅给一纹理单元添加了岩石纹理,没什么特别。在第二步中添加了一个float2来保存纹理坐标信息;同样我们第一次使用了sampler2D。sampler2D是一个二维纹理查找函数的名称,而且因为它没有改变每个片段,而且来自于外部的CG程序,我们声明它为uniform。在第三步中,使用了tex2D函数,此函数使用接收一个sampler2D和float2参数,并返回一个类型为float4的颜色。这个函数使用float2作为位置来检索sampler2D对象的颜色并返回这个颜色。基本上,它仅是用给定的纹理坐标在纹理中查找。在第四步中,添加两个纹理坐标到顶点着色器——一个是传入参数,一个是传出参数。在第五步赋值传入参数到传出参数。这些奇妙的处理发生在渲染线管中。 

 

【 在渲染线管中发生了什么?】

   我们的顶点着色器获取每个顶点并变换它到摄像机空间。在所有的顶点通过这种变换之后,渲染线管了解到哪个顶点来自三角形,然后光栅化它们。在这个过程中,三角形被分为很片段。每个片段都成为屏幕上显示像素区域的缓冲区域。如果它们没有被别的片段所覆盖的话,它们将会显示出来。反之,就会看不到。在此过程中,渲染线管插入顶点数据,就如纹理坐标遍历每个片段一样。在此过程之后,每个片段都有了自己纹理坐标,而且我们使用这种方法在查找纹理中的颜色值。每个片段有自己的纹理坐标。这也显示出我们如何把纹理坐标关联到点的。在现实世界中,这种依附于渲染线管的方式是可变的,虽说这种模型有助于我们理解,但也不是百分百准确的。

  当赋值给每个顶点一个颜色时,我们使用了相同的修改。现在让我们更深入的了解这种效果吧。

 

【 让英雄动起来 ——把颜色和纹理坐标联合起来 】

分别创建一个新的MyVertexShader3顶点着色器和称为MyFragmentShader3片段着色器。片段着色器应该渲染所有的东西为绿色,顶点着色器应计算出在摄像机空间顶点的位置,并简单的把纹理坐标传递给片段着色器。片段着色器目前还对它们做不了任何事,但是我们稍后会用到它。

【 改变颜色 】

  为了使修改的效果看起来更明显,让我们以颜色代替纹理。

  研究一下颜色的修改是如何起作用的,我们现在需要修改一下代码。

  1. 同样的,复制关于材质文件并确保校正了所有的名字。 

  2. 我们唯一需要做的就是在材质文件中删除我们不需要的纹理。我们可以直接删除它。

  3. 在程序的代码中,我们需要以color() 函数来代替textureCoord()函数 

manual->position(5.0, 0.0, 0.0);

manual->color(0,0,1);

manual->position(-5.0, 10.0, 0.0);

manual->color(0,1,0);

manual->position(-5.0, 0.0, 0.0);

manual->color(0,1,0);

manual->position(5.0, 10.0, 0.0);

manual->color(0,0,1);


  4. 在顶点着色器中也需要一些修改。用颜色参数替换两个纹理坐标的参数并同样修改赋值的那一行: 

void MyVertexShader4(

        float4 position        : POSITION,

        out float4 oPosition    : POSITION,

           float4 color    :COLOR,

           out float4 ocolor    :COLOR,   

        uniform float4x4 worldViewMatrix)

{

    oPosition = mul(worldViewMatrix, position);

      ocolor = color;

}


  5 片段着色器现在有两个颜色参数 —— 一个是传入参数和另一个传出参数:

void MyFragmentShader4(  float4 color : COLOR,       out float4 oColor : COLOR)
{

    oColor = color;

}


   6. 编译并运行程序。你可以看到一个右边是蓝色,左边是绿色,中间为渐变的四边形。

   在第三步,我们看到另一个manual对象的成员函数,使用三个浮点型的代表红色,绿色和蓝色来添加颜色到一个顶点。第四步中替换纹理坐标为一个颜色参数 —— 这次我们想要的是颜色而非纹理。第五步也是同理的。这个例子并不是特别困难和令人感到惊喜,但是它显示出修改是如何起作用的。这会使我们对顶点和片段着色器有个更好的理解。

 

【 用模型替代四边形 】

   四边形作为实验的例子显得略微无聊,那么让我们用Sinbad的模型来替换他。

  使用之前的代码我们将会使用Sinbad来代替四边形。 

 

  1. 删除关于创建四边形的所有代码;仅仅留着创建场景结点的代码。

  2. 创建一个Sinbad.mesh的实例,并管理他到场景结点,并使用MaterialManager(材质管理器)来设置实体的材质到MyMaterial14:

void createScene()

{

  Ogre::SceneNode* node = mSceneMgr->getRootSceneNode()-

>createChildSceneNode("Node1");

  Ogre::Entity* ent = mSceneMgr->createEntity("Entity1","Sinbad.

mesh");

  ent->setMaterialName("MyMaterial14");

  node->attachObject(ent);

}


  3. 编译并运行程序;因为MyMaterial14使用了颜色纹理,Sinbad将会看起来是用岩石铸成的。

  这里刚刚添加的代码对于现在你来说应该已经轻车熟路了。我们创建了一个模型的实例,并关联他到场景结点上,然后改变材质为MyMaterial14。

 

【 使模型在X轴上振动 】

  目前为止,我们仅仅使用了片段着色器。现在是时候让我们使用顶点着色器了。

  添加一个振动到我们的模型是十分简单的并且仅需要改变一些我们的代码。

 

  1. 这次,我们仅需要一个新的顶点着色器因为我们将会使用已有的片段着色器。创建一个名为MyVertexShader5新的顶点着色器并在新的材质MyMaterial17使用它,仅使用MyFragmentShader2,是因为这个着色器在我们的模型上添加了纹理。

material MyMaterial17
{
  technique
  {

      pass

      { 

    vertex_program_ref MyVertexShader5

          {

          }

        fragment_program_ref MyFragmentShader2

          {

          }

   
    texture_unit
          {
              texture terr_rock6.jpg
          }
      }
  }


  2. 这个新的顶点着色器和我们之前看到过的着色器近乎相同;仅仅在default_params代码段中添加了一个新的参数,称为pulseTime。它能从时间关键字中获取值:】

vertex_program MyVertexShader5 cg
 {

  source Ogre3DBeginnerGuideShaders.cg 

  entry_point MyVertexShader5

  profiles vs_1_1 arbvp1                    

  default_params                   

  {       

    param_named_auto worldViewMatrix worldviewproj_matrix 

    param_named_auto pulseTime time   

  }
}


  3. 我们不需要修改程序本身的代码(译者注:实际需要修改一下setMaterialName的参数)。我们所需要做的最后一件事就是创建一个新的顶点着色器。MyVertexShader5是基于MyVertexShader3的。仅添加新的一行来使oPosition变量的x值乘以(2+sin(pulseTime)):

void MyVertexShader5( uniform float pulseTime, float4 position : POSITION,  out float4 oPosition : POSITION, float2 uv : TEXCOORD0, out float2 oUv : TEXCOORD0, uniform float4x4 worldViewMatrix)
{
	oPosition = mul(worldViewMatrix, position);
	oPosition.x *= (2+sin(pulseTime));
	oUv = uv;
}






  4. 编译运行程序。你将会看到Sinbad在x轴方向振动,振幅在他的普通宽度和三倍宽度之间

  我们使得模型在X轴上有了个振动。我们需要给顶点着色器包含目前时间的第二个参数。我们使用了时间的正弦值以得到1到3之间的值(译注:1~3是由 sin范围的[-1,1]+2 = [1,3]得来),使得我们模型顶点的x部分乘以变化的正弦值。创建了一个振动的效果。使用这种技术,我们可以传递任何信息到一个着色器中,以此来修改它的行为。这是在很多游戏中已使用特效的一个原型。

 

 概要 

  我们这章学习了很多有关材质和Ogre 3D的知识。

具体来说,我们的内容涵盖了:

l         如何创建新的材质。

l         如何使用材质应用到实体上。

l         如何创建着色器和如何在材质文件中引用它们。

l         渲染线管是如何工作的和如何使用顶点着色器来修改几何模型。

 

  在下一章,我们将会创建post-processing效果以提升我们场景的视觉质量和创建崭新的视觉风格。

原文地址:https://www.cnblogs.com/SunkingYang/p/11049202.html