ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

3D游戏图形渲染数学原理:从矩阵变换到PBR光照实战

3D游戏图形渲染数学原理:从矩阵变换到PBR光照实战 在游戏开发领域我们常常惊叹于《荒野大镖客2》这样的大作所呈现的视觉盛宴——从广袤的西部荒野到细腻的人物表情每一个画面细节都让人沉浸其中。但你是否想过这些令人叹为观止的视觉效果背后其实是一系列精妙的数学算法在支撑本文将带你深入探索3D游戏画面背后的数学艺术从基础概念到实际应用为你揭开视觉魔法的神秘面纱。1. 3D图形渲染的基本原理1.1 从3D到2D的转换过程3D游戏画面的核心挑战在于如何将三维空间中的物体投影到二维屏幕上。这个过程涉及三个关键变换模型变换、视图变换和投影变换。模型变换将物体从模型坐标系转换到世界坐标系。以一个简单的立方体为例我们需要定义它在世界空间中的位置、旋转和缩放import numpy as np # 定义立方体的8个顶点模型坐标系 cube_vertices np.array([ [-1, -1, -1], # 0 [ 1, -1, -1], # 1 [ 1, 1, -1], # 2 [-1, 1, -1], # 3 [-1, -1, 1], # 4 [ 1, -1, 1], # 5 [ 1, 1, 1], # 6 [-1, 1, 1] # 7 ]) # 模型变换矩阵平移、旋转、缩放 def create_model_matrix(position, rotation, scale): # 平移矩阵 translation_matrix np.array([ [1, 0, 0, position[0]], [0, 1, 0, position[1]], [0, 0, 1, position[2]], [0, 0, 0, 1] ]) # 缩放矩阵 scale_matrix np.array([ [scale[0], 0, 0, 0], [0, scale[1], 0, 0], [0, 0, scale[2], 0], [0, 0, 0, 1] ]) return translation_matrix scale_matrix # 示例将立方体放置在(2, 3, 5)位置缩放为原来的0.5倍 model_matrix create_model_matrix([2, 3, 5], [0, 0, 0], [0.5, 0.5, 0.5])视图变换将世界坐标系转换到相机坐标系。这相当于确定观察者的位置和视角方向def create_view_matrix(camera_position, target, up_vector): # 计算相机的前向向量 forward (target - camera_position) forward forward / np.linalg.norm(forward) # 计算相机的右向量 right np.cross(up_vector, forward) right right / np.linalg.norm(right) # 重新计算上向量以确保正交 up np.cross(forward, right) # 构建视图矩阵 view_matrix np.array([ [right[0], right[1], right[2], -np.dot(right, camera_position)], [up[0], up[1], up[2], -np.dot(up, camera_position)], [forward[0], forward[1], forward[2], -np.dot(forward, camera_position)], [0, 0, 0, 1] ]) return view_matrix # 示例相机在(0, 0, 10)位置看向原点上向量为Y轴 view_matrix create_view_matrix( np.array([0, 0, 10]), np.array([0, 0, 0]), np.array([0, 1, 0]) )1.2 投影变换与透视校正投影变换将三维场景投影到二维屏幕上分为正交投影和透视投影两种。游戏中最常用的是透视投影因为它能模拟人眼的视觉效果def create_perspective_projection(fov, aspect_ratio, near, far): # 将角度转换为弧度 fov_rad np.radians(fov) # 计算投影矩阵 tan_half_fov np.tan(fov_rad / 2) projection_matrix np.array([ [1/(aspect_ratio * tan_half_fov), 0, 0, 0], [0, 1/tan_half_fov, 0, 0], [0, 0, -(far near)/(far - near), -2*far*near/(far - near)], [0, 0, -1, 0] ]) return projection_matrix # 示例60度视野16:9宽高比近裁剪面0.1远裁剪面100 projection_matrix create_perspective_projection(60, 16/9, 0.1, 100)1.3 坐标系的完整变换流程完整的变换流程可以表示为顶点坐标 → 模型变换 → 视图变换 → 投影变换 → 屏幕坐标这个流程中的每个步骤都涉及矩阵运算最终将三维空间中的点映射到二维屏幕上。在《荒野大镖客2》这样的游戏中每帧需要处理数百万个顶点这就对GPU的并行计算能力提出了极高要求。2. 光照与着色模型2.1 基础光照模型真实感渲染的核心在于光照计算。冯氏光照模型是最常用的模型之一包含环境光、漫反射和镜面反射三个分量// GLSL着色器代码示例 #version 330 core out vec4 FragColor; in vec3 Normal; in vec3 FragPos; uniform vec3 lightPos; uniform vec3 viewPos; uniform vec3 lightColor; uniform vec3 objectColor; void main() { // 环境光 float ambientStrength 0.1; vec3 ambient ambientStrength * lightColor; // 漫反射 vec3 norm normalize(Normal); vec3 lightDir normalize(lightPos - FragPos); float diff max(dot(norm, lightDir), 0.0); vec3 diffuse diff * lightColor; // 镜面反射 float specularStrength 0.5; vec3 viewDir normalize(viewPos - FragPos); vec3 reflectDir reflect(-lightDir, norm); float spec pow(max(dot(viewDir, reflectDir), 0.0), 32); vec3 specular specularStrength * spec * lightColor; vec3 result (ambient diffuse specular) * objectColor; FragColor vec4(result, 1.0); }2.2 法线贴图与细节增强为了在低多边形模型上表现高细节的表面效果游戏中使用法线贴图技术import numpy as np from PIL import Image def load_normal_map(image_path): 加载法线贴图并转换为法线向量 normal_map Image.open(image_path) normal_data np.array(normal_map) / 255.0 * 2.0 - 1.0 # 法线贴图中的RGB分别对应XYZ分量 # 通常需要从切线空间转换到世界空间 return normal_data def calculate_tangent_space(normal, tangent, bitangent): 构建切线空间矩阵 TBN_matrix np.column_stack([tangent, bitangent, normal]) return TBN_matrix # 示例用法 normal np.array([0, 1, 0]) # 表面法线 tangent np.array([1, 0, 0]) # 切线 bitangent np.cross(normal, tangent) # 副切线 TBN calculate_tangent_space(normal, tangent, bitangent)2.3 高级光照技术现代游戏如《荒野大镖客2》使用了更复杂的光照技术PBR基于物理的渲染使用微表面理论来更准确地模拟光线与表面的交互// PBR光照计算的简化示例 float DistributionGGX(vec3 N, vec3 H, float roughness) { float a roughness * roughness; float a2 a * a; float NdotH max(dot(N, H), 0.0); float NdotH2 NdotH * NdotH; float nom a2; float denom (NdotH2 * (a2 - 1.0) 1.0); denom PI * denom * denom; return nom / denom; } float GeometrySchlickGGX(float NdotV, float roughness) { float r (roughness 1.0); float k (r * r) / 8.0; float nom NdotV; float denom NdotV * (1.0 - k) k; return nom / denom; }3. 纹理映射与材质系统3.1 纹理坐标与UV映射纹理映射是将2D图像贴到3D模型表面的技术。关键在于UV坐标的映射class TextureMapper: def __init__(self, texture_image): self.texture np.array(texture_image) self.height, self.width self.texture.shape[:2] def sample_texture(self, u, v, filter_typelinear): 根据UV坐标采样纹理 # 将UV坐标转换为纹理坐标 x u * (self.width - 1) y v * (self.height - 1) if filter_type nearest: # 最近邻插值 x_int int(round(x)) y_int int(round(y)) return self.texture[y_int, x_int] else: # 双线性插值 x0, y0 int(np.floor(x)), int(np.floor(y)) x1, y1 min(x0 1, self.width - 1), min(y0 1, self.height - 1) # 计算权重 wx x - x0 wy y - y0 # 四个相邻像素的插值 top self.texture[y0, x0] * (1 - wx) self.texture[y0, x1] * wx bottom self.texture[y1, x0] * (1 - wx) self.texture[y1, x1] * wx return top * (1 - wy) bottom * wy # 示例使用 texture_image Image.open(rock_texture.jpg) mapper TextureMapper(texture_image) color mapper.sample_texture(0.75, 0.25) # 采样特定UV坐标的颜色3.2 多纹理混合技术在复杂场景中需要混合多种纹理来表现丰富的材质效果// 地形纹理混合示例 uniform sampler2D grassTexture; uniform sampler2D rockTexture; uniform sampler2D snowTexture; uniform sampler2D blendMap; in vec2 TexCoord; void main() { // 从混合贴图中读取混合权重 vec4 blendValues texture(blendMap, TexCoord); // 采样各个纹理 vec4 grassColor texture(grassTexture, TexCoord * 10.0); // 平铺10次 vec4 rockColor texture(rockTexture, TexCoord * 20.0); vec4 snowColor texture(snowTexture, TexCoord * 5.0); // 根据权重混合颜色 vec4 finalColor grassColor * blendValues.r rockColor * blendValues.g snowColor * blendValues.b; FragColor finalColor; }3.3 procedural纹理生成程序化纹理通过算法动态生成节省存储空间并支持无限细节def generate_perlin_noise(width, height, scale100.0, octaves6, persistence0.5, lacunarity2.0): 生成Perlin噪声纹理 noise_map np.zeros((height, width)) for y in range(height): for x in range(width): amplitude 1.0 frequency 1.0 noise_value 0.0 max_value 0.0 for octave in range(octaves): sample_x x / scale * frequency sample_y y / scale * frequency # 简化版的Perlin噪声计算 perlin_value improved_perlin(sample_x, sample_y) noise_value perlin_value * amplitude max_value amplitude amplitude * persistence frequency * lacunarity noise_map[y, x] noise_value / max_value return noise_map def improved_perlin(x, y): 改进的Perlin噪声实现 # 简化实现实际应用中使用更复杂的梯度计算 X int(np.floor(x)) 255 Y int(np.floor(y)) 255 x - np.floor(x) y - np.floor(y) # 双线性插值 u fade(x) v fade(y) # 简化计算实际需要更完整的实现 return lerp(u, lerp(v, grad(X, Y, x, y), grad(X, Y1, x, y-1)), lerp(v, grad(X1, Y, x-1, y), grad(X1, Y1, x-1, y-1))) def fade(t): return t * t * t * (t * (t * 6 - 15) 10) def lerp(t, a, b): return a t * (b - a)4. GPU渲染管线详解4.1 现代GPU架构概述现代GPU采用高度并行的流处理器架构专门为图形计算优化GPU架构层次 - 流多处理器SM包含多个CUDA核心 - 纹理单元专门处理纹理采样 - 光栅化引擎处理三角形光栅化 - 显存控制器管理显存访问4.2 渲染管线各阶段分析完整的图形渲染管线包含多个可编程和固定功能阶段顶点着色器阶段#version 450 core layout (location 0) in vec3 aPos; layout (location 1) in vec3 aNormal; layout (location 2) in vec2 aTexCoord; uniform mat4 model; uniform mat4 view; uniform mat4 projection; out vec3 FragPos; out vec3 Normal; out vec2 TexCoord; void main() { gl_Position projection * view * model * vec4(aPos, 1.0); FragPos vec3(model * vec4(aPos, 1.0)); Normal mat3(transpose(inverse(model))) * aNormal; TexCoord aTexCoord; }曲面细分阶段可选// 曲面细分控制着色器 #version 450 core layout (vertices 3) out; void main() { if (gl_InvocationID 0) { gl_TessLevelInner[0] 5.0; gl_TessLevelOuter[0] 5.0; gl_TessLevelOuter[1] 5.0; gl_TessLevelOuter[2] 5.0; } gl_out[gl_InvocationID].gl_Position gl_in[gl_InvocationID].gl_Position; } // 曲面细分评估着色器 #version 450 core layout (triangles, equal_spacing, cw) in; void main() { vec4 p0 gl_TessCoord.x * gl_in[0].gl_Position; vec4 p1 gl_TessCoord.y * gl_in[1].gl_Position; vec4 p2 gl_TessCoord.z * gl_in[2].gl_Position; gl_Position p0 p1 p2; }4.3 几何着色器与实例化渲染几何着色器可以在图元级别进行操作适合实现粒子效果、毛发渲染等#version 450 core layout (triangles) in; layout (triangle_strip, max_vertices 3) out; in vec3 Normal[]; in vec2 TexCoord[]; out vec3 gNormal; out vec2 gTexCoord; void main() { for (int i 0; i 3; i) { gl_Position gl_in[i].gl_Position; gNormal Normal[i]; gTexCoord TexCoord[i]; EmitVertex(); } EndPrimitive(); }实例化渲染大幅提升渲染效率特别适合渲染大量相似物体// OpenGL实例化渲染示例 glBindVertexArray(VAO); glDrawArraysInstanced(GL_TRIANGLES, 0, 36, instanceCount); // 绘制instanceCount个实例 // 或者在着色器中使用实例ID #version 450 core layout (location 0) in vec3 aPos; layout (location 3) in mat4 instanceMatrix; // 实例变换矩阵 uniform mat4 view; uniform mat4 projection; void main() { gl_Position projection * view * instanceMatrix * vec4(aPos, 1.0); }5. 高级渲染技术实战5.1 延迟渲染与光照计算延迟渲染将几何处理与光照计算分离适合处理大量光源的场景// G-Buffer生成阶段 struct GBuffer { vec3 position; vec3 normal; vec3 albedo; float metallic; float roughness; float ao; }; // 光照计算阶段延迟着色 uniform sampler2D gPosition; uniform sampler2D gNormal; uniform sampler2D gAlbedo; uniform sampler2D gMetallicRoughnessAO; void main() { // 从G-Buffer读取数据 vec3 WorldPos texture(gPosition, TexCoords).rgb; vec3 Normal texture(gNormal, TexCoords).rgb; vec3 Albedo texture(gAlbedo, TexCoords).rgb; vec3 MRA texture(gMetallicRoughnessAO, TexCoords).rgb; // PBR光照计算 vec3 Lo vec3(0.0); for(int i 0; i lightCount; i) { Lo CalculateRadiance(WorldPos, Normal, Albedo, MRA.r, MRB.g, lights[i]); } // 环境光遮蔽 vec3 ambient vec3(0.03) * Albedo * MRB.b; vec3 color ambient Lo; // 色调映射和伽马校正 color color / (color vec3(1.0)); color pow(color, vec3(1.0/2.2)); FragColor vec4(color, 1.0); }5.2 屏幕空间反射与全局光照屏幕空间反射SSR实时计算反射效果vec3 calculateSSR(vec3 worldPos, vec3 normal, vec3 viewDir, float roughness) { // 计算反射方向 vec3 reflectDir reflect(-viewDir, normal); // 基于粗糙度添加随机偏移 if (roughness 0.0) { vec2 noise texture(noiseTexture, TexCoords * 4.0).rg; reflectDir normalize(reflectDir roughness * vec3(noise, 0.0)); } // 步进追踪 vec3 currentPos worldPos; float stepSize 0.1; for (int i 0; i maxSteps; i) { currentPos reflectDir * stepSize; // 将世界坐标转换为屏幕坐标 vec4 screenPos projection * view * vec4(currentPos, 1.0); screenPos.xyz / screenPos.w; vec2 uv screenPos.xy * 0.5 0.5; if (uv.x 0.0 || uv.x 1.0 || uv.y 0.0 || uv.y 1.0) { break; // 超出屏幕范围 } // 从深度缓冲区获取深度信息 float depth texture(depthBuffer, uv).r; vec3 depthWorldPos worldPosFromDepth(uv, depth); // 检查是否命中表面 if (length(depthWorldPos - worldPos) hitThreshold) { return texture(sceneColor, uv).rgb; } stepSize * 1.1; // 自适应步长 } return vec3(0.0); // 未命中返回黑色或环境贴图 }5.3 体积光与大气散射体积光效果模拟光线在介质中的散射vec3 calculateVolumetricLight(vec3 worldPos, vec3 lightDir, float lightIntensity) { // 光线步进参数 int steps 64; float stepSize length(lightDir) / steps; vec3 currentPos worldPos; vec3 accumulatedLight vec3(0.0); for (int i 0; i steps; i) { currentPos lightDir * stepSize; // 计算当前点的光照贡献简化版 float density calculateDensity(currentPos); vec3 scattering calculateScattering(currentPos, lightDir); // 指数衰减 float attenuation exp(-density * stepSize * i); accumulatedLight scattering * attenuation * stepSize; } return accumulatedLight * lightIntensity; } float calculateDensity(vec3 position) { // 基于高度和噪声计算密度 float heightFactor exp(-position.y * 0.1); // 随高度指数衰减 vec3 noiseCoord position * 0.01; float noise texture(noiseTexture, noiseCoord.xz).r; return heightFactor * (0.5 0.5 * noise); }6. 性能优化与内存管理6.1 渲染状态优化策略合理的渲染状态管理可以显著提升性能// 渲染状态批处理示例 class RenderStateManager { private: std::unordered_mapuint32_t, std::vectorRenderable stateBatches; public: void batchRenderable(const Renderable renderable) { uint32_t stateHash calculateStateHash(renderable); stateBatches[stateHash].push_back(renderable); } void flushBatches() { for (auto batch : stateBatches) { setRenderState(batch.first); // 设置渲染状态 for (const auto renderable : batch.second) { renderable.draw(); // 批量绘制 } } stateBatches.clear(); } private: uint32_t calculateStateHash(const Renderable r) { // 基于材质、着色器、混合状态等计算哈希 uint32_t hash 0; hash_combine(hash, r.material-getId()); hash_combine(hash, r.shader-getId()); hash_combine(hash, r.blendState); return hash; } };6.2 层级细节LOD系统LOD系统根据距离动态调整模型细节class LODSystem: def __init__(self): self.lod_levels [] # 存储不同细节级别的模型 self.distance_thresholds [10, 30, 100] # 距离阈值 def get_appropriate_lod(self, camera_position, object_position): distance np.linalg.norm(camera_position - object_position) for i, threshold in enumerate(self.distance_thresholds): if distance threshold: return self.lod_levels[i] return self.lod_levels[-1] # 返回最低细节级别 def update_lod_for_scene(self, camera_position, scene_objects): for obj in scene_objects: appropriate_lod self.get_appropriate_lod(camera_position, obj.position) if obj.current_lod ! appropriate_lod: obj.switch_lod(appropriate_lod)6.3 显存管理与纹理流送高效的显存管理对大型开放世界游戏至关重要class TextureStreamingSystem { private: struct TextureInfo { uint32_t textureId; size_t memoryUsage; int priority; float lastUsedTime; }; std::unordered_mapstd::string, TextureInfo loadedTextures; size_t maxMemoryBudget; size_t currentMemoryUsage; public: bool loadTexture(const std::string path, int priority 0) { if (loadedTextures.find(path) ! loadedTextures.end()) { // 纹理已加载更新使用时间 loadedTextures[path].lastUsedTime getCurrentTime(); return true; } // 检查内存预算 size_t textureSize estimateTextureSize(path); if (currentMemoryUsage textureSize maxMemoryBudget) { if (!makeRoomForTexture(textureSize, priority)) { return false; // 无法释放足够空间 } } // 加载纹理 TextureInfo newTexture loadTextureToGPU(path); newTexture.priority priority; newTexture.lastUsedTime getCurrentTime(); loadedTextures[path] newTexture; currentMemoryUsage textureSize; return true; } private: bool makeRoomForTexture(size_t requiredSize, int newPriority) { // 按优先级和最近使用时间排序 std::vectorstd::string texturesToUnload; size_t freedMemory 0; // 收集可以卸载的纹理低优先级、长时间未使用 for (const auto [path, info] : loadedTextures) { if (info.priority newPriority getCurrentTime() - info.lastUsedTime UNLOAD_THRESHOLD) { texturesToUnload.push_back(path); freedMemory info.memoryUsage; if (freedMemory requiredSize) { break; } } } // 卸载选中的纹理 for (const auto path : texturesToUnload) { unloadTexture(path); } return freedMemory requiredSize; } };7. 数学工具与算法实现7.1 矩阵运算优化游戏渲染中大量使用矩阵运算优化这些运算能显著提升性能import numpy as np from numba import jit jit(nopythonTrue) def fast_matrix_multiply(A, B): 优化的矩阵乘法实现 m, n A.shape n, p B.shape C np.zeros((m, p)) for i in range(m): for k in range(n): if A[i, k] ! 0: # 跳过零元素 for j in range(p): C[i, j] A[i, k] * B[k, j] return C jit(nopythonTrue) def fast_vector_transform(vertices, matrix): 批量顶点变换优化 result np.empty_like(vertices) for i in range(vertices.shape[0]): v vertices[i] # 手动展开的矩阵向量乘法 result[i, 0] matrix[0,0]*v[0] matrix[0,1]*v[1] matrix[0,2]*v[2] matrix[0,3] result[i, 1] matrix[1,0]*v[0] matrix[1,1]*v[1] matrix[1,2]*v[2] matrix[1,3] result[i, 2] matrix[2,0]*v[0] matrix[2,1]*v[1] matrix[2,2]*v[2] matrix[2,3] return result7.2 四元数与旋转插值四元数在旋转插值和动画中比欧拉角更稳定class Quaternion: def __init__(self, w, x, y, z): self.w w self.x x self.y y self.z z classmethod def from_axis_angle(cls, axis, angle): 从轴角创建四元数 half_angle angle * 0.5 s np.sin(half_angle) return cls(np.cos(half_angle), axis[0]*s, axis[1]*s, axis[2]*s) def slerp(self, other, t): 球面线性插值 # 计算点积余弦值 cos_half_theta self.w*other.w self.x*other.x self.y*other.y self.z*other.z # 如果四元数方向相反取反其中一个 if cos_half_theta 0: other Quaternion(-other.w, -other.x, -other.y, -other.z) cos_half_theta -cos_half_theta # 如果四元数非常接近使用线性插值避免除零 if abs(cos_half_theta) 1.0: return Quaternion(self.w, self.x, self.y, self.z) half_theta np.arccos(cos_half_theta) sin_half_theta np.sqrt(1.0 - cos_half_theta*cos_half_theta) # 避免除零 if abs(sin_half_theta) 0.001: return Quaternion( self.w * 0.5 other.w * 0.5, self.x * 0.5 other.x * 0.5, self.y * 0.5 other.y * 0.5, self.z * 0.5 other.z * 0.5 ) ratio_a np.sin((1 - t) * half_theta) / sin_half_theta ratio_b np.sin(t * half_theta) / sin_half_theta return Quaternion( self.w * ratio_a other.w * ratio_b, self.x * ratio_a other.x * ratio_b, self.y * ratio_a other.y * ratio_b, self.z * ratio_a other.z * ratio_b ) def to_rotation_matrix(self): 四元数转旋转矩阵 w, x, y, z self.w, self.x, self.y, self.z return np.array([ [1-2*y*y-2*z*z, 2*x*y-2*z*w, 2*x*z2*y*w], [2*x*y2*z*w, 1-2*x*x-2*z*z, 2*y*z-2*x*w], [2*x*z-2*y*w, 2*y*z2*x*w, 1-2*x*x-2*y*y] ])7.3 碰撞检测与物理模拟游戏中的物理交互基于精确的数学计算class PhysicsEngine: def __init__(self): self.gravity np.array([0, -9.8, 0]) def sphere_sphere_collision(self, sphere1, sphere2): 球体碰撞检测 distance np.linalg.norm(sphere1.position - sphere2.position) return distance (sphere1.radius sphere2.radius) def ray_triangle_intersect(self, ray_origin, ray_dir, triangle): 射线与三角形求交Möller–Trumbore算法 v0, v1, v2 triangle.vertices edge1 v1 - v0 edge2 v2 - v0 h np.cross(ray_dir, edge2) a np.dot(edge1, h) if abs(a) 1e-8: return False, None # 射线与三角形平行 f 1.0 / a s ray_origin - v0 u f * np.dot(s, h) if u 0.0 or u 1.0: return False, None q np.cross(s, edge1) v f * np.dot(ray_dir, q) if v 0.0 or u v 1.0: return False, None t f * np.dot(edge2, q) if t 1e-8: return True, ray_origin ray_dir * t else: return False, None def resolve_collision(self, obj1, obj2, collision_point, normal): 碰撞响应计算 # 计算相对速度 relative_velocity obj2.velocity - obj1.velocity # 计算冲量 velocity_along_normal np.dot(relative_velocity, normal) if velocity_along_normal 0: return # 物体正在分离 # 恢复系数 e min(obj1.restitution, obj2.restitution) # 冲量计算 j -(1 e) * velocity_along_normal j / obj1.inv_mass obj2.inv_mass # 应用冲量 impulse j * normal obj1.velocity - impulse * obj1.inv_mass obj2.velocity impulse * obj2.inv_mass8. 实战案例《荒野大镖客2》技术分析8.1 地形渲染系统《荒野大镖客2》的地形系统结合了高度图、纹理混合和LOD技术// 简化版地形渲染系统 class TerrainRenderer { public: void renderTerrain(const Camera camera) { // 计算可见的地形块 auto visibleChunks calculateVisibleChunks(camera); for (auto chunk : visibleChunks) { // 根据距离确定LOD级别 int lodLevel calculateLODLevel(camera, chunk); // 应用相应的细节级别 chunk.applyLOD(lodLevel); // 纹理混合计算 blendTexturesForChunk(chunk); // 渲染地形块 chunk.render(); } } private: void blendTexturesForChunk(const TerrainChunk chunk) { // 基于高度、坡度和噪声混合多种纹理 for (int y 0; y chunk.size; y) { for (int x 0
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进