当前位置: 首页 > news >正文

3D地球可视化教程 - 第2篇:夜晚纹理与着色器入门

难度: ⭐⭐⭐☆☆ 中级
预计时间: 45分钟
技术栈: Three.js + GLSL + CustomShaderMaterial
ps:该系列后期我会录制教学视频,以及更为通俗易懂的介绍,所以这个系列算是教学草案

教程简介

在第1篇中,我们成功创建了基础的3D地球渲染系统。现在,我们将进入Three.js的进阶领域 - 自定义着色器编程

通过本篇教程,你将学会创建令人惊叹的地球夜晚效果,包括城市灯光、昼夜过渡,以及高级的图像处理技术。

🎯 本篇学习目标

  • 🧠 理解GPU着色器的工作原理
  • 🎨 掌握GLSL着色器语言基础
  • 🌍 实现地球昼夜过渡效果
  • ✨ 学习高级图像处理技术
  • 🔧 掌握CustomShaderMaterial的使用
    在这里插入图片描述

🏆 最终效果预览

完成本篇教程后,你将获得:

  • ✅ 真实的地球夜晚城市灯光效果
  • ✅ 平滑的昼夜分界线过渡
  • ✅ 可调节的亮度、对比度、色彩
  • ✅ 智能的透明度处理

🧠 着色器理论基础

什么是着色器?

着色器是运行在GPU上的小程序,用于控制3D图形的渲染过程。

着色器类型
  1. 顶点着色器 (Vertex Shader) - 处理顶点位置和属性
  2. 片段着色器 (Fragment Shader) - 处理像素颜色
GLSL语言特点
  • 类似C语言的语法
  • 专为并行计算设计
  • 内置丰富的数学函数
  • 支持向量和矩阵运算

为什么需要自定义着色器?

标准材质的局限性:

  • ❌ 无法实现复杂的光照计算
  • ❌ 不支持多纹理混合
  • ❌ 无法实现动态效果
  • ❌ 性能不够优化

自定义着色器的优势:

  • ✅ 完全控制渲染过程
  • ✅ GPU并行计算,性能极高
  • ✅ 实现任意复杂的视觉效果
  • ✅ 支持实时参数调节

🎨 夜晚纹理系统解析

核心实现原理

我们的夜晚纹理系统基于以下原理:

  1. 光照计算 - 判断地球表面哪些区域处于夜晚
  2. 纹理混合 - 将夜晚纹理与白天纹理智能混合
  3. 透明度处理 - 让黑暗区域透明,城市灯光不透明
  4. 图像增强 - 通过亮度、对比度、伽马校正优化效果

技术架构

夜晚纹理系统
├── 顶点着色器 → 计算光照方向和强度
├── 片段着色器 → 处理像素颜色和透明度
├── Uniforms → 传递参数到着色器
└── 纹理采样 → 获取夜晚和白天纹理

🔍 顶点着色器详解

核心功能

顶点着色器负责计算每个顶点的光照信息:

// EarthNight.js - 顶点着色器
uniform vec3 uLight;        // 光源位置
varying vec2 vUv2;          // UV坐标传递给片段着色器
varying float vDist;        // 光照强度传递给片段着色器void main() {vUv2 = uv;  // 传递UV坐标// 🌍 将顶点位置转换到世界坐标系vec4 worldPosition = modelMatrix * vec4(position, 1.0);vec3 worldNormal = normalize(worldPosition.xyz);// ☀️ 计算从顶点到光源的方向vec3 lightDirection = normalize(uLight - worldPosition.xyz);// 📐 计算点积,确定光照强度float dotProduct = dot(worldNormal, lightDirection);// 📊 传递光照强度到片段着色器vDist = dotProduct;
}

关键概念解释

  • worldPosition: 顶点在世界空间中的位置
  • worldNormal: 顶点的法向量(垂直于表面的方向)
  • lightDirection: 从顶点指向光源的方向
  • dotProduct: 点积值,范围[-1, 1]
    • > 0: 面向光源(白天区域)
    • < 0: 背离光源(夜晚区域)
    • = 0: 昼夜分界线

🎨 片段着色器详解

核心算法流程

片段着色器是夜晚效果的核心,包含多个处理步骤:

// EarthNight.js - 片段着色器核心算法
void main() {// 🖼️ 1. 采样纹理vec4 texNight = texture2D(uNight, vUv2);  // 夜晚纹理vec4 texDay = texture2D(uDay, vUv2);      // 白天纹理// 🔍 2. 计算透明度 - 使用感知亮度公式float alpha = dot(texNight.rgb, vec3(0.299, 0.587, 0.114));alpha *= uAlphaMultiplier;alpha = alpha < uAlphaThreshold ? 0.0 : alpha;// 🌗 3. 昼夜分界线计算float adjustedVDist = vDist + uNightOffset;float fadeOut = smoothstep(uTransitionStart, uTransitionEnd, adjustedVDist);// 🎭 4. 纹理混合vec4 nightColor = mix(vec4(texNight.rgb, 1.0), vec4(0,0,0,0), fadeOut);// 🎨 5. 图像处理// 对比度调整nightColor.rgb = ((nightColor.rgb - 0.5) * uNightContrast + 0.5) * uNightBrightness;// 伽马校正nightColor.rgb = pow(max(nightColor.rgb, 0.0), vec3(1.0 / uNightGamma));// RGB通道调整nightColor.rgb *= uNightRGBMultiplier;// 🔀 6. 屏幕混合(可选)vec3 screenBlend = vec3(1.0) - (vec3(1.0) - nightColor.rgb) * (vec3(1.0) - texDay.rgb);nightColor.rgb = mix(nightColor.rgb, screenBlend, uDayMixStrength);csm_DiffuseColor = nightColor;
}

🔧 核心技术详解

1. 感知亮度计算

// 人眼对不同颜色的感知权重
float alpha = dot(texNight.rgb, vec3(0.299, 0.587, 0.114));
//                                   红     绿     蓝

这个公式基于人眼视觉特性:

  • 绿色权重最高 (0.587) - 人眼对绿色最敏感
  • 红色权重中等 (0.299) - 次敏感
  • 蓝色权重最低 (0.114) - 最不敏感

2. 昼夜分界线算法

// 光照强度范围: [-1, 1]
float adjustedVDist = vDist + uNightOffset;// 平滑过渡函数
float fadeOut = smoothstep(uTransitionStart, uTransitionEnd, adjustedVDist);

参数控制

  • uTransitionStart: 夜晚区域开始位置 (如 -0.15)
  • uTransitionEnd: 白天区域开始位置 (如 0.12)
  • uNightOffset: 整体偏移,调整昼夜平衡 (如 0.4)

3. 图像处理技术

对比度调整
// 标准对比度公式
color.rgb = ((color.rgb - 0.5) * contrast + 0.5) * brightness;
伽马校正
// 伽马校正增强细节
color.rgb = pow(max(color.rgb, 0.0), vec3(1.0 / gamma));
屏幕混合
// 屏幕混合模式,避免过亮
vec3 screenBlend = vec3(1.0) - (vec3(1.0) - base) * (vec3(1.0) - overlay);

🎛️ 参数配置系统

可调节参数

// EarthNight.js - 参数配置
this.nightParams = {// 🎨 外观调节brightness: 0.7,        // 亮度 [0.1 - 3.0]contrast: 1.15,         // 对比度 [0.1 - 3.0]gamma: 1.1,             // 伽马值 [0.3 - 2.5]// 🌈 颜色调节redMultiplier: 0,       // 红色通道 [0.0 - 2.0]greenMultiplier: 0.68,  // 绿色通道 [0.0 - 2.0]blueMultiplier: 1.6,    // 蓝色通道 [0.0 - 2.0]// 🌗 昼夜分界线transitionStart: -0.15, // 过渡开始点transitionEnd: 0.12,    // 过渡结束点nightOffset: 0.4,       // 夜间偏移// 🔍 透明度控制alphaMultiplier: 1.0,   // 透明度倍数alphaThreshold: 0.1,    // 透明度阈值// 🔀 混合控制dayMixStrength: 0.08,   // 白天纹理混合强度
};

🚀 实践操作

查看效果

  1. 🌍 基础地球 - 带有真实纹理的地球
  2. 🌙 夜晚效果 - 地球背光面显示城市灯光
  3. 🌗 昼夜分界 - 平滑的明暗过渡

效果对比

功能第1篇效果第2篇效果
地球表面只有白天纹理白天+夜晚双纹理
背光面完全黑暗显示城市灯光
过渡区域生硬边界平滑渐变
视觉层次单一层次丰富的明暗层次

🔬 技术深度分析

1. CustomShaderMaterial的优势

import CustomShaderMaterial from "three-custom-shader-material/vanilla";this.material = new CustomShaderMaterial({baseMaterial: THREE.MeshBasicMaterial,  // 基础材质vertexShader: `/* 自定义顶点着色器 */`,fragmentShader: `/* 自定义片段着色器 */`,uniforms: this.uniforms,               // 参数传递transparent: true,                     // 支持透明度
});

CSM的优势

  • ✅ 保留Three.js材质的内置功能
  • ✅ 无缝集成自定义着色器代码
  • ✅ 支持所有标准材质属性
  • ✅ 更好的兼容性和稳定性

2. 光照计算的数学原理

点积 (Dot Product)
float dotProduct = dot(worldNormal, lightDirection);

点积的几何意义:

  • = 1: 法向量与光线方向相同(正面受光)
  • = 0: 法向量与光线方向垂直(侧面受光)
  • = -1: 法向量与光线方向相反(背面受光)
平滑过渡函数
float fadeOut = smoothstep(start, end, value);

smoothstep函数特点:

  • 在[start, end]区间内产生S型曲线
  • 边缘平滑,中间陡峭
  • 比线性插值更自然

3. 高级图像处理算法

感知亮度 (Luminance)
float luminance = dot(color.rgb, vec3(0.299, 0.587, 0.114));

基于人眼视觉特性的权重分配,比简单的RGB平均值更准确。

伽马校正 (Gamma Correction)
color.rgb = pow(color.rgb, vec3(1.0 / gamma));
  • gamma < 1.0: 增强对比度,让亮处更亮
  • gamma > 1.0: 减少对比度,整体变亮
  • gamma = 1.0: 无变化
屏幕混合模式
vec3 screenBlend = vec3(1.0) - (vec3(1.0) - base) * (vec3(1.0) - overlay);

屏幕混合的特点:

  • 永远不会产生比输入更暗的颜色
  • 适合叠加发光效果
  • 保持亮部细节

🎛️ 参数调节技巧

获得最佳效果的参数组合

城市灯光突出
{brightness: 0.7,      // 适中亮度contrast: 1.15,       // 稍高对比度gamma: 1.1,           // 轻微伽马校正blueMultiplier: 1.6,  // 增强蓝色,营造夜晚感
}
自然过渡效果
{transitionStart: -0.15,  // 夜晚区域范围transitionEnd: 0.12,     // 白天区域范围nightOffset: 0.4,        // 向夜晚偏移
}
透明度优化
{alphaMultiplier: 1.0,    // 保持原始透明度alphaThreshold: 0.1,     // 过滤暗区域
}

🎨 视觉效果分析

渲染层次

我们的夜晚纹理系统创建了多个视觉层次:

  1. 基础层 - 地球白天纹理 (renderOrder: 0)
  2. 夜晚层 - 城市灯光纹理 (renderOrder: 0, 稍大scale)
  3. 过渡层 - 昼夜分界线的平滑混合

颜色科学

夜晚效果的颜色设计:

  • 蓝色增强 (1.6倍) - 营造夜晚的冷色调
  • 绿色保持 (0.68倍) - 平衡整体色调
  • 红色减少 (0倍) - 避免过于温暖

🔧 实现细节

纹理坐标处理

varying vec2 vUv2;  // 从顶点着色器传入
vec4 texNight = texture2D(uNight, vUv2);  // 采样夜晚纹理

条件渲染

// 只在夜晚区域显示城市灯光
vec4 nightColor = mix(vec4(texNight.rgb, 1.0),  // 夜晚纹理vec4(0, 0, 0, 0),         // 透明fadeOut                   // 过渡因子
);

多重处理管道

原始夜晚纹理 ↓
透明度计算↓  
昼夜过渡混合↓
亮度对比度调整↓
伽马校正↓
RGB通道调整↓
屏幕混合(可选)↓
最终输出

🎯 学习要点总结

✅ 核心知识点

  1. 着色器基础

    • 顶点着色器处理几何变换
    • 片段着色器处理像素颜色
    • varying变量在两者间传递数据
  2. 光照计算

    • 点积计算光照强度
    • 世界坐标系变换
    • 法向量计算
  3. 图像处理

    • 感知亮度计算
    • 伽马校正原理
    • 混合模式应用
  4. 参数系统

    • uniform变量传递参数
    • 实时参数调节
    • 效果参数优化

🎨 视觉效果成果

  • 真实感夜晚 - 城市灯光清晰可见
  • 平滑过渡 - 昼夜分界线自然
  • 可调参数 - 支持实时效果调节
  • 性能优化 - GPU并行处理,流畅运行

🚀 下一篇预告

在第3篇教程中,我们将学习:

🎬 地球动画与相机控制

  • GSAP动画库的使用
  • 地球初始入场动画
  • 相机动画控制系统
  • 自动旋转功能实现

预期效果

  • ✨ 炫酷的地球入场动画
  • 🎥 流畅的相机运动
  • 🔄 智能的自动旋转

💡 实践建议

实验参数

尝试调整以下参数观察效果变化:

  1. 修改 brightness 值 (0.5 - 1.5)
  2. 调整 blueMultiplier 值 (1.0 - 2.0)
  3. 改变 transitionStarttransitionEnd

扩展练习

  1. 尝试添加其他颜色通道的调节
  2. 实验不同的混合模式
  3. 创建自己的图像处理效果

🎓 知识检验

回答以下问题检验学习效果:

  1. 着色器中的varying变量有什么作用?
  2. 为什么使用感知亮度而不是简单的RGB平均值?
  3. smoothstep函数相比线性插值有什么优势?
  4. 屏幕混合模式适合用在什么场景?

🎉 恭喜完成第2篇教程!

你现在已经掌握了自定义着色器的基础知识,可以创建复杂的视觉效果了。在下一篇中,我们将学习动画系统,让地球"动"起来!


本文是《3D地球可视化教程系列》的第2篇。下一篇:《地球动画与相机控制》


文章转载自:

http://czXK5Jhp.kjgdm.cn
http://RS2NIF2v.kjgdm.cn
http://nPpRZLsZ.kjgdm.cn
http://y8y1S6wW.kjgdm.cn
http://fyQ5ZFzG.kjgdm.cn
http://A9KcCBDq.kjgdm.cn
http://WwTasXAt.kjgdm.cn
http://GxltYvXe.kjgdm.cn
http://vzMHuG8m.kjgdm.cn
http://0bq4f7Ak.kjgdm.cn
http://TwE1CVJ9.kjgdm.cn
http://kOPSVd9y.kjgdm.cn
http://cLA9IIMf.kjgdm.cn
http://GGiqpqvz.kjgdm.cn
http://RqfQDtZb.kjgdm.cn
http://eZyVQAYv.kjgdm.cn
http://JGPhg0QW.kjgdm.cn
http://nP6MZLtQ.kjgdm.cn
http://z5eSNEPn.kjgdm.cn
http://s2P1zro8.kjgdm.cn
http://wXGunJFj.kjgdm.cn
http://oiKoRdbI.kjgdm.cn
http://nfW8WUoj.kjgdm.cn
http://EbIbLK2S.kjgdm.cn
http://pRTAN5cT.kjgdm.cn
http://DHKXUMkd.kjgdm.cn
http://5T1WYcjJ.kjgdm.cn
http://d6FGqWNc.kjgdm.cn
http://R5pTCIKk.kjgdm.cn
http://XA43aNQj.kjgdm.cn
http://www.dtcms.com/a/385025.html

相关文章:

  • Ajax笔记2
  • DDoS高防IP是什么? DDoS攻击会暴露IP吗?
  • Java 设计模式——原则:从理论约束到项目落地指南
  • 从零开始打造个性化浏览器导航扩展:极简高级风格设计
  • 软件包安装
  • QARM:Quantitative Alignment Multi-Modal Recommendation at Kuaishou
  • 通达信抓波段指标(主图)
  • Django基础环境入门
  • Java学习笔记2——简单语法
  • LLM-LLM大语言模型快速认识
  • Winogender:衡量NLP模型性别偏见的基准数据集
  • Oracle UNDO表空间使用率过高解决方案
  • Qt 中 OPC UA 通讯实战
  • 生产制造数智化
  • ensp配置学习笔记 比赛版 vlan 静态路由 ospf bgp dhcp
  • java-代码随想录第33天|62.不同路径、63.不同路径II
  • 突破限制:FileCodeBox远程文件分享新体验
  • 对讲机模块 TDD 噪音:原理、快速止噪解决方案
  • 知识点11:总线驱动的多Agent调度
  • 使用 Docker 搭建私有 PyPI 镜像仓库:支持多平台二进制包同步
  • HarmonyOS实现快递APP自动识别地址(国际版)
  • IPsec实验笔记
  • 工业IOT平台助力水泥集团实现数字化转型
  • 【CSS】图片自适应等比例缩放
  • Java 21 虚拟线程高并发落地全指南:中间件适配、场景匹配与细节优化的技术实践
  • 设计模式(C++)详解—适配器模式(1)
  • 圆周点生成的数学原理与Python实现
  • 牛客:校门外的树
  • JavaScript数据网格方案AG Grid 34.2 发布:更灵活的数据结构、更流畅的大数据交互与全新 UI 体验
  • U8g2库为XFP1116-07AY(128x64 OLED)实现菜单功能[ep:esp8266]