1. 纹理映射:从“贴纸”到“皮肤”的魔法
如果你玩过模型,比如拼装高达或者给汽车模型上色,你肯定知道,一个光秃秃的塑料模型和最终成品之间,差的就是那些精美的贴纸和喷漆。在OpenGL的3D世界里,纹理(Texture)干的就是这个“贴纸”的活儿,但它远比贴纸强大和智能。
想象一下,你想在屏幕上画一个木箱。最笨的办法是什么?用成千上万个不同颜色的顶点,一点点拼出木头的纹路。这就像用像素点去画蒙娜丽莎,不是不行,但效率低到令人发指,而且模型文件会变得巨大无比。纹理映射的出现,完美解决了这个问题。它允许我们把一张现成的、高分辨率的图片(比如一张高清的木纹照片)“贴”到一个简单的几何模型(比如一个只有8个顶点的立方体)表面。这样一来,模型瞬间就拥有了丰富的表面细节,而计算负担却只增加了一点点。
这个过程的核心,就是建立模型顶点与图片像素之间的对应关系。我们为模型的每个顶点指定一对纹理坐标(Texture Coordinates),通常用 (s, t) 或 (u, v) 表示,范围在0到1之间。你可以把整张纹理图片想象成一个边长为1的正方形,左下角是(0,0),右上角是(1,1)。那么,一个立方体的某个面的四个顶点,其纹理坐标可能就是(0,0), (1,0), (1,1), (0,1),正好对应图片的四个角。在渲染时,OpenGL会为顶点之间的每一个片段(Fragment,你可以理解为最终屏幕上的一个像素候选)插值计算出对应的纹理坐标,然后去图片上“取样”(Sampling),取出颜色,最终赋予这个片段。
所以,纹理映射的全流程,本质上就是三步:加载图片数据到内存 -> 在OpenGL中创建并配置一个纹理对象来管理这份数据 -> 在着色器中通过纹理坐标进行采样并应用到模型表面。接下来,我们就一步步拆解,手把手带你走完这个流程,我会分享很多我实际编码中踩过的坑和总结的技巧。
2. 第一步:把图片“搬”进程序——stb_image.h实战
OpenGL本身并不负责从硬盘上的JPEG或PNG文件里读取数据,它只关心已经加载到内存中的、原始像素数据数组。所以,我们的第一项工作就是找一个靠谱的“搬运工”,把图片文件解码成OpenGL能理解的格式。这里我强烈推荐 stb_image.h,一个单头文件的C语言库,它几乎支持所有常见格式(JPG, PNG, BMP, TGA等),而且使用简单到令人发指。
2.1 集成与基本使用
首先,去它的GitHub仓库下载 stb_image.h 文件,扔到你的项目里。使用它只需要在一个C或CPP源文件中(通常是你的主程序文件)定义宏并包含它:
#define STB_IMAGE_IMPLEMENTATION
#include “stb_image.h”
这个 STB_IMAGE_IMPLEMENTATION 宏是关键,它告诉编译器:“别把它当头文件看了,就在这里展开所有函数实现。” 这样你就无需编译额外的库文件。
加载图片的核心函数是 stbi_load:
int width, height, nrChannels;
unsigned char *data = stbi_load(“container.jpg”, &width, &height, &nrChannels, 0);
if (!data) {
std::cerr << “Failed to load texture!” << std::endl;
// 处理错误,比如返回或退出
}
我来解释一下这几个参数:
“container.jpg”:图片文件路径。注意相对路径是相对于你程序运行的工作目录,不是源码目录。我建议用绝对路径或者把图片放在可执行文件旁边,避免找不到文件的坑。&width,&height:函数会把图片的宽度和高度(像素数)回填到这两个变量里。后面OpenGL创建纹理时必须知道尺寸。&nrChannels:回填颜色通道数。比如JPEG通常是3(RGB),PNG可能带有透明度通道,就是4(RGBA)。- 最后一个参数
0:指定“强制通道数”。如果填0,就按文件本身的通道数加载。如果填3或4,函数会尝试将图片强制转换为3通道(RGB)或4通道(RGBA)。比如加载一个带透明度的PNG,但你只想要RGB,就可以传3。
加载成功后,data 就是一个指向无符号字符(unsigned char)数组的指针,每个元素值在0-255之间,代表了红、绿、蓝(和可能有的透明度)的强度。数据在内存中是连续排列的,例如RGB格式下,排列就是 [R, G, B, R, G, B, ...]。
2.2 常见问题与技巧
这里有几个新手常掉进去的坑,我当年也摔得

967

被折叠的 条评论
为什么被折叠?



