前言
回顾:
黑暗面城市的照明
太阳的反光主要在海洋上可见白天和黑夜之间的部分
(黄昏)看起来是红色的
大气在地球周围产生光辉(就像一个体积)
我们不追求基于物理效果的渲染,这不会阻止最终结果看起来良好且逼真。
细分球体缓慢旋转
src/shaders/earth/中的基础着色器
lil-gui
vite-plugin-glsl
轨道控制
纹理加载器
在颜色纹理上更改颜色空间
earthDayTexture.colorSpace = THREE.SRGBColorSpace
问题
混合白天 黑天 与太阳位置相比 的颜色? mix
我们将使用通常的点状产品,但我们需要一个光的方向。目前,我们将在GLSL中创建太阳方向,稍后我们将使用一个统一的系统来控制
updateSun()
云层
一种做法是在地球上方的一个球体上添加云层,这使得云层具有一些灵活性,例如可以独立旋转云层。将整个云层旋转看起来不好,我们需要稍微增大球体的大小以防止Z轴冲突。
真实环境下 球体在白天 黑夜和中间得过渡 应该有颜色变化
还可以添加功能
添加更多调整
测试与其他行星纹理
按照现实中地球相对于太阳的自转来使地球转动通过在UV上添加一些位移来动画化云使用Perin函数或Perlin纹理创建云为太阳添加Lensflare(示例)(在静态/镜头中提供了纹理)
在后面添加星星或银河系环境图
项目结构
一、代码
three.js
import * as THREE from 'three'
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'
import GUI from 'lil-gui'
import earthVertexShader from './shaders/earth/vertex.glsl'
import earthFragmentShader from './shaders/earth/fragment.glsl'
import atmosphereVertexShader from './shaders/atmosphere/vertex.glsl'
import atmosphereFragmentShader from './shaders/atmosphere/fragment.glsl'/*** Base*/
// Debug
const gui = new GUI()// Canvas
const canvas = document.querySelector('canvas.webgl')// Scene
const scene = new THREE.Scene()// Loaders
const textureLoader = new THREE.TextureLoader()/*** Earth 地球*/
const earthParameters = {}
earthParameters.atmosphereDayColor = '#00aaff' // 白天颜色 大气层
earthParameters.atmosphereTwilightColor = '#ff6600' // 傍晚gui.addColor(earthParameters,'atmosphereDayColor').onChange(()=>{earthMaterial.uniforms.uAtmosphereDayColor.value.set(earthParameters.atmosphereDayColor) // 设置颜色atmosphereMaterial.uniforms.uAtmosphereDayColor.value.set(earthParameters.atmosphereDayColor) // 设置颜色})gui.addColor(earthParameters,'atmosphereTwilightColor').onChange(()=>{earthMaterial.uniforms.uAtmosphereTwilightColor.value.set(earthParameters.atmosphereTwilightColor) // 设置颜色atmosphereMaterial.uniforms.uAtmosphereTwilightColor.value.set(earthParameters.atmosphereTwilightColor) // 设置颜色})// texture
const earthDayTexture = textureLoader.load('./earth/day.jpg')
earthDayTexture.colorSpace = THREE.SRGBColorSpace
earthDayTexture.anisotropy = 8 // 边缘处 防止有割裂const earthNightTexture = textureLoader.load('./earth/night.jpg')
earthNightTexture.colorSpace = THREE.SRGBColorSpace
earthNightTexture.anisotropy = 8const earthSpecularCloudsTexture = textureLoader.load('./earth/specularClouds.jpg')
earthSpecularCloudsTexture.anisotropy = 8// Mesh
const earthGeometry = new THREE.SphereGeometry(2, 64, 64)
const earthMaterial = new THREE.ShaderMaterial({vertexShader: earthVertexShader,fragmentShader: earthFragmentShader,uniforms:{uDayTexture:new THREE.Uniform(earthDayTexture),uNightTexture:new THREE.Uniform(earthNightTexture),uSpecularCloudsTexture:new THREE.Uniform(earthSpecularCloudsTexture),uSunDirection:new THREE.Uniform(new THREE.Vector3(0,0,1)),uAtmosphereDayColor: new THREE.Uniform(new THREE.Color(earthParameters.atmosphereDayColor)),uAtmosphereTwilightColor: new THREE.Uniform(new THREE.Color(earthParameters.atmosphereTwilightColor)),}
})
const earth = new THREE.Mesh(earthGeometry, earthMaterial)
scene.add(earth)// Atmosphere 效果大气层周围光晕
const atmosphereMaterial = new THREE.ShaderMaterial({vertexShader:atmosphereVertexShader,fragmentShader:atmosphereFragmentShader,uniforms:{uSunDirection:new THREE.Uniform(new THREE.Vector3(0,0,1)),uAtmosphereDayColor: new THREE.Uniform(new THREE.Color(earthParameters.atmosphereDayColor)),uAtmosphereTwilightColor: new THREE.Uniform(new THREE.Color(earthParameters.atmosphereTwilightColor)),},side:THREE.BackSide, // 只展示后面transparent:true}
)
const atmosphere = new THREE.Mesh(earthGeometry,atmosphereMaterial)
atmosphere.scale.set(1.04,1.04,1.04)
scene.add(atmosphere)/* sun
*/
const sunSpherical = new THREE.Spherical(1,Math.PI * 0.5 * 0.5) // 创建几何体
const sunDirection = new THREE.Vector3()// Debug
const debugSun = new THREE.Mesh(new THREE.IcosahedronGeometry(0.1,2),new THREE.MeshBasicMaterial()
)
scene.add(debugSun)// Update
const updateSun = () =>{// Sun directionsunDirection.setFromSpherical(sunSpherical)// DebugdebugSun.position.copy(sunDirection).multiplyScalar(5) // 设置位置// UniformsearthMaterial.uniforms.uSunDirection.value.copy(sunDirection);atmosphereMaterial.uniforms.uSunDirection.value.copy(sunDirection);
}
updateSun()// Tweaks
gui .add(sunSpherical,'phi').min(0).max(Math.PI).onChange(updateSun)gui .add(sunSpherical,'theta').min(-Math.PI).max(Math.PI).onChange(updateSun)/*** Sizes*/
const sizes = {width: window.innerWidth,height: window.innerHeight,pixelRatio: Math.min(window.devicePixelRatio, 2)
}window.addEventListener('resize', () =>
{// Update sizessizes.width = window.innerWidthsizes.height = window.innerHeightsizes.pixelRatio = Math.min(window.devicePixelRatio, 2)// Update cameracamera.aspect = sizes.width / sizes.heightcamera.updateProjectionMatrix()// Update rendererrenderer.setSize(sizes.width, sizes.height)renderer.setPixelRatio(sizes.pixelRatio)
})/*** Camera*/
// Base camera
const camera = new THREE.PerspectiveCamera(25, sizes.width / sizes.height, 0.1, 100)
camera.position.x = 12
camera.position.y = 5
camera.position.z = 4
scene.add(camera)// Controls
const controls = new OrbitControls(camera, canvas)
controls.enableDamping = true/*** Renderer*/
const renderer = new THREE.WebGLRenderer({canvas: canvas,antialias: true
})
renderer.setSize(sizes.width, sizes.height)
renderer.setPixelRatio(sizes.pixelRatio)
renderer.setClearColor('#000011')// console.log(renderer.antialias)/*** Animate*/
const clock = new THREE.Clock()const tick = () =>
{const elapsedTime = clock.getElapsedTime()earth.rotation.y = elapsedTime * 0.1// Update controlscontrols.update()// Renderrenderer.render(scene, camera)// Call tick again on the next framewindow.requestAnimationFrame(tick)
}tick()
style.css
*
{margin: 0;padding: 0;
}html,
body
{overflow: hidden;
}.webgl
{position: fixed;top: 0;left: 0;outline: none;
}
index.html
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Earth</title><link rel="stylesheet" href="./style.css">
</head>
<body><canvas class="webgl"></canvas><script type="module" src="./script.js"></script>
</body>
</html>
earth/fragment.glsl
uniform sampler2D uDayTexture; // texture2D 2D采样器
uniform sampler2D uNightTexture;
uniform sampler2D uSpecularCloudsTexture;
uniform vec3 uSunDirection;
uniform vec3 uAtmosphereDayColor;
uniform vec3 uAtmosphereTwilightColor;varying vec2 vUv;
varying vec3 vNormal;
varying vec3 vPosition;/* dot向量x,y之间的点积mix(x, y, a)返回线性混合的x和y,如:x*(1−a)+y*asmoothstep(edge0, edge1, x)如果x <= edge0,返回0.0 ;如果x >= edge1 返回1.0;如果edge0 < x < edge1,则执行0~1之间的平滑埃尔米特差值。如果edge0 >= edge1,结果是未定义的。pow(x,y) x的y次方。如果x小于0,结果是未定义的。同样,如果x=0并且y<=0,结果也是未定义的。*/ void main()
{vec3 viewDirection = normalize(vPosition - cameraPosition);vec3 normal = normalize(vNormal);vec3 color = vec3(0.0);// Sun orientationfloat sunOrientation = dot(uSunDirection,normal);// Day / night colorfloat dayMix = smoothstep(-0.25,0.5,sunOrientation); // 平滑过渡 限制值vec3 dayColor = texture2D(uDayTexture,vUv).rgb;vec3 nightColor = texture2D(uNightTexture,vUv).rgb;color = mix(nightColor,dayColor,dayMix);// Specular clouds colorvec2 specularCloudsColor = texture2D(uSpecularCloudsTexture,vUv).rg;// Cloudsfloat cloudsMix = smoothstep(0.5,1.0,specularCloudsColor.g); // 改变云得多少cloudsMix *= dayMix; // 晚上不想要云,相乘这样到了晚上云就会消失color = mix(color,vec3(1.0),cloudsMix); // 将云和原本得混合起来// Fresnel 菲涅尔效果// Fresnel 根据相机角度,进行判断,相同方向1,直角0 ,相反 1// 同样法线应该渲染,vertex.glslfloat fresnel = dot(viewDirection,normal) + 1.0;fresnel = pow(fresnel,2.0);// Atmosphere 得到大气float almosphereDayMix = smoothstep(-0.5,1.0,sunOrientation); // 白天vec3 atmosphereColor = mix(uAtmosphereTwilightColor,uAtmosphereDayColor,almosphereDayMix);color = mix(color,atmosphereColor,fresnel * almosphereDayMix); // 相乘会得到在黄昏只是轻微的// Speculare 太阳的反射光 根据光线计算反射vec3 reflection = reflect(- uSunDirection , normal); // 镜面折射float specular = - dot(reflection , viewDirection); // 倒影,所以取负数specular = max(specular, 0.0); // 设置界限值specular = pow(specular, 32.0); // 设置值的大小// 大陆和海洋的反射光不应一样 ,specularCloudsColor.rspecular *= specularCloudsColor.r;// 从后面看 白色光太强 应减少 ,应该和大气层结合 在特定角度 改变成大气的颜色// 混合大气颜色,菲涅尔效果vec3 specularColor = mix(vec3(1.0),atmosphereColor,fresnel);// 和 反射光相乘 就可以得到根据 大气颜色反射光颜色改变color += specular * specularColor;// Final colorgl_FragColor = vec4(color, 1.0);#include <tonemapping_fragment>#include <colorspace_fragment>
}
earth/vertex.glsl
varying vec2 vUv;
varying vec3 vNormal;
varying vec3 vPosition;void main()
{// Positionvec4 modelPosition = modelMatrix * vec4(position, 1.0);gl_Position = projectionMatrix * viewMatrix * modelPosition;// Model normalvec3 modelNormal = (modelMatrix * vec4(normal, 0.0)).xyz;// VaryingsvUv = uv;vNormal = modelNormal;vPosition = modelPosition.xyz;
}
atmosphere/fragment.glsl
uniform vec3 uSunDirection;
uniform vec3 uAtmosphereDayColor;
uniform vec3 uAtmosphereTwilightColor;varying vec3 vNormal;
varying vec3 vPosition;/* dot向量x,y之间的点积mix(x, y, a)返回线性混合的x和y,如:x*(1−a)+y*asmoothstep(edge0, edge1, x)如果x <= edge0,返回0.0 ;如果x >= edge1 返回1.0;如果edge0 < x < edge1,则执行0~1之间的平滑埃尔米特差值。如果edge0 >= edge1,结果是未定义的。pow(x,y) x的y次方。如果x小于0,结果是未定义的。同样,如果x=0并且y<=0,结果也是未定义的。*/ void main()
{vec3 viewDirection = normalize(vPosition - cameraPosition);vec3 normal = normalize(vNormal);vec3 color = vec3(0.0);// Sun orientationfloat sunOrientation = dot(uSunDirection,normal);// Atmosphere 得到大气float almosphereDayMix = smoothstep(-0.5,1.0,sunOrientation); // 白天vec3 atmosphereColor = mix(uAtmosphereTwilightColor,uAtmosphereDayColor,almosphereDayMix);color =atmosphereColor; // 相乘会得到在黄昏只是轻微的// Alphafloat edgeAlpha = dot(viewDirection,normal);edgeAlpha = smoothstep(0.0,0.5,edgeAlpha);float dayAlpha = smoothstep(-0.5, 0.0 ,sunOrientation);float alpha = edgeAlpha * dayAlpha;// Final colorgl_FragColor = vec4(color, alpha);#include <tonemapping_fragment>#include <colorspace_fragment>
}
atmosphere/vertex.glsl
varying vec3 vNormal;
varying vec3 vPosition;void main()
{// Positionvec4 modelPosition = modelMatrix * vec4(position, 1.0);gl_Position = projectionMatrix * viewMatrix * modelPosition;// Model normalvec3 modelNormal = (modelMatrix * vec4(normal, 0.0)).xyz;// VaryingsvNormal = modelNormal;vPosition = modelPosition.xyz;
}
二、效果
地球着色器
总结
关于地球着色器的实现!