最近在看一些 3D 游戏玩家的录屏演示时,弹幕里总有观众在说“这个地面的纹理都画出来了”“连贴图细节都能看清”。很多人只是当作画面表现力强,但实际上,从“地面看起来真实”到“纹理细节丰富”,背后涉及一整套纹理绘制、UV 展开、贴图压缩、GPU 采样与 LOD 优化流程。这篇文章打算从玩家的直观感受出发,把游戏纹理从“画出来”到“渲染出来”的完整链路拆开讲一遍,并配合可复现的 Python 和 WebGL 示例,带你在本地亲手生成纹理、写一个最小渲染 Demo,再聊聊潜行类地图场景中常用的纹理优化方案。
无论你是刚接触游戏开发的学生,还是在公司里维护 3D 场景渲染的工程师,这篇文章都能给你一套完整可用的实操思路。
1. 纹理细节背后的技术链路
1.1 从“画面里的纹理”说起
我们在游戏里看到的墙皮裂纹、地面石缝、草地颗粒,本质上都不是三维模型自带的信息,而是美术同学提前画好的一张张二维图片,再通过 UV 坐标映射到 3D 模型表面。这张图在游戏引擎里叫纹理贴图(Texture Map),英文语境里常直接称作 Texture。
很多开发新手容易混淆两个概念:模型决定了物体的“形状和轮廓”,纹理决定了物体的“表面细节”。同样是拿一个长方体放在场景里,贴上纯白材质和贴上一张岩石纹理之后,视觉效果完全不同。玩家在直播画面里感叹“连纹理都画出来了”,其实是在说贴图分辨率足够高,纹理过滤设置足够好,UV 分布足够合理,最终让 GPU 把纹理细节清晰地呈现到了屏幕上。
值得注意的是,纹理细节并不是“贴一张大图”这么简单。如果 UV 展开不合理,哪怕贴图分辨率很高,模型表面仍然可能模糊、拉伸甚至出现接缝。所以在进入代码之前,先要建立起“纹理不是图片本身,而是图片与模型之间的映射关系”这个概念。
1.2 纹理在渲染管线中的位置
一张纹理在 GPU 渲染管线中大致经过以下环节:
- 美术制作原始贴图,可能包含颜色贴图(Albedo/Base Color)、法线贴图(Normal Map)、粗糙度贴图(Roughness Map)、金属度贴图(Metalness Map)等。
- 纹理通过纹理压缩格式(如 BC1、BC3、BC7、ASTC)存储到磁盘或内存。
- 引擎加载纹理后生成 Mipmap 链,方便在不同距离下采样合适分辨率的层级。
- 模型渲染时,顶点着色器输出 UV 坐标,片元着色器根据 UV 坐标从纹理中采样颜色。
- GPU 按纹理过滤方式(最近邻、双线性、三线性、各向异性)计算最终像素颜色。
从玩家视角看,纹理质量好坏最直接的体现是“近距离看会不会模糊”和“远处看会不会闪烁”。这两个问题正好对应了纹理分辨率规划与 Mipmap 配置。
1.3 为什么纹理清晰度会直接影响沉浸感
潜行类游戏场景里,玩家会长时间近距离观察地面、墙壁、通风管道、碎石堆。如果这些物体的纹理分辨率低、拉伸明显,玩家很容易感受到“纸片感”和“塑料感”,沉浸感会瞬间崩塌。
反过来说,如果纹理清晰、细节丰富,玩家会下意识觉得这个场景是“真实存在”的,这也是很多玩家看主播游戏时发出“纹理都画出来了”感叹的原因。对开发者来说,纹理既要在近距离保持清晰,又要在远距离控制带宽和显存消耗,这是纹理工程的核心矛盾。
2. 纹理绘制基础概念
2.1 UV 展开:3D 模型与二维贴图的桥梁
UV 展开是将三维模型表面“摊平”到二维坐标系的过程。U 和 V 分别对应二维纹理坐标的横轴和纵轴,取值范围一般在 0 到 1 之间。模型上的每个顶点除了 Position、Normal 外,通常还有一个 UV 属性。
例如一个平面矩形,四个顶点的 UV 可以设计为:
- 左上角:(0, 1)
- 左下角:(0, 0)
- 右下角:(1, 0)
- 右上角:(1, 1)
纹理采样时,GPU 拿到一个片元的 UV 坐标后,会在纹理上找到对应位置的颜色。如果 UV 超出 [0,1] 范围,则根据纹理寻址模式(Repeat、Mirror、Clamp)决定如何取值。
UV 展开质量直接决定纹理是否会拉伸。想象把一个皮球用力压平,边缘必定会产生变形,这种变形反映到贴图上就是纹理拉伸。对于复杂角色或地形,需要在建模软件里通过自动展开 + 手动调整接缝的方式降低拉伸。
2.2 像素、纹素与纹理分辨率
普通图片的最小单位叫像素(Pixel),而纹理图像上的像素在渲染语境里也叫纹素(Texel)。纹理分辨率通常写作 512x512、1024x1024、2048x2048 等。
纹素密度(Texel Density)则是衡量纹理清晰度的常用指标,单位是“每米纹素数”或“每单位纹素数”。一个经验法则是:如果人物模型高度约 1.8 米,主纹理使用 1024x1024,那么纹素密度大致在 100 到 200 Texel/m 左右;如果想获得更高的近景细节,可以提升到 512 Texel/m。
纹素密度的意义在于:它让你对“一张贴图能覆盖多少真实尺寸”有数量级概念。如果一张 1024 贴图铺在一面 100 米长的墙上,每个纹素对应的物理尺寸很大,玩家走近后自然会看到模糊。这时单纯提高滤镜效果没有用,必须提升纹理分辨率或缩小单张纹理覆盖的物理尺寸。
2.3 纹理压缩与 Mipmap
纹理如果不压缩,显存占用会非常夸张。以 RGBA8 为例,每像素 4 字节,一张 2048x2048 的贴图占用 16MB 显存。大型 3D 场景动辄上千张贴图,必须使用块压缩格式。
不同平台常见的压缩格式:
| 压缩格式 | 常见平台 | 说明 |
|---|---|---|
| BC1 / DXT1 | PC | 适用于不带透明通道的颜色贴图,4bit/像素 |
| BC3 / DXT5 | PC | 支持 8bit Alpha,常用于带透明细节的贴图 |
| BC7 | PC | 高质量通用格式,支持更大范围的颜色细节 |
| ETC2 | Android / 移动端 | OpenGL ES 3.0 后的通用标准 |
| ASTC | 移动端 / 主机 | 压缩率灵活,块大小可配置 |
Mipmap 是一组由原始纹理逐级降采样生成的纹理链。加载资源时,引擎会为每张纹理生成从原始分辨率到 1x1 的若干层级。GPU 根据片元距离摄像机的远近选择合适层级的纹理采样,避免远处出现严重的摩尔纹抖动。
3. 从零生成纹理贴图实战
3.1 环境准备
进入代码之前,先准备好本地实验环境。本文使用 Python 生成纹理,然后使用 WebGL 在浏览器里渲染,两者都不需要大型商业引擎,方便读者快速复现。
需要安装的 Python 库:
- Pillow:图像读写和像素操作
- NumPy:矩阵化生成高度图、法线贴图
安装命令如下:
pip install pillow numpyWebGL 部分不需要额外安装依赖,使用浏览器原生 WebGL 1.0 接口即可。建议使用 Chrome 或 Edge 浏览器。
演示文件结构:
texture-demo/ ├── generate_texture.py ├── index.html ├── texture.js └── checker.png3.2 使用 Python 生成棋盘格纹理
棋盘格纹理在图形学里是最经典的测试贴图,因为它可以直观显示模型的 UV 是否有拉伸、旋转、镜像等问题。它的原理很简单:按固定周期将像素划分为黑白相间的方块。
完整代码如下,文件路径为texture-demo/generate_texture.py:
from PIL import Image # 纹理分辨率 size = 512 # 横向和纵向划分的格子数量 tile_count = 8 block_size = size // tile_count img = Image.new("RGB", (size, size), (255, 255, 255)) pixels = img.load() for y in range(size): for x in range(size): bx = x // block_size by = y // block_size if (bx + by) % 2 == 0: pixels[x, y] = (255, 255, 255) else: pixels[x, y] = (40, 40, 40) img.save("checker.png") print("棋盘格纹理已生成:checker.png")运行命令:
python generate_texture.py运行后会在当前目录生成一张 512x512 的checker.png。打开图片可以看到 8x8 的黑白棋盘格。
这段代码的核心是(bx + by) % 2,它利用行列奇偶性交替生成黑白方块。如果后续在模型上发现贴图有拉伸,棋盘格会出现不均匀的宽窄变化,这是排查 UV 问题的第一步。
3.3 生成法线贴图
法线贴图并不直接作为颜色使用,而是把模型表面的“高低起伏”方向编码到 RGB 值里。普通颜色贴图告诉你“这块区域是什么颜色”,法线贴图告诉光线“这块区域应该朝哪个方向反射光”,从而让平整的模型表面也能产生凹凸质感。
生成法线贴图的思路是:先定义一张高度图,再去计算每个像素的梯度,最后将梯度映射为法线方向。
示例代码如下,文件路径仍然放入generate_texture.py,可以追加在棋盘格代码后面:
import numpy as np from PIL import Image size = 256 h = np.zeros((size, size), dtype=np.float32) cx = size // 2 cy = size // 2 # 在中心画一个半球形凸起 for y in range(size): for x in range(size): dx = x - cx dy = y - cy dist = np.sqrt(dx * dx + dy * dy) if dist < 40: h[y, x] = 1.0 - dist / 40.0 # 计算高度图的梯度 dzdx = np.gradient(h, axis=1) dzdy = np.gradient(h, axis=0) # 法线 = (-dz/dx, -dz/dy, 1),然后归一化 nx = -dzdx ny = -dzdy nz = np.ones_like(h) norm = np.sqrt(nx**2 + ny**2 + nz**2) nx /= norm ny /= norm nz /= norm # 将法线分量从 [-1, 1] 映射到 [0, 255] r = (nx + 1.0) * 127.5 g = (ny + 1.0) * 127.5 b = (nz + 1.0) * 127.5 rgb = np.stack([r, g, b], axis=-1).astype(np.uint8) Image.fromarray(rgb).save("normal.png") print("法线贴图已生成:normal.png")生成后,用图片查看器打开normal.png,会看到中心区域有明显颜色变化,周围则是偏蓝紫色。法线贴图偏蓝是因为大多数表面法线指向 Z 轴正方向,而 Z 分量存储到蓝色通道后接近 1.0。
需要说明,这里的法线贴图只是一个教学演示,真实的 PBR 材质法线贴图常由高质量的 16 位高度图烘焙得到。
3.4 查看与验证纹理
生成完成后,可以用下面的 Python 脚本快速检查纹理的基本信息:
from PIL import Image for name in ["checker.png", "normal.png"]: img = Image.open(name) print(f"{name}: 尺寸 {img.size}, 模式 {img.mode}")输出大致为:
checker.png: 尺寸 (512, 512), 模式 RGB normal.png: 尺寸 (256, 256), 模式 RGB这里有一个细节值得注意:实际纹理图片的宽高不限制必须为 2 的幂,但为了兼容旧设备、Mipmap 生成和压缩格式对齐,业界通常仍然习惯使用 512、1024、2048 这类 2 的幂尺寸。你可以把普通图片当作纹理上传,但往往会遇到格式不支持或压缩效率低的问题。
4. 在 WebGL 中完成纹理渲染
现在把刚生成的棋盘格纹理贴到一个矩形平面上,并显示在浏览器里。这部分使用原生 WebGL 实现,便于展示纹理从 CPU 到 GPU 的完整路径。
4.1 页面与画布
创建texture-demo/index.html:
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>WebGL 纹理渲染 Demo</title> <style> body { margin: 0; background: #222; display: flex; align-items: center; justify-content: center; height: 100vh; } canvas { border: 2px solid #fff; } </style> </head> <body> <canvas id="glCanvas" width="512" height="512"></canvas> <script src="texture.js"></script> </body> </html>4.2 编写 WebGL 基础初始化代码
创建texture-demo/texture.js,第一步是初始化 WebGL 上下文并定义着色器。
const canvas = document.getElementById('glCanvas'); const gl = canvas.getContext('webgl'); if (!gl) { alert('当前浏览器不支持 WebGL'); throw new Error('WebGL not supported'); } // 顶点着色器:接收顶点位置和纹理坐标 const vsSource = ` attribute vec2 a_position; attribute vec2 a_texcoord; varying vec2 v_texcoord; void main() { gl_Position = vec4(a_position, 0.0, 1.0); v_texcoord = a_texcoord; } `; // 片元着色器:从纹理中采样颜色 const fsSource = ` precision mediump float; varying vec2 v_texcoord; uniform sampler2D u_texture; void main() { gl_FragColor = texture2D(u_texture, v_texcoord); } `; function createShader(type, source) { const shader = gl.createShader(type); gl.shaderSource(shader, source); gl.compileShader(shader); if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { console.error(gl.getShaderInfoLog(shader)); gl.deleteShader(shader); return null; } return shader; } function createProgram(vsSource, fsSource) { const vs = createShader(gl.VERTEX_SHADER, vsSource); const fs = createShader(gl.FRAGMENT_SHADER, fsSource); const program = gl.createProgram(); gl.attachShader(program, vs); gl.attachShader(program, fs); gl.linkProgram(program); if (!gl.getProgramParameter(program, gl.LINK_STATUS)) { console.error(gl.getProgramInfoLog(program)); return null; } return program; } const program = createProgram(vsSource, fsSource); gl.useProgram(program);4.3 设置顶点数据与纹理坐标
定义一个铺满视口的矩形,并给每个顶点指定对应的纹理坐标。注意 WebGL 坐标系原点在左下角,y 轴向上;纹理坐标 v 轴也是从下往上,因此这里可以保持直观对应。
const vertices = new Float32Array([ // x, y, u, v -1.0, -1.0, 0.0, 0.0, 1.0, -1.0, 1.0, 0.0, -1.0, 1.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0, ]); const buffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, buffer); gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW); const positionLoc = gl.getAttribLocation(program, 'a_position'); const texcoordLoc = gl.getAttribLocation(program, 'a_texcoord'); gl.enableVertexAttribArray(positionLoc); gl.vertexAttribPointer(positionLoc, 2, gl.FLOAT, false, 16, 0); gl.enableVertexAttribArray(texcoordLoc); gl.vertexAttribPointer(texcoordLoc, 2, gl.FLOAT, false, 16, 8);这里顶点数据用stride = 16字节,因为每组顶点包含 4 个 float 数据,即 x、y、u、v。位置属性的偏移是 0 字节,纹理坐标属性的偏移是 8 字节。
4.4 加载纹理并渲染
WebGL 的纹理加载必须等到图片加载完成后再上传,否则纹理对象里没有完整数据。
function loadTexture(url) { const texture = gl.createTexture(); const image = new Image(); image.onload = function () { gl.bindTexture(gl.TEXTURE_2D, texture); gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, image); gl.generateMipmap(gl.TEXTURE_2D); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT); draw(); }; image.src = url; return texture; } const texture = loadTexture('checker.png'); function draw() { gl.viewport(0, 0, canvas.width, canvas.height); gl.clearColor(0.2, 0.2, 0.2, 1.0); gl.clear(gl.COLOR_BUFFER_BIT); const samplerLoc = gl.getUniformLocation(program, 'u_texture'); gl.uniform1i(samplerLoc, 0); gl.activeTexture(gl.TEXTURE0); gl.bindTexture(gl.TEXTURE_2D, texture); gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); }在浏览器中打开index.html,就能看到一个铺满画布的棋盘格纹理。此时如果修改纹理坐标范围,比如把 u 坐标改成 2.0,配合gl.REPEAT寻址模式,贴图就会在水平方向重复两次,这是潜行地图中地面贴图常用的平铺技巧。
5. 让“潜行地图”纹理更精细的工程手段
玩家感叹“纹理都画出来了”,对开发者来说意味着要在有限性能预算内把纹理细节推到极致。下面几个手段在真实 3D 项目中最常用。
5.1 设置正确的纹素密度
在规划地图资源时,美术和 TA 会把场景按区域划分,规定每平方米使用的纹理像素数。潜行地图中,玩家经常贴脸观察的地面、墙面、门板,纹素密度应设置在 256 到 512 Texel/m 之间;而玩家很少靠近的高空建筑、远景山脉,可以降到 64 Texel/m 以下。
这个做法并不只是为了节省显存,更重要的是减少纹理资源开发量。统一纹素密度后,美术可以按相同模板制作贴图,避免出现“某个物体特别清晰、另一个物体特别模糊”的视觉断层。
5.2 使用纹理图集合并小贴图
场景中的小道具如果每件都单独使用一张 1024 贴图,会出现大量显存浪费和 DrawCall 上升。更优的做法是把多个小物件的贴图合并到一张纹理图集(Texture Atlas)中,然后用 UV 偏移指向图集中的对应区域。
例如把一块砖、一个木箱、一罐油漆的贴图放满一张 2048 纹理,在建模时让每个模型的 UV 只落在自己的区域。这样多对象可以使用同一张纹理、同一次渲染批次,绘制效率更高。
使用纹理图集时要注意三点:
- 各区域的 UV 不能互相重叠。
- 每个子区域之间要留出 2 到 4 像素的边距,防止相邻贴图采样的颜色渗入。
- Mipmap 生成时,低分辨率层级会把不同区域混在一起,需要根据项目情况开启或关闭自动 Mipmap。
5.3 过滤方式与 Mipmap 配合
在刚才的 WebGL 示例中,纹理最小化过滤使用了LINEAR_MIPMAP_LINEAR,也就是三线性过滤,它能缓解远处纹理闪烁。如果想让远距离纹理更清晰,可以开启各向异性过滤。WebGL 扩展开启方式如下:
const extension = gl.getExtension('EXT_texture_filter_anisotropic'); if (extension) { const maxAniso = gl.getParameter(extension.MAX_TEXTURE_MAX_ANISOTROPY_EXT); gl.texParameterf(gl.TEXTURE_2D, extension.TEXTURE_MAX_ANISOTROPY_EXT, maxAniso); }各向异性过滤会沿着摄像机视线方向对纹理进行多次采样,从而减少倾斜地面上纹理模糊的问题。潜行地图中大量存在地板、天花板、斜坡,这种过滤设置非常值得开启。
5.4 流式加载与纹理内存控制
大型地图不可能让所有纹理同时驻留显存。常见方案是使用纹理流送(Texture Streaming)技术:只加载当前视野可见范围内的纹理,当玩家靠近某块区域时再加载高分辨率层级,远离后降级到低分辨率或者卸载。
具体到 Unity 和 Unreal 中都有成熟方案,Unity 的QualitySettings.streamingMipmapMaxLevelReduction与 Unreal 的 Texture Streaming 都是同类思路。实现时需要注意的是:流送粒度要按纹理层级设计,避免在玩家转头瞬间出现高分辨率纹理突然闪现的“纹理弹入”问题。
6. 常见问题与排查思路
纹理渲染相关的问题在开发中很常见,这里整理一份排查清单:
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
| 模型贴图显示全黑 | 纹理未正确绑定到采样器,或着色器 uniform 位置没取得 | 检查gl.activeTexture和gl.bindTexture,确认 uniform 设置为对应的纹理单元 |
| 贴图显示成纯色 | UV 坐标全为 0 或 1 | 检查建模软件中 UV 是否展开,导出时是否丢失 UV 数据 |
| 贴图有明显的拉伸 | UV 分布不均匀或文理密度设置不合理 | 重新展开 UV,或提高该区域纹理分辨率 |
| 远处纹理剧烈闪烁 | 未生成 Mipmap 或最小化过滤设置不当 | 调用gl.generateMipmap并使用LINEAR_MIPMAP_LINEAR |
| 地面纹理倾斜后模糊 | 各向异性过滤未开启 | 启用EXT_texture_filter_anisotropic扩展 |
| 贴图边缘出现淡色边 | 纹理图集子区域之间发生颜色渗入 | 在图集子区域周围增加边距,或关闭压缩边缘渗色 |
| 纹理加载很慢、显存爆掉 | 贴图尺寸过大、未使用压缩格式 | 替换为 BC/ASTC 压缩格式,控制单张贴图分辨率 |
如果你在 WebGL 示例中遇到纹理没法显示,优先检查浏览器控制台是否有着色器编译错误,以及图片是否真的完成加载。很多时候问题不是出在纹理采样,而是图片跨域、在线资源地址错误这类基础问题上。
7. 最佳实践与工程建议
这一节把项目中的纹理优化经验整理成几条可复用的原则。
7.1 纹理资源命名与目录规范
贴图资源建议按“类型_用途_尺寸”的规则命名,例如:
T_Floor_Concrete_1024T_Wall_Brick_2048N_Floor_Concrete_1024
前缀 T 表示颜色贴图,N 表示法线贴图,R 表示粗糙度贴图。这样在文件浏览器里扫一眼就能判断资源类型,也方便脚本批量处理。
7.2 以 2 的幂作为纹理基准尺寸
除非是 UI 特殊资源,建议把纹理宽高统一为 2 的幂。这样做的好处包括:
- 兼容更多压缩格式;
- Mipmap 生成时算法更高效;
- 纹理坐标在各层级之间过渡更稳定。
制作时还应该限制最大纹理尺寸,例如项目统一规定单张贴图不超过 2048,特殊场景需提交 TA 审批。
7.3 优先使用平台压缩格式
开发期用无损 PNG 方便迭代,发布期务必转成平台对应的压缩格式。PC 端优先 BC7,Android 端优先 ASTC 6x6 或 8x8。压缩纹理能节省 50% 以上的显存,加载速度也会明显提升。
7.4 批量检查纹理资源
在 CI 流程中加入纹理资源检查,自动扫描项目中的贴图,检测是否存在非 2 的幂尺寸、超大贴图、重复贴图、缺少 Mipmap 标记等问题。这一步虽然初期配置成本不高,但能有效避免美术资源量上来后出现问题不好定位的情况。
7.5 在日志中记录纹理显存占用
游戏运行期可以通过内置 profiling 工具输出纹理显存占用明细,按贴图路径排序。这样如果某一关卡显存突然飙升,可以快速定位是哪些资源占用了显存。对于 Unity 项目,在 Editor 的 Profiler 面板中可以直接查看 Texture 内存分类。
7.6 预留 LOD 与远景替代方案
潜行地图的一大特点是视觉遮挡多,玩家通常只在几条通道内活动。可以利用这个特性做区域化资源管理:为每个房间维护一张可见场景的纹理资源清单,及时卸载远处不可见房间的高清纹理,只保留低分辨率 Mipmap。这与室内游戏常用的遮挡剔除(Occlusion Culling)配合起来效果更好。
8. 总结与学习路线
回到开头的问题:为什么玩家会感叹“纹理都画出来了”?因为场景里的纹理贴图分辨率足够高、UV 展开足够合理、过滤方式足够细腻、内存调度足够聪明,四个环节缺一不可。
这篇文章从概念入手,讲清了 UV、纹素、纹理压缩和 Mipmap;然后用 Python 生成了棋盘格与法线贴图;接着在 WebGL 中写了一个最小纹理渲染示例;最后围绕潜行地图场景整理了纹素密度控制、纹理图集、各向异性过滤和流式加载等实战手段。代码部分可以直接复制,按文中步骤运行,建议读者先跑通 WebGL 示例,再回到自己的项目中检查一遍纹理资源设置。
如果继续深入学习,可以按这个顺序推进:
- 学习 Blender 或 Maya 中 UV 展开与贴图烘焙;
- 深入 PBR 材质原理,掌握 Albedo、Normal、Roughness、Metallic 四张贴图的配合方式;
- 阅读 Unity 或 Unreal 的纹理流送源码,理解引擎级纹理调度逻辑;
- 研究纹理压缩算法原理,了解 BC 和 ASTC 的差异。
纹理只是 3D 渲染的一环,但它的工程细节非常多。建议你在本地多生成几种贴图,放到不同模型上观察效果,通过肉眼对比快速建立对分辨率、UV 与过滤方式的直觉。这样再回到复杂项目里排查纹理问题时,你很快就能找到问题出在哪一层。