用 Three.js 创建一个酷炫且真实的地球

image.png
接下来我会分步骤讲解,在线示例在数字孪生平台。

先添加一个球体

我们用threejs中的SphereGeometry来创建球体,给他贴一张地球纹理。

let earthGeo = new THREE.SphereGeometry(10, 64, 64)
let earthMat = new THREE.MeshStandardMaterial({map: albedoMap,
})
this.earth = new THREE.Mesh(earthGeo, earthMat)
this.group.add(this.earth)

然后写一个函数让这个球体能够进行自转,并且该函数每帧都会调用。

updateScene(interval, elapsed) {this.controls.update()this.stats1.update()this.earth.rotateY(interval * 0.005 * params.speedFactor)
}

在屏幕上我们就会得到如下的效果。
image.png

添加凹凸贴图

地球上有山脉海洋,本质上是凹凸不平的,在太阳下也会呈现不同的阴影效果。那么为了让我们的地球更加真实,我们可以用 NASA 对外提供的地球的凹凸贴图,将其应用到地球的MeshStandardMaterial.

this.dirLight = new THREE.DirectionalLight(0xffffff, params.sunIntensity)
this.dirLight.position.set(-50, 0, 30)
scene.add(this.dirLight)let earthMat = new THREE.MeshStandardMaterial({map: albedoMap,bumpMap: bumpMap,bumpScale: 0.03, 
})

这样我们就能够立即看山脉在阳光照耀下投下的阴影。
image.png
bumpScale这个值要尽量小一点,如果太大的话,会发现即使在地球的阴影面,山脉也会被照亮。

添加云

NASA官方同样提供了云的贴图。我们将云的贴图添加到一个单独的网格球体,并且半径要比地球大。这么做是为了后面模拟云在地球上投射阴影的效果。

const cloudsMap = await loadTexture(Clouds)
...let cloudGeo = new THREE.SphereGeometry(10.05, 64, 64)
let cloudsMat = new THREE.MeshStandardMaterial({alphaMap: cloudsMap,transparent: true,
})
this.clouds = new THREE.Mesh(cloudGeo, cloudsMat)
this.group.add(this.clouds)

添加云后真个效果就会看起来更加真实。
image.png
接下来我们实现云的投射阴影效果。具体做法:在地球纹理贴图上的任意 uv 点(X 点)上,找到云图上叠加的 uv 点(Y 点),提取 Y 点的颜色值。然后,我们根据 Y 点的颜色值(即 Y 点云的强度)使 X 点的颜色值变暗。

同时,因为云的旋转速度是地球的两倍,为了在地球的片段着色器中获得正确的阴影(云)位置,我们需要减去地球的坐标 uv_xOffset,这样结果将在 -1 到 1 的范围内,我们还需要为云纹理的wrapS设置RepeatWrapping,以便texture2D仍然适用于-1到0。

云层越浓的地方阴影应该越浓,因此我们用1.0减去cloudsMapValue来获得shadowValue,并将其乘以diffuseColor,我们还将 ShadowValue 限制为最小值 0.2,这样它就不会变得太暗。

shader.uniforms.tClouds = { value: cloudsMap }
shader.uniforms.tClouds.value.wrapS = THREE.RepeatWrapping;
shader.uniforms.uv_xOffset = { value: 0 }
shader.fragmentShader = shader.fragmentShader.replace('#include <emissivemap_fragment>', `// 因为这是在完成照明计算之前以及应用大多数默认颜色/纹理计算之后#include <emissivemap_fragment>float cloudsMapValue = texture2D(tClouds, vec2(vMapUv.x - uv_xOffset, vMapUv.y)).r;diffuseColor.rgb *= max(1.0 - cloudsMapValue, 0.2 );`)updateScene(interval, elapsed) {...this.earth.rotateY(interval * 0.005 * params.speedFactor)this.clouds.rotateY(interval * 0.01 * params.speedFactor)// 计算 uv_xOffset 并将其传递给地球的 MeshStandardMaterial 使用的着色器// X 点每旋转 n 弧度,Y 点就会旋转 2n 弧度// 因此,Y 点的 uv.x 始终等于 X 点的 (uv.x - n / 2π)// n 除以 2π 是将弧度(即 0 到 2π)转换为 uv 空间(即 0 到 1)// 偏移量 n / 2π 将通过 uv_xOffset 传递到着色器程序中// "offset % 1" 是因为 uv.x 的值 1 意味着完整的圆// 每当 uv_xOffset 大于 1 时,偏移 2π 弧度就像根本没有偏移一样const shader = this.earth.material.userData.shaderif ( shader ) {let offset = (interval * 0.005 * params.speedFactor) / (2 * Math.PI)shader.uniforms.uv_xOffset.value += offset % 1}}

完成这些更改后,现在我们得到了微妙但逼真的云阴影。
image.png

让海洋反射阳光

接下来我们可以让海洋看起来更加真实。水是一种很强的光反射体,尤其是当波浪安静或光的入射角较小时。通过利用粗糙度roughness和金属度metalness,这对于 MeshStandardMaterial 来说并不难做到。

const oceanMap = await loadTexture("./assets/Ocean.png")
const params = {...metalness: 0.1,
}
let earthMat = new THREE.MeshStandardMaterial({map: albedoMap,bumpMap: bumpMap,bumpScale: 0.03, // 一定要很小,如果太大连背面的凸的地方都会被照亮roughnessMap: oceanMap, // 以海洋贴图代替粗糙度贴图,将在着色器中反转灰度值metalness: params.metalness, // 与金属度贴图的纹理值相乘metalnessMap: oceanMap,
})
earthMat.onBeforeCompile = function( shader ) {...shader.fragmentShader = shader.fragmentShader.replace('#include <roughnessmap_fragment>', `float roughnessFactor = roughness;#ifdef USE_ROUGHNESSMAPvec4 texelRoughness = texture2D( roughnessMap, vRoughnessMapUv );texelRoughness = vec4(1.0) - texelRoughness;roughnessFactor *= clamp(texelRoughness.g, 0.5, 1.0);#endif`);...}

因为这里用的海洋贴图其实就只有黑白色,如果海洋区域为白色,我们必须反转黑白值以使粗糙度正确,因为我们希望陆地的粗糙度为“白色”(值:1)。对于水域,我们不希望它的粗糙度为零,因为这会使其看起来太像金属并且反光效果太强,海洋会失去其通常的蓝色,因此我们将粗糙度值限制在最小值 0.5。
image.png

夜景图

如果地球的夜晚是完全黑暗的,那就没有意义了,因为通常情况下都是会有夜景灯光。那我们接下的实现逻辑就是:在白天显示正常地球贴图,在阳光照不到的区域,用夜景图代替。

const lightsMap = await loadTexture(NightLights)
...let earthMat = new THREE.MeshStandardMaterial({...emissiveMap: lightsMap,emissive: new THREE.Color(0xffff88),
})
earthMat.onBeforeCompile = function( shader ) {...shader.fragmentShader = shader.fragmentShader.replace('#include <emissivemap_fragment>', `#ifdef USE_EMISSIVEMAPvec4 emissiveColor = texture2D( emissiveMap, vEmissiveMapUv );// geometryNormal是视图空间中的标准化法线// 对于地球的夜晚一侧,geometryNormal和方向光之间的点积将为负// 对于地球的光照面,情况相反,因此 emissiveColor 将乘以 0// smoothstep用来平滑昼夜的变化emissiveColor *= 1.0 - smoothstep(-0.02, 0.0, dot(geometryNormal, directionalLights[0].direction));totalEmissiveRadiance *= emissiveColor.rgb;#endif...`)...
}

添加大气菲涅尔效果

菲涅尔效应可以想象,当我们在湖边看到离我们较远的湖水表面比我们近处的表面更清晰的反射时产生的效果;其实就是理解成,可以以更小的角度来更清晰地看到表面的反射。

虽然我们的海洋已经有了反光效果,但我们仍然缺少大气层。我们将分两步模拟大气效果:

  1. 调整地球纹理的漫反射颜色diffuseColor,使其在观察者观察到的球体边缘附近呈现出更亮的蓝色(由于从较厚的路径积累了更多的光,边缘总是看起来更亮,看看下面的 ba 路径长得多)

image.png

  1. 在云层和地球网格之上添加另一个球体网格作为大气层本身。
shader.fragmentShader = shader.fragmentShader.replace('#include <emissivemap_fragment>', `...计算自发光...计算云影// 添加少量大气菲涅耳效果,使其更加真实// 微调下面的第一个常数以获得更强或更弱的效果float intensity = 1.4 - dot( geometryNormal, vec3( 0.0, 0.0, 1.0 ) );vec3 atmosphere = vec3( 0.3, 0.6, 1.0 ) * pow(intensity, 5.0);diffuseColor.rgb += atmosphere;
`)

geometryNormal 是视图空间中地球表面的法线,视图空间就是以相机为原点的3D空间。所以 vec3( 0.0, 0.0, 1.0 ) 将是一个指向你自己(相机)的归一化向量。因此,在地球表面中心,dot(geometryNormal, vec3(0.0, 0.0, 1.0))的结果将恰好是1.0;从观察者(相机)的角度来看,地球边缘点结果将为 0.0,地球背面中心点的结果将为 -1.0。vec3( 0.3, 0.6, 1.0 )只是浅蓝色,想让效果更真实。
左边添加了大气菲涅尔,右边没有

添加大气层

这将是我们要添加到地球上的最后一个效果。我们都看过从太空拍摄的地球的美丽照片,周围总是有一圈浅蓝色的细带,这就是大气层。至于材质,我们将使用 ShaderMaterial,这意味着我们将提供自己的顶点和片段着色器。

varying vec3 vNormal;
varying vec3 eyeVector;void main() {vec4 mvPos = modelViewMatrix * vec4( position, 1.0 );vNormal = normalize( normalMatrix * normal );eyeVector = normalize(mvPos.xyz);gl_Position = projectionMatrix * mvPos;
}

这里的 gl_Position 仍然使用标准公式计算,通常在一行内完成,但我将其分为两步,因为我想将 mvPos 的归一化值分配给 eyeVector。这两个变量将与 vNormal 一起用于片段着色器。

varying vec3 vNormal;
varying vec3 eyeVector;
uniform float atmOpacity;
uniform float atmPowFactor;
uniform float atmMultiplier;void main() {// 从边缘开始到地球背部的中心,dotP会从0增加到1float dotP = dot( vNormal, eyeVector );// 该因子是为了创造真实的大气增厚的效果float factor = pow(dotP, atmPowFactor) * atmMultiplier;// 在颜色中添加一点 dotP,使其更白,同时颜色加深vec3 atmColor = vec3(0.35 + dotP/4.5, 0.35 + dotP/4.5, 1.0);// 使用 atmOpacity 控制大气颜色的整体强度gl_FragColor = vec4(atmColor, atmOpacity) * factor;
}

对于片段着色器中应用的方法,我参考了一个方法,是仅渲染该大气网格的背面,使用附加混合使其看起来透明,并从球体边缘向其中心快速增加着色。这里为什么 dotP 会向中心从 0 增加到 1,是因为我们正在渲染背面,vNormal 逐渐指向相机而不是远离相机。
image.png
atmOpacity 用于控制大气着色的不透明度,atmMultiplier 是与着色相乘的因子,可以使用它来使颜色更加强烈或不那么强烈。atmPowFactor 用于控制大气颜色变化的速度。请注意,我还将 dotP 添加到 atmColor 的r和g分量中,以便随着气氛“增强”,颜色变得更白,从而呈现更真实的色彩。

import vertexShader from "./shaders/vertex.glsl"
import fragmentShader from "./shaders/fragment.glsl"
...
const params = {...atmOpacity: { value: 0.7 },atmPowFactor: { value: 4.1 },atmMultiplier: { value: 9.5 },}...let app = {async initScene() {...let atmosGeo = new THREE.SphereGeometry(12.5, 64, 64)let atmosMat = new THREE.ShaderMaterial({vertexShader: vertexShader,fragmentShader: fragmentShader,uniforms: {atmOpacity: params.atmOpacity,atmPowFactor: params.atmPowFactor,atmMultiplier: params.atmMultiplier},// 注意,默认情况下,Three.js 使用 NormalBlending,如果输出颜色的不透明度变低,显示的颜色可能会变白blending: THREE.AdditiveBlending, // 比设置transparent:true效果更好,因为它避免了地球周围奇怪的黑暗边缘side: THREE.BackSide // 使其不会覆盖在地球顶部;这在顶点着色器中指向相反方向的法线})this.atmos = new THREE.Mesh(atmosGeo, atmosMat)this.group.add(this.atmos)...},...
}

这里有很多预定义的数字,例如大气半径是12.5、atmPowFactor是4.1 、atmMultiplier是9.5 等……这里没有明确的答案,这只是我测试过的组合之一,并且效果很好。
image.png

最后一步:星空背景

背景就是插入任何你认为适合的天空盒,我这里使用等距柱状图像来实现。

const envMap = await loadTexture(GaiaSky)
envMap.mapping = THREE.EquirectangularReflectionMappingscene.background = envMap

来看下最终效果:
image.png

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/158749.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【汇编语言学习笔记】一、基础知识

引言 汇编语言是直接在硬件之上工作的编程语言&#xff0c;首先要了解硬件系统的结构&#xff0c;才能有效的应用汇编语言对其编程。 1.1机器语言 机器语言是机器指令的集合。 机器指令展开来讲就是一台机器可以正确执行的命令。 1.2汇编语言 汇编语言的主体是汇编指令。 …

【云计算】相关解决方案介绍

文章目录 1.1 云服务环境 Eucalyptus1.1.1 介绍1.1.2 开源协议及语言1.1.3 官方网站 1.2 开源云计算平台 abiCloud1.2.1 开源协议及语言1.2.2 官方网站 1.3 分布式文件系统 Hadoop1.3.1 开源协议及语言1.3.2 官方网站 1.4 JBoss云计算项目集 StormGrind1.4.1 开源协议及语言1.4…

uniapp打包配置

安卓&#xff1a; 首先不管是什么打包都需要证书&#xff0c;安卓的证书一般都是公司提供或者自己去申请。然后把包名等下图框住的信息填上&#xff0c;点击打包即可。 ios&#xff1a;ios需要使用mac到苹果开发者平台去申请证书&#xff0c;流程可以参考下边的链接 参考链接…

解决git在window11操作很慢,占用很大cpu的问题

【git在window11操作很慢&#xff0c;占用很大cpu&#xff0c;最后也执行失败】 在谷歌输入&#xff1a;git very slow in window 11。通过下面链接终于找到了解决方案&#xff1a; https://www.reddit.com/r/vscode/comments/sulebx/slow_git_in_wsl_after_updating_to_window…

【Java基础知识】

文章目录 前言1. 添加环境变量的方法2.JDK JRE JVM3.main方法4.注释5.标识符6.数据类型与变量6.1字节&#xff1a;6.2变量6.3找最大值和最小值范围整型变量浮点型变量字符型变量布尔型变量 前言 最近焦虑迷茫&#xff0c;学习过程中遇到了困难&#xff0c;所以想重新复习一遍S…

【C++】适配器模式 - - stack/queue/deque

目录 一、适配器模式 1.1迭代器模式 1.2适配器模式 二、stack 2.1stack 的介绍和使用 2.2stack的模拟实现 三、queue 3.1queue的介绍和使用 3.2queue的模拟实现 四、deque&#xff08;不满足先进先出&#xff0c;和队列无关&#xff09; 4.1deque的原理介绍 4.2dequ…

在线兴趣教学类线上学习APP应用开发部署程序组建研发团队需要准备什么?

哈哈哈&#xff0c;同学们&#xff0c;我又来了&#xff0c;这个问题最近问的人有点多&#xff0c;但是说实话我也不知道&#xff0c;但是我还是总结了一下&#xff0c;毕竟我懂点代码的皮毛&#xff0c;同时我检索内容的时候&#xff0c;都是一些没有很新鲜的文案&#xff0c;…

大数据笔记-大数据处理流程

大家对大数据处理流程大体上认识差不多&#xff0c;具体做起来可能细节各不相同&#xff0c;一幅简单的大数据处理流程图如下&#xff1a; 1&#xff09;数据采集&#xff1a;数据采集是大数据处理的第一步。 数据采集面对的数据来源是多种多样的&#xff0c;包括各种传感器、社…

极简c++(4)类的静态成员

静态数据成员 ::是作用域操作符&#xff01; #include<iostream> using namespace std;class Point{private:int x,y;public:point(int x 0,int y 0):x(x),y(y){}~point();int getX(){return x;}int getY(){return x;} }假设需要统计点的个数&#xff0c;考虑添加一个…

计算机网络 | 网络层

计算机网络 | 网络层 计算机网络 | 网络层功能概述SDN&#xff08;Software-Defined Networking&#xff09;路由算法与路由协议IPv4IPv4 分组IPv4 分组的格式IPv4 数据报分片 参考视频&#xff1a;王道计算机考研 计算机网络 参考书&#xff1a;《2022年计算机网络考研复习指…

【VSCode】Windows环境下,VSCode 搭建 cmake 编译环境(VSCode 插件配置)

目录 一、下载编译器 1、下载 Windows GCC 2、选择编译器路径 二、下载插件 三、配置 cmake generator 四、编译工程 一、下载编译器 1、下载 Windows GCC 这里是在Windows环境下&#xff0c;所以下载的是 Windows 环境使用的 gcc 编译器。 下载地址: MinGW-w64 - for…

【mfc/VS2022】计图实验:绘图工具设计知识笔记

绘制曲线&#xff08;贝塞尔曲线&#xff09;&#xff1a; 转自&#xff1a;CDC 类 | Microsoft Learn 绘制一条或多条贝塞尔曲线。 BOOL PolyBezier(const POINT* lpPoints,int nCount);参数 lpPoints 指向包含曲线端点和控制点的 POINT 数据结构数组。 nCount 指定 lpPo…

伦敦金的交易时间究竟多长?

接触过伦敦金交易的投资者&#xff0c;应该都知道自己根本不用担心市场上没有交易的机会&#xff0c;因为它全天的交易时间长达20多个小时&#xff0c;也就是在每一个正常的交易日&#xff0c;除去交易平台中途短暂的系统维护时间&#xff0c;投资者几乎全天都可以做盘。 伦敦金…

mssql还原数据库失败

标题: Microsoft SQL Server Management Studio ------------------------------ 服务器 "192.168.31.132" 的 附加数据库 失败。 (Microsoft.SqlServer.Smo) 有关帮助信息&#xff0c;请单击: https://go.microsoft.com/fwlink?ProdNameMicrosoftSQLServer&…

第四篇Android--TextView使用详解

TextView是View体系中的一员&#xff0c;继承自View&#xff0c;用于在界面中展示文字。 基本用法&#xff1a; <TextViewandroid:id"id/textview"android:layout_width"wrap_content"android:layout_height"wrap_content"android:padding&q…

VScode运行C/C++

VScode运行C/C VScode的安装这里不讲 一、mingw64的下载 二、VS code打开文件夹与创建C文件 ----------------这一步给萌新看&#xff0c;有C和VScode的基础可跳过---------------- 1.创建一个文件夹 2.vscode打开刚刚创建的文件夹 3.新建文件&#xff0c;在输入文件名1.c后…

一种更具破坏力的DDoS放大攻击新模式

近日&#xff0c;内容分发网络&#xff08;CDN&#xff09;运营商Akamai表示&#xff0c;一种使网站快速瘫痪的DDoS放大攻击新方法正在被不法分子所利用。这种方法是通过控制数量巨大的中间设备&#xff08;middlebox&#xff0c;主要是指配置不当的服务器&#xff09;&#xf…

Git 回退代码的两种方法对比

Git 回退代码版本 在项目的开发中&#xff0c;有时候还是会出现&#xff0c;一些误提交了一些代码&#xff0c;这时候就会想撤回提交的代码&#xff0c;在Git中有两种方法可以使用&#xff0c;现在通过对比方法比较这两种方法的区别&#xff0c;分别适用于哪些情况&#xff1f…

软件架构设计(业务架构、应用架构、数据架构、技术架构)

一、架构相关概念 1、系统 系统&#xff1a;由一群有关联的个体组成&#xff0c;根据某种规则运作&#xff0c;能完成个别原件不能独立完成的工作的群体。大的系统可以嵌套小系统&#xff0c;被嵌套的小系统往往称为大系统的子系统。 2、模块 模块是从逻辑上将系统分解&#…

一种针对嵌入式KEIL工程的版本管理和跟踪的python脚本

这是去年写的一个python脚本&#xff0c;和KEIL V5配套使用的&#xff0c;借助git对工程文件进行版本管理和跟踪。打包后的exe和源文件整理到网盘了&#xff0c;有需要的可以自取&#xff0c;链接&#xff1a;https://pan.quark.cn/s/6c28fb43e8dc 提取码&#xff1a;R17N 关于…