├── .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 | MagmaEffect | ICS 9 | 10 | 37 | 38 | 39 | 40 |

マウスドラッグで操作できます

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 | MagmaEffect | ICS 9 | 36 | 37 | 38 | 39 | 40 |

マウスドラッグで操作できます

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; 27 | viewVector: THREE.IUniform; 28 | }, 29 | // language=GLSL 30 | vertexShader: ` 31 | uniform vec3 viewVector; // カメラ位置 32 | varying float opacity; // 透明度 33 | void main() 34 | { 35 | // 頂点法線ベクトル x 36 | vec3 nNomal = normalize(normal); 37 | vec3 nViewVec = normalize(viewVector); 38 | 39 | // 透明度 40 | opacity = dot(nNomal, nViewVec); 41 | // 反転 42 | opacity = 1.0 - opacity; 43 | 44 | // お決まり 45 | gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); 46 | } 47 | `, 48 | // language=GLSL 49 | fragmentShader: ` 50 | uniform vec3 glowColor; 51 | varying float opacity; 52 | void main() 53 | { 54 | gl_FragColor = vec4(glowColor, opacity); 55 | } 56 | `, 57 | side: THREE.FrontSide, 58 | blending: THREE.AdditiveBlending, 59 | transparent: true, 60 | }); 61 | 62 | // メッシュ 63 | const mesh = new THREE.Mesh(geometry, material); 64 | this.add(mesh); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/magmaFlare/MagmaFlare.ts: -------------------------------------------------------------------------------- 1 | import * as THREE from "three"; 2 | import { Magma } from "./Magma"; 3 | import { Aura } from "./Aura"; 4 | import { InGlow } from "./InGlow"; 5 | import { FlareEmitter } from "./FlareEmitter"; 6 | import { SparkEmitter } from "./SparkEmitter"; 7 | import { OutGlow } from "./OutGlow"; 8 | import GUI from "lil-gui"; 9 | 10 | /** 11 | * マグマフレアクラスです。 12 | */ 13 | export class MagmaFlare extends THREE.Object3D { 14 | /** マグマ */ 15 | private readonly _magma: Magma; 16 | /** オーラ球 */ 17 | private readonly _aura: Aura; 18 | /** フレアエミッター */ 19 | private readonly _flareEmitter: FlareEmitter; 20 | /** スパークエミッター */ 21 | private readonly _sparkEmitter: SparkEmitter; 22 | 23 | /** 24 | * コンストラクター 25 | * @constructor 26 | */ 27 | constructor() { 28 | super(); 29 | 30 | // マグマ 31 | this._magma = new Magma(); 32 | 33 | // オーラ 34 | this._aura = new Aura(); 35 | 36 | // イングロー 37 | const inGlow = new InGlow(); 38 | 39 | // フレア 40 | this._flareEmitter = new FlareEmitter(); 41 | 42 | // スパーク 43 | this._sparkEmitter = new SparkEmitter(); 44 | 45 | // アウトグロー 46 | const outGlow = new OutGlow(); 47 | 48 | this.add(this._magma); 49 | this.add(this._aura); 50 | this.add(inGlow); 51 | this.add(this._flareEmitter); 52 | this.add(this._sparkEmitter); 53 | this.add(outGlow); 54 | 55 | const layers = { 56 | Magma: true, 57 | Aura: true, 58 | Flare: true, 59 | Spark: true, 60 | "Glow Inside": true, 61 | "Glow Outside": true, 62 | }; 63 | type LayerName = keyof typeof layers; 64 | 65 | const gui = new GUI(); 66 | const layerKeys = Object.keys(layers) as LayerName[]; 67 | layerKeys.forEach((key) => { 68 | gui.add(layers, key); 69 | }); 70 | gui.onChange((event) => { 71 | this._magma.visible = layers["Magma"]; 72 | this._aura.visible = layers["Aura"]; 73 | this._flareEmitter.visible = layers["Flare"]; 74 | this._sparkEmitter.visible = layers["Spark"]; 75 | outGlow.visible = layers["Glow Outside"]; 76 | inGlow.visible = layers["Glow Inside"]; 77 | }); 78 | gui.close(); 79 | } 80 | 81 | /** 82 | * フレーム毎の更新 83 | */ 84 | public update() { 85 | this._magma.update(); 86 | this._aura.update(); 87 | this._flareEmitter.update(); 88 | this._sparkEmitter.update(); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/Main.ts: -------------------------------------------------------------------------------- 1 | import { Camera } from "./Camera"; 2 | import { Plane } from "./Plane"; 3 | import { MagmaFlare } from "./magmaFlare/MagmaFlare"; 4 | import * as THREE from "three"; 5 | import { OrbitControls } from "three/examples/jsm/controls/OrbitControls"; 6 | 7 | /** 8 | * デモのメインクラスです。 9 | */ 10 | export class Main { 11 | /** シーンオブジェクトです。 */ 12 | private readonly _scene: THREE.Scene; 13 | /** カメラオブジェクトです。 */ 14 | private readonly _camera: Camera; 15 | /** レンダラーオブジェクトです。 */ 16 | private _renderer: THREE.WebGLRenderer; 17 | 18 | /** マグマフレア */ 19 | private readonly _magmaFlare: MagmaFlare; 20 | private readonly _controls: OrbitControls; 21 | 22 | /** 23 | * コンストラクターです。 24 | */ 25 | constructor() { 26 | // シーン 27 | this._scene = new THREE.Scene(); 28 | 29 | // カメラ 30 | this._camera = Camera.getInstance(); 31 | this._camera.position.set(8, 6, 0); 32 | 33 | const canvas = document.querySelector("#myCanvas")!; 34 | 35 | // カメラコントローラーを作成 36 | const controls = new OrbitControls(this._camera, canvas); 37 | controls.target.set(0, 0, 0); 38 | // 滑らかにカメラコントローラーを制御する 39 | controls.enableDamping = true; 40 | controls.dampingFactor = 0.02; 41 | // マウスホイールでのズームの範囲を指定 42 | controls.minDistance = 8; 43 | controls.maxDistance = 15; 44 | // パンできる範囲を指定 45 | controls.minPolarAngle = Math.PI / 8; 46 | controls.maxPolarAngle = Math.PI / 2.1; 47 | controls.autoRotate = true; 48 | 49 | this._controls = controls; 50 | 51 | // レンダラー 52 | this._renderer = new THREE.WebGLRenderer({ 53 | antialias: true, 54 | canvas: canvas, 55 | }); 56 | this._renderer.setClearColor(0x000000); 57 | this._renderer.setPixelRatio(devicePixelRatio); 58 | this._resize(); 59 | 60 | // 地面 61 | const plane = new Plane(); 62 | plane.position.y = -3; 63 | this._scene.add(plane); 64 | 65 | // マグマフレア 66 | this._magmaFlare = new MagmaFlare(); 67 | this._magmaFlare.position.y = 1; 68 | this._scene.add(this._magmaFlare); 69 | 70 | // 負荷分散のためちょっと待機 71 | const nextTick = 72 | "requestIdleCallback" in window 73 | ? window.requestIdleCallback 74 | : requestAnimationFrame; 75 | 76 | nextTick(() => { 77 | this._tick(); 78 | }); 79 | 80 | // リサイズを監視 81 | this._resize = this._resize.bind(this); 82 | window.addEventListener("resize", this._resize); 83 | } 84 | 85 | /** 86 | * フレーム毎のアニメーションの更新をかけます。 87 | */ 88 | private _tick() { 89 | requestAnimationFrame(() => { 90 | this._tick(); 91 | }); 92 | 93 | // カメラの更新 94 | this._controls.update(); 95 | 96 | this._magmaFlare.update(); 97 | 98 | // 描画 99 | this._renderer.render(this._scene, this._camera); 100 | } 101 | 102 | /** 103 | * リサイズ処理 104 | */ 105 | private _resize() { 106 | const width = window.innerWidth; 107 | const height = window.innerHeight; 108 | this._renderer.setSize(width, height); 109 | this._camera.aspect = width / height; 110 | this._camera.updateProjectionMatrix(); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/magmaFlare/Flare.ts: -------------------------------------------------------------------------------- 1 | import * as THREE from "three"; 2 | 3 | /** 4 | * フレアクラスです。 5 | */ 6 | export class Flare extends THREE.Object3D { 7 | /** カラーマップ */ 8 | private readonly _map: THREE.Texture; 9 | /** オフセット */ 10 | private readonly _offset: THREE.Vector2 = new THREE.Vector2(); 11 | 12 | /** ランダム係数 */ 13 | private _randomRatio: number = Math.random() + 1; 14 | 15 | /** 16 | * コンストラクター 17 | */ 18 | constructor() { 19 | super(); 20 | 21 | // 上面の半径 22 | const topRadius = 6; 23 | // 下面の半径 24 | const bottomRadius = 2; 25 | // ドーナツの太さ 26 | const diameter = topRadius - bottomRadius; 27 | 28 | // ジオメトリ 29 | const geometry = new THREE.CylinderGeometry( 30 | topRadius, 31 | bottomRadius, 32 | 0, 33 | 30, 34 | 3, 35 | true, 36 | ); 37 | 38 | // カラーマップ 39 | const loader = new THREE.TextureLoader(); 40 | this._map = loader.load("./assets/texture/aura3_type2.png"); 41 | this._map.colorSpace = THREE.SRGBColorSpace; 42 | this._map.wrapS = this._map.wrapT = THREE.RepeatWrapping; 43 | this._map.repeat.set(10, 10); 44 | 45 | // マテリアル 46 | const material = this._createMaterial(bottomRadius, diameter); 47 | 48 | // メッシュ 49 | const mesh = new THREE.Mesh(geometry, material); 50 | this.add(mesh); 51 | } 52 | 53 | /** 54 | * マテリアルを生成します。 55 | * @param bottomRadius 下面の半径 56 | * @param diameter ドーナツの太さ 57 | * @private 58 | */ 59 | private _createMaterial( 60 | bottomRadius: number, 61 | diameter: number, 62 | ): THREE.ShaderMaterial { 63 | const material = new THREE.ShaderMaterial({ 64 | uniforms: { 65 | map: { 66 | type: "t", 67 | value: this._map, 68 | }, 69 | offset: { 70 | type: "v2", 71 | value: this._offset, 72 | }, 73 | opacity: { 74 | type: "f", 75 | value: 0.15, 76 | }, 77 | innerRadius: { 78 | type: "f", 79 | value: bottomRadius, 80 | }, 81 | diameter: { 82 | type: "f", 83 | value: diameter, 84 | }, 85 | } as { 86 | map: THREE.IUniform; 87 | offset: THREE.IUniform; 88 | opacity: THREE.IUniform; 89 | innerRadius: THREE.IUniform; 90 | diameter: THREE.IUniform; 91 | }, 92 | // language=GLSL 93 | vertexShader: ` 94 | varying vec2 vUv; // フラグメントシェーダーに渡すUV座標 95 | varying float radius; // フラグメントシェーダーに渡す半径 96 | uniform vec2 offset; // カラーマップのズレ位置 97 | 98 | void main() 99 | { 100 | // 本来の一からuvをずらす 101 | vUv = uv + offset; 102 | // 中心から頂点座標までの距離 103 | radius = length(position); 104 | // 3次元上頂点座標を画面上の二次元座標に変換(お決まり) 105 | gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); 106 | } 107 | `, 108 | // language=GLSL 109 | fragmentShader: ` 110 | uniform sampler2D map; // テクスチャ 111 | uniform float opacity; // 透明度 112 | uniform float diameter; // ドーナツの太さ 113 | uniform float innerRadius; // 内円の半径 114 | varying vec2 vUv; // UV座標 115 | varying float radius; // 中心ドットまでの距離 116 | const float PI = 3.1415926; // 円周率 117 | 118 | void main() { 119 | // UVの位置からテクスチャの色を取得 120 | vec4 tColor = texture2D(map, vUv); 121 | // 描画位置がドーナツの幅の何割の位置になるか 122 | float ratio = (radius - innerRadius) / diameter; 123 | float opacity = opacity * sin(PI * ratio); 124 | // ベースカラー 125 | vec4 baseColor = (tColor + vec4(0.0, 0.0, 0.3, 1.0)); 126 | // 透明度を反映させる 127 | gl_FragColor = baseColor * vec4(1.0, 1.0, 1.0, opacity); 128 | } 129 | `, 130 | side: THREE.DoubleSide, 131 | blending: THREE.AdditiveBlending, 132 | depthTest: false, 133 | transparent: true, 134 | }); 135 | return material; 136 | } 137 | 138 | /** 139 | * フレーム毎の更新 140 | */ 141 | public update() { 142 | this._offset.x = (performance.now() / 1000) * 0.2 * this._randomRatio; 143 | this._offset.y = (-performance.now() / 1000) * 0.8 * this._randomRatio; 144 | } 145 | } 146 | --------------------------------------------------------------------------------