从零开始游戏开发——3.5 纹理基础

  之前的小节,我们显示了使用木箱子外观的三角形,纹理可以极大丰富物体的表现,在这节中,我们将介绍一张图像是如何做为纹理进行显示的,最终实现下图效果:

首先,我们拥有一张.tga格式的图片,tga文件头结构如下:

 1 #pragma pack(1)
 2 typedef struct
 3 {
 4     char identsize;                   // Size of ID field that follows header (0)
 5     char colorMapType;               // 0 = None, 1 = paletted
 6     char imageType;                   // 0 = none, 1 = indexed, 2 = rgb, 3 = grey, +8=rle
 7     unsigned short colorMapStart;  // First colour map entry
 8     unsigned short colorMapLength; // Number of colors
 9     unsigned char colorMapBits;       // bits per palette entry
10     unsigned short xstart;           // image x origin
11     unsigned short ystart;           // image y origin
12     unsigned short width;           // width in pixels
13     unsigned short height;           // height in pixels
14     char bits;                       // bits per pixel (8 16, 24, 32)
15     char descriptor;               // image descriptor
16 } TGAHEADER;
17 #pragma pack(pop)

第1行和第17行的作用将头结构按1字节内存对齐,然后进行文件读取,获取到图像的颜色数据:

 1 bool CTGAImage::Load(const char *filename)
 2 {
 3     FILE *pFile;  // File pointer
 4     short sDepth; // Pixel depth;
 5 
 6     // Attempt to open the file
 7     pFile = fopen(filename, "rb");
 8     if (pFile == NULL)
 9     {
10         printf("Openfile %s failed\n", filename);
11         return false;
12     }
13     // Read in header (binary)
14     fread(&_TgaHeader, 18 /* sizeof(TGAHEADER)*/, 1, pFile);
15 
16     // Get width, height, and depth of texture
17     _Width = _TgaHeader.width;
18     _Height = _TgaHeader.height;
19     sDepth = _TgaHeader.bits / 8;
20 
21     // Put some validity checks here. Very simply, I only understand
22     // or care about 8, 24, or 32 bit targa's.
23     if (_TgaHeader.bits != 8 && _TgaHeader.bits != 24 && _TgaHeader.bits != 32)
24     {
25         return false;
26     }
27     // Calculate size of image buffer
28     _ImageSize = _TgaHeader.width * _TgaHeader.height * sDepth;
29 
30     // Allocate memory and check for success
31     _pData = (unsigned char *)malloc(_ImageSize * sizeof(unsigned char));
32     if (_pData == NULL)
33     {
34         return false;
35     }
36     // Read in the bits
37     // Check for read error. This should catch RLE or other
38     // weird formats that I don't want to recognize
39     if (fread(_pData, _ImageSize, 1, pFile) != 1)
40     {
41         free(_pData);
42         _pData = NULL;
43         return false;
44     }
45 
46     // Set OpenGL format expected
47     switch (sDepth)
48     {
49 #ifndef OPENGL_ES
50     case 3: // Most likely case
51         _Format = BGR;
52         break;
53 #endif
54     case 4:
55         _Format = BGRA;
56         break;
57     case 1:
58         _Format = LUMINANCE;
59         break;
60     default: // RGB
61                 // If on the iPhone, TGA's are BGR, and the iPhone does not
62                 // support BGR without alpha, but it does support RGB,
63                 // so a simple swizzle of the red and blue bytes will suffice.
64                 // For faster iPhone loads however, save your TGA's with an Alpha!
65 #ifdef OPENGL_ES
66         for (int i = 0; i < lImageSize; i += 3)
67         {
68             GLbyte temp = pBits[i];
69             pBits[i] = pBits[i + 2];
70             pBits[i + 2] = temp;
71         }
72 #endif
73         break;
74     }
75 
76     // Done with File
77     fclose(pFile);
78 
79     return false;
80 }

上面的代码读取8位、16位和32位图像的头信息并进行赋值,然后获取实现的颜色数据地址,根据头信息中的位数,获得图像的模式,由于iPhone不支持BRG格式,这里对gles下的使用作了RGB的转换。现在已经有用于显示的图像的颜色数据,现在定义一个用于软件渲染的纹理类如下,类中Image类包含了一张图像的头信息和颜色数据,在模型初始化的阶段,将这张纹理做为渲染器的输入数据进行传递,同时顶点数据需要传入纹理坐标。

1 class CSoftTexture2D : public ITexture
2 {
3 public:
4     CSoftTexture2D(const char *fullName);
5     Color GetPixel(const Vector2f &uv) const;
6     Color GetPixel(int x, int y) const;
7 private:
8     IImage *_Image;
9 };

在渲染过程中的片断着色器处理阶段,通过使用插值计算得到当前片断的uv值,就可以对纹理数据进行采样了,我们的Shader代码如下:

 1 typedef map<string, vector<float>> UniformMap;
 2 
 3 static void VertexShader(const void *globalUniforms, const void *uniforms, const void *datas, unsigned char *out)
 4 {
 5     UniformMap globalU = *((UniformMap *)globalUniforms);
 6     UniformMap u = *((UniformMap *)uniforms);
 7     Matrix4x4f mvpMat4(u["mvpMat"].data());
 8 
 9     const Vector3f &inPosition = *(Vector3f *)(datas);
10     const Vector2f &inUV = *(Vector2f *)(&inPosition + 1);
11 
12     Vector4f &outPosition = *(Vector4f *)out;
13     Vector2f &outUV = *(Vector2f *)(&outPosition + 1);
14     outPosition = mvpMat4 * Vector4f(inPosition.x, inPosition.y, inPosition.z, 1.0f);
15     outUV = inUV;
16 }
17 
18 REGISTER_VPROGRAM(VertexShader);
19 
20 static Color FragmentShader(const void *globalUniforms, const void *uniforms, ISampler **samplers, const void *datas)
21 {
22     Vector2f &inUV = V2F_UV(datas);
23 
24     ISampler *sampler1 = samplers[0];
25     Color albedo = sampler1->Sample(inUV);
26     return albedo;
27 }
28 
29 REGISTER_FPROGRAM(FragmentShader);

在FragmentShader代码中,第25行使用采样器进行采样,在不考虑多重采样等情况下 ,对于BGR格式的图像最简单的采样代码如下,这样,模型上就可以显示出带纹理的外观了。

int width = _Image->GetWidth();
int height = _Image->GetHeight();
if (x >= 0 && x < width && y >=0 && y < height)
{
    unsigned char *data = (unsigned char*)_Image->GetData();
    unsigned char *addr = data + width * y * 3 + x * 3;
    float inv = 1.f / 255.f;
    return Color(1.f, *(addr + 2) * inv, *(addr + 1) * inv, (*addr) *inv );
}

  上面过程介绍了一张图像从加载到显示的过程,展示了纹理的简单应用,然后纹理的作用远不止如此,上面介绍的的是一张2D纹理的使用,在游戏中,纹理还有1D纹理、3D纹理、Cube纹理。1D纹理在卡通渲染中明暗过滤处理比较常见,3D纹理则相当于2D纹理叠在了一起,在体积渲染和一些drawcall合批优化中可以看到相关应用,Cube纹理则可以在天空盒、环境映射中看到它的身影。

  通常对一张纹理进行采样,其Wrap Mode通常有Repeat、Clamp、Mirror几种,Wrap Mode是指当纹理坐标是Normalized即0~1时,uv超出这个范围的处理,下图是几种模式的效果图:

上面的例子中,我们没有处理纹理过滤的情况,由于纹理坐标与屏幕分辨率无关,纹理尺寸与模型尺寸的对应需要纹理过滤来处理这种适应关系,通常用到的有Nearest——取最邻近的像素值和Linear——获取附近像素加权平均值,同时当一个模型距离摄像机很远时,可以对纹理使用Mipmap来避免远处的闪烁现象,一张32*32的纹理其Mipmap级别从0到5分别为32*32、16*16、8*8、4*4、2*2、1*1。如果一张纹理启用了Mipmap其占用内存大小约增加1/3,推导如下:

  设S = 1/4 + 1/16 + ... + 1/(4^n) 为增加的内存大小,

  1 + 1/4 + 1/16 + ... + 1/(4^(n-1)) == 4S 为总占用内存大小,

  4S - S = 3S = 1 - 1/(4^n)

  n趋向于无穷大时,3S = 1,因此S = 1/3。

  本节介绍了纹理的基础知识,后面介绍完光照后,将继续介绍凹凸映射、环境映射等高级技术,总之,游戏开发很多渲染表现都需要用到纹理相关技术。

posted @ 2022-10-15 15:27  毅安  阅读(198)  评论(0编辑  收藏  举报