草庐IT

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

Primary Code 2023-03-28 原文

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

首先,我们拥有一张.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。

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

有关从零开始游戏开发——3.5 纹理基础的更多相关文章

  1. ruby - 使用 C 扩展开发 ruby​​gem 时,如何使用 Rspec 在本地进行测试? - 2

    我正在编写一个包含C扩展的gem。通常当我写一个gem时,我会遵循TDD的过程,我会写一个失败的规范,然后处理代码直到它通过,等等......在“ext/mygem/mygem.c”中我的C扩展和在gemspec的“扩展”中配置的有效extconf.rb,如何运行我的规范并仍然加载我的C扩展?当我更改C代码时,我需要采取哪些步骤来重新编译代码?这可能是个愚蠢的问题,但是从我的gem的开发源代码树中输入“bundleinstall”不会构建任何native扩展。当我手动运行rubyext/mygem/extconf.rb时,我确实得到了一个Makefile(在整个项目的根目录中),然后当

  2. Ruby Sinatra 配置用于生产和开发 - 2

    我已经在Sinatra上创建了应用程序,它代表了一个简单的API。我想在生产和开发上进行部署。我想在部署时选择,是开发还是生产,一些方法的逻辑应该改变,这取决于部署类型。是否有任何想法,如何完成以及解决此问题的一些示例。例子:我有代码get'/api/test'doreturn"Itisdev"end但是在部署到生产环境之后我想在运行/api/test之后看到ItisPROD如何实现? 最佳答案 根据SinatraDocumentation:EnvironmentscanbesetthroughtheRACK_ENVenvironm

  3. ruby - 是否可以覆盖 gemfile 进行本地开发? - 2

    我们的git存储库中目前有一个Gemfile。但是,有一个gem我只在我的环境中本地使用(我的团队不使用它)。为了使用它,我必须将它添加到我们的Gemfile中,但每次我checkout到我们的master/dev主分支时,由于与跟踪的gemfile冲突,我必须删除它。我想要的是类似Gemfile.local的东西,它将继承从Gemfile导入的gems,但也允许在那里导入新的gems以供使用只有我的机器。此文件将在.gitignore中被忽略。这可能吗? 最佳答案 设置BUNDLE_GEMFILE环境变量:BUNDLE_GEMFI

  4. ruby - 在 Windows 机器上使用 Ruby 进行开发是否会适得其反? - 2

    这似乎非常适得其反,因为太多的gem会在window上破裂。我一直在处理很多mysql和ruby​​-mysqlgem问题(gem本身发生段错误,一个名为UnixSocket的类显然在Windows机器上不能正常工作,等等)。我只是在浪费时间吗?我应该转向不同的脚本语言吗? 最佳答案 我在Windows上使用Ruby的经验很少,但是当我开始使用Ruby时,我是在Windows上,我的总体印象是它不是Windows原生系统。因此,在主要使用Windows多年之后,开始使用Ruby促使我切换回原来的系统Unix,这次是Linux。Rub

  5. ruby-on-rails - 在 Rails 开发环境中为 .ogv 文件设置 Mime 类型 - 2

    我正在玩HTML5视频并且在ERB中有以下片段:mp4视频从在我的开发环境中运行的服务器很好地流式传输到chrome。然而firefox显示带有海报图像的视频播放器,但带有一个大X。问题似乎是mongrel不确定ogv扩展的mime类型,并且只返回text/plain,如curl所示:$curl-Ihttp://0.0.0.0:3000/pr6.ogvHTTP/1.1200OKConnection:closeDate:Mon,19Apr201012:33:50GMTLast-Modified:Sun,18Apr201012:46:07GMTContent-Type:text/plain

  6. 世界前沿3D开发引擎HOOPS全面讲解——集3D数据读取、3D图形渲染、3D数据发布于一体的全新3D应用开发工具 - 2

    无论您是想搭建桌面端、WEB端或者移动端APP应用,HOOPSPlatform组件都可以为您提供弹性的3D集成架构,同时,由工业领域3D技术专家组成的HOOPS技术团队也能为您提供技术支持服务。如果您的客户期望有一种在多个平台(桌面/WEB/APP,而且某些客户端是“瘦”客户端)快速、方便地将数据接入到3D应用系统的解决方案,并且当访问数据时,在各个平台上的性能和用户体验保持一致,HOOPSPlatform将帮助您完成。利用HOOPSPlatform,您可以开发在任何环境下的3D基础应用架构。HOOPSPlatform可以帮您打造3D创新型产品,HOOPSSDK包含的技术有:快速且准确的CAD

  7. 【鸿蒙应用开发系列】- 获取系统设备信息以及版本API兼容调用方式 - 2

    在应用开发中,有时候我们需要获取系统的设备信息,用于数据上报和行为分析。那在鸿蒙系统中,我们应该怎么去获取设备的系统信息呢,比如说获取手机的系统版本号、手机的制造商、手机型号等数据。1、获取方式这里分为两种情况,一种是设备信息的获取,一种是系统信息的获取。1.1、获取设备信息获取设备信息,鸿蒙的SDK包为我们提供了DeviceInfo类,通过该类的一些静态方法,可以获取设备信息,DeviceInfo类的包路径为:ohos.system.DeviceInfo.具体的方法如下:ModifierandTypeMethodDescriptionstatic StringgetAbiList​()Obt

  8. Observability:从零开始创建 Java 微服务并监控它 (二) - 2

    这篇文章是继上一篇文章“Observability:从零开始创建Java微服务并监控它(一)”的续篇。在上一篇文章中,我们讲述了如何创建一个Javaweb应用,并使用Filebeat来收集应用所生成的日志。在今天的文章中,我来详述如何收集应用的指标,使用APM来监控应用并监督web服务的在线情况。源码可以在地址 https://github.com/liu-xiao-guo/java_observability 进行下载。摄入指标指标被视为可以随时更改的时间点值。当前请求的数量可以改变任何毫秒。你可能有1000个请求的峰值,然后一切都回到一个请求。这也意味着这些指标可能不准确,你还想提取最小/

  9. postman接口测试工具-基础使用教程 - 2

    1.postman介绍Postman一款非常流行的API调试工具。其实,开发人员用的更多。因为测试人员做接口测试会有更多选择,例如Jmeter、soapUI等。不过,对于开发过程中去调试接口,Postman确实足够的简单方便,而且功能强大。2.下载安装官网地址:https://www.postman.com/下载完成后双击安装吧,安装过程极其简单,无需任何操作3.使用教程这里以百度为例,工具使用简单,填写URL地址即可发送请求,在下方查看响应结果和响应状态码常用方法都有支持请求方法:getpostputdeleteGet、Post、Put与Delete的作用get:请求方法一般是用于数据查询,

  10. 软件测试基础 - 2

    Ⅰ软件测试基础一、软件测试基础理论1、软件测试的必要性所有的产品或者服务上线都需要测试2、测试的发展过程3、什么是软件测试找bug,发现缺陷4、测试的定义使用人工或自动的手段来运行或者测试某个系统的过程。目的在于检测它是否满足规定的需求。弄清预期结果和实际结果的差别。5、测试的目的以最小的人力、物力和时间找出软件中潜在的错误和缺陷6、测试的原则28原则:20%的主要功能要重点测(eg:支付宝的支付功能,其他功能都是次要的)80%的错误存在于20%的代码中7、测试标准8、测试的基本要求功能测试性能测试安全性测试兼容性测试易用性测试外观界面测试可靠性测试二、质量模型衡量一个优秀软件的维度①功能性功

随机推荐