o3d教程4 - 纹理映射

来源:互联网 发布:教皇的权力 知乎 编辑:程序博客网 时间:2024/05/29 18:24

这一章讲纹理,纹理,不能顾名思义了,其实就是一张图片,我们要做的就是把这张图片贴到模型上面,从而让模型一下子生动起来,说得有文采点就是栩栩如生,这个过程就叫纹理映射(也可以叫贴图)。纹理映射有时候能产生非常神奇的效果,比如说凹凸贴图,和凹凸贴图衍生出来的法线贴图,能够让只有几百个几千个多边形的模型产生几万个,几十万个多边形的模型的效果。

 

         接下来就要介绍怎么创建一个纹理采样器(texture sampler),如何设置采样的属性,然后把一张图片(纹理)成功地贴到那个立方体上面,图片的格式可以是TGAJPEGPNGDDS,其中像TGA,PNG这样的格式是支持半透明的,可以用来模拟阴影(当然只是简单地模拟)。

        

纹理采样器负责怎么把一张图片上的各个像素映射到模型的像素上,反过来说,就是为每个像素选取纹理像素。看似很简单,但是当显示的图片大于(或小于)要覆盖的那个面时,就得就结了,这个不能一一映射了,那得怎么做呢,这是门学问,我们可以通过设置这个纹理采样器的各个属性来达到较好的效果。

Address Mode

首先是addressModeUaddressModeV属性,这两个属性决定了当纹理坐标超出(01)后该怎么做。纹理坐标,顾名思义,就是在纹理上的坐标,这是我们截取纹理中的一部分是要用到的。它的范围是(01)。其中左下角的坐标是(0.0 , 1.0),右上角的坐标是(1.0 , 1.0)


         这两个属性有下面四种可能值

        

Value

Meaning

WRAP

(默认为此值)重复贴图来填充贴图区域

MIRROR

重复贴图,当遇到UV的边界时反向(UV边界为0.0或者1.0

CLAMP

用贴图的最后一行像素,重复覆盖没贴到的区域

BORDER

超过的贴图区域,用一种特殊颜色的边标记出来

        

 

Minification Filter

        

Minificationfilter,minFilter这个属性具体说明了怎么把纹理贴到一个像素比它少的模上面(这个上面有提到过),这个属性可能有下面3个值



Value

Meaning

POINT

用最近的那个像素

LINEAR

线性插值

ANISOTROPIC

各向异性过滤

 

这三个滤镜效果是单调递增的,具体是怎么实现的,这里不多讲了(其实也不会讲-  -),想了解的话可以去看图像处理方面的书籍。。。。

MipmapFilter

    由于处理缩小纹理比处理放大纹理复杂多了,如果只是简单地取最近的那个像素,或者说只是和这个像素的周围几个像素简单的比较一下,获得平均值就插进去的话,会严重降低图片缩小后的质量,而如果去周围大量的像素,做大量的计算又会严重降低程序效率。于是有才的人就想到了mipmap这个方法。它先将这张图片按原来尺寸一半一半地压缩,举个例子,原来的是64*64的图片,就压缩成一张32*32的,一张16*16的,一张8*8的......当这张图片需要映射成20*20的大小的,就取32*3216*16这两张图片来计算出20*20大小的图片,这比只是计算单张图片的效率和效果要好得多.

    下面是这个参数可能的值


Value ofmipFilter

Which Mipmap Is Used

NONE

不用mipmap

POINT

取与需要映射的大小最近的那张图片,然后依据上面的minification filter进行过滤

LINEAR

取与需要映射的大小最近的两张图片,为每张图片按照上面的miniFilter进行过滤,然后把这两张图片一起进行线性插值等到最终的图片

   MagnificationFilter

    放大纹理的处理就比较简单了,有下面两种可能的值。

        

Value

Meaning

POINT

Use the closest pixel.

LINEAR

Perform a linear interpolation between neighboring pixels and use the result.

   

 minFilter差不多。

   BorderColor

    当前面的address mode设成border时,这个属性就派上用场了,它可以用来设置那个边框的颜色。

   Anisotropy

    当前面的minFilterAnisotropy时,这个值就是决定了各向异性过滤的质量。

        

    这些属性介绍完了,下面就举个实例,把那张求是潮的logo贴到那个立方体上。

    首先要建立一个texture sampler,同shape material 一样,这也是一个Object,非常好地体现了"面向对象"的思想。

 

g_sampler =g_pack.createObject('Sampler');
g_sampler.minFilter= g_o3d.Sampler.ANISOTROPIC;
g_sampler.maxAnisotropy= 4;



    可以看到第一句就是建立sampler对象,后面两句就是设置上面提到的属性,minFilter设为各向异性过滤,maxAnisotropy设为4

 

    这样一个简单的sampler就建立了,但是比较打击人的是这个还只是设了下最基本的参数,这个参数设了给谁用呢,又是在哪里把图片贴到那个模型上面去的呢

        

    ~o3d里面纹理贴图就是在pixelshader里面用了一个函数tex2D把图片像素一个个根据上面设好的各个参数填进已经经过vertex shader变换和primitive assembly(就是shape一章里讲过的把各个顶点按照一定规则连接成图元的操作)了的东西里面。而传给tex2D的参数有两个,一个是一开始提到的纹理坐标(texCoord),还有一个就是采样器的各个参数了(这里面也包括了整个纹理素材),要把js中设的参数传到shader里面,o3d中的做法是先在shader里面建一个变量,比如texSampler0,然后在js中用materialgetParam方法获取这个texSampler0变量,具体的如下:

 

effect.createUniformParameters(material);   //这句话是不可少的,否则就不能获取shader里面设的变量了

var samplerParam = material.getParam('texSampler0');

samplerParam.value = g_sampler;            //g_sampler的参数赋给samplerParam


    这里的material effect就是前面几章为那个立方体建立的材质对象。所以可把上面几句代码加在原来的建立materialeffect的代码的后面(具体的shader代码在最后放出来)

 

    下面要做的是把纹理资源载入内存。

o3djs.io.loadTexture(g_pack,textureUrl, function(texture) {
         // set the texture on the sampler object to the newly created texture
      // object returnedby the request.
      g_sampler.texture = texture; //
可以看到这里把纹理资源也附到sampler了以供shader使用
})


其中function(texuture)。。。。。是回调函数,这里声明成匿名函数了(js非常好用的一个特性),如果要处理的量比较大的话,可以独立出来写成一个函数。也可以在其它纹理载入的时候使用。textureURL顾名思义就是图片地址了。

        

    最后是声明各个顶点的纹理坐标,前面说到过要传给shader的,这个和声明顶点坐标差不多(顶点坐标数组也得相应的改过,不能几个面公用同一个顶点了,具体的建sample里的代码吧,再放出来就太长了= =

 

var texCoordsArray = [
    0, 0,
    1, 0,
    1, 1,
    0, 1,
    0, 0,
    1, 0,
    1, 1,
    0, 1,
    1, 1,
    0, 1,
    0, 0,
    1, 0,
    0, 0,
    1, 0,
    1, 1,
    0, 1,
    0, 0,
    1, 0,
    1, 1,
    0, 1,
    0, 0,
    1, 0,
    1, 1,
    0, 1
  ];

var texCoordsBuffer = g_pack.createObject('VertexBuffer');
var texCoordsField = texCoordsBuffer.createField('FloatField', 2);
texCoordsBuffer.set(texCoordsArray);

streamBank.setVertexStream(
   g_o3d.Stream.TEXCOORD,  // semantic
   0,                     // semantic index
   texCoordsField,         // field
   0);                    // start_index

 

不多做解释了。

 

最后的最后:

Shader代码

// World View Projection matrixthat will transform the input vertices
  // to screen space.
  float4x4 worldViewProjection : WorldViewProjection;

  // The texture sampler is used to access the texturebitmap in the fragment
  // shader.
  sampler texSampler0;

  // input for our vertex shader
  struct VertexShaderInput {
    float4 position : POSITION;
    float2 tex : TEXCOORD0;  // Texturecoordinates
  };

  // input for our pixel shader
  struct PixelShaderInput {
    float4 position : POSITION;
    float2 tex : TEXCOORD0;  // Texturecoordinates
  };

  /**
   * The vertex shader simply transforms the inputvertices to screen space.
   */
  PixelShaderInput vertexShaderFunction(VertexShaderInputinput) {
    PixelShaderInput output;

    // Multiply the vertex positions by theworldViewProjection matrix to
    // transform them to screen space.
    output.position = mul(input.position,worldViewProjection);

    output.tex = input.tex;
    return output;
  }

 /**
  * Given the texture coordinates, our pixel shader grabsthe corresponding
  * color from the texture.
  */
  float4 pixelShaderFunction(PixelShaderInput input):COLOR {
    return tex2D(texSampler0, input.tex);
  }

  // Here we tell our effect file *which* functions are
  // our vertex and pixel shaders.

  // #o3d VertexShaderEntryPoint vertexShaderFunction
  // #o3d PixelShaderEntryPoint pixelShaderFunction
  // #o3d MatrixLoadOrder RowMajor

 

Shader的代码就不解释了,下一章会比较详细地讲下shadinglanguage和图形渲软管线到底是怎么样的,下下章可能会讲光照,光照就是都在shader里面计算了,这个纯考数学物理知识啊,不过幸好已经有前人弄好的公式给我们套了。然后就是讲下阴影(shadow)的生成了,注意的是并不是有光照就会自然而然地产生阴影了,阴影的处理是游戏中比较关键的,也是很占资源的一个地方。阴影处理的好的话游戏的真实性倍增啊啊。。。。

还有要说的就是shapes和这章的纹理映射以后不会这么详细地用到,不会让你一个个顶点地去定义,更多的是载入一个用3d软件诸如3DMAX maya做的模型,或者是用自带的函数简单地创个球,毕竟像这次这样创建一个带有贴图的立方体会让人崩溃的。

 

刚刚在整sample的时候发现纹理素材载入的时候总是出现未知错误,折腾了半天都没找到原因,后来复制到别的地方发现又莫名其妙地可以用了,总算找到了原因:路径中不能有中文,哎,感叹一下,其实很多游戏安装路径中也是不允许有中文的= =

原创粉丝点击