青岛团购网站建设如何做国外外贸网站

张小明 2026/1/7 14:46:12
青岛团购网站建设,如何做国外外贸网站,广告制作费,可以自学网站开发上一篇#xff1a;在UI渲染通道中绘制 | 下一篇#xff1a;法线贴图 | 返回目录 #x1f4da; 快速导航 目录 简介学习目标光照基础 光照模型概述环境光漫反射光方向光 法线向量 什么是法线法线的重要性法线变换 顶点着色器更新 输入布局修改法线变换数据传递 片段着色器更…上一篇在UI渲染通道中绘制 | 下一篇法线贴图 | 返回目录 快速导航目录简介学习目标光照基础光照模型概述环境光漫反射光方向光法线向量什么是法线法线的重要性法线变换顶点着色器更新输入布局修改法线变换数据传递片段着色器更新方向光结构Lambert漫反射光照计算立方体几何体生成立方体顶点布局每面法线计算索引生成平面几何体法线渲染器集成光照效果可视化常见问题练习 简介在之前的教程中,我们实现了纹理映射,但渲染的3D物体看起来很平(flat),缺乏深度感和立体感。这是因为我们还没有实现光照(Lighting)。本教程将实现最基础的光照模型:方向光照(Directional Lighting),包括:环境光(Ambient Light):场景的基础照明漫反射(Diffuse):根据光照方向和表面朝向计算的光照Lambert 漫反射模型:一个简单但有效的光照公式通过添加光照,3D物体将展现出真实的立体感和深度,为更高级的渲染技术(如镜面高光、法线贴图、阴影)奠定基础。Output 输出Calculation 计算Inputs 输入Lighting Pipeline 光照管线Final Color最终颜色Normal Transform法线变换Dot Product点积Ambient环境光Diffuse漫反射Normal法线Light Direction光源方向Ambient Color环境光颜色Light Color光源颜色Vertex Shader顶点着色器Fragment Shader片段着色器 学习目标目标描述理解光照基础掌握环境光、漫反射、方向光的概念实现法线变换正确变换法线向量以适应模型变换Lambert漫反射实现经典的Lambert光照模型立方体几何体生成带法线的立方体光照着色器更新着色器以计算光照 光照基础光照模型概述真实世界的光照非常复杂,但我们可以用简化的模型近似它:完整的 Phong 光照模型: ┌─────────────────────────────────┐ │ Final Color Ambient │ │ Diffuse │ │ Specular │ └─────────────────────────────────┘ 本教程实现 (简化版): ┌─────────────────────────────────┐ │ Final Color Ambient │ │ Diffuse │ └─────────────────────────────────┘ 未来教程将添加: - Specular (镜面高光) - Normal Mapping (法线贴图) - Shadows (阴影) - Global Illumination (全局光照)环境光环境光(Ambient Light) 是场景中的基础照明,模拟间接光照:// 环境光是常数,所有点接收相同的环境光vec4 ambient_colorvec4(0.2,0.2,0.2,1.0);// 20% 亮度vec4 ambientambient_color*diffuse_color*texture_sample;环境光特性:没有环境光: ┌────────────┐ │ ████ │ 背光面完全黑暗 │ ██ ██ │ (不真实) │ ██ ██ │ │ ████ │ └────────────┘ 有环境光: ┌────────────┐ │ ████ │ 背光面仍可见 │ ██▓▓██ │ (更真实) │ ██▓▓██ │ │ ████ │ └────────────┘ 环境光强度: 0.0 → 完全黑暗 0.2 → 微弱环境光 (推荐) 0.5 → 中等环境光 1.0 → 完全照亮 (无阴影)漫反射光漫反射(Diffuse Light) 是光线照射到粗糙表面时的散射:光线照射粗糙表面: 光源 │ │ 入射光 ▼ ╱──────╲ ╱ ▲ ▲ ▲ ▲ ╲ 表面粗糙 ╱ │ │ │ │ ╲ 光线向各方向散射 ╱───┴─┴─┴─┴───╲ 特性: 1. 与视角无关 (从任何角度看都一样亮) 2. 依赖表面朝向 (法线方向) 3. 依赖光源方向Lambert 漫反射公式:diffuse_factor max(dot(normal, -light_direction), 0.0) - dot(): 点积,计算两个向量的夹角 - normal: 表面法线 (单位向量) - light_direction: 从表面指向光源的方向 (单位向量) - max(..., 0.0): 夹角 90° 时,表面背光,漫反射为 0 示例: 法线与光线同向 (0°): dot 1.0 → 最亮 法线与光线垂直 (90°): dot 0.0 → 不受光照 法线背向光线 (180°): dot -1.0 → max(dot, 0.0) 0.0 → 完全黑暗方向光方向光(Directional Light) 模拟无限远处的光源 (如太阳):// 方向光结构structdirectional_light{vec3 direction;// 光线方向 (从光源出发)vec4 colour;// 光源颜色和强度};// 示例:从右上方照射的白光directional_light sun{vec3(-0.57735,-0.57735,-0.57735),// 归一化的方向向量vec4(0.8,0.8,0.8,1.0)// 80% 强度的白光};方向光特性:方向光 vs 点光源: ┌─────────────────────────────────┐ │ 方向光 (Directional Light) │ │ │ │ 光源 (无限远) │ │ │││││ │ │ │││││ 平行光线 │ │ │││││ │ │ ▼▼▼▼▼ │ │ ████████ 场景 │ │ │ │ 优点:性能好,适合室外场景 │ └─────────────────────────────────┘ ┌─────────────────────────────────┐ │ 点光源 (Point Light) │ │ │ │ ● 光源 │ │ ╱│╲ │ │ ╱ │ ╲ 发散光线 │ │ ╱ │ ╲ │ │ ▼ ▼ ▼ │ │ ████████ 场景 │ │ │ │ 优点:更真实,适合室内场景 │ └─────────────────────────────────┘ 法线向量什么是法线法线(Normal) 是垂直于表面的单位向量:法线可视化: │ ← 法线 (垂直于表面) │ │ ────┴──── 表面 立方体的法线: ▲ 顶面法线 (0, 1, 0) │ ┌───┴───┐ ◄───┤ ├───► 右面法线 (1, 0, 0) │ │ └───┬───┘ │ ▼ 底面法线 (0, -1, 0) 每个顶点都有自己的法线向量 平面表面:所有顶点共享相同的法线 曲面表面:每个顶点的法线不同 (平滑过渡)法线的重要性法线决定了表面如何与光照交互:相同的几何体,不同的法线: ┌────────────────────────────────┐ │ Flat Shading (平坦着色) │ │ - 每个三角形使用一个法线 │ │ │ │ ▲ ▲ │ │ ╱│ │╲ │ │ ╱ │ │ ╲ │ │ ───┴─┴─── │ │ │ │ 效果:棱角分明,适合立方体 │ └────────────────────────────────┘ ┌────────────────────────────────┐ │ Smooth Shading (平滑着色) │ │ - 每个顶点使用插值的法线 │ │ │ │ ▲ │ │ ╱ ╲ │ │ ╱ ╲ │ │ ─────── │ │ │ │ 效果:平滑曲面,适合球体、圆柱 │ └────────────────────────────────┘法线变换当模型变换 (缩放、旋转、平移) 时,法线也需要变换:// ❌ 错误:直接使用模型矩阵vec3 world_normalmat3(model)*in_normal;// ✓ 正确:使用法线矩阵 (normal matrix)mat3 normal_matrixtranspose(inverse(mat3(model)));vec3 world_normalnormal_matrix*in_normal;// 但在本教程中,我们只使用旋转和平移 (无非均匀缩放)// 所以可以简化为:vec3 world_normalmat3(model)*in_normal;为什么法线需要特殊处理?非均匀缩放的问题: 原始: │ 法线 ↑ │ ──┴── 正方形 非均匀缩放 (X 轴 2 倍): │ 法线 ↑ (错误!) │ 应该倾斜 ↗ ──┴──── 矩形 使用法线矩阵后: ↗ 法线 (正确!) ╱ ────── 矩形 顶点着色器更新输入布局修改添加法线作为顶点输入:// assets/shaders/Builtin.MaterialShader.vert.glsl #version 450 // 输入 (更新) layout(location 0) in vec3 in_position; // 位置 layout(location 1) in vec3 in_normal; // ← 新增:法线 layout(location 2) in vec2 in_texcoord; // 纹理坐标 // 全局 UBO (更新) layout(set 0, binding 0) uniform global_uniform_object { mat4 projection; mat4 view; vec4 ambient_colour; // ← 新增:环境光颜色 } global_ubo; // Push Constants layout(push_constant) uniform push_constants { mat4 model; // 64 bytes } u_push_constants; // 输出 layout(location 0) out int out_mode; // Data Transfer Object layout(location 1) out struct dto { vec4 ambient; // ← 新增:环境光 vec2 tex_coord; // 纹理坐标 vec3 normal; // ← 新增:法线 } out_dto; void main() { // 传递纹理坐标 out_dto.tex_coord in_texcoord; // 变换法线到世界空间 // 注意:这里假设模型矩阵只包含旋转和平移 (无非均匀缩放) out_dto.normal mat3(u_push_constants.model) * in_normal; // 传递环境光颜色 out_dto.ambient global_ubo.ambient_colour; // 计算顶点位置 gl_Position global_ubo.projection * global_ubo.view * u_push_constants.model * vec4(in_position, 1.0); }输入布局变化:旧版本新版本变化location 0: positionlocation 0: position不变location 1: texcoordlocation 1: normal←新增❌location 2: texcoord位置变化法线变换法线从模型空间变换到世界空间:// 提取模型矩阵的旋转部分 (3x3) mat3 rotation_part mat3(u_push_constants.model); // 变换法线 vec3 world_normal rotation_part * in_normal; // 注意:如果模型包含非均匀缩放,需要使用法线矩阵 // mat3 normal_matrix transpose(inverse(mat3(model))); // vec3 world_normal normal_matrix * in_normal;矩阵分解:模型矩阵 (4x4): ┌───────────────┬────┐ │ 旋转 缩放 │ 0 │ │ (3x3) │ 0 │ │ │ 0 │ ├───────────────┼────┤ │ 平移 (3) │ 1 │ └───────────────┴────┘ 法线只需要旋转部分: ┌───────────────┐ │ mat3(model) │ │ (3x3) │ │ │ └───────────────┘数据传递通过 Data Transfer Object (DTO) 将数据传递到片段着色器:// 顶点着色器输出 (每个顶点) out_dto.ambient global_ubo.ambient_colour; // vec4 out_dto.tex_coord in_texcoord; // vec2 out_dto.normal world_normal; // vec3 // 光栅化阶段自动插值 // 片段着色器输入 (每个片段/像素) in_dto.ambient // 插值后的环境光 in_dto.tex_coord // 插值后的纹理坐标 in_dto.normal // 插值后的法线 (需要重新归一化!) 片段着色器更新方向光结构定义方向光结构:// assets/shaders/Builtin.MaterialShader.frag.glsl #version 450 layout(location 0) out vec4 out_colour; // 材质 UBO layout(set 1, binding 0) uniform local_uniform_object { vec4 diffuse_colour; } object_ubo; // 方向光结构 struct directional_light { vec3 direction; // 光线方向 (从光源出发,指向场景) vec4 colour; // 光源颜色和强度 (RGB 未使用的 A) }; // TODO: 未来将从 CPU 传递,目前硬编码 directional_light dir_light { vec3(-0.57735, -0.57735, -0.57735), // 归一化向量,指向右下后方 vec4(0.8, 0.8, 0.8, 1.0) // 80% 强度的白光 }; // 纹理采样器 layout(set 1, binding 1) uniform sampler2D diffuse_sampler; // 输入 (从顶点着色器) layout(location 1) in struct dto { vec4 ambient; // 环境光颜色 vec2 tex_coord; // 纹理坐标 vec3 normal; // 世界空间法线 } in_dto; // 函数声明 vec4 calculate_directional_light(directional_light light, vec3 normal); void main() { // 计算最终颜色 out_colour calculate_directional_light(dir_light, in_dto.normal); }光线方向解释:光线方向 (-0.57735, -0.57735, -0.57735): Y (上) │ │ └────── X (右) ╱ ╱ Z (前) 方向向量: 从原点指向 (-1, -1, -1) 并归一化 normalize((-1, -1, -1)) ≈ (-0.57735, -0.57735, -0.57735) 可视化: 光源 (无限远) ╱ ╱ 光线方向 ╱ ▼ 场景Lambert漫反射实现 Lambert 漫反射计算:vec4 calculate_directional_light(directional_light light, vec3 normal) { // 1. 采样漫反射纹理 vec4 diff_samp texture(diffuse_sampler, in_dto.tex_coord); // 2. 计算漫反射因子 // dot(normal, -light.direction): // - normal: 表面法线 (从表面指向外) // - -light.direction: 从表面指向光源的方向 // - dot 结果: cos(θ),其中 θ 是法线与光线的夹角 float diffuse_factor max(dot(normal, -light.direction), 0.0); // 3. 计算环境光分量 vec4 ambient vec4(vec3(in_dto.ambient * object_ubo.diffuse_colour), diff_samp.a); ambient * diff_samp; // 乘以纹理颜色 // 4. 计算漫反射分量 vec4 diffuse vec4(vec3(light.colour * diffuse_factor), diff_samp.a); diffuse * diff_samp; // 乘以纹理颜色 // 5. 组合环境光和漫反射 return (ambient diffuse); }光照计算完整的光照计算流程:光照计算管线: ┌─────────────────────────────────┐ │ 1. 采样纹理 │ │ diff_samp texture(...) │ └──────────────┬──────────────────┘ │ ▼ ┌─────────────────────────────────┐ │ 2. 计算漫反射因子 │ │ diffuse_factor max( │ │ dot(normal, -light_dir), │ │ 0.0 │ │ ) │ └──────────────┬──────────────────┘ │ ┌─────┴─────┐ │ │ ▼ ▼ ┌───────────┐ ┌─────────────┐ │ 3. 环境光 │ │ 4. 漫反射 │ │ ambient │ │ diffuse │ │ ambient_ │ │ light_color │ │ color * │ │ * diffuse_ │ │ texture │ │ factor * │ │ │ │ texture │ └─────┬─────┘ └──────┬──────┘ │ │ └──────┬───────┘ ▼ ┌─────────────────────────────────┐ │ 5. 组合 │ │ final ambient diffuse │ └─────────────────────────────────┘数学公式:Lambert 漫反射公式: I_diffuse I_light × max(N · L, 0) × K_d 其中: I_diffuse 漫反射光强度 I_light 光源强度 N 表面法线 (单位向量) L 从表面指向光源的方向 (单位向量) N · L 点积 (dot product) K_d 材质的漫反射系数 (纹理颜色 × 材质颜色) 最终颜色: Final Ambient Diffuse (I_ambient × K_d) (I_light × max(N · L, 0) × K_d) 立方体几何体生成立方体顶点布局立方体有 6 个面,每个面 4 个顶点,共 24 个顶点:// engine/src/systems/geometry_system.cgeometry_configgeometry_system_generate_cube_config(f32 width,f32 height,f32 depth,f32 tile_x,f32 tile_y,constchar*name,constchar*material_name){geometry_config config;config.vertex_sizesizeof(vertex_3d);config.vertex_count4*6;// 4 verts per side, 6 sidesconfig.verticeskallocate(sizeof(vertex_3d)*config.vertex_count,MEMORY_TAG_ARRAY);config.index_sizesizeof(u32);config.index_count6*6;// 6 indices per side, 6 sidesconfig.indiceskallocate(sizeof(u32)*config.index_count,MEMORY_TAG_ARRAY);f32 half_widthwidth*0.5f;f32 half_heightheight*0.5f;f32 half_depthdepth*0.5f;vertex_3d verts[24];// 6 个面,每面 4 个顶点// ...}立方体布局:立方体的 6 个面: 顶面 (5) ┌───────┐ ╱│ ╱│ ╱ │ ╱ │ ╱ │ ╱ │ 右面 (3) ┌───────┐ │ │ │ │ │ 左│ └───│───┘ 面│ ╱ │ ╱ (2│ ╱ 前 │ ╱ │╱ 面 │╱ └───────┘ (0) 底面 (4) 后面 (1) 索引顺序: - 面 0 (前面): 顶点 0-3 - 面 1 (后面): 顶点 4-7 - 面 2 (左面): 顶点 8-11 - 面 3 (右面): 顶点 12-15 - 面 4 (底面): 顶点 16-19 - 面 5 (顶面): 顶点 20-23每面法线计算每个面的所有顶点共享相同的法线:// 前面 (面向 Z 方向)verts[(0*4)0].position(vec3){min_x,min_y,max_z};verts[(0*4)1].position(vec3){max_x,max_y,max_z};verts[(0*4)2].position(vec3){min_x,max_y,max_z};verts[(0*4)3].position(vec3){max_x,min_y,max_z};// 纹理坐标verts[(0*4)0].texcoord(vec2){min_uvx,min_uvy};verts[(0*4)1].texcoord(vec2){max_uvx,max_uvy};verts[(0*4)2].texcoord(vec2){min_uvx,max_uvy};verts[(0*4)3].texcoord(vec2){max_uvx,min_uvy};// 法线 (前面指向 Z)verts[(0*4)0].normal(vec3){0.0f,0.0f,1.0f};verts[(0*4)1].normal(vec3){0.0f,0.0f,1.0f};verts[(0*4)2].normal(vec3){0.0f,0.0f,1.0f};verts[(0*4)3].normal(vec3){0.0f,0.0f,1.0f};// 后面 (面向 -Z 方向)verts[(1*4)0].position(vec3){max_x,min_y,min_z};verts[(1*4)1].position(vec3){min_x,max_y,min_z};verts[(1*4)2].position(vec3){max_x,max_y,min_z};verts[(1*4)3].position(vec3){min_x,min_y,min_z};// 法线 (后面指向 -Z)verts[(1*4)0].normal(vec3){0.0f,0.0f,-1.0f};verts[(1*4)1].normal(vec3){0.0f,0.0f,-1.0f};verts[(1*4)2].normal(vec3){0.0f,0.0f,-1.0f};verts[(1*4)3].normal(vec3){0.0f,0.0f,-1.0f};// 左面 (面向 -X 方向)// normal (-1.0f, 0.0f, 0.0f)// 右面 (面向 X 方向)// normal (1.0f, 0.0f, 0.0f)// 底面 (面向 -Y 方向)// normal (0.0f, -1.0f, 0.0f)// 顶面 (面向 Y 方向)// normal (0.0f, 1.0f, 0.0f)法线方向总结:面方向法线前面Z(0, 0, 1)后面-Z(0, 0, -1)左面-X(-1, 0, 0)右面X(1, 0, 0)底面-Y(0, -1, 0)顶面Y(0, 1, 0)索引生成为每个面生成索引:// 为 6 个面生成索引for(u32 i0;i6;i){u32 v_offseti*4;// 顶点偏移u32 i_offseti*6;// 索引偏移// 两个三角形组成一个四边形// Triangle 1: 0 → 1 → 2((u32*)config.indices)[i_offset0]v_offset0;((u32*)config.indices)[i_offset1]v_offset1;((u32*)config.indices)[i_offset2]v_offset2;// Triangle 2: 0 → 3 → 1((u32*)config.indices)[i_offset3]v_offset0;((u32*)config.indices)[i_offset4]v_offset3;((u32*)config.indices)[i_offset5]v_offset1;}四边形三角化:四边形顶点: 2 ────── 1 │ │ │ │ 0 ────── 3 三角形 1 (逆时针): 2 │╲ │ ╲ 0 ─ 1 三角形 2 (逆时针): 0 ─ 3 ╲ │ ╲│ 1 索引顺序: Triangle 1: [0, 1, 2] Triangle 2: [0, 3, 1] 平面几何体法线平面 (Plane) 的法线非常简单,所有顶点共享相同的法线:// 生成平面几何体时,添加法线for(u32 y0;yy_segment_count;y){for(u32 x0;xx_segment_count;x){// ... 设置位置和纹理坐标 ...// 平面位于 XY 平面,法线指向 Zv0-normal(vec3){0.0f,0.0f,1.0f};v1-normal(vec3){0.0f,0.0f,1.0f};v2-normal(vec3){0.0f,0.0f,1.0f};v3-normal(vec3){0.0f,0.0f,1.0f};}}平面法线可视化:XY 平面: Y │ │ │ └────── X ╱ ╱ Z 所有法线指向 Z: ▲ ▲ ▲ ▲ │ │ │ │ │ │ │ │ ──┴─┴─┴─┴── 平面 渲染器集成更新全局 UBO添加环境光颜色到全局 UBO:// engine/src/renderer/vulkan/vulkan_types.inltypedefstructvulkan_material_shader_global_ubo{mat4 projection;// 64 bytesmat4 view;// 64 bytesvec4 ambient_colour;// ← 新增:16 bytesmat4 m_reserved0;// 64 bytes, reserved for future use}vulkan_material_shader_global_ubo;传递环境光颜色从渲染器前端传递环境光颜色:// engine/src/renderer/renderer_frontend.cvoidrenderer_draw_frame(render_packet*packet){if(backend.begin_frame(backend,packet-delta_time)){// World Renderpassbackend.begin_renderpass(BUILTIN_RENDERPASS_WORLD);// 传递环境光颜色vec4 ambient_colour(vec4){0.25f,0.25f,0.25f,1.0f};// 25% 强度backend.update_global_world_state(projection,view,vec3_zero(),// camera_position (未使用)ambient_colour,// ← 环境光颜色0// mode (未使用));// 绘制几何体for(u32 i0;ipacket-geometry_count;i){backend.draw_geometry(packet-geometries[i]);}backend.end_renderpass(BUILTIN_RENDERPASS_WORLD);// UI Renderpass// ...}}️ 光照效果可视化光照前后的对比:无光照 (之前的教程): ┌─────────────────────┐ │ ████████ │ 立方体看起来平 │ ██████████ │ 所有面亮度相同 │ ██████████ │ 缺乏深度感 │ ████████ │ └─────────────────────┘ 有光照 (本教程): ┌─────────────────────┐ │ ████▓▓▓▓ │ 立方体有立体感 │ ████▓▓▓▓▓▓ │ 不同面亮度不同 │ ████▒▒▒▒▒▒ │ 背光面较暗 │ ████▒▒▒▒ │ └─────────────────────┘ 光照方向可视化: 光源 (右上方) ╲ ╲ 光线 ╲ ▼ ┌────┐ ╱│ ╱│ ← 右面:受光,亮 ╱ │ ╱ │ ┌────┐ │ │ ╲ │ │ ← 前面:部分受光,中等亮度 │ ╲│ │ │ └──┘ └────┘ ↑ 左面:背光,暗❓ 常见问题1. 为什么光照后物体变暗了?原因:之前没有光照时,纹理直接显示,亮度为 100%。添加光照后:最终颜色 环境光 漫反射 (0.25 × 纹理) (0.8 × diffuse_factor × 纹理) 最亮的情况 (diffuse_factor 1.0): 0.25 0.8 1.05 ≈ 1.0 (clamp) 一般情况 (diffuse_factor 0.5): 0.25 0.4 0.65 (比之前暗)解决方案:增加光源强度:directional_light dir_light { vec3(-0.57735, -0.57735, -0.57735), vec4(1.5, 1.5, 1.5, 1.0) // ← 150% 强度 };增加环境光:vec4 ambient_colour(vec4){0.5f,0.5f,0.5f,1.0f};// 50% 强度添加多个光源:// 主光源 辅助光源 vec4 lighting calculate_directional_light(main_light, normal); lighting calculate_directional_light(fill_light, normal) * 0.3;2. 为什么有些面完全黑暗?原因:当表面背向光源时,dot(normal, -light.direction) 0,经过max(..., 0.0)处理后变为 0,导致漫反射为 0。只剩下环境光。// 背光面 float diffuse_factor max(dot(normal, -light.direction), 0.0); // normal (1, 0, 0) (右面) // -light.direction (0.57735, 0.57735, 0.57735) (从右上方来的光) // dot 0.57735 0 → 受光 // normal (-1, 0, 0) (左面) // dot -0.57735 0 → max(..., 0.0) 0 → 不受光可视化:光源从右上方照射: 光源 │ ▼ ┌───┐ ╱│ ╱│ ← 右面:亮 ╱ │ ╱ │ ┌───┐ │ │ ╲ │ │ ← 前面:中等亮度 │ ╲│ │ │ └──┘ └───┘ ↑ 左面:背光,只有环境光解决方案:增加环境光:vec4 ambient_colour(vec4){0.3f,0.3f,0.3f,1.0f};添加多个光源:// 主光源 背光源 vec4 main_lighting calculate_directional_light(main_light, normal); vec4 back_lighting calculate_directional_light(back_light, normal) * 0.5; return main_lighting back_lighting;使用双面光照 (Two-sided lighting):float diffuse_factor abs(dot(normal, -light.direction));3. 法线需要归一化吗?需要!法线在插值后可能不再是单位向量:顶点着色器输出 (单位向量): v0.normal (1, 0, 0) ← 长度 1 v1.normal (0, 1, 0) ← 长度 1 光栅化插值 (中点): interpolated (0.5, 0.5, 0) length sqrt(0.5² 0.5²) 0.707 ≠ 1 如果不归一化: dot(interpolated, light_dir) 会产生错误的结果解决方案:在片段着色器中归一化:vec4 calculate_directional_light(directional_light light, vec3 normal) { // 归一化插值后的法线 vec3 normalized_normal normalize(normal); // 使用归一化的法线计算 float diffuse_factor max(dot(normalized_normal, -light.direction), 0.0); // ... }性能考虑:normalize()有一定开销 (sqrt 3 个除法)但对于正确的光照计算是必需的现代 GPU 对normalize()有硬件优化4. 为什么立方体的边缘不平滑?原因:立方体使用Flat Shading(平坦着色),每个面的法线相同:Flat Shading (当前实现): ┌───┐ ╱│ ╱│ 每个三角形内部颜色相同 ╱ │ ╱ │ 边缘有明显的断层 ┌───┐ │ │ │ │ │ │ │ └───┘ │ 原因: - 共享顶点但法线不同 - 顶点 A 在前面:normal (0, 0, 1) - 顶点 A 在右面:normal (1, 0, 0) - 无法共享,必须重复顶点Smooth Shading (平滑着色):// 计算顶点的平均法线 vec3 vertex_normal normalize( face1_normal face2_normal face3_normal ); 效果: ┌───┐ ╱│ ╱│ 边缘平滑过渡 ╱ │ ╱ │ 看起来像圆角 ┌───┐ │ │ │ │ │ │ │ └───┘ │ 适用于:球体、圆柱、有机形状 不适用于:立方体、建筑物 (需要保留锐利边缘)何时使用哪种?Flat Shading: 立方体、建筑物、机械零件Smooth Shading: 球体、人物、有机物体5. 如何添加多个方向光?方法 1: 在着色器中硬编码多个光源// 主光源 (太阳) directional_light sun { vec3(-0.57735, -0.57735, -0.57735), vec4(0.8, 0.8, 0.8, 1.0) }; // 辅助光源 (天空光) directional_light sky { vec3(0.0, 1.0, 0.0), // 从上方照射 vec4(0.3, 0.3, 0.4, 1.0) // 微弱的蓝色光 }; void main() { vec4 lighting vec4(0.0); // 累加所有光源 lighting calculate_directional_light(sun, in_dto.normal); lighting calculate_directional_light(sky, in_dto.normal); out_colour lighting; }方法 2: 从 CPU 传递光源数组 (更灵活)// C 代码 - 定义光源数组typedefstructdirectional_light{vec3 direction;f32 padding;vec4 colour;}directional_light;directional_light lights[MAX_LIGHTS];lights[0](directional_light){.direction{-0.57735f,-0.57735f,-0.57735f},.colour{0.8f,0.8f,0.8f,1.0f}};lights[1](directional_light){.direction{0.0f,1.0f,0.0f},.colour{0.3f,0.3f,0.4f,1.0f}};// 上传到 UBOupload_to_ubo(lights,sizeof(lights));// GLSL 代码 - 接收光源数组 #define MAX_LIGHTS 4 layout(set 0, binding 1) uniform lights_ubo { int light_count; directional_light lights[MAX_LIGHTS]; } scene_lights; void main() { vec4 lighting vec4(0.0); // 遍历所有光源 for (int i 0; i scene_lights.light_count; i) { lighting calculate_directional_light(scene_lights.lights[i], in_dto.normal); } out_colour lighting; } 练习练习 1: 实现可调节的光源方向任务:允许用户通过键盘输入旋转光源方向。// 应用状态typedefstructapp_light_state{f32 light_angle_x;// 光源在 X 轴的旋转角度f32 light_angle_y;// 光源在 Y 轴的旋转角度}app_light_state;// 更新函数voidupdate_light_direction(app_light_state*state,f32 delta_time){// 键盘输入if(input_is_key_down(KEY_LEFT)){state-light_angle_y-90.0f*delta_time;// 旋转速度:90度/秒}if(input_is_key_down(KEY_RIGHT)){state-light_angle_y90.0f*delta_time;}if(input_is_key_down(KEY_UP)){state-light_angle_x-90.0f*delta_time;}if(input_is_key_down(KEY_DOWN)){state-light_angle_x90.0f*delta_time;}// 计算光源方向f32 x_raddeg_to_rad(state-light_angle_x);f32 y_raddeg_to_rad(state-light_angle_y);vec3 light_direction;light_direction.xsin(y_rad)*cos(x_rad);light_direction.ysin(x_rad);light_direction.zcos(y_rad)*cos(x_rad);light_directionvec3_normalized(light_direction);// 更新着色器中的光源方向// (需要修改着色器以从 UBO 读取光源方向)}提示:修改着色器以从 UBO 读取光源方向,而不是硬编码添加 UI 显示当前光源角度练习 2: 实现半球光照 (Hemisphere Lighting)任务:实现半球光照,上半球和下半球使用不同的颜色。// 半球光照结构 struct hemisphere_light { vec3 up_direction; // 上方向 (通常是 (0, 1, 0)) vec4 sky_color; // 天空颜色 vec4 ground_color; // 地面颜色 }; hemisphere_light hemi_light { vec3(0.0, 1.0, 0.0), vec4(0.5, 0.6, 0.7, 1.0), // 蓝色天空 vec4(0.3, 0.2, 0.1, 1.0) // 棕色地面 }; // 半球光照计算 vec4 calculate_hemisphere_light(hemisphere_light light, vec3 normal) { // 计算法线与上方向的点积 float up_factor dot(normal, light.up_direction) * 0.5 0.5; // up_factor: 0.0 (向下) → 1.0 (向上) // 在天空颜色和地面颜色之间插值 vec4 hemisphere_color mix(light.ground_color, light.sky_color, up_factor); // 采样纹理 vec4 diff_samp texture(diffuse_sampler, in_dto.tex_coord); return hemisphere_color * diff_samp; } void main() { vec4 lighting calculate_hemisphere_light(hemi_light, in_dto.normal); out_colour lighting; }效果:面向上的表面:蓝色调 (天空光)面向下的表面:棕色调 (地面反射光)侧面:两种颜色的混合练习 3: 实现 Per-Vertex vs Per-Pixel 光照对比任务:实现两种光照计算方式的对比。Per-Vertex Lighting (Gouraud Shading):// Vertex Shader layout(location 1) out vec4 out_color; // ← 输出颜色而不是法线 void main() { vec3 world_normal mat3(u_push_constants.model) * in_normal; // 在顶点着色器中计算光照 float diffuse_factor max(dot(world_normal, -dir_light.direction), 0.0); vec4 lighting global_ubo.ambient_colour (dir_light.colour * diffuse_factor); out_color lighting; // 输出光照颜色 gl_Position ...; } // Fragment Shader layout(location 1) in vec4 in_color; // ← 接收插值后的颜色 void main() { vec4 diff_samp texture(diffuse_sampler, in_dto.tex_coord); out_colour in_color * diff_samp; // 直接使用插值颜色 }Per-Pixel Lighting (Phong Shading,当前实现):// Vertex Shader layout(location 1) out vec3 out_normal; // ← 输出法线 void main() { out_normal mat3(u_push_constants.model) * in_normal; gl_Position ...; } // Fragment Shader layout(location 1) in vec3 in_normal; // ← 接收插值后的法线 void main() { // 在片段着色器中计算光照 vec3 normalized_normal normalize(in_normal); float diffuse_factor max(dot(normalized_normal, -dir_light.direction), 0.0); vec4 lighting ambient (dir_light.colour * diffuse_factor); vec4 diff_samp texture(diffuse_sampler, in_dto.tex_coord); out_colour lighting * diff_samp; }对比:特性Per-VertexPer-Pixel计算位置顶点着色器片段着色器性能更快 (计算次数少)更慢 (计算次数多)质量较低 (可见三角形)较高 (平滑)适用场景移动平台、远景物体PC/主机、近景物体恭喜!你已经掌握了方向光照的实现!Tutorial written by 上手实验室
版权声明:本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!

网上做家教的网站外包手工活在哪里拿货

快速体验 打开 InsCode(快马)平台 https://www.inscode.net输入框内输入如下内容: 创建三个功能相同的文件下载管理器实现:1) 使用传统回调方式;2) 使用Promise.then()链式调用;3) 使用async/await。每个实现都要包含&#xff1a…

张小明 2026/1/4 0:36:25 网站建设

asp.net网站开发代码wordpress批量提交

大数据领域分布式计算的分布式性能调优工具:从流水线堵车到全局最优的魔法工具箱关键词:分布式计算、性能调优工具、大数据、性能瓶颈、资源利用率摘要:在大数据时代,分布式计算就像一个超大型流水线工厂,成百上千个“…

张小明 2026/1/4 8:11:41 网站建设

网站建设网络公司整站源码北京建网

Lotus Domino 6 for Linux:备份、迁移与相关配置指南 1. 第三方备份软件介绍 在Linux系统和Domino服务器的使用过程中,备份是保障数据安全的重要环节。许多第三方供应商提供了丰富的备份软件产品。不过,在撰写本文时,适用于Linux版Domino服务器的备份产品仅有一款。 1.1…

张小明 2026/1/3 18:30:22 网站建设

怎么建设境外网站合肥网站制作前3名的

Linly-Talker在拉丁舞双人配合中的默契建立 在舞蹈的世界里,尤其是像拉丁舞这样高度依赖情感流动与身体对话的艺术形式中,真正的挑战往往不在于动作本身,而在于两个人之间能否“听”到彼此的节奏、“看”懂对方的意图。引带者一个微小的身体倾…

张小明 2026/1/4 23:48:09 网站建设

静态网站开发软件网站建设一条

卷积神经网络的结构 卷积神经网络(CNN)主要由输入层、卷积层、激活函数、池化层和全连接层组成。典型结构为: 输入层(INPUT):接收原始图像数据(如RGB图像为3通道)。卷积层&#xf…

张小明 2026/1/4 14:49:57 网站建设

什么网站做护工沙洋网站定制

微信自动答题小工具终极指南:Python开发者的效率利器 【免费下载链接】微信自动答题小工具使用说明 微信自动答题小工具是一款专为PyCharm环境设计的实用工具,支持在PC端运行的微信小程序中实现自动答题功能。通过预设的智能算法,该工具能够高…

张小明 2026/1/4 23:46:07 网站建设