3D地球可视化教程 - 第2篇:夜晚纹理与着色器入门
难度: ⭐⭐⭐☆☆ 中级
预计时间: 45分钟
技术栈: Three.js + GLSL + CustomShaderMaterial
ps:该系列后期我会录制教学视频,以及更为通俗易懂的介绍,所以这个系列算是教学草案
教程简介
在第1篇中,我们成功创建了基础的3D地球渲染系统。现在,我们将进入Three.js的进阶领域 - 自定义着色器编程!
通过本篇教程,你将学会创建令人惊叹的地球夜晚效果,包括城市灯光、昼夜过渡,以及高级的图像处理技术。
🎯 本篇学习目标
- 🧠 理解GPU着色器的工作原理
- 🎨 掌握GLSL着色器语言基础
- 🌍 实现地球昼夜过渡效果
- ✨ 学习高级图像处理技术
- 🔧 掌握CustomShaderMaterial的使用
🏆 最终效果预览
完成本篇教程后,你将获得:
- ✅ 真实的地球夜晚城市灯光效果
- ✅ 平滑的昼夜分界线过渡
- ✅ 可调节的亮度、对比度、色彩
- ✅ 智能的透明度处理
🧠 着色器理论基础
什么是着色器?
着色器是运行在GPU上的小程序,用于控制3D图形的渲染过程。
着色器类型
- 顶点着色器 (Vertex Shader) - 处理顶点位置和属性
- 片段着色器 (Fragment Shader) - 处理像素颜色
GLSL语言特点
- 类似C语言的语法
- 专为并行计算设计
- 内置丰富的数学函数
- 支持向量和矩阵运算
为什么需要自定义着色器?
标准材质的局限性:
- ❌ 无法实现复杂的光照计算
- ❌ 不支持多纹理混合
- ❌ 无法实现动态效果
- ❌ 性能不够优化
自定义着色器的优势:
- ✅ 完全控制渲染过程
- ✅ GPU并行计算,性能极高
- ✅ 实现任意复杂的视觉效果
- ✅ 支持实时参数调节
🎨 夜晚纹理系统解析
核心实现原理
我们的夜晚纹理系统基于以下原理:
- 光照计算 - 判断地球表面哪些区域处于夜晚
- 纹理混合 - 将夜晚纹理与白天纹理智能混合
- 透明度处理 - 让黑暗区域透明,城市灯光不透明
- 图像增强 - 通过亮度、对比度、伽马校正优化效果
技术架构
夜晚纹理系统
├── 顶点着色器 → 计算光照方向和强度
├── 片段着色器 → 处理像素颜色和透明度
├── 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篇效果 |
---|---|---|
地球表面 | 只有白天纹理 | 白天+夜晚双纹理 |
背光面 | 完全黑暗 | 显示城市灯光 |
过渡区域 | 生硬边界 | 平滑渐变 |
视觉层次 | 单一层次 | 丰富的明暗层次 |
🔬 技术深度分析
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, // 过滤暗区域
}
🎨 视觉效果分析
渲染层次
我们的夜晚纹理系统创建了多个视觉层次:
- 基础层 - 地球白天纹理 (renderOrder: 0)
- 夜晚层 - 城市灯光纹理 (renderOrder: 0, 稍大scale)
- 过渡层 - 昼夜分界线的平滑混合
颜色科学
夜晚效果的颜色设计:
- 蓝色增强 (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通道调整↓
屏幕混合(可选)↓
最终输出
🎯 学习要点总结
✅ 核心知识点
-
着色器基础
- 顶点着色器处理几何变换
- 片段着色器处理像素颜色
- varying变量在两者间传递数据
-
光照计算
- 点积计算光照强度
- 世界坐标系变换
- 法向量计算
-
图像处理
- 感知亮度计算
- 伽马校正原理
- 混合模式应用
-
参数系统
- uniform变量传递参数
- 实时参数调节
- 效果参数优化
🎨 视觉效果成果
- 真实感夜晚 - 城市灯光清晰可见
- 平滑过渡 - 昼夜分界线自然
- 可调参数 - 支持实时效果调节
- 性能优化 - GPU并行处理,流畅运行
🚀 下一篇预告
在第3篇教程中,我们将学习:
🎬 地球动画与相机控制
- GSAP动画库的使用
- 地球初始入场动画
- 相机动画控制系统
- 自动旋转功能实现
预期效果:
- ✨ 炫酷的地球入场动画
- 🎥 流畅的相机运动
- 🔄 智能的自动旋转
💡 实践建议
实验参数
尝试调整以下参数观察效果变化:
- 修改
brightness
值 (0.5 - 1.5) - 调整
blueMultiplier
值 (1.0 - 2.0) - 改变
transitionStart
和transitionEnd
扩展练习
- 尝试添加其他颜色通道的调节
- 实验不同的混合模式
- 创建自己的图像处理效果
🎓 知识检验
回答以下问题检验学习效果:
- 着色器中的
varying
变量有什么作用? - 为什么使用感知亮度而不是简单的RGB平均值?
smoothstep
函数相比线性插值有什么优势?- 屏幕混合模式适合用在什么场景?
🎉 恭喜完成第2篇教程!
你现在已经掌握了自定义着色器的基础知识,可以创建复杂的视觉效果了。在下一篇中,我们将学习动画系统,让地球"动"起来!
本文是《3D地球可视化教程系列》的第2篇。下一篇:《地球动画与相机控制》