Hi everyone, I’m trying to create a realistic Earth in three.js. Currently I have two SphereGeometries, the Earth and the Clouds. I want to find a way for the Clouds to cast shadows onto the inner Earth sphere, but I don’t have a clue yet.
let earthGeo = new THREE.SphereGeometry(10, 64, 64)
let earthMat = new THREE.MeshStandardMaterial({
map: albedoMap,
bumpMap: bumpMap,
bumpScale: 0.03, // must be really small, if too high even bumps on the back side got lit up
})
this.earth = new THREE.Mesh(earthGeo, earthMat)
scene.add(this.earth)
Code for the Clouds:
let cloudGeo = new THREE.SphereGeometry(10.05, 64, 64)
let cloudsMat = new THREE.MeshPhongMaterial({
map: cloudsMap,
alphaMap: cloudsMap,
transparent: true
})
this.clouds = new THREE.Mesh(cloudGeo, cloudsMat)
scene.add(this.clouds)
Thanks for the prompt reply! I’ve just tried to add the alphaTest attribute onto my Clouds material and make sure shadow related properties are set, but still it doesn’t seem to work for me.
Here’s my full code in index.js:
// ThreeJS and Third-party deps
import * as THREE from "three"
import * as dat from 'dat.gui'
import Stats from "three/examples/jsm/libs/stats.module"
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls"
// Core boilerplate code deps
import { createCamera, createComposer, createRenderer, runApp } from "./core-utils"
// Other deps
import Albedo from "./assets/Albedo.jpg"
import Clouds from "./assets/Clouds.png"
import Bump from "./assets/Bump.jpg"
global.THREE = THREE
THREE.ColorManagement.enabled = true
let scene = new THREE.Scene()
let renderer = createRenderer({ antialias: true }, (_renderer) => {
// best practice: ensure output colorspace is in sRGB, see Color Management documentation:
// https://threejs.org/docs/#manual/en/introduction/Color-management
_renderer.outputEncoding = THREE.sRGBEncoding
_renderer.shadowMap.enabled = true
})
let camera = createCamera(45, 1, 1000, { x: 0, y: 0, z: 30 })
let app = {
async initScene() {
// OrbitControls
this.controls = new OrbitControls(camera, renderer.domElement)
this.controls.enableDamping = true
this.dirLight = new THREE.DirectionalLight()
this.dirLight.position.set(-50, 50, 0)
this.dirLight.castShadow = true
scene.add(this.dirLight)
const albedoMap = await this.loadTexture(Albedo)
const cloudsMap = await this.loadTexture(Clouds)
const bumpMap = await this.loadTexture(Bump)
let earthGeo = new THREE.SphereGeometry(10, 64, 64)
let earthMat = new THREE.MeshStandardMaterial({
map: albedoMap,
bumpMap: bumpMap,
bumpScale: 0.03, // must be really small, if too high even bumps on the back side got lit up
})
this.earth = new THREE.Mesh(earthGeo, earthMat)
this.earth.receiveShadow = true
scene.add(this.earth)
let cloudGeo = new THREE.SphereGeometry(10.05, 64, 64)
let cloudsMat = new THREE.MeshStandardMaterial({
map: cloudsMap,
alphaMap: cloudsMap,
transparent: true,
alphaTest: 0.1
})
this.clouds = new THREE.Mesh(cloudGeo, cloudsMat)
this.clouds.castShadow = true
scene.add(this.clouds)
// GUI controls
const gui = new dat.GUI()
// Stats - show fps
this.stats1 = new Stats()
this.stats1.showPanel(0) // Panel 0 = fps
this.stats1.domElement.style.cssText = "position:absolute;top:0px;left:0px;"
// this.container is the parent DOM element of the threejs canvas element
this.container.appendChild(this.stats1.domElement)
},
async loadTexture(url) {
this.textureLoader = this.textureLoader || new THREE.TextureLoader()
return new Promise(resolve => {
this.textureLoader.load(url, texture => {
resolve(texture)
})
})
},
// @param {number} interval - time elapsed between 2 frames
// @param {number} elapsed - total time elapsed since app start
updateScene(interval, elapsed) {
this.controls.update()
this.stats1.update()
this.earth.rotation.y += interval * 0.006
this.clouds.rotation.y += interval * 0.01
}
}
runApp(app, scene, renderer, camera, true, undefined, undefined)
You can actually simply use the clouds texture as direct shadow multiplying by inversed alpha. It requires a little shader change but the result will look much smoother, more stable and is cheaper than shadow mapping. You can also displace/deform the texture by light direction.
Sounds more complicated if that includes tweaking shaders but I’d like to learn more actually. Could you elaborate a little more so that I can further explore this option ! I do know a little bit about shaders but I’m not familiar with handling lighting in shaders yet.
once in a while fyrestar has good ideas, and this is one of those times. to implement it, you will need to 1) learn glsl, 2) look up material.onBeforeCompile example (fyrestar has some helper for that, Im sure it will be linked to in a reply), and 3) study 3js shader chunks to find the place to make your changes. this does sound complicated, but once you are at least on step 2, it is not. and the result should be great, too.
You should be familiar with shaders, here is some pseudocode how a simple approach would look like, assuming earth and cloud are a sphere with same UV coordinates.
earthMaterial.onBeforeCompile = function( shader ) {
shader.uniforms.tClouds = { value: cloudTexture };
shader.fragmentShader = shader.fragmentShader.replace('#include <map_pars_fragment>', '#include <map_pars_fragment>\nuniform sampler2D tClouds;');
shader.fragmentShader = shader.fragmentShader.replace('#include <emissivemap_fragment>', `#include <emissivemap_fragment>
diffuseColor.rgb *= max( 1.0 - texture( tClouds, vUv ).a, 0.2 ); // Clamp it up so it doesn't get too dark unless you want
`);
}
You can also smoothstep or pow the alpha of the clouds to get thicker shadows/different falloff, displacing/deforming by light direction is another addition you could add.
The pseudocode will be the left, for the right you need to do the directional displacement also dotting light direction with the surface normal then so at the horizon they fade out.
Although this topic is already solved, here is a way to achieve a similar effect without shadow casting and without custom shaders. The texture for the clouds is reused as a negative light map for the earth.
That was just what i suggested? Wether using as lightmap or with the 2 line code addition for a dedicated feature, for it to be actual shadows you need a code addition with displaced UV according to the direction/surface normal.