├── .gitignore ├── docs ├── assets │ └── texture │ │ ├── glow.png │ │ ├── magma.png │ │ ├── Burst01.png │ │ ├── Particle01.png │ │ └── aura3_type2.png └── index.html ├── public └── assets │ └── texture │ ├── glow.png │ ├── magma.png │ ├── Burst01.png │ ├── Particle01.png │ └── aura3_type2.png ├── src ├── index.ts ├── Plane.ts ├── magmaFlare │ ├── OutGlow.ts │ ├── FlareEmitter.ts │ ├── SparkEmitter.ts │ ├── Aura.ts │ ├── Magma.ts │ ├── Spark.ts │ ├── InGlow.ts │ ├── MagmaFlare.ts │ └── Flare.ts ├── Camera.ts └── Main.ts ├── tsconfig.json ├── package.json ├── vite.config.ts ├── README.md ├── LICENSE └── index.html /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | Thumbs.db 3 | node_modules 4 | *.map 5 | .idea 6 | /yarn.lock 7 | /package-lock.json 8 | -------------------------------------------------------------------------------- /docs/assets/texture/glow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ics-creative/160907_magma_effect/HEAD/docs/assets/texture/glow.png -------------------------------------------------------------------------------- /docs/assets/texture/magma.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ics-creative/160907_magma_effect/HEAD/docs/assets/texture/magma.png -------------------------------------------------------------------------------- /public/assets/texture/glow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ics-creative/160907_magma_effect/HEAD/public/assets/texture/glow.png -------------------------------------------------------------------------------- /docs/assets/texture/Burst01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ics-creative/160907_magma_effect/HEAD/docs/assets/texture/Burst01.png -------------------------------------------------------------------------------- /public/assets/texture/magma.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ics-creative/160907_magma_effect/HEAD/public/assets/texture/magma.png -------------------------------------------------------------------------------- /docs/assets/texture/Particle01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ics-creative/160907_magma_effect/HEAD/docs/assets/texture/Particle01.png -------------------------------------------------------------------------------- /docs/assets/texture/aura3_type2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ics-creative/160907_magma_effect/HEAD/docs/assets/texture/aura3_type2.png -------------------------------------------------------------------------------- /public/assets/texture/Burst01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ics-creative/160907_magma_effect/HEAD/public/assets/texture/Burst01.png -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { Main } from "./Main"; 2 | 3 | window.addEventListener("DOMContentLoaded", () => { 4 | new Main(); 5 | }); 6 | -------------------------------------------------------------------------------- /public/assets/texture/Particle01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ics-creative/160907_magma_effect/HEAD/public/assets/texture/Particle01.png -------------------------------------------------------------------------------- /public/assets/texture/aura3_type2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ics-creative/160907_magma_effect/HEAD/public/assets/texture/aura3_type2.png -------------------------------------------------------------------------------- /src/Plane.ts: -------------------------------------------------------------------------------- 1 | import * as THREE from "three"; 2 | 3 | /** 4 | * 地面クラス 5 | */ 6 | export class Plane extends THREE.Object3D { 7 | /** 8 | * コンストラクター 9 | */ 10 | constructor() { 11 | super(); 12 | 13 | // グリッドヘルパー 14 | const gridHelper = new THREE.GridHelper(20, 30); 15 | this.add(gridHelper); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "module": "ESNext", 5 | "strict": true, 6 | "lib": [ 7 | "ESNext", 8 | "DOM" 9 | ], 10 | "moduleResolution": "bundler", 11 | }, 12 | "include": ["src"], 13 | "exclude": [ 14 | "node_modules", 15 | "docs" 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "dev": "vite", 4 | "build": "vite build", 5 | "preview": "vite preview", 6 | "format": "prettier --write src" 7 | }, 8 | "devDependencies": { 9 | "prettier": "^3.5.3", 10 | "typescript": "^5.8.3", 11 | "vite": "^6.3.2" 12 | }, 13 | "dependencies": { 14 | "@types/three": "^0.175.0", 15 | "lil-gui": "^0.20.0", 16 | "three": "^0.175.0" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | 3 | export default defineConfig({ 4 | // Base public path when served in production 5 | base: './', 6 | 7 | // Configure build output 8 | build: { 9 | // Output directory (same as webpack) 10 | outDir: 'docs', 11 | }, 12 | 13 | // Development server configuration 14 | server: { 15 | // Open browser on server start 16 | open: true, 17 | }, 18 | }); 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # マグマエフェクト 2 | 3 | three.jsを使ったマグマフェクトサンプルです。 4 | 記事『[エフェクト作成入門講座 マグマエフェクト作成 three.js編](https://ics.media/entry/13973/)』で解説しています。 5 | 6 | このプロジェクトはViteを使用しています。 7 | 8 | ## Setup 9 | 10 | ``` 11 | npm install 12 | ``` 13 | 14 | 15 | ## Run development server 16 | 17 | ``` 18 | npm run dev 19 | ``` 20 | 21 | 22 | ## Build for production 23 | 24 | ``` 25 | npm run build 26 | ``` 27 | 28 | 29 | ## Preview production build 30 | 31 | ``` 32 | npm run preview 33 | ``` 34 | -------------------------------------------------------------------------------- /src/magmaFlare/OutGlow.ts: -------------------------------------------------------------------------------- 1 | import * as THREE from "three"; 2 | 3 | /** 4 | * アウトグロークラスです。 5 | */ 6 | export class OutGlow extends THREE.Object3D { 7 | /** 8 | * コンストラクター 9 | * @constructor 10 | */ 11 | constructor() { 12 | super(); 13 | 14 | // テクスチャーを読み込みます。 15 | const loader = new THREE.TextureLoader(); 16 | const map = loader.load("./assets/texture/Particle01.png"); 17 | map.colorSpace = THREE.SRGBColorSpace; 18 | // マテリアル 19 | const material = new THREE.SpriteMaterial({ 20 | map: map, 21 | color: 0xffffff, 22 | blending: THREE.AdditiveBlending, 23 | opacity: 0.8, 24 | transparent: true, 25 | }); 26 | 27 | // スプライト 28 | const sprite = new THREE.Sprite(material); 29 | sprite.scale.multiplyScalar(11); 30 | this.add(sprite); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/magmaFlare/FlareEmitter.ts: -------------------------------------------------------------------------------- 1 | import * as THREE from "three"; 2 | import { Flare } from "./Flare"; 3 | 4 | /** 5 | * フレアエミッタークラスです。 6 | */ 7 | export class FlareEmitter extends THREE.Object3D { 8 | /** フレアの数 */ 9 | private _flareNum: number = 10; 10 | /** フレアリスト */ 11 | private _flareList: Flare[] = []; 12 | 13 | /** 14 | * コンストラクター 15 | */ 16 | constructor() { 17 | super(); 18 | 19 | const perAngle = 360 / this._flareNum; 20 | for (let i = 0; i < this._flareNum; i++) { 21 | const rad = (perAngle * i * Math.PI) / 180; 22 | const flare = new Flare(); 23 | flare.rotation.x = rad; 24 | flare.rotation.y = rad; 25 | flare.rotation.z = rad / 2; 26 | this.add(flare); 27 | this._flareList.push(flare); 28 | } 29 | } 30 | 31 | /** 32 | * フレーム毎の更新です。 33 | */ 34 | public update() { 35 | this._flareList.forEach((flare) => { 36 | flare.update(); 37 | }); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/magmaFlare/SparkEmitter.ts: -------------------------------------------------------------------------------- 1 | import * as THREE from "three"; 2 | import { Spark } from "./Spark"; 3 | 4 | /** 5 | * スパークのエミッタークラス 6 | */ 7 | export class SparkEmitter extends THREE.Object3D { 8 | /** スパークリスト */ 9 | private _sparkList: Spark[] = []; 10 | /** スパークの数 */ 11 | private _sparkNum: number = 500; 12 | 13 | /** 14 | * コンストラクター 15 | * @constructor 16 | */ 17 | constructor() { 18 | super(); 19 | 20 | const perAngle = 360 / this._sparkNum; 21 | for (let i = 0; i < this._sparkNum; i++) { 22 | const rad = (perAngle * i * Math.PI) / 180; 23 | const spark = new Spark(); 24 | spark.rotation.x = 360 * Math.sin(rad); 25 | spark.rotation.z = rad; 26 | this.add(spark); 27 | this._sparkList.push(spark); 28 | } 29 | } 30 | 31 | /** 32 | * フレーム毎の更新 33 | */ 34 | public update() { 35 | this._sparkList.forEach((spark: Spark) => { 36 | spark.update(); 37 | }); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Camera.ts: -------------------------------------------------------------------------------- 1 | import * as THREE from "three"; 2 | import { OrbitControls } from "three/examples/jsm/controls/OrbitControls"; 3 | 4 | /** 5 | * カメラのクラスです。 6 | */ 7 | export class Camera extends THREE.PerspectiveCamera { 8 | private static _instance: Camera; 9 | 10 | /** 11 | * シングルトン参照 12 | */ 13 | public static getInstance(): Camera { 14 | return Camera._instance || new Camera(); 15 | } 16 | 17 | /** アニメーションに用いる角度の値です。 */ 18 | private _angle: number = 0; 19 | 20 | /** 21 | * コンストラクターです。 22 | */ 23 | constructor() { 24 | super(45, window.innerWidth / window.innerHeight, 1, 1000); 25 | 26 | this.update(); 27 | 28 | Camera._instance = this; 29 | } 30 | 31 | /** 32 | * 毎フレームの更新をかけます。 33 | */ 34 | public update() { 35 | // this._angle = performance.now() * 0.01; 36 | // const lad = (this._angle * Math.PI) / 180; 37 | // 38 | // const radius = 12 + 5 * Math.sin(lad * 2); 39 | // this.position.x = radius * Math.sin(lad); 40 | // this.position.z = radius * Math.cos(lad); 41 | // this.lookAt(new THREE.Vector3(0, 0, 0)); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 - 2023 ICS INC. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/magmaFlare/Aura.ts: -------------------------------------------------------------------------------- 1 | import * as THREE from "three"; 2 | 3 | /** 4 | * オーラ球クラスです。 5 | */ 6 | export class Aura extends THREE.Object3D { 7 | /** カラーマップ */ 8 | private readonly _map: THREE.Texture; 9 | 10 | /** 11 | * コンストラクター 12 | * @constructor 13 | */ 14 | constructor() { 15 | super(); 16 | 17 | // ジオメトリ 18 | const geometry = new THREE.SphereGeometry(2.02, 40, 40); 19 | 20 | // カラーマップ 21 | const loader = new THREE.TextureLoader(); 22 | this._map = loader.load("./assets/texture/aura3_type2.png"); 23 | this._map.colorSpace = THREE.SRGBColorSpace; 24 | 25 | this._map.wrapS = this._map.wrapT = THREE.RepeatWrapping; 26 | 27 | // マテリアル 28 | const material = new THREE.MeshBasicMaterial({ 29 | map: this._map, 30 | blending: THREE.AdditiveBlending, 31 | transparent: true, 32 | }); 33 | 34 | // メッシュ 35 | const mesh = new THREE.Mesh(geometry, material); 36 | this.add(mesh); 37 | } 38 | 39 | /** 40 | * フレーム毎の更新 41 | */ 42 | public update() { 43 | this._map.offset.x = -performance.now() / 1000 / 4; 44 | this._map.offset.y = -performance.now() / 1000 / 4; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 | 7 | 8 |マウスドラッグで操作できます
41 | 42 | 43 | -------------------------------------------------------------------------------- /src/magmaFlare/Magma.ts: -------------------------------------------------------------------------------- 1 | import * as THREE from "three"; 2 | 3 | /** 4 | * マグマ球クラスです。 5 | */ 6 | export class Magma extends THREE.Object3D { 7 | /** カラーマップ */ 8 | private readonly _map: THREE.Texture; 9 | 10 | /** 11 | * コンストラクター 12 | * @constructor 13 | */ 14 | constructor() { 15 | super(); 16 | 17 | // テクスチャーを読み込みます。 18 | const loader = new THREE.TextureLoader(); 19 | const map = loader.load("./assets/texture/magma.png"); 20 | map.colorSpace = THREE.SRGBColorSpace; 21 | 22 | // テクスチャーをあてた球のMeshを作成します。 23 | const mesh = new THREE.Mesh( 24 | // ジオメトリ 25 | new THREE.SphereGeometry(2, 40, 40), 26 | // マテリアル 27 | new THREE.MeshBasicMaterial({ map }), 28 | ); 29 | this.add(mesh); 30 | 31 | // 縦横でリピートするように設定します。 32 | map.wrapS = map.wrapT = THREE.RepeatWrapping; 33 | 34 | this._map = map; 35 | } 36 | 37 | /** 38 | * フレーム毎の更新 39 | */ 40 | public update() { 41 | // 時間の経過でテクスチャーをずらします。 42 | // performance.now()はページを開いてからの経過ミリ秒です。 43 | // 1000で割って単位を秒にします。 44 | this._map.offset.x = performance.now() / 1000 / 2; 45 | this._map.offset.y = performance.now() / 1000 / 2.5; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |マウスドラッグで操作できます
41 | 42 | 43 | -------------------------------------------------------------------------------- /src/magmaFlare/Spark.ts: -------------------------------------------------------------------------------- 1 | import * as THREE from "three"; 2 | 3 | /** 4 | * スパーククラス 5 | */ 6 | export class Spark extends THREE.Object3D { 7 | /** メッシュ */ 8 | private readonly _mesh: THREE.Mesh; 9 | 10 | /** スピード */ 11 | private _speed: number = Math.random() * 0.2 + 0.1; 12 | /** 透明度 */ 13 | private _opacity: number = 0.5; 14 | 15 | /** 16 | * コンストラクター 17 | */ 18 | constructor() { 19 | super(); 20 | 21 | // ジオメトリ 22 | 23 | // カラーマップ 24 | const loader = new THREE.TextureLoader(); 25 | const map = loader.load("./assets/texture/Burst01.png"); 26 | map.colorSpace = THREE.SRGBColorSpace; 27 | map.wrapS = map.wrapT = THREE.RepeatWrapping; 28 | 29 | // マテリアル 30 | const material = new THREE.MeshBasicMaterial({ 31 | map, 32 | transparent: true, 33 | side: THREE.DoubleSide, 34 | depthWrite: false, 35 | blending: THREE.AdditiveBlending, 36 | opacity: this._opacity, 37 | }); 38 | 39 | // メッシュ 40 | this._mesh = new THREE.Mesh(new THREE.PlaneGeometry(0.1, 2), material); 41 | this._mesh.position.y = Math.random() * 5; 42 | this._mesh.rotation.y = Math.random() * 2; 43 | this.add(this._mesh); 44 | } 45 | 46 | private _time: number = 0; 47 | /** 48 | * フレーム毎の更新 49 | */ 50 | public update() { 51 | const time = performance.now() - this._time; 52 | const speedRatio = time / 16; 53 | 54 | // 毎フレーム少しずつ移動し透明に近づける。 55 | const m = this._mesh.material as THREE.Material; 56 | m.opacity -= 0.01 * speedRatio; 57 | this._mesh.position.y -= this._speed * speedRatio; 58 | // 透明度が0以下だったら位置と透明度を初期化する。 59 | if (this._mesh.position.y < 0 || m.opacity < 0) { 60 | this._mesh.position.y = 8; 61 | m.opacity = this._opacity; 62 | } 63 | this._time = performance.now(); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/magmaFlare/InGlow.ts: -------------------------------------------------------------------------------- 1 | import * as THREE from "three"; 2 | import { Camera } from "../Camera"; 3 | 4 | /** 5 | * イングロークラスです。 6 | */ 7 | export class InGlow extends THREE.Object3D { 8 | /** 9 | * コンストラクター 10 | */ 11 | constructor() { 12 | super(); 13 | 14 | // ジオメトリ 15 | const geometry = new THREE.SphereGeometry(2.03, 40, 40); 16 | 17 | // カメラ 18 | const camera = Camera.getInstance(); 19 | 20 | // マテリアル 21 | const material = new THREE.ShaderMaterial({ 22 | uniforms: { 23 | glowColor: { type: "c", value: new THREE.Color(0x96ecff) }, 24 | viewVector: { type: "v3", value: camera.position }, 25 | } as { 26 | glowColor: THREE.IUniform