【Three.js基础学习】34.Earch Shaders
前言
回顾:
黑暗面城市的照明
太阳的反光主要在海洋上可见白天和黑夜之间的部分
(黄昏)看起来是红色的
大气在地球周围产生光辉(就像一个体积)
我们不追求基于物理效果的渲染,这不会阻止最终结果看起来良好且逼真。
细分球体缓慢旋转
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 = 8
const 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 direction
sunDirection.setFromSpherical(sunSpherical)
// Debug
debugSun.position.copy(sunDirection).multiplyScalar(5) // 设置位置
// Uniforms
earthMaterial.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 sizes
sizes.width = window.innerWidth
sizes.height = window.innerHeight
sizes.pixelRatio = Math.min(window.devicePixelRatio, 2)
// Update camera
camera.aspect = sizes.width / sizes.height
camera.updateProjectionMatrix()
// Update renderer
renderer.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 controls
controls.update()
// Render
renderer.render(scene, camera)
// Call tick again on the next frame
window.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*a
smoothstep(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 orientation
float sunOrientation = dot(uSunDirection,normal);
// Day / night color
float 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 color
vec2 specularCloudsColor = texture2D(uSpecularCloudsTexture,vUv).rg;
// Clouds
float cloudsMix = smoothstep(0.5,1.0,specularCloudsColor.g); // 改变云得多少
cloudsMix *= dayMix; // 晚上不想要云,相乘这样到了晚上云就会消失
color = mix(color,vec3(1.0),cloudsMix); // 将云和原本得混合起来
// Fresnel 菲涅尔效果
// Fresnel 根据相机角度,进行判断,相同方向1,直角0 ,相反 1
// 同样法线应该渲染,vertex.glsl
float 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.r
specular *= specularCloudsColor.r;
// 从后面看 白色光太强 应减少 ,应该和大气层结合 在特定角度 改变成大气的颜色
// 混合大气颜色,菲涅尔效果
vec3 specularColor = mix(vec3(1.0),atmosphereColor,fresnel);
// 和 反射光相乘 就可以得到根据 大气颜色反射光颜色改变
color += specular * specularColor;
// Final color
gl_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()
{
// Position
vec4 modelPosition = modelMatrix * vec4(position, 1.0);
gl_Position = projectionMatrix * viewMatrix * modelPosition;
// Model normal
vec3 modelNormal = (modelMatrix * vec4(normal, 0.0)).xyz;
// Varyings
vUv = 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*a
smoothstep(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 orientation
float sunOrientation = dot(uSunDirection,normal);
// Atmosphere 得到大气
float almosphereDayMix = smoothstep(-0.5,1.0,sunOrientation); // 白天
vec3 atmosphereColor = mix(uAtmosphereTwilightColor,uAtmosphereDayColor,almosphereDayMix);
color =atmosphereColor; // 相乘会得到在黄昏只是轻微的
// Alpha
float edgeAlpha = dot(viewDirection,normal);
edgeAlpha = smoothstep(0.0,0.5,edgeAlpha);
float dayAlpha = smoothstep(-0.5, 0.0 ,sunOrientation);
float alpha = edgeAlpha * dayAlpha;
// Final color
gl_FragColor = vec4(color, alpha);
#include <tonemapping_fragment>
#include <colorspace_fragment>
}
atmosphere/vertex.glsl
varying vec3 vNormal;
varying vec3 vPosition;
void main()
{
// Position
vec4 modelPosition = modelMatrix * vec4(position, 1.0);
gl_Position = projectionMatrix * viewMatrix * modelPosition;
// Model normal
vec3 modelNormal = (modelMatrix * vec4(normal, 0.0)).xyz;
// Varyings
vNormal = modelNormal;
vPosition = modelPosition.xyz;
}
二、效果
地球着色器
总结
关于地球着色器的实现!