[READ-ONLY] Mirror of https://github.com/flo-bit/tiny-planets. procedurally generated tiny planets in the browsers flo-bit.dev/tiny-planets/
low-poly planets procedural-generation threejs
0

Configure Feed

Select the types of activity you want to include in your feed.

add ocean caustics, morphing

Florian (Sep 23, 2024, 10:27 AM +0200) 1b36d97c 1427c947

+426 -245
+2 -203
src/script.ts
··· 59 59 scene.add(ambientLight); 60 60 61 61 62 - const atmosphereShader = { 63 - uniforms: { 64 - lightDirection: { value: new THREE.Vector3(1.0, 1.0, 0.0).normalize() }, // Sunlight direction 65 - atmosphereColor: { value: new THREE.Vector3(0.3, 0.6, 1.0) }, // Blue tint for atmosphere 66 - 67 - }, 68 - vertexShader: ` 69 - varying vec3 vNormal; 70 - varying vec3 vViewLightDirection; // Light direction relative to the camera 71 - varying vec3 vViewPosition; // View position 72 - 73 - uniform vec3 lightDirection; // Light direction in world space 74 - 75 - void main() { 76 - // Transform the vertex normal to world space 77 - vNormal = normalize(normalMatrix * normal); 78 - // Transform the light direction to view space 79 - vViewLightDirection = (viewMatrix * vec4(lightDirection, 0.0)).xyz; 80 - vViewPosition = (modelViewMatrix * vec4(position, 1.0)).xyz; 81 - 82 - gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); 83 - } 84 - `, 85 - fragmentShader: ` 86 - varying vec3 vNormal; 87 - varying vec3 vViewLightDirection; 88 - varying vec3 vViewPosition; 89 - uniform vec3 atmosphereColor; 90 - 91 - void main() { 92 - 93 - // Normalize the normal and the view direction 94 - vec3 viewDirection = normalize(vViewPosition); 95 - 96 - // Calculate how much of the surface is perpendicular to the view direction 97 - float viewFactor = dot(normalize(vNormal), -viewDirection); 98 - 99 - // Calculate the dot product of the light direction and the surface normal 100 - float lightFactor = dot(normalize(vNormal), normalize(vViewLightDirection)); 101 - 102 - // Use smoothstep to soften the transition from light to dark side 103 - lightFactor = smoothstep(-0.2, 0.4, lightFactor); 104 - 105 - // Ensure a minimum glow, even on the dark side 106 - float minGlow = 0.2; // Adjust this to control how much it glows on the dark side 107 - lightFactor = mix(minGlow, 1.0, lightFactor); 108 - 109 - // Adjust the intensity for the atmosphere's glow, including the minimum glow 110 - float dotProduct = dot(normalize(vNormal), vec3(0, 0.0, 1.0)); 111 - float intensity = pow(dotProduct, 8.0); 112 - intensity *= lightFactor; 113 - 114 - viewFactor = clamp(1.0 - viewFactor, 0.0, 1.0); 115 - 116 - // Use smoothstep for a smooth transition on the edges 117 - float atmosphereFactor = smoothstep(0.0, 0.6, viewFactor); 118 - 119 - // Output the final color 120 - gl_FragColor = vec4(atmosphereColor, intensity * atmosphereFactor); 121 - // Set the final fragment color with the computed intensity 122 - //gl_FragColor = vec4(0.3, 0.6, 1.0, 1.0) * intensity; 123 - }`, 124 - side: THREE.FrontSide, 125 - transparent: true, 126 - depthWrite: false, 127 - }; 128 - 129 - // Create the atmosphere material 130 - const atmosphereMaterial = new THREE.ShaderMaterial(atmosphereShader); 131 - 132 - // Create the atmosphere geometry 133 - const atmosphereGeometry = new THREE.IcosahedronGeometry(1.2, 20); 134 - const atmosphere = new THREE.Mesh(atmosphereGeometry, atmosphereMaterial); 135 - atmosphere.renderOrder = 1; 136 - scene.add(atmosphere); 137 - 138 - 139 - 140 - // const atmosphereShader = { 141 - // uniforms: { 142 - // lightDirection: { value: new THREE.Vector3(1.0, 1.0, 0.0).normalize() }, // Sunlight direction 143 - // atmosphereColor: { value: new THREE.Vector3(0.3, 0.6, 1.0) }, // Blue tint for atmosphere 144 - // }, 145 - // vertexShader: ` 146 - // varying vec3 vNormal; 147 - // varying vec3 vViewPosition; 148 - 149 - // void main() { 150 - // // Pass the vertex normal to the fragment shader 151 - // vNormal = normalize(normalMatrix * normal); 152 - 153 - // // Get the view-space position (relative to the camera) 154 - // vViewPosition = (modelViewMatrix * vec4(position, 1.0)).xyz; 155 - 156 - // // Standard vertex position transformation 157 - // gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); 158 - // } 159 - // `, 160 - // fragmentShader: ` 161 - // varying vec3 vNormal; 162 - // varying vec3 vViewPosition; 163 - 164 - // uniform vec3 atmosphereColor; 165 - 166 - // void main() { 167 - // // Normalize the normal and the view direction 168 - // vec3 viewDirection = normalize(vViewPosition); 169 - 170 - // // Calculate how much of the surface is perpendicular to the view direction 171 - // float viewFactor = dot(normalize(vNormal), -viewDirection); 172 - 173 - // // Invert and clamp the viewFactor to ensure a smooth fade on the edges 174 - // viewFactor = clamp(1.0 - viewFactor, 0.0, 1.0); 175 - 176 - // // Use smoothstep for a smooth transition on the edges 177 - // float atmosphereFactor = smoothstep(0.2, 0.8, viewFactor); 178 - 179 - // // Apply the blue tint more strongly on the edges 180 - // vec3 finalColor = mix(vec3(1.0), atmosphereColor, atmosphereFactor); 181 - 182 - // // Output the final color 183 - // gl_FragColor = vec4(finalColor, atmosphereFactor); 184 - // } 185 - // `, 186 - // side: THREE.FrontSide, 187 - // transparent: true, 188 - // depthWrite: false, 189 - // }; 190 - 191 - // // Create atmosphere material using the shader 192 - // const atmosphereMaterial = new THREE.ShaderMaterial(atmosphereShader); 193 - 194 - // // Create your atmosphere geometry and apply the material 195 - // const atmosphereGeometry = new THREE.SphereGeometry(1.3, 32, 32); 196 - // const atmosphereMesh = new THREE.Mesh(atmosphereGeometry, atmosphereMaterial); 197 - // atmosphereMesh.renderOrder = 1; 198 - // scene.add(atmosphereMesh); 199 - 200 - // const atmosphereShader = { 201 - // uniforms: { 202 - // lightDirection: { value: new THREE.Vector3(1.0, 1.0, 0.0).normalize() }, // Sunlight direction 203 - // }, 204 - // vertexShader: ` 205 - // varying vec3 vNormal; 206 - // varying vec3 vViewLightDirection; // Light direction relative to the camera 207 - 208 - // uniform vec3 lightDirection; // Light direction in world space 209 - 210 - // void main() { 211 - // // Transform the vertex normal to world space 212 - // vNormal = normalize(normalMatrix * normal); 213 - // // Transform the light direction to view space 214 - // vViewLightDirection = (viewMatrix * vec4(lightDirection, 0.0)).xyz; 215 - 216 - // gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); 217 - // } 218 - // `, 219 - // fragmentShader: ` 220 - // varying vec3 vNormal; 221 - // varying vec3 vViewLightDirection; 222 - 223 - // void main() { 224 - // // Calculate the dot product of the light direction and the surface normal 225 - // float lightFactor = dot(normalize(vNormal), normalize(vViewLightDirection)); 226 - 227 - // // Use smoothstep to soften the transition from light to dark side 228 - // lightFactor = smoothstep(0.0, 0.5, lightFactor); 229 - 230 - // // Ensure a minimum glow, even on the dark side 231 - // float minGlow = 0.5; // Adjust this to control how much it glows on the dark side 232 - // lightFactor = mix(minGlow, 1.0, lightFactor); 233 - 234 - // // Adjust the intensity for the atmosphere's glow, including the minimum glow 235 - // float intensity = pow(0.7 - dot(normalize(vNormal), vec3(0, 0, 1.0)), 2.0); 236 - // intensity *= lightFactor; 237 - 238 - // // Calculate an alpha value for fog-like fading effect near the edges 239 - // // This will make the atmosphere more transparent at the edges 240 - // float alpha = lightFactor * 0.6; // Adjust alpha factor for stronger or softer fade 241 - 242 - // // Set the final fragment color with the computed intensity and alpha 243 - // gl_FragColor = vec4(0.3, 0.6, 1.0, alpha) * intensity; 244 - // }`, 245 - // side: THREE.BackSide, // Only render the back faces of the atmosphere 246 - // transparent: true, // Enable transparency for the fade effect 247 - // }; 248 - 249 - // // Create the atmosphere material 250 - // const atmosphereMaterial = new THREE.ShaderMaterial(atmosphereShader); 251 - 252 - // // Create the atmosphere geometry 253 - // const atmosphereGeometry = new THREE.IcosahedronGeometry(1, 20); 254 - // const atmosphere = new THREE.Mesh(atmosphereGeometry, atmosphereMaterial); 255 - // const scale = 1.2; 256 - // atmosphere.scale.set(scale, scale, scale); 257 - // scene.add(atmosphere); 258 - 259 62 let total = 0; 260 63 let lastDelta = 0; 261 64 renderer.setAnimationLoop((delta) => { ··· 270 73 271 74 if (!hasPlanet) { 272 75 console.log("Creating planet"); 273 - createPlanet(); 76 + createPlanet("beach"); 274 77 hasPlanet = true; 275 78 } 276 79 }); ··· 283 86 createPlanet("forest"); 284 87 } else if (event.key === "3") { 285 88 createPlanet("snowForest"); 286 - } 287 - 288 - if (event.key === "a") { 289 - atmosphere.visible = !atmosphere.visible; 290 89 } 291 90 }); 292 91 ··· 306 105 } 307 106 308 107 console.time("planet"); 309 - const planet = new Planet({ preset }); 108 + const planet = new Planet({ biome: { preset } }); 310 109 let mesh = await planet.create(); 311 110 scene.remove(planetMesh); 312 111 scene.add(mesh);
+63 -15
src/worlds/planet.ts
··· 6 6 Object3D, 7 7 Quaternion, 8 8 Vector3, 9 + IcosahedronGeometry, 9 10 } from "three"; 10 11 import { Biome, type BiomeOptions } from "./biome"; 11 12 import { loadModels } from "./models"; 13 + 14 + import oceansCausticMaterial from "./materials/OceanCausticsMaterial"; 15 + import atmosphereMaterial from "./materials/AtmosphereMaterial"; 12 16 13 17 export type PlanetOptions = { 14 18 scatter?: number; ··· 16 20 ground?: number; 17 21 18 22 detail?: number; 23 + 24 + biome?: BiomeOptions; 19 25 }; 20 26 21 27 export class Planet { ··· 31 37 32 38 vegetationPositions?: Record<string, Vector3[]>; 33 39 34 - constructor(biomeOptions: BiomeOptions, planetOptions: PlanetOptions = {}) { 35 - this.options = planetOptions; 40 + constructor(options: PlanetOptions = {}) { 41 + this.options = options; 36 42 37 - this.biome = new Biome(biomeOptions); 43 + this.biome = new Biome(options.biome); 38 44 this.biomeOptions = this.biome.options; 39 45 40 46 this.worker = new Worker(new URL("worker.ts", import.meta.url), { ··· 56 62 oceanColors: number[]; 57 63 oceanNormals: number[]; 58 64 vegetation: Record<string, Vector3[]>; 65 + oceanMorphPositions: number[]; 66 + oceanMorphNormals: number[]; 59 67 }; 60 68 requestId: number; 61 69 }; ··· 92 100 "color", 93 101 new Float32BufferAttribute(new Float32Array(data.oceanColors), 3), 94 102 ); 103 + // set morph targets 104 + oceanGeometry.morphAttributes.position = [ 105 + new Float32BufferAttribute( 106 + new Float32Array(data.oceanMorphPositions), 107 + 3, 108 + ), 109 + ]; 110 + oceanGeometry.morphAttributes.normal = [ 111 + new Float32BufferAttribute(new Float32Array(data.oceanMorphNormals), 3), 112 + ]; 95 113 96 114 oceanGeometry.computeVertexNormals(); 97 115 98 116 this.vegetationPositions = data.vegetation; 99 117 100 - const planetMesh = new Mesh( 101 - geometry, 102 - new MeshStandardMaterial({ 103 - vertexColors: true, 104 - }), 105 - ); 118 + const planetMesh = new Mesh(geometry, oceansCausticMaterial); 106 119 planetMesh.castShadow = true; 120 + 121 + planetMesh.onBeforeRender = ( 122 + renderer, 123 + scene, 124 + camera, 125 + geometry, 126 + material, 127 + ) => { 128 + if (material.userData.shader?.uniforms?.time) { 129 + material.userData.shader.uniforms.time.value = 130 + performance.now() / 1000; 131 + } 132 + //material.userData.shader.uniforms.time.value = performance.now() / 1000; 133 + }; 107 134 108 135 const oceanMesh = new Mesh( 109 136 oceanGeometry, ··· 117 144 ); 118 145 119 146 planetMesh.add(oceanMesh); 147 + oceanMesh.onBeforeRender = ( 148 + renderer, 149 + scene, 150 + camera, 151 + geometry, 152 + material, 153 + ) => { 154 + // update morph targets 155 + if (oceanMesh.morphTargetInfluences) 156 + oceanMesh.morphTargetInfluences[0] = 157 + Math.sin(performance.now() / 1000) * 0.5 + 0.5; 158 + }; 159 + 160 + this.addAtmosphere(planetMesh); 120 161 callback(planetMesh); 121 162 } 122 163 ··· 124 165 } 125 166 126 167 async create(): Promise<Mesh> { 168 + // let collection = "stylized_nature"; 169 + 127 170 const models = this.biomeOptions.vegetation?.items.map((item) => { 128 171 return item.name; 129 172 }); ··· 131 174 const loaded: Promise<Object3D[] | Mesh>[] = []; 132 175 133 176 for (const model of models ?? []) { 134 - const loadedModels = loadModels(model); 177 + const loadedModels = loadModels(model); //, collection); 135 178 loaded.push(loadedModels); 136 179 } 137 180 ··· 185 228 return planetPromise; 186 229 } 187 230 188 - createMesh(): Promise<Mesh> { 231 + async createMesh(): Promise<Mesh> { 189 232 return new Promise((resolve) => { 190 233 const requestId = this.requestId++; 191 234 this.callbacks[requestId] = resolve; ··· 193 236 this.worker.postMessage({ 194 237 type: "createGeometry", 195 238 requestId, 196 - data: { 197 - biomeOptions: this.biome.options, 198 - planetOptions: this.options, 199 - }, 239 + data: this.options, 200 240 }); 201 241 }); 242 + } 243 + 244 + addAtmosphere(planet: Mesh) { 245 + // Create the atmosphere geometry 246 + const atmosphereGeometry = new IcosahedronGeometry(1.2, 20); 247 + const atmosphere = new Mesh(atmosphereGeometry, atmosphereMaterial); 248 + atmosphere.renderOrder = 1; 249 + planet.add(atmosphere); 202 250 } 203 251 204 252 updatePosition(item: Object3D, pos: Vector3) {
+5 -4
src/worlds/presets.ts
··· 25 25 26 26 seaColors: [ 27 27 [-1, 0x000066], 28 - [-0.52, 0x0000aa], 28 + [-0.55, 0x0000aa], 29 29 [-0.1, 0x00f2e5], 30 30 ], 31 31 seaNoise: { 32 - min: -0.005, 33 - max: 0.005, 34 - scale: 5, 32 + min: -0.008, 33 + max: 0.008, 34 + scale: 8, 35 + octaves: 2, 35 36 }, 36 37 37 38 vegetation: {
+100 -23
src/worlds/worker.ts
··· 1 - import { IcosahedronGeometry, Vector3, BufferAttribute } from "three"; 1 + import { 2 + IcosahedronGeometry, 3 + Vector3, 4 + BufferAttribute, 5 + Float32BufferAttribute, 6 + } from "three"; 2 7 3 8 import { Biome, type BiomeOptions } from "./biome"; 4 9 import { type PlanetOptions } from "./planet"; ··· 17 22 const oceanPositions = oceanGeometry.getAttribute("position").array.buffer; 18 23 const oceanColors = oceanGeometry.getAttribute("color").array.buffer; 19 24 const oceanNormals = oceanGeometry.getAttribute("normal").array.buffer; 25 + const oceanMorphPositions = 26 + oceanGeometry.morphAttributes.position[0].array.buffer; 27 + const oceanMorphNormals = 28 + oceanGeometry.morphAttributes.normal[0].array.buffer; 20 29 21 30 postMessage( 22 31 { ··· 29 38 oceanColors, 30 39 oceanNormals, 31 40 vegetation, 41 + oceanMorphPositions, 42 + oceanMorphNormals, 32 43 }, 33 44 requestId, 34 45 }, 35 46 // @ts-expect-error - hmm 36 - [positions, colors, normals, oceanPositions, oceanColors, oceanNormals], 47 + [ 48 + positions, 49 + colors, 50 + normals, 51 + oceanPositions, 52 + oceanColors, 53 + oceanNormals, 54 + oceanMorphPositions, 55 + oceanMorphNormals, 56 + ], 37 57 ); 38 58 } else { 39 59 console.error("Unknown message type", type); 40 60 } 41 61 }; 42 62 43 - function createGeometry({ 44 - biomeOptions, 45 - planetOptions, 46 - }: { 47 - biomeOptions: BiomeOptions; 48 - planetOptions: PlanetOptions; 49 - }): [IcosahedronGeometry, IcosahedronGeometry, Record<string, Vector3[]>] { 63 + function createGeometry( 64 + planetOptions: PlanetOptions, 65 + ): [IcosahedronGeometry, IcosahedronGeometry, Record<string, Vector3[]>] { 50 66 const sphere = new IcosahedronGeometry(1, planetOptions.detail ?? 50); 51 67 const oceanSphere = new IcosahedronGeometry(1, planetOptions.detail ?? 50); 52 68 53 - const biome = new Biome(biomeOptions); 69 + const biome = new Biome(planetOptions.biome); 54 70 55 71 const vertices = sphere.getAttribute("position"); 56 72 const oceanVertices = oceanSphere.getAttribute("position"); ··· 62 78 string, 63 79 { 64 80 height: number; 65 - seaHeight: number; 66 81 scatter: Vector3; 82 + 83 + seaHeight: number; 84 + seaMorph: number; 67 85 } 68 86 >(); 69 87 ··· 84 102 b.fromBufferAttribute(vertices, 1); 85 103 86 104 // default to scatter = distance of first edge 87 - const scatterAmount = planetOptions.scatter ?? b.distanceTo(a); 105 + const scatterAmount = (planetOptions.scatter ?? 1) * b.distanceTo(a); 88 106 const scatterScale = 100; 89 107 90 108 const scatterNoise = new UberNoise({ ··· 94 112 seed: 0, 95 113 }); 96 114 115 + oceanSphere.morphAttributes.position = []; 116 + oceanSphere.morphAttributes.normal = []; 117 + 118 + const oceanMorphPositions: number[] = []; 119 + const oceanMorphNormals: number[] = []; 120 + 121 + const oceanA = new Vector3(), 122 + oceanB = new Vector3(), 123 + oceanC = new Vector3(), 124 + oceanD = new Vector3(), 125 + oceanE = new Vector3(), 126 + oceanF = new Vector3(); 127 + 128 + const temp = new Vector3(); 129 + 97 130 for (let i = 0; i < vertices.count; i += 3) { 98 131 a.fromBufferAttribute(vertices, i); 99 132 b.fromBufferAttribute(vertices, i + 1); 100 133 c.fromBufferAttribute(vertices, i + 2); 134 + 135 + oceanA.fromBufferAttribute(oceanVertices, i); 136 + oceanB.fromBufferAttribute(oceanVertices, i + 1); 137 + oceanC.fromBufferAttribute(oceanVertices, i + 2); 101 138 102 139 mid.set(0, 0, 0); 103 140 mid.addVectors(a, b).add(c).divideScalar(3); ··· 108 145 let v = a; 109 146 if (j === 1) v = b; 110 147 if (j === 2) v = c; 148 + 111 149 const key = `${v.x.toFixed(5)},${v.y.toFixed(5)},${v.z.toFixed(5)}`; 112 150 113 151 let move = calculatedVertices.get(key); ··· 126 164 v.y - scatterScale * 200, 127 165 ); 128 166 const seaHeight = biome.getSeaHeight(v) + 1; 167 + const secondSeaHeight = biome.getSeaHeight(v.addScalar(100)) + 1; 168 + 169 + v.subScalar(100); 129 170 130 171 move = { 131 172 height, 132 173 scatter: new Vector3(scatterX, scatterY, scatterZ), 133 174 seaHeight, 175 + seaMorph: secondSeaHeight, 134 176 }; 135 177 calculatedVertices.set(key, move); 136 178 } 137 179 138 180 normalizedHeight += move.height - 1; 139 181 v.add(move.scatter).normalize().multiplyScalar(move.height); 140 - 141 182 vertices.setXYZ(i + j, v.x, v.y, v.z); 142 183 143 - const oceanV = v.clone().normalize().multiplyScalar(move.seaHeight); 184 + let oceanV = oceanA; 185 + if (j === 1) oceanV = oceanB; 186 + if (j === 2) oceanV = oceanC; 187 + 188 + oceanV.add(move.scatter).normalize().multiplyScalar(move.seaMorph); 189 + oceanMorphPositions.push(oceanV.x, oceanV.y, oceanV.z); 190 + 191 + if (j === 0) { 192 + oceanD.copy(oceanV); 193 + } else if (j === 1) { 194 + oceanE.copy(oceanV); 195 + } else if (j === 2) { 196 + oceanF.copy(oceanV); 197 + } 198 + 199 + oceanV.normalize().multiplyScalar(move.seaHeight); 144 200 oceanVertices.setXYZ(i + j, oceanV.x, oceanV.y, oceanV.z); 145 201 } 146 202 ··· 182 238 } 183 239 184 240 // calculate new normal 185 - const normal = new Vector3(); 186 - normal.crossVectors(b.clone().sub(a), c.clone().sub(a)).normalize(); 241 + temp.crossVectors(b.clone().sub(a), c.clone().sub(a)).normalize(); 187 242 188 243 // flat shading, so all normals for the face are the same 189 - normals.setXYZ(i, normal.x, normal.y, normal.z); 190 - normals.setXYZ(i + 1, normal.x, normal.y, normal.z); 191 - normals.setXYZ(i + 2, normal.x, normal.y, normal.z); 244 + normals.setXYZ(i, temp.x, temp.y, temp.z); 245 + normals.setXYZ(i + 1, temp.x, temp.y, temp.z); 246 + normals.setXYZ(i + 2, temp.x, temp.y, temp.z); 192 247 193 248 // calculate steepness (acos of dot product of normal and up vector) 194 249 // (up vector = old mid point on sphere) 195 - const steepness = Math.acos(Math.abs(normal.dot(mid))); 250 + const steepness = Math.acos(Math.abs(temp.dot(mid))); 196 251 // steepness is between 0 and PI/2 197 252 198 253 const color = biome.getColor(mid, normalizedHeight, steepness); ··· 229 284 oceanColors[i * 3 + 8] = oceanColor.b; 230 285 } 231 286 232 - oceanNormals.setXYZ(i, mid.x, mid.y, mid.z); 233 - oceanNormals.setXYZ(i + 1, mid.x, mid.y, mid.z); 234 - oceanNormals.setXYZ(i + 2, mid.x, mid.y, mid.z); 287 + // calculate ocean normals 288 + temp 289 + .crossVectors(oceanB.clone().sub(oceanA), oceanC.clone().sub(oceanA)) 290 + .normalize(); 291 + 292 + oceanNormals.setXYZ(i, temp.x, temp.y, temp.z); 293 + oceanNormals.setXYZ(i + 1, temp.x, temp.y, temp.z); 294 + oceanNormals.setXYZ(i + 2, temp.x, temp.y, temp.z); 295 + 296 + temp 297 + .crossVectors(oceanE.clone().sub(oceanD), oceanF.clone().sub(oceanD)) 298 + .normalize(); 299 + 300 + oceanMorphNormals.push(temp.x, temp.y, temp.z); 301 + oceanMorphNormals.push(temp.x, temp.y, temp.z); 302 + oceanMorphNormals.push(temp.x, temp.y, temp.z); 235 303 } 304 + 305 + oceanSphere.morphAttributes.position[0] = new Float32BufferAttribute( 306 + oceanMorphPositions, 307 + 3, 308 + ); 309 + oceanSphere.morphAttributes.normal[0] = new Float32BufferAttribute( 310 + oceanMorphNormals, 311 + 3, 312 + ); 236 313 237 314 sphere.setAttribute("color", new BufferAttribute(colors, 3)); 238 315 oceanSphere.setAttribute("color", new BufferAttribute(oceanColors, 3));
+70
src/worlds/materials/AtmosphereMaterial.ts
··· 1 + import { ShaderMaterial, Vector3 } from "three"; 2 + 3 + const atmosphereShader = { 4 + uniforms: { 5 + lightDirection: { value: new Vector3(1.0, 1.0, 0.0).normalize() }, // Sunlight direction 6 + atmosphereColor: { value: new Vector3(0.3, 0.6, 1.0) }, // Blue tint for atmosphere 7 + }, 8 + vertexShader: ` 9 + varying vec3 vNormal; 10 + varying vec3 vViewLightDirection; // Light direction relative to the camera 11 + varying vec3 vViewPosition; // View position 12 + 13 + uniform vec3 lightDirection; // Light direction in world space 14 + 15 + void main() { 16 + // Transform the vertex normal to world space 17 + vNormal = normalize(normalMatrix * normal); 18 + // Transform the light direction to view space 19 + vViewLightDirection = (viewMatrix * vec4(lightDirection, 0.0)).xyz; 20 + vViewPosition = (modelViewMatrix * vec4(position, 1.0)).xyz; 21 + 22 + gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); 23 + } 24 + `, 25 + fragmentShader: ` 26 + varying vec3 vNormal; 27 + varying vec3 vViewLightDirection; 28 + varying vec3 vViewPosition; 29 + uniform vec3 atmosphereColor; 30 + 31 + void main() { 32 + 33 + // Normalize the normal and the view direction 34 + vec3 viewDirection = normalize(vViewPosition); 35 + 36 + // Calculate how much of the surface is perpendicular to the view direction 37 + float viewFactor = dot(normalize(vNormal), -viewDirection); 38 + 39 + // Calculate the dot product of the light direction and the surface normal 40 + float lightFactor = dot(normalize(vNormal), normalize(vViewLightDirection)); 41 + 42 + // Use smoothstep to soften the transition from light to dark side 43 + lightFactor = smoothstep(-0.2, 0.4, lightFactor); 44 + 45 + // Ensure a minimum glow, even on the dark side 46 + float minGlow = 0.2; // Adjust this to control how much it glows on the dark side 47 + lightFactor = mix(minGlow, 1.0, lightFactor); 48 + 49 + // Adjust the intensity for the atmosphere's glow, including the minimum glow 50 + float dotProduct = dot(normalize(vNormal), vec3(0, 0.0, 1.0)); 51 + float intensity = pow(dotProduct, 8.0); 52 + intensity *= lightFactor; 53 + 54 + viewFactor = clamp(1.0 - viewFactor, 0.0, 1.0); 55 + 56 + // Use smoothstep for a smooth transition on the edges 57 + float atmosphereFactor = smoothstep(0.2, 0.6, viewFactor); 58 + 59 + // Output the final color 60 + gl_FragColor = vec4(atmosphereColor, intensity * atmosphereFactor); 61 + // Set the final fragment color with the computed intensity 62 + //gl_FragColor = vec4(0.3, 0.6, 1.0, 1.0) * intensity; 63 + }`, 64 + transparent: true, 65 + depthWrite: false, 66 + }; 67 + 68 + const atmosphereMaterial = new ShaderMaterial(atmosphereShader); 69 + 70 + export default atmosphereMaterial;
+64
src/worlds/materials/OceanCausticsMaterial.ts
··· 1 + import { MeshStandardMaterial } from "three"; 2 + import { noise } from "./noise"; 3 + 4 + const oceansCausticMaterial = new MeshStandardMaterial({ 5 + vertexColors: true, 6 + }); 7 + oceansCausticMaterial.onBeforeCompile = (shader) => { 8 + const caustics = ` 9 + float caustics(vec4 vPos) { 10 + // More intricate warping for marble patterns 11 + // float warpFactor = 2.0; 12 + // vec4 warpedPos = vPos * warpFactor + snoise(vPos * warpFactor * 0.5); 13 + // vec4 warpedPos2 = warpedPos * warpFactor * 0.3 + snoise(warpedPos * warpFactor * 0.5 + vec4(0, 2, 4, 8)) + vPos; 14 + 15 + // // Modulate the color intensity based on the noise 16 + // float vein = snoise(warpedPos2 * warpFactor) * snoise(warpedPos); 17 + 18 + // float a = 1.0 - (sin(vein * 12.0) + 1.0) * 0.5; 19 + // float diff = snoise(vPos * warpFactor); 20 + // diff = diff * snoise(diff * vPos) * a; 21 + // return vec3((diff)); 22 + 23 + vec4 warpedPos = vPos * 2.0 + snoise(vPos * 3.0); 24 + vec4 warpedPos2 = warpedPos * 0.3 + snoise(warpedPos * 2.0 + vec4(0, 2, 4, 8)) + vPos; 25 + float vein = snoise(warpedPos2) * snoise(warpedPos); 26 + float a = 1.0 - (sin(vein * 2.0) + 1.0) * 0.5; 27 + 28 + return snoise(vPos + warpedPos + warpedPos2) * a * 1.5; 29 + }`; 30 + shader.vertexShader = `varying vec3 vPos;\n${shader.vertexShader}`.replace( 31 + `#include <begin_vertex>`, 32 + `#include <begin_vertex>\nvPos = position;`, 33 + ); 34 + 35 + shader.fragmentShader = ` 36 + uniform float time; 37 + varying vec3 vPos; 38 + ${noise} 39 + ${caustics} 40 + ${shader.fragmentShader}`; 41 + 42 + shader.fragmentShader = shader.fragmentShader.replace( 43 + "#include <color_fragment>", 44 + `#include <color_fragment> 45 + vec3 pos = vPos * 3.0; 46 + float len = length(vPos); 47 + // Fade in 48 + float fadeIn = smoothstep(0.96, 0.985, len); 49 + // Fade out 50 + float fadeOut = 1.0 - smoothstep(0.994, 0.999, len); 51 + float causticIntensity = fadeIn * fadeOut * 0.7; 52 + diffuseColor.rgb = mix(diffuseColor.rgb, vec3(1.0), causticIntensity * smoothstep(0.0, 1.0, caustics(vec4(pos, time * 0.05)))); 53 + `, 54 + ); 55 + 56 + shader.uniforms.time = { value: 0 }; 57 + oceansCausticMaterial.userData.shader = shader; 58 + 59 + // console.log("FRAGMENT", shader.fragmentShader); 60 + // console.log(); 61 + // console.log("VERTEX", shader.vertexShader); 62 + }; 63 + 64 + export default oceansCausticMaterial;
+122
src/worlds/materials/noise.ts
··· 1 + export const noise = /* GLSL */ ` 2 + // Description : Array and textureless GLSL 2D/3D/4D simplex 3 + // noise functions. 4 + // Author : Ian McEwan, Ashima Arts. 5 + // Maintainer : stegu 6 + // Lastmod : 20110822 (ijm) 7 + // License : Copyright (C) 2011 Ashima Arts. All rights reserved. 8 + // Distributed under the MIT License. See LICENSE file. 9 + // https://github.com/ashima/webgl-noise 10 + // https://github.com/stegu/webgl-noise 11 + // 12 + 13 + vec4 mod289(vec4 x) { 14 + return x - floor(x * (1.0 / 289.0)) * 289.0; 15 + } 16 + 17 + float mod289(float x) { 18 + return x - floor(x * (1.0 / 289.0)) * 289.0; 19 + } 20 + 21 + vec4 permute(vec4 x) { 22 + return mod289(((x * 34.0) + 10.0) * x); 23 + } 24 + 25 + float permute(float x) { 26 + return mod289(((x * 34.0) + 10.0) * x); 27 + } 28 + 29 + vec4 taylorInvSqrt(vec4 r) { 30 + return 1.79284291400159 - 0.85373472095314 * r; 31 + } 32 + 33 + float taylorInvSqrt(float r) { 34 + return 1.79284291400159 - 0.85373472095314 * r; 35 + } 36 + 37 + vec4 grad4(float j, vec4 ip) { 38 + const vec4 ones = vec4(1.0, 1.0, 1.0, -1.0); 39 + vec4 p, s; 40 + 41 + p.xyz = floor(fract(vec3(j) * ip.xyz) * 7.0) * ip.z - 1.0; 42 + p.w = 1.5 - dot(abs(p.xyz), ones.xyz); 43 + s = vec4(lessThan(p, vec4(0.0))); 44 + p.xyz = p.xyz + (s.xyz * 2.0 - 1.0) * s.www; 45 + 46 + return p; 47 + } 48 + 49 + // (sqrt(5) - 1)/4 = F4, used once below 50 + #define F4 0.309016994374947451 51 + 52 + float snoise(vec4 v) { 53 + const vec4 C = vec4(0.138196601125011, // (5 - sqrt(5))/20 G4 54 + 0.276393202250021, // 2 * G4 55 + 0.414589803375032, // 3 * G4 56 + -0.447213595499958); // -1 + 4 * G4 57 + 58 + // First corner 59 + vec4 i = floor(v + dot(v, vec4(F4))); 60 + vec4 x0 = v - i + dot(i, C.xxxx); 61 + 62 + // Other corners 63 + 64 + // Rank sorting originally contributed by Bill Licea-Kane, AMD (formerly ATI) 65 + vec4 i0; 66 + vec3 isX = step(x0.yzw, x0.xxx); 67 + vec3 isYZ = step(x0.zww, x0.yyz); 68 + // i0.x = dot( isX, vec3( 1.0 ) ); 69 + i0.x = isX.x + isX.y + isX.z; 70 + i0.yzw = 1.0 - isX; 71 + // i0.y += dot( isYZ.xy, vec2( 1.0 ) ); 72 + i0.y += isYZ.x + isYZ.y; 73 + i0.zw += 1.0 - isYZ.xy; 74 + i0.z += isYZ.z; 75 + i0.w += 1.0 - isYZ.z; 76 + 77 + // i0 now contains the unique values 0,1,2,3 in each channel 78 + vec4 i3 = clamp(i0, 0.0, 1.0); 79 + vec4 i2 = clamp(i0 - 1.0, 0.0, 1.0); 80 + vec4 i1 = clamp(i0 - 2.0, 0.0, 1.0); 81 + 82 + // x0 = x0 - 0.0 + 0.0 * C.xxxx 83 + // x1 = x0 - i1 + 1.0 * C.xxxx 84 + // x2 = x0 - i2 + 2.0 * C.xxxx 85 + // x3 = x0 - i3 + 3.0 * C.xxxx 86 + // x4 = x0 - 1.0 + 4.0 * C.xxxx 87 + vec4 x1 = x0 - i1 + C.xxxx; 88 + vec4 x2 = x0 - i2 + C.yyyy; 89 + vec4 x3 = x0 - i3 + C.zzzz; 90 + vec4 x4 = x0 + C.wwww; 91 + 92 + // Permutations 93 + i = mod289(i); 94 + float j0 = permute(permute(permute(permute(i.w) + i.z) + i.y) + i.x); 95 + vec4 j1 = permute(permute(permute(permute(i.w + vec4(i1.w, i2.w, i3.w, 1.0)) + i.z + vec4(i1.z, i2.z, i3.z, 1.0)) + i.y + vec4(i1.y, i2.y, i3.y, 1.0)) + i.x + vec4(i1.x, i2.x, i3.x, 1.0)); 96 + 97 + // Gradients: 7x7x6 points over a cube, mapped onto a 4-cross polytope 98 + // 7*7*6 = 294, which is close to the ring size 17*17 = 289. 99 + vec4 ip = vec4(1.0 / 294.0, 1.0 / 49.0, 1.0 / 7.0, 0.0); 100 + 101 + vec4 p0 = grad4(j0, ip); 102 + vec4 p1 = grad4(j1.x, ip); 103 + vec4 p2 = grad4(j1.y, ip); 104 + vec4 p3 = grad4(j1.z, ip); 105 + vec4 p4 = grad4(j1.w, ip); 106 + 107 + // Normalise gradients 108 + vec4 norm = taylorInvSqrt(vec4(dot(p0, p0), dot(p1, p1), dot(p2, p2), dot(p3, p3))); 109 + p0 *= norm.x; 110 + p1 *= norm.y; 111 + p2 *= norm.z; 112 + p3 *= norm.w; 113 + p4 *= taylorInvSqrt(dot(p4, p4)); 114 + 115 + // Mix contributions from the five corners 116 + vec3 m0 = max(0.6 - vec3(dot(x0, x0), dot(x1, x1), dot(x2, x2)), 0.0); 117 + vec2 m1 = max(0.6 - vec2(dot(x3, x3), dot(x4, x4)), 0.0); 118 + m0 = m0 * m0; 119 + m1 = m1 * m1; 120 + return 49.0 * (dot(m0 * m0, vec3(dot(p0, x0), dot(p1, x1), dot(p2, x2))) + dot(m1 * m1, vec2(dot(p3, x3), dot(p4, x4)))); 121 + 122 + }`;