
- TOP
- ã¿ã°äžèЧ
- Laravel
Laravel
ã€ãã³ã

ãã¬ãžã³
æè¡ããã°
ã¯ãããŸããŠã5æã«M&Aã¯ã©ãŠããžå
¥ç€Ÿããã峯岞ã§ãã æ¢ã«1ã¶æãçµéããŸããããå
¥ç€Ÿãšã³ããªãæžããŠãããŸãã ãŸãã¯è»œãèªå·±ç޹ä»ãã ç¥å¥å·çåºèº«ã§ãæ°åããããã¯ãšã³ããšã³ãžãã¢ãšããŠåãå§ããçŸåš4幎ç®ã§ãã è¶£å³ã¯ã²ãŒã ã§ãã¢ã³ã¹ã¿ãŒãã³ã¿ãŒã®ãããªååãã¬ã€ãã§ããã²ãŒã ãç¹ã«å¥œãã æå€ãããããŸããããæãçŽãããŸã«ãããŸããå°åŠçã®é ã«ç±äžããŠããåæ®ã§ãä»ã§ã¯èšæ¶ããã©ããªããäœãããšãããçšåºŠã§ããã åè·ã«ã€ã㊠以åã¯ã€ãã³ãç³»ã®äŒç€Ÿã«3幎ã»ã©åšç±ããŠãããLaravelãCakePHPçã®PHPãã¬ãŒã ã¯ãŒã¯ãäž»ã«çšãããããã¯ãéçºã»æ¡ä»¶éçºãè¡ã£ãŠããŸãããâŠ
ã¯ããã« ããã«ã¡ã¯ããªããŒã«ããéçºéšå°å£²ã¢ããªããŒã ã®æ± ã§ãã æ¥åã§ Laravel Octane ã®ã¡ã¢ãªãæ®ãæåã«ã€ããŠèª¿æ»ããæ©äŒããããŸããã Laravel Octane ã¯ãé·æé皌åããããã»ã¹äžã§ Laravel ã¢ããªã±ãŒã·ã§ã³ãåãããŠé«éåããããŒã«ã§ãã䟿å©ãªäžæ¹ã§ãããã»ã¹ãé·ãçããããã¡ã¢ãªãæ®ãç¶ããæžãæ¹æ¬¡ç¬¬ã§ã¯ãªã¯ãšã¹ãéã§ç¶æ
ãåŒãç¶ãããŠããŸããšãããåŸæ¥ã® Nginx + PHP-FPM æ§æã® Laravel ã§ã¯çºçãã«ããç¹æ§ãæã£ãŠããŸãããã®ç¹æ§ãçè§£ããã«äœ¿ããšäºæããªãäºæ
ã«ã€ãªããå¯èœæ§ããããšæããŸããã ããã§æ¬èšäºã§ã¯ãOctane + Swoole ã®ä»çµã¿ãæŽçããäžã§ããµã³ãã«ããã°ã©ã ã§æåãæ€èšŒããWorker ããã»ã¹ãåžžé§ããããšã«èµ·å ããŠæ°ãã€ããã¹ããã€ã³ãã«ã€ããŠæŽçããããšæããŸãã Laravel ããã³ Octane ã«ã€ããŠå€å°ã®ç¥èãããæ¹ãåæã«æžããŠãããLaravel æ¬äœã®è§£èª¬çã«ã¯è§ŠããŸããã ãªããæ¬èšäºã®å
容ã¯äžæ¬¡æ
å ±ãã確èªããããã«åªããŠããŸãããç§ã®çè§£éãã Octane / Swoole ã®ããŒãžã§ã³å·®ã«ããæåã®éããå«ãŸããŠããå¯èœæ§ããããŸãã誀ã£ãŠå®è£
ãããšäºæ
ã«ã€ãªããåŸãé åã§ããããããæçµçã«ã¯ãèªèº«ã§ãœãŒã¹ã³ãŒããå
¬åŒããã¥ã¡ã³ããã確èªã®äžã§é©çšãã ããã ä»çµã¿ã®æŽç ãã®ç« ã§ã¯ãOctane + Swoole ã§åã®ãªã¯ãšã¹ãã®æ
å ±ãæ¬¡ã®ãªã¯ãšã¹ãã«æ®ãä»çµã¿ããæ¬¡ã® 4 ã€ã®èгç¹ããæŽçããŸãã Octane + Swoole ã§ã¯ Worker ããã»ã¹ãåžžé§ãããããPHP ããã»ã¹å
ã®ã¡ã¢ãªããªã¯ãšã¹ãããšã«åæåãããªã Octane ã¯ãªã¯ãšã¹ãããšã« $this->app ã clone ã㊠$sandbox äžã§åŠçããããšã§ãããŒã¹ã®ã¢ããªã€ã³ã¹ã¿ã³ã¹ãçŽæ¥æžãæããªãããã«ããŠãã ãã ã PHP ã® clone ã¯ã·ã£ããŒã³ããŒãªã®ã§ãå
±æããããªããžã§ã¯ãã®å
éšç¶æ
ã¯ãªã¯ãšã¹ãéã§æ®ãåŸã Octane 㯠RequestReceived ã€ãã³ãã«çŽã¥ããªã¹ããŒçŸ€ã§ãã¬ãŒã ã¯ãŒã¯åŽã®ç¶æ
ããªã»ããããŠãã æ¬èšäºã«ç»å Žããã¢ãŒããã¯ãã£å³ãã©ã€ããµã€ã¯ã«å³ã¯å¿
èŠãªèŠçŽ ã«çµã£ãŠç°¡ç¥åããŠããŸãã 1. Worker ããã»ã¹ãåžžé§ãããããã¡ã¢ãªã¯åæåãããªã Octane + Swoole ã¯ãé·æéçãã PHP ããã»ã¹ãç«ã¡äžããä»çµã¿ã§ãã Swoole ã®ããã»ã¹ã¢ãŒããã¯ã㣠Worker 㯠OS ããã»ã¹ã§ããManager ããè€æ°çæãããäžå®æ°ã®ãªã¯ãšã¹ããåŠçããã忢ã·ã°ãã«ãåä¿¡ãããŸã§èµ°ãç¶ããŸãã Worker ã®ã©ã€ããµã€ã¯ã« é·æéçãç¶ãã Worker ããã»ã¹ã¯æ¬¡ã®ããã«åããŸãã æ³šç®ãããã®ã¯ã1 ã€ã® Worker ããã»ã¹ãçãç¶ãããŸãŸè€æ°ã®ãªã¯ãšã¹ããé ã«åŠçãç¶ããæ§é ã§ããWorker èµ·åæã« 1 åã ã Laravel ã¢ããªã±ãŒã·ã§ã³ãçµã¿ç«ãŠãŠ $this->app ã«ä¿æãããã®åŸã¯ãªã¯ãšã¹ãã®ãã³ã«ãã® $this->app ã clone ã㊠$sandbox ãšããŠäœ¿ãåããŸãããã®ãåã $this->app ãè€æ°ãªã¯ãšã¹ãã«ãŸããã£ãŠäœ¿ããããç¹ããåŸã§èŠããåã®ãªã¯ãšã¹ãã®æ
å ±ãæ¬¡ã®ãªã¯ãšã¹ãã«æ®ã£ãŠããŸããä»çµã¿ã®äžéšã«ãªã£ãŠããŸãã Worker èµ·åæã« Laravel ã¢ããªã±ãŒã·ã§ã³ãçµã¿ç«ãŠãŠããåŠç Worker::boot() ã®å®è£
ã¯ä»¥äžã®éãã§ãã <?php // vendor/laravel/octane/src/Worker.php public function boot ( array $ initialInstances = []) : void { // ããŒã¹ãšãªã Laravel ã¢ããªã€ã³ã¹ã¿ã³ã¹ã1ã€çæ // 以éããªã¯ãšã¹ãã®ãã³ã«ãããã clone ãã $ this -> app = $ app = $ this -> appFactory -> createApplication ( ... ) ; $ this -> dispatchEvent ( $ app , new WorkerStarting ( $ app )) ; } ãªãã¡ã¢ãªãæ®ãã®ã Swoole + Octane ã§ã¯ä»¥äžã®ä»çµã¿ã§ã¡ã¢ãªãæ®ããŸãã Worker ããã»ã¹ãçãç¶ããããããªã¯ãšã¹ãçµäºæã«å€æ°ã»ãªããžã§ã¯ãã«å²ãåœãŠãããã¡ã¢ãªãè§£æŸãããªã çµæãšããŠã $this->app ãå«ã倿°ã»ãªããžã§ã¯ãããªã¯ãšã¹ãããŸããã§ã¡ã¢ãªã«æ®ãç¶ãã ããããå
ã¯ããã®ãã¡ã¢ãªã«æ®ãç¶ãã $this->app ããªã¯ãšã¹ãããšã«ã©ãæ±ããããšããããšã«çŠç¹ãåœãŠãŸãã 2. Octane ã¯ãªã¯ãšã¹ãããšã« $this->app ã clone ãã Worker ãåžžé§ããã° $this->app ãæ®ããŸãããã ããªã¯ãšã¹ãåŠçã®äžã§ $this->app ãçŽæ¥æžãæããŠããŸããšããã®å€æŽã次ã®ãªã¯ãšã¹ãã«ãã®ãŸãŸæ®ããŸããOctane ã¯ãããé¿ããããã«ããªã¯ãšã¹ãã®ãã³ã« $this->app ã clone ã㊠$sandbox ãäœãããã®äžã§ãªã¯ãšã¹ãåŠçãåãèšèšã«ãªã£ãŠããŸãã <?php // vendor/laravel/octane/src/Worker.php public function handle ( Request $ request , RequestContext $ context ) : void { // ã¢ããªã€ã³ã¹ã¿ã³ã¹ã clone ããŠãªã¯ãšã¹ãçšã® sandbox ãäœã CurrentApplication :: set ( $ sandbox = clone $ this -> app ) ; $ gateway = new ApplicationGateway ( $ this -> app, $ sandbox ) ; try { $ response = $ gateway -> handle ( $ request ) ; // ... ã¬ã¹ãã³ã¹è¿åŽ ... } finally { $ sandbox -> flush () ; // sandbox åŽã® bindings ã¯ãªã¢ unset ( $ gateway , $ sandbox , ... ) ; CurrentApplication :: set ( $ this -> app ) ; // å
ã®ã¢ããªã€ã³ã¹ã¿ã³ã¹ã«æ»ã } } ããã§ã® clone ã®åœ¹å²ã¯ããªã¯ãšã¹ãåŠçã $sandbox åŽã«éé¢ããŠãããŒã¹ã® $this->app ãçŽæ¥æžãæããªãããã«ããããšã§ãã $this->app èªäœã¯ Worker 寿åœãŸã§ãã£ãšçãç¶ããŸãããæ¯ãªã¯ãšã¹ãã®åŠçã $this->app ãçŽæ¥æžãæããªããã°ãçµæãšããŠããŒã¹ã倿Žããã«äœ¿ãåããããšããèšèšã«ãªã£ãŠããŸãã 3. clone ã¯ã·ã£ããŒã³ããŒãªã®ã§ãå
éšç¶æ
ã¯ãªã¯ãšã¹ãéã§æ®ãåŸã ãã ããPHP ã® clone ã¯ã·ã£ããŒã³ããŒã§ãããããåç¯ã®ãããŒã¹ãæžãæããªãããæãç«ã€ç¯å²ã«ã¯éçããããŸãã Application ãªããžã§ã¯ãèªäœã¯æ°èŠïŒãªã¯ãšã¹ãããšã®åšïŒ é
åããããã£ïŒ bindings / instances ãªã©ïŒã¯ã³ããŒãããïŒsandbox åŽã§æžãæããŠãå
ã«ã¯åæ ãããªãïŒ é
åã®äžèº«ïŒå®éã®ãªããžã§ã¯ãïŒã¯å
ã®ã¢ã㪠$this->app ãš $sandbox ã§å
±æããã ããã«ããã以äžã®ãããªæåã«ãªããŸãã äŸãã°ãªã¯ãšã¹ãäžã« app()->instance('request', $req) ã®ããã«å·®ãæ¿ããŠãã $sandbox åŽã«ã®ã¿åæ ãããããŒã¹ã® $this->app ã«ã¯åæ ãããªã äžæ¹ã§ãããŒã¹ã® $this->app ã«ç»é²ããã解決æžã¿ singleton ã€ã³ã¹ã¿ã³ã¹ã¯äž¡è
ã§å
±æããããŸãŸ ãsingleton ã«ãªã¯ãšã¹ãåºæããŒã¿ãå
¥ãããšæ¬¡ã®ãªã¯ãšã¹ãã«ãæ®ãããšããçŸè±¡ã¯ããã®ãclone ããŠããªããžã§ã¯ãèªäœã¯å
±æããããããšãèŠå ãšèããããŸãã clone ã¯ã³ã³ããã®é
åã¬ãã«ã§ã®éé¢ã¯æäŸãããã®ã®ãé
åã®äžã«å
¥ã£ãŠãããªããžã§ã¯ãã®å
éšç¶æ
ãŸã§ã¯å®ã£ãŠãããªãããšããã®ããã€ã³ãã§ãã 4. Octane ã® RequestReceived ãªã¹ããŒãäžéšã®ç¶æ
ããªã»ãããã clone ã ãã§ã¯é
åã®äžã«å
¥ã£ãŠãããªããžã§ã¯ãã®ããããã£æžãæããé²ããªããããOctane ã¯ãããããªã¯ãšã¹ãããšã«çºç«ããã€ãã³ããšããã«çŽã¥ããªã¹ããŒã§ãæç€ºçã«ç¶æ
ããªã»ããããããšã§è£ã£ãŠããŸãã Worker ã®ã¡ã€ã³ã«ãŒã Worker ã®ã¡ã€ã³ã«ãŒãã®äžã§ã¯ RequestReceived ã€ãã³ããçºç«ããããã©ã«ãã§ 8 åã®ãªã¹ããŒãé ã«å®è¡ããŠãã HTTP Kernel ã«åŠçãæž¡ããŸãã Worker èµ·å â WorkerStarting ã€ãã³ã â âââ ã¡ã€ã³ã«ãŒã ââââââââââââââââââââââââââââââââââââââââââ â RequestReceived ã€ãã³ã ââ [8 listeners] â â ââ FlushLocaleState â â ââ FlushQueuedCookies â â ââ FlushSessionState â â ââ FlushAuthenticationState â â ââ EnforceRequestScheme â â ââ EnsureRequestServerPortMatchesScheme â â ââ GiveNewRequestInstanceToApplication â â ââ GiveNewRequestInstanceToPaginator â â â â â HTTP Kernel (Middleware â Controller) â â â â â ãªã¯ãšã¹ãçµäº â âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ â max_requests ã«éããã Worker åèµ·å ããã 8 åã®ãªã¹ããŒã¯ãLocale / Cookie / Session / Auth ãšãã£ããã¬ãŒã ã¯ãŒã¯ç¶æ
ã®ãªã»ãããã app('request') ã®å·®ãæ¿ããHTTPS ã¹ããŒã ãããŒãã®æŽåæ§ãã§ãã¯ãªã©ãæ
ããŸãã å®è£
ã®äžã§ããç¹ã«æåãææ¡ããŠãããã 2 ã€ã確èªããŸãã ãŸããèªèšŒã¬ãŒããæ¯ãªã¯ãšã¹ãç Žæ£ãã FlushAuthenticationState ãèŠãŠã¿ãŸãã Laravel ã® AuthManager ã¯å
éšã§ Guard ã€ã³ã¹ã¿ã³ã¹ã $guards é
åã«ãã£ãã·ã¥ããå Guard ã¯ããã«èªèšŒæžã¿ãŠãŒã¶ãŒãããããã£ã«ä¿æããŸããOctane ã§ãã®ã€ã³ã¹ã¿ã³ã¹ãã¯ãªã¢ããªããšã singleton ã§èµ·ããçŸè±¡ãšåãæ§é ã§ãåã®ãªã¯ãšã¹ãã®èªèšŒãŠãŒã¶ãŒã次ã®ãªã¯ãšã¹ãã«åŒãç¶ãããŠããŸããŸãã FlushAuthenticationState ã¯ããã®ãã£ãã·ã¥ãæ¯ãªã¯ãšã¹ãç Žæ£ããããšã§ãåã®ãªã¯ãšã¹ãã®æ
å ±ãæ¬¡ã®ãªã¯ãšã¹ãã«æ®ããªãããã«ããŠããŸãã å®è£
ã¯ä»¥äžã®éãã§ãã <?php // vendor/laravel/octane/src/Listeners/FlushAuthenticationState.php class FlushAuthenticationState { public function handle ( $ event ) : void { if ( $ event -> sandbox -> resolved ( 'auth.driver' )) { $ event -> sandbox -> forgetInstance ( 'auth.driver' ) ; } if ( $ event -> sandbox -> resolved ( 'auth' )) { with ( $ event -> sandbox -> make ( 'auth' ) , function ( $ auth ) use ( $ event ) { $ auth -> setApplication ( $ event -> sandbox ) ; $ auth -> forgetGuards () ; }) ; } } } auth ãã³ã³ããã§è§£æ±ºæžã¿ã®å Žåã« forgetGuards() ãåŒã³ã ããGuards ã®ãã£ãã·ã¥ãã¯ãªã¢ããŠããããšãããããŸãã æ¬¡ã«ãRequest ã€ã³ã¹ã¿ã³ã¹ãå·®ãæ¿ãã GiveNewRequestInstanceToApplication ã®å®è£
ã¯ä»¥äžã®éãã§ãã <?php // vendor/laravel/octane/src/Listeners/GiveNewRequestInstanceToApplication.php class GiveNewRequestInstanceToApplication { public function handle ( $ event ) : void { $ event -> app -> instance ( 'request' , $ event -> request ) ; $ event -> sandbox -> instance ( 'request' , $ event -> request ) ; } } app('request') ãæ°ãã Request ã€ã³ã¹ã¿ã³ã¹ã«å·®ãæ¿ããŸãã app('request') ãåŒã¶ã³ãŒããåžžã«ææ°ã® Request ãèŠãããã®ã¯ããã®ãªã¹ããŒã®åãã«ãããã®ãšçè§£ã§ããŸãã äœãæ®ã£ãŠãäœãæ¶ããã®ã 2 ã€ã®ã¬ã€ã€ãŒã«åããŠæŽçããŸã ã¬ã€ã€ãŒ äœãåžžé§ããã ãªã»ããææ®µ Worker ããã»ã¹å
šäœ Worker ããã»ã¹èªäœ + 颿°ããŒãã« / ã¯ã©ã¹ããŒãã« / static 倿° / ã°ããŒãã«å€æ° octane:reload / max_request å°éã«ãã Worker åèµ·å Laravel Application ( $this->app ) bindingsãsingleton ã€ã³ã¹ã¿ã³ã¹ãboot æžã¿ ServiceProvider RequestReceived ãªã¹ããŒïŒéšåçïŒ ãªããå Worker ã¯ç¬ç«ãã OS ããã»ã¹ã§ãããããWorker éã®ã¡ã¢ãªã¯åé¢ãããŠããŸããã³ã«ãŒãã³ç¡å¹æã«ã¯åã Worker å
ã®ãªã¯ãšã¹ããé æ¬¡åŠçããããããæ°ã«ãã¹ãã¯ãåã Worker ã®äžã§ãåã®ãªã¯ãšã¹ãã®ããŒã¿ã次ã®ãªã¯ãšã¹ãã«åŒãç¶ãããŠããŸããªããããšããç¹ã«çµããããšèããããŸãã ãã®ããã§ãLaravel ã¢ããªã³ãŒãäžã§ãã䜿ãç¶æ
ä¿æã®æ¹æ³ããšã«ãåã Workerã»å¥ãªã¯ãšã¹ãã§ã©ãæ¯ãèãããæŽçãããšæ¬¡ã®ããã«ãªããŸãã ç¶æ
ä¿æã®æ¹æ³ å Workerã»å¥ãªã¯ãšã¹ã static 倿° æ®ã ã°ããŒãã«å€æ° / $GLOBALS æ®ã Worker boot æç¹ãªã©ã§è§£æ±ºæžã¿ã® singleton ã€ã³ã¹ã¿ã³ã¹ã®ãããã㣠æ®ã éåžž bind (æ¯åæ°èŠ) æ¯åæ°èŠ scoped ãã€ã³ãã£ã³ã° 次ã®ã©ã€ããµã€ã¯ã«éå§æã« flush $request->attributes Request èªäœãæ°èŠçæ ãæ®ãããšãªã£ãŠãã static / ã°ããŒãã«å€æ° / singleton ããããã£ã¯ããªã¯ãšã¹ãåºæã®ããŒã¿ãããªã¯ãšã¹ãããšã«å¢ãç¶ããããŒã¿ã眮ããšäºæ
ã«ã€ãªããåŸãç¹ã«æ³šæãå¿
èŠã§ããã¢ããªèµ·åæã« 1 床ã ãåæåããããããªäžå€ãªããŒã¿ããWorker å
ã§æå³çã«å
±æãããããŒã¿ã眮ãåã«ã¯åé¡ãªããšèããŠããŸãã ãããŸã§ã¯ä»æ§äžãããªã£ãŠããã¯ãããšããæŽçã§ãããæ¬¡ã¯ç°¡æçãªããã°ã©ã ã§åäœãæ€èšŒããŸãã æ€èšŒ ä»çµã¿ã®æŽçã§ç€ºãã芳ç¹ãã宿©ã§é ã«ç¢ºèªããŠãããŸãã static 倿° / ã°ããŒãã«å€æ°ããªã¯ãšã¹ãéã§æ®ãããš $this->app ã®å
éšç¶æ
ïŒsingleton ã€ã³ã¹ã¿ã³ã¹ã®ããããã£ïŒããªã¯ãšã¹ãéã§æ®ãããš RequestReceived ãªã¹ããŒãäžéšã®ç¶æ
ãå®éã«ãªã»ããããããš å ããŠããããã®æ€èšŒãæç«ããåæãšããŠãå Worker å
ã§ã¯ãªã¯ãšã¹ããé æ¬¡åŠçããããããšãæåã«ç¢ºèªããŸãã æ€èšŒç°å¢ æ€èšŒã¯ macOS äžã®ããŒã«ã« Docker ã³ã³ããã§ã以äžã®ããŒãžã§ã³æ§æã§è¡ããŸãã ãœãããŠã§ã¢ ããŒãžã§ã³ Laravel 12.59.0 Octane 2.17.3 Swoole 6.2.1 Octane ã¯æ¬¡ã®ã³ãã³ãã§èµ·åããŸãã php artisan octane:start --server=swoole --workers=10 --max-requests=500 ãã®ãšãå
éšã§é©çšããã䞻㪠Swoole ãªãã·ã§ã³ã¯æ¬¡ã®éãã§ãã èšå® å€ ç±æ¥ enable_coroutine false Octane ããã©ã«ãïŒLaravel æ¬äœãã³ã«ãŒãã³ã»ãŒãã§ãªãããæå³çã«ç¡å¹åïŒ worker_num 10 --workers=10 ã§æå® max_request 500 --max-requests=500 ã§æå®ïŒã¡ã¢ãªãªãŒã¯å¯ŸçïŒ ç¹å¥ãªèšå®ã¯ããŠããããOctane / Swoole ã®ããã©ã«ãã®ãŸãŸã§ãã æ€èšŒçšã«ãŒã㯠routes/web.php ã«çšæããcurl ã§å©ããŠçµæã芳å¯ããŸãã åæã®ç¢ºèª: å Worker å
ã§ãªã¯ãšã¹ããé æ¬¡åŠçãããããš ãŸã倧åæãšããŠã1 Worker å
ã§ã¯è€æ°ãªã¯ãšã¹ãã䞊è¡åŠçãããã1 ã€ãã€é çªã«åŠçãããããšã確èªããŸãããã以éã®æ€èšŒã¯ãã¹ãŠãåã Worker ã«æ¥ãè€æ°ãªã¯ãšã¹ããé çªã«åŠçãããããšããåæã§è°è«ãçµã¿ç«ãŠãããã確èªããŸãã 確èªçšã³ãŒã ãªã¯ãšã¹ããåãåã£ãã 2 ç§ã¹ãªãŒãã㊠PID ãšã³ã«ãŒãã³ ID ãè¿ãã ãã®åçŽãªã«ãŒããçšæããŸãã <?php Route :: get ( '/test-coroutine' , function () { $ pid = getmypid () ; $ cid = \Swoole\Coroutine :: getCid () ; sleep ( 2 ) ; return [ 'pid' => $ pid , 'cid' => $ cid , 'is_coroutine' => $ cid !== - 1 , ] ; }) ; å®è¡ Worker æ°ã 1 ã«çµã£ãç¶æ
ïŒ --workers=1 ïŒã§ã3 ãªã¯ãšã¹ãã䞊åã§æããŸãããã䞊è¡åŠçããããªãåèš 2 ç§ååŸã§å®äºããã¯ãã§ãã start= $( date +%s ) curl -s http://localhost:8001/test-coroutine > /tmp/r1 & curl -s http://localhost:8001/test-coroutine > /tmp/r2 & curl -s http://localhost:8001/test-coroutine > /tmp/r3 & wait echo " Total: $(( $ ( date +%s ) - start )) s " çµæ { " pid ": 14 ," cid ": -1 ," is_coroutine ": false } { " pid ": 14 ," cid ": -1 ," is_coroutine ": false } { " pid ": 14 ," cid ": -1 ," is_coroutine ": false } Total : 6s 3 ãªã¯ãšã¹ãã® PID ãäžèŽïŒ=14ïŒãã cid = -1 ã§ã³ã«ãŒãã³å€ãåèšæéã 2 ç§Ã3 ïŒ 6 ç§ãšãªã£ãŠããããšããã1 Worker å
ã§ã¯ãªã¯ãšã¹ãã䞊è¡ã§ã¯ãªãé æ¬¡åŠçãããããšã確èªã§ããŸããã æ€èšŒ 1: static 倿° / ã°ããŒãã«å€æ°ããªã¯ãšã¹ãéã§æ®ãããš ä»çµã¿ã®æŽçã§ãstatic 倿°ã¯å Worker å
ã§ã¯æç¶ããããšæžããŸããããããå®éã«ç¢ºãããŸãã 確èªçšã³ãŒã <?php Route :: get ( '/test-static' , function () { static $ counter = 0 ; $ counter ++ ; $ GLOBALS [ 'global_counter' ] = ( $ GLOBALS [ 'global_counter' ] ?? 0 ) + 1 ; return [ 'pid' => getmypid () , 'static_counter' => $ counter , 'global_counter' => $ GLOBALS [ 'global_counter' ] , ] ; }) ; static 倿°ãšã°ããŒãã«å€æ°ã®äž¡æ¹ãã€ã³ã¯ãªã¡ã³ãããã·ã³ãã«ãªã³ãŒãã§ãã å®è¡ ãŸããå Worker å
æåãèŠããã Worker æ° 1ïŒ --workers=1 ïŒã§é 次 10 ãªã¯ãšã¹ããæããŸãã for i in { 1 .. 10 } ; do curl -s http://localhost:8001/test-static echo done 次ã«ãWorker æ° 10ïŒ --workers=10 ïŒã§äžŠå 20 ãªã¯ãšã¹ããæããWorker éã®ç¬ç«æ§ã確èªããŸãã for i in { 1 .. 20 } ; do curl -s http://localhost:8001/test-static > /tmp/s_ $i & done wait for i in { 1 .. 20 } ; do cat /tmp/s_ $i ; echo; done | sort çµæ é æ¬¡ 10 ãªã¯ãšã¹ãïŒ --workers=1 ïŒïŒ { " pid ": 14 ," static_counter ": 1 ," global_counter ": 1 } { " pid ": 14 ," static_counter ": 2 ," global_counter ": 2 } { " pid ": 14 ," static_counter ": 3 ," global_counter ": 3 } ... { " pid ": 14 ," static_counter ": 10 ," global_counter ": 10 } 䞊å 20 ãªã¯ãšã¹ãïŒ --workers=10 ïŒïŒ { " pid ": 22 ," static_counter ": 1 ," global_counter ": 1 } { " pid ": 22 ," static_counter ": 2 ," global_counter ": 2 } { " pid ": 23 ," static_counter ": 1 ," global_counter ": 1 } { " pid ": 23 ," static_counter ": 2 ," global_counter ": 2 } { " pid ": 24 ," static_counter ": 1 ," global_counter ": 1 } { " pid ": 24 ," static_counter ": 2 ," global_counter ": 2 } ... { " pid ": 31 ," static_counter ": 1 ," global_counter ": 1 } { " pid ": 31 ," static_counter ": 2 ," global_counter ": 2 } é æ¬¡ 10 ãªã¯ãšã¹ãã§ã¯å
šãŠåã PIDïŒ=14ïŒã§ã static_counter ãš global_counter ã 1 ãã 10 ãŸã§é£ç¶ããŠå¢å ããŠããŸããå Worker å
ã§ã¯ static / ã°ããŒãã«å€æ°ãæç¶ããŠããããšã確èªã§ããŸã 䞊å 20 ãªã¯ãšã¹ãã§ã¯ 10 åã®ç°ãªã PIDïŒ22 ã 31ïŒã«ãªã¯ãšã¹ãã忣ããããããã® Worker ã§ã«ãŠã³ã¿ãç¬ç«ã« 1 ããå§ãŸã£ãŠããŸããWorker éã§ã¡ã¢ãªãåé¢ãããŠããããšãåãããŸã 以äžããã static 倿°ãšã°ããŒãã«å€æ°ã¯å Worker å
ã®ãªã¯ãšã¹ãéã§æç¶ããWorker éã§ã¯åé¢ãããããšã確èªã§ããŸããã ããã¯ã1 ãªã¯ãšã¹ãç®ã®å€ã 2 ãªã¯ãšã¹ãç®ã«æå³ããèŠããŠããŸãå¯èœæ§ãæå³ããŸããåã®ãªã¯ãšã¹ãã®æ
å ±ãæ¬¡ã®ãªã¯ãšã¹ãã«æ®ããã¿ãŒã³ãšèãããããããå©çšã«ã¯æ³šæãå¿
èŠããã§ãã æ€èšŒ 2: $this->app ã®å
éšç¶æ
ããªã¯ãšã¹ãéã§æ®ãããš singleton ã®äžã«ãªã¯ãšã¹ãåºæã®ããŒã¿ãä¿æããŠããªã¯ãšã¹ãéã§å€ãæ®ãããšã確èªããŸãã 確èªçšã³ãŒã <?php // app/Services/UserContextSingletonService.php class UserContextSingletonService { private ? string $ currentUserName = null ; public function setCurrentUser ( string $ name ) : void { $ this -> currentUserName = $ name ; } public function getCurrentUser () : ? string { return $ this -> currentUserName; } } <?php // app/Providers/AppServiceProvider.php public function register () : void { $ this -> app -> singleton ( UserContextSingletonService :: class ) ; } public function boot () : void { // ããŒã¹ã® $this->app->instances ã«ã€ã³ã¹ã¿ã³ã¹ãæ ŒçŽãããããboot æç¹ã§ resolve ãã $ this -> app -> make ( UserContextSingletonService :: class ) ; } éåžžã® singleton() ã ãã§ã¯ãåå app(...) 解決æã«ã€ã³ã¹ã¿ã³ã¹ã sandbox åŽã«å
¥ãããªã¯ãšã¹ãçµäºæã® $sandbox->flush() ã§ç Žæ£ãããŸãããªã¯ãšã¹ãéã§æç¶ããç¶æ
ãåçŸããããããµã³ãã«ã§ã¯ boot() ã§ make() ãåŒãã§ããŒã¹ã® $this->app->instances ã«ã€ã³ã¹ã¿ã³ã¹ãç©ãã§ããŸãã <?php use App\Services\UserContextSingletonService; Route :: get ( '/test-singleton-set/{name}' , function ( string $ name ) { app ( UserContextSingletonService :: class ) -> setCurrentUser ( $ name ) ; return [ 'pid' => getmypid () , 'action' => 'SET' , 'value' => app ( UserContextSingletonService :: class ) -> getCurrentUser () , ] ; }) ; Route :: get ( '/test-singleton-get' , function () { return [ 'pid' => getmypid () , 'action' => 'GET' , 'value' => app ( UserContextSingletonService :: class ) -> getCurrentUser () , ] ; }) ; å®è¡ curl http://localhost:8001/test-singleton-set/Alice curl http://localhost:8001/test-singleton-get curl http://localhost:8001/test-singleton-get çµæ { " pid ": 14 ," action ":" SET "," value ":" Alice " } { " pid ": 14 ," action ":" GET "," value ":" Alice " } â å¥ãªã¯ãšã¹ããªã®ã«æ®ã£ãŠãã { " pid ": 14 ," action ":" GET "," value ":" Alice " } â ãŸã æ®ã£ãŠãã å¥ã®ãªã¯ãšã¹ãã«ãããããããAlice ãšããå€ããã®ãŸãŸèŠããŠããŸãã singleton ãã€ã³ãã£ã³ã°ã®ã€ã³ã¹ã¿ã³ã¹ããããã£ã«æ ŒçŽããå€ãæ®ãããšã確èªã§ããŸããã æ€èšŒ 3: RequestReceived ãªã¹ããŒãäžéšã®ç¶æ
ããªã»ããããããš FlushAuthenticationState ãªã¹ããŒã Guard ãã£ãã·ã¥ãç Žæ£ããŠããããšã確ãããŸãã 確èªçšã³ãŒã <?php use App\Models\User; use Illuminate\Support\Facades\Auth; Route :: get ( '/test-auth-set/{name}' , function ( string $ name ) { $ user = new User ([ 'name' => $ name ]) ; Auth :: setUser ( $ user ) ; return [ 'pid' => getmypid () , 'action' => 'SET' , 'user_name' => Auth :: user () ?-> name , ] ; }) ; Route :: get ( '/test-auth-get' , function () { return [ 'pid' => getmypid () , 'action' => 'GET' , 'user_name' => Auth :: user () ?-> name , ] ; }) ; Auth::setUser() ãå©çšããŠããã©ã«ãã® Guardã® $user ããããã£ã«çŽæ¥ User ã€ã³ã¹ã¿ã³ã¹ãå
¥ããGuard ãã£ãã·ã¥ã®ç¶æ
ãäœã£ãŠæ€èšŒããŸãã å®è¡ (1) ããã©ã«ãæ§æïŒ FlushAuthenticationState æå¹ïŒ curl http://localhost:8001/test-auth-set/Alice curl http://localhost:8001/test-auth-get çµæã¯ä»¥äžã®éãã§ãã { " pid ": 14 ," action ":" SET "," user_name ":" Alice " } { " pid ": 14 ," action ":" GET "," user_name ": null } â flush ã§æ¶ããŠãã å®è¡ (2) FlushAuthenticationState ãå€ããå Žå æ€èšŒã®ããã RequestReceived ãªã¹ããŒãã FlushAuthenticationState ãé€å€ããŸãã <?php // config/octane.php ïŒlisteners éšåã®ã¿æç²ïŒ use Laravel\Octane\Events\RequestReceived; use Laravel\Octane\Listeners\FlushAuthenticationState; use Laravel\Octane\Octane; return [ // ... 'listeners' => [ // ... RequestReceived :: class => array_values ( array_filter ( Octane :: prepareApplicationForNextRequest () , fn ( $ listener ) => $ listener !== FlushAuthenticationState :: class , )) , // ... ] , ] ; åã curl ãå®è¡ããŸãã { " pid ": 15 ," action ":" SET "," user_name ":" Alice " } { " pid ": 15 ," action ":" GET "," user_name ":" Alice " } â åã®ãªã¯ãšã¹ãã®æ
å ±ãæ®ã£ãŠãã ãªã¯ãšã¹ãããŸããã§ Alice ãšããå€ãæ®ã£ãŠããŸãã æ€èšŒçµæããã FlushAuthenticationState ãå€ããšèªèšŒç¶æ
ããªã¯ãšã¹ãéã§æ®ããæå¹ãªå Žå㯠Guard ãã£ãã·ã¥ãæ¯ãªã¯ãšã¹ãç Žæ£ãããããšã確èªã§ããŸããããã¬ãŒã ã¯ãŒã¯æšæºã® Auth ã Octane ã§ãå®å
šã«äœ¿ããã®ã¯ããã®ãªã¹ããŒãè£ã§åããŠããããããã ãšèšããŸãã ãããã« æ¬èšäºã§ã¯ãOctane + Swoole ã§åã®ãªã¯ãšã¹ãã®æ
å ±ãæ¬¡ã®ãªã¯ãšã¹ãã«æ®ãä»çµã¿ãæŽçãããµã³ãã«ã³ãŒãã§åäœãæ€èšŒããŸããããã®çµæã clone ãšãªã¹ããŒã«ãã£ãŠ Auth ãªã©ã®ãã¬ãŒã ã¯ãŒã¯åŽã®ç¶æ
ã¯ãªã¯ãšã¹ãããšã«ã¯ãªã¢ãããäžæ¹ã static 倿° / ã°ããŒãã«å€æ°ããWorker boot æã«è§£æ±ºæžã¿ã® singleton ã€ã³ã¹ã¿ã³ã¹ã®ããããã£ã¯èªåã§ã¯ã¯ãªã¢ãããªãããšã確èªã§ããŸããã æç¶ãããŠã¯ãããªãå Žæã«ç¶æ
ã眮ããªãããšããŸããããç¬èªã®ã°ããŒãã«ç¶æ
ãæãããå Žåã«ã¯ RequestReceived ãªã©ã®ã€ãã³ãã«èªåã®ãªã¹ããŒã远å ããŠã¯ãªã¢ããããšãæèã§ããã°ãšæããŸãã æåŸãŸã§èªãã§ããã ãããããšãããããŸããã åè Deep Dive into Laravel OctaneïŒAlbert ChenïŒ
ã¯ããã« NTTè¥¿æ¥æ¬ã®äžå· æåã§ãã æ¬èšäºã§ã¯ãThree.jsïŒWebGLãã©ããããJavaScriptã©ã€ãã©ãªïŒã§æ¥å€ç§å¬ã衚ããµã³ãã«ãäœããåã·ãŒã³ã§äœ¿ã£ãŠããWebGL/Three.jsã®æ©èœã解説ããŸãã æ¬èšäºã¯2026幎4ææç¹ã®æ
å ±ã«åºã¥ããŸãã Webã®ç¹ã«ããã³ããšã³ãã«æºãããããšæã人ã®å€ãã¯ããŸãèŠãç®ã®åãç»é¢ã®é
åãæããŠããã³ããšã³ãã«é¢ãããããšæãããã®ã§ã¯ãªãã§ããããã ããã³ããšã³ãã¯ããŒã¿ã®ç®¡çãªã©ãåãæã€ããšãå€ãã§ããããªããšèšã£ãŠããªãããªèŠãç®ãã¬ãã¬ãåãåäœãæå€§ã®é
åã®äžã€ã§ãã ç§èªèº«ãã¬ãã¬ãåãèŠãç®ããããã³ããšã³ãã«é
åãæããäžäººã§ãã ä»åã玹ä»ããThree.jsã¯ã3Dãæããã©ã€ãã©ãªãã§ããã²ãŒã ãªã©ãäœæã§ããéåžžã«å€åœ©ãªäœ¿ãæ¹ãã§ããŸãããå人çã«ã¯ãUIãå¯èŠåã« å£ç¯æã»ç©ºæ°æ ãå°ãªãèŠçŽ ã§è¶³ãããã®â衚çŸâãšããŠäœ¿ãå Žé¢ãå€ãã§ããå£ç¯çãªããã¢ãŒã·ã§ã³ãµã€ãã®èæ¯ããäžã€ã®ååãå€è§çãªã€ã¡ãŒãžã§äŒãããã©ã³ãã£ã³ã°ããŒãžãªã©ã§ãããã®èšäºã§ã¯ãWebGLã®å©çšã€ã¡ãŒãžãæŽãã§ããã ãããã®å
¥å£ãšããŠåå£ã顿ã«ãã€ã€ãåãã€ã³ãã«ã€ããŠè§£èª¬ããŠãããŸãã åå£ãµã³ãã«ã®å®æã€ã¡ãŒãžïŒæ¥ã»å€ã»ç§ã»å¬ïŒ 察象èªè
æ¬èšäºãæ³å®ãã察象èªè
ã¯ä»¥äžã®éãã§ãã æ¬èšäºã§ã¯ãããã¬ã³ãã£ã³ã°ããããã©ã°ããšãã£ãèããªããªãçšèªãåºãŠããŸãããWebã§3Dã衚çŸããã«ããã£ãŠã¯å°éçãªçšèªã倿°ååšããŸããäžã€äžã€ã解説ããŠãããšçšèªã®è§£èª¬èšäºã«ãªã£ãŠããŸãããããã®èšäºã¯äžå®ã®ç¥èãããæ¹ãåæãšããŠããŸãã ããã³ããšã³ãã®ãªãããªè¡šçŸã詊ããŠã¿ããæ¹ 3Dæç»ã«ã€ããŠäžå®ã®ç¥èãããæ¹ Webãµã€ãã«ãç«äœçãªæŒåºããå
¥ãããæ¹ ç®æ¬¡ ã¯ãã㫠察象èªè
ç®æ¬¡ 1.èæ¯ã»ç®ç 2.åææ¡ä»¶ã»åäœç°å¢ 2.1 ããŸãåããªããšãã®æçãã§ã㯠2.2 ç°¡åãªèªå¥ã®èª¬æ 3.ãŸãåããïŒåå£ãµã³ãã«ã®å
šäœåïŒ 3.1 ãµã³ãã«ã®æ§æïŒInput / Update / RenderïŒ 4.æ¥ïŒæ¡ã®éãæ¯è²ïŒããŒãã£ã¯ã« + ã¢ã«ãã¡ãã¬ã³ãã£ã³ã°ïŒ 4.1 å©çšããäž»ãªæ©èœ 4.2 æ¡ã£ãœãèŠããã³ãïŒåœ¢ç¶ + åãïŒ ãµã³ãã«äœæã§èŠåŽããããš 4.3 ã€ãŸãããã€ã³ãïŒéæè¡šçŸã»æ·±åºŠïŒ 4.4 ã«ã¹ã¿ãã€ãºããå Žåã®ãã€ã³ã 5.å€ïŒè±ç«ã®æã¡äžãïŒæã¡äžã + å ç®åæ + ããŒã¹ãïŒ 5.1 å©çšããäž»ãªæ©èœ 5.2 ãæã¡äžããããèŠããïŒãã±ãã + è»è·¡ïŒ ãµã³ãã«äœæã§èŠåŽããããš 5.3 泚æç¹ïŒå ç®ã¯â䜿ãéãããšçœé£ã³âããïŒ 5.4 ã«ã¹ã¿ãã€ãºããå Žåã®ãã€ã³ã 6.ç§ïŒçŽ
èã®æ¯è²ïŒInstancedMesh + é¢šã®æºãïŒ ãµã³ãã«äœæã§èŠåŽããããš 6.1 å©çšããäž»ãªæ©èœ 6.2 泚æç¹ 6.3 ã³ãŒãã®ãã€ã³ãïŒInstancedMeshã®æŽæ°ïŒ 6.4 ã«ã¹ã¿ãã€ãºããå Žåã®ãã€ã³ã 7.å¬ïŒéªæ¯è²ïŒãã©ã° + ã©ã€ãã£ã³ã° + ããŒãã£ã¯ã«ïŒ ãµã³ãã«äœæã§èŠåŽããããš 7.1 å©çšããäž»ãªæ©èœ 7.2 ã³ãŒãã®ãã€ã³ãïŒFogãšéªã®èŠãæ¹ïŒ 7.3 ã«ã¹ã¿ãã€ãºããå Žåã®ãã€ã³ã 8.ãŸãšã ä»é²ïŒãã«ã»ããã®ã³ãŒã å·çè
åèè³æã»åºå
ž åæš 1.èæ¯ã»ç®ç Webãµã€ãããã¢ã«å€§ããªåããå
¥ããããšãããšã掟æã«äœãããšããã»ã©å®è£
ãç Žç¶»ããã¡ã§ããäžæ¹ã§ã衚çŸãçµã£ãŠãããã£ãœãããåºãããšã«çšéãçµããšãçãæéã§äœéšãåºäžãã§ããŸãã æ¬èšäºã®ç®çã¯ãåå£ã®è¡šçŸã顿ã«ããŠã以äžã®2ç¹ãæºããããšã§ãã ã§ããã ãå°ããªä»çµã¿ ïŒPoints/InstancedMesh/Fog/Blending ãªã©ïŒã§æŒåºãäœã äœãæå¹ã«ãããšäœãèµ·ããã ïŒWebGLã®æ©èœïŒã説æã§ããããã«ãªã 2.åææ¡ä»¶ã»åäœç°å¢ Three.js: 0.164.1 ïŒä»åã®ãµã³ãã«ã§ã®å©çšããŒãžã§ã³ïŒCDNã§èªã¿èŸŒã¿ïŒ èµ·åæ¹æ³: file:// ã§ã¯ãªã http://localhost ãªã©ã§é
ä¿¡ããïŒVS Codeã®Live ServerãApache HTTP Serverç°å¢ãªã©ïŒ 察å¿ãã©ãŠã¶: Chromiumç³» / Firefox / Safari ãªã©ãWebGLãæå¹ãªç°å¢ïŒç«¯æ«å·®ãåºãå ŽåããããŸãïŒ è£è¶³ : Three.jsãWebGLã¯éåžžã«å¥¥æ·±ãæè¡ã§ããæ¬èšäºã®ãµã³ãã«ã¯ãæŠèŠã¬ãã«ã®èª¬æã§ãããããå€éšç»åã®å©çšã¯ããããã¯ã¹ãã£ãCanvasã§çæããŸãã å©ç¹ãšããŠããããã¯ãŒã¯ãäžå®å®ãªç°å¢ã§ãåçŸããããã§ãããç»åãå©çšããªãçºããªã¢ã«ãªè¡šçŸã§ã¯ãªãããšããçæãã ããã 2.1 ããŸãåããªããšãã®æçãã§ã㯠ãã®æã®èšäºã¯ãã³ãŒãã®èšè¿°ãããåã«ãããããåããæ¹ã»éãæ¹ãã§ã€ãŸããããšããããŸãã ç§èªèº«ãæå㯠Three.jsã®ã³ãŒãããåã«ãå®è¡ç°å¢ã§æ¢ãŸã£ããè©°ãŸã£ããããããšãäœåºŠããããŸããããã®ããããã®èšäºã§ã¯ã§ããã ãç°¡åã«æç»ãäœéšããŠããã ãããããªæ§æã«ããŠããŸãã®ã§ããã²ãããã詊ããŠã¿ãŠãã ããã å®è¡ç°å¢ã«é¢ããŠã¯ããŸãã¯æ¬¡ã®3ç¹ã ãèŠãã°ååã§ãã file:// ã§éããŠããªããïŒååãšã㊠http://localhost ãªã©ã§éã ç»é¢ãçã£é»ãªãŸãŸãïŒã³ã³ãœãŒã«ãšã©ãŒãšWebGLã®æå¹/ç¡å¹ã確èªãã åããåäœãéããªã©ããªããïŒãŸãæç»ã®æ°ãã€ã³ã¹ã¿ã³ã¹æ°ãååã«ããŠãå·®ãåºãããèŠã ããŸãæç»ã®æ°ãã€ã³ã¹ã¿ã³ã¹æ°ãååã«ããŠãå·®ãåºãããèŠããã¯ãå°å³ã§ããäžçªå¹ãåãåãã§ãã 2.2 ç°¡åãªèªå¥ã®èª¬æ æ¿ããªïŒæ¿ããªãŽã³ïŒå¹³é¢ã®ããªãŽã³1æã«ãéæãã¯ã¹ãã£ã貌ã£ããã®ïŒ FogïŒãã©ã°ïŒïŒ3Dæç»ã§ãé ããã®ã»ã©éãã§èŠãããããã«ããé§ã»ç©ºæ°é è¿æ³ã®è¡šçŸ 3.ãŸãåããïŒåå£ãµã³ãã«ã®å
šäœåïŒ æ¬èšäºã¯ ãã®èšäºã®äžã ãã§å®çµ ããããã«ããµã³ãã«ã³ãŒãããã¹ãŠæ¬æå
ã«æ²èŒããŸãã èµ·åæ¹æ³ã®è©³çްã¯åç¯ã®éãã§ããããŒã«ã«ãµãŒããŒçµç±ã§éããŠãã ããã ç»é¢å·Šäžã®åãæ¿ãã§ãæ¥ïŒæ¡ïŒã»å€ïŒè±ç«ïŒã»ç§ïŒçŽ
èïŒã»å¬ïŒéªïŒãåãæ¿ããããŸãã å£ç¯åãæ¿ãUI 以éã¯ãã€ã³ãã ãã³ãŒãæç²ãããã«ã³ãŒãã¯æåŸã«ãŸãšããŸãã 3.1 ãµã³ãã«ã®æ§æïŒInput / Update / RenderïŒ æ§æã¯ãå®åã§ã䜿ããããã«æ¬¡ã®ãããªæ§æã«ããŠããŸãã Input : ãã¿ã³æäœïŒå£ç¯åãæ¿ãïŒãç¶æ
ã«èœãšã Update : å£ç¯ããšã®ãç¶æ
æŽæ°ã Render : renderer.render(scene, camera) ã¯åºæ¬1è¡ïŒéãåŠçãéäžãããªãïŒ æåããå
šéšã®åŠçã远ãããã以äžã®ããã« äžã€ã®å£ç¯ãã€ãã®å£ç¯ããšã®åŠçã®ç¹åŸŽãçè§£ããªãã詊ã ã»ããå
šäœçã«çè§£ããããã§ãã æ¥ã®æ¡ïŒéæã»æ·±åºŠã®ã¯ã»ãåããããã å€ã®è±ç«ïŒãã±ãããè»è·¡ãããŒã¹ãã§ãæŒåºã®çµã¿ç«ãŠããåããããã ç§ã®çŽ
èïŒ InstancedMesh ã®å®åçãªæŽ»çšæ³ãåããããã å¬ã®éªïŒFogãšã©ã€ãã ãã§ãååã«æŒåºã§ããããšãåããããã ãå
šéšãçè§£ããããšãããããããã®å£ç¯ã®ãã®èŠãæ¹ã¯èªåã®æ¡ä»¶ã§ã䜿ãããããšããããã«ããããã®æè¡ã«æ³šåããæ¹ã3Dç³»ã¯çè§£ãããããšæããŸãã 4.æ¥ïŒæ¡ã®éãæ¯è²ïŒããŒãã£ã¯ã« + ã¢ã«ãã¡ãã¬ã³ãã£ã³ã°ïŒ æ¥ã¯ãèœã¡ãç²ãâæ¡ã®è±ã³ãâã«èŠãããããšãäžæ°ã«å£ç¯æãåºãããã§ãã ãã ã THREE.Points ã¯ãç¹ã¹ãã©ã€ãããªã®ã§ã調æŽãçããš ãã³ã¯ã®éª ã£ãœããªã£ãŠããŸããŸããããã§æ¬ãµã³ãã«ã§ã¯ãæ¡ã ã㯠InstancedMesh ïŒæ¿ããªïŒã§è±ã³ããæãããã«ããŠããŸãã â»äœè«ã§ããããããã£ã詊è¡é¯èª€ã3Dæç»ã®è¡šçŸã®æ¥œãã¿ã ãšå人çã«ã¯æããŸãã â» InstancedMesh ã¯Three.jsã®ã¯ã©ã¹ãšãªããŸããïŒ Three.js - InstancedMesh ïŒ 4.1 å©çšããäž»ãªæ©èœ BufferGeometry : ç²ã®äœçœ®ã»é床ãªã©ãTypedArrayã§æã€ïŒæŽæ°ãéãïŒ éæïŒalphaïŒ : ã¢ã«ãã¡ä»ããã¯ã¹ãã£ã§è±ã³ã圢ã«èŠãã Blending : NormalBlending ïŒèªç¶ã«éãªãïŒ InstancedMesh : è±ã³ããâæ¿ããªâãšããŠå€§éæç»ããïŒå転ãçµµã«ä¹ãïŒ 4.2 æ¡ã£ãœãèŠããã³ãïŒåœ¢ç¶ + åãïŒ ããã³ã¯ã®éªãã«èŠããå Žåãåå ã¯ã次ã®2ã€ãå€ãã§ãã 圢ãäžžããã ïŒç²ã«èŠãããšéªã«èŠããã¡ã§ãïŒ åããå調ããã ïŒéªã®èœäžã«è¿ãé床ã«ãªã£ãŠããŸã£ãŠããïŒ å¯ŸçãšããŠã(1) è±ã³ã圢ã®ãã¯ã¹ãã£ãäœãã(2) æ¿ããªãå転ãããªããèœãšããŸãïŒã²ãã²ãæãåºããŸãïŒã ãµã³ãã«äœæã§èŠåŽããããš æå㯠Points ã«ãã³ã¯ã®å圢ãã¯ã¹ãã£ãåœãŠãŠãããã§æ¡ã£ãœãã¯ãããšæã£ãŠããŸããããå®éã«åãããšã»ãŒéªã§ããã 詊è¡é¯èª€ããŠã決å®çãªåå ã ãªãšæã£ãã®ã¯ â圢âïŒã»ãŒäžžïŒ ãš âå転ãçµµã«ä¹ããâ ã§ããã ãã®ãããæ¡ã ã InstancedMesh ã«åãæ¿ããããšã§å¯ŸçããŸããã ãã¯ã¹ãã£åŽãã茪éãå°ãå
¥ããŠãäžžãã§ã¯ãªã圢ã«ããã ãã§ãå°è±¡ãããªãå€ãããŸãã®ã§ãæ¯éè²ã
詊ããŠã¿ãŠãã ããã function makePetalTexture () { const c = document . createElement ( 'canvas' ) ; c . width = 64 ; c . height = 64 ; const g = c . getContext ( '2d' ) ; g . clearRect ( 0 , 0 , 64 , 64 ) ; g . translate ( 32 , 32 ) ; g . rotate ( -0 . 25 ) ; const base = g . createRadialGradient ( 0 , -6 , 2 , 0 , -6 , 30 ) ; base . addColorStop ( 0 , 'rgba(255,210,225,0.95)' ) ; base . addColorStop ( 0 . 55 , 'rgba(255,175,205,0.75)' ) ; base . addColorStop ( 1 , 'rgba(255,175,205,0.0)' ) ; g . fillStyle = base ; g . beginPath () ; g . moveTo ( 0 , -28 ) ; g . bezierCurveTo ( 18 , -22 , 18 , 10 , 0 , 26 ) ; g . bezierCurveTo ( -18 , 10 , -18 , -22 , 0 , -28 ) ; g . closePath () ; g . fill () ; g . globalCompositeOperation = 'destination-out' ; g . beginPath () ; g . ellipse ( 0 , 18 , 7 . 5 , 5 . 5 , 0 , 0 , Math . PI * 2 ) ; g . fill () ; g . globalCompositeOperation = 'source-over' ; g . strokeStyle = 'rgba(255,120,160,0.28)' ; g . lineWidth = 2 ; g . stroke () ; return new THREE . CanvasTexture ( c ) ; } æ¿ããªïŒ InstancedMesh ïŒã§ãã²ãã²ãããåºãæå°äŸã§ãã Points ãšéããå転ãçµµã«ä¹ããããã®ããã€ã³ãã§ãã function makeSakuraPetals ({ count = 820 , texture }) { const geo = new THREE . PlaneGeometry ( 0 . 28 , 0 . 28 ) ; const mat = new THREE . MeshStandardMaterial ({ map : texture , transparent : true , depthWrite : false , side : THREE . DoubleSide , }) ; const mesh = new THREE . InstancedMesh ( geo , mat , count ) ; mesh . instanceMatrix . setUsage ( THREE . DynamicDrawUsage ) ; // äœçœ®ã»èœäžã»å転ãªã©ã®åäœå·®ãæã¡ãupdateã§è¡åãæŽæ°ãã return mesh ; } 4.3 ã€ãŸãããã€ã³ãïŒéæè¡šçŸã»æ·±åºŠïŒ éæè¡šçŸã¯ãæå¹ã«ããã ãã§ã¯äžèªç¶ã«ãªããããã§ãã厩ãããšãã¯ããŸã次ã®ç¹ã確èªããŠãã ããã depthWrite: false : éæã深床ãããã¡ã«æžã蟌ããšãåŸãã®ç²ãäžèªç¶ã«æ¬ ããããšããããŸã transparent: true : ãã¬ã³ãã£ã³ã°ãæå¹åããåºæ¬æ¡ä»¶ 4.4 ã«ã¹ã¿ãã€ãºããå Žåã®ãã€ã³ã æ¡ã®è¡šçŸãã«ã¹ã¿ãã€ãºãããªãããŸãã¯æ¬¡ã®3ã€ã§ååã§ãã count : è±ã³ãã®ææ°ãå¢ãããšè¯ããã§ãããããããããšäžæ°ã«éããªããŸã fall / drift : èœã¡æ¹ããããè§Šããšãéªã£ãœãããšãæ¡ã£ãœããã®å·®ãåºãããã§ã ãã¯ã¹ãã£ã®èŒªéè² : 茪éãå°ãå
¥ããã ãã§ãç²æãæžã£ãŠè±ã³ãã«å¯ããŸã å®éãç§ããã¥ãŒãã³ã°ããã®ã¯ããã§ããã¢ãã«ãåãããããŸãåããšå¯åºŠãåãããã»ããâãããâãæ©ãåºãŸããïŒæ¬ãµã³ãã«ã®ãããªç»åã䜿ããã«ã³ãŒãã ãã§è¡šçŸããå Žåã¯ç¹ã«å¹æçããªãšæããŸããïŒ 5.å€ïŒè±ç«ã®æã¡äžãïŒæã¡äžã + å ç®åæ + ããŒã¹ãïŒ è±ç«ã¯ãç¹ã®éåãã§ãããã£ãœãèŠããŸãã 5.1 å©çšããäž»ãªæ©èœ AdditiveBlending : æãããšããã»ã©å
ãè¶³ãããïŒè±ç«ã»å
è·¡åãïŒ å¯¿åœïŒlifeïŒ : ç²ããšã«ãã§ãŒãã¢ãŠããããïŒãã£ãšæ®ãç¶ããã«æ¶ãïŒ ã¬ã³ã/ããŒã³ ïŒç°¡æïŒ: ããã§ã¯åŒ·ããã¹ãåŠçã¯å
¥ãããè²èšèšãšãã¬ã³ãã£ã³ã°ã§å¯ãã 5.2 ãæã¡äžããããèŠããïŒãã±ãã + è»è·¡ïŒ ççºïŒããŒã¹ãïŒã ãã ãšãè±ç«ãçªç¶åºããããã«èŠããŸãïŒãã ã®ç Žè£ã¿ããã«ããèŠãããè±ç«ã«ã¯èŠããªããªããŸãïŒã ãã®ããããã±ãããäžæãããè»è·¡ïŒãã¬ã€ã«ïŒãæ®ããŠããççºãããã®ãéèŠã§ãã äž»ã«äžèšã倧äºããªãšæããŸãã ãµã³ãã«äœæã§èŠåŽããããš æåã¯ããŒã¹ãã ããæç»ããŠããŸããã ã§ããå®éã®è±ç«ã®æ åãèŠãŠã¿ããšãã£ã±ãæã¡äžãäžã®å
ããã£ãŠããããããŒã¹ããããšããæµãã倧äºã ãªãšæããŸããã ãã®ããããã±ããïŒäžæããç¹ïŒãäžã€çœ®ããŠèŠç·ã®è¿œåŸå
ãäœãããã¬ã€ã«ãå°ã倪ãïŒ1ãã¬ãŒã ã§è€æ°ç²ïŒããããšã§è±ç«ã£ãœãæŒåºãåºãããã«ããŠããŸãã ãã±ããç¶æ
ãäžã€æã€ïŒäœçœ®ãšéåºŠïŒ äžæäžã«ããã§ãŒãã¢ãŠããããã¬ã€ã«ç²ãå°ããã€çæãã ç®æšé«åºŠã«éããã burst(origin) ãã³ãŒã«ããããã«ãã // ãã±ããïŒ1çºïŒ const rocket = { active : false , x : 0 , y : 0 , z : 0 , vx : 0 , vy : 0 , vz : 0 , targetY : 5 . 0 } ; function updateRocket ( dt ) { if ( ! rocket . active ) return; rocket . x += rocket . vx * dt ; rocket . y += rocket . vy * dt ; rocket . z += rocket . vz * dt ; rocketTrail . userData . spawnTrail ( rocket . x , rocket . y , rocket . z , ( Math . random () - 0 . 5 ) * 0 . 2 , -0 . 4 - Math . random () * 0 . 5 , ( Math . random () - 0 . 5 ) * 0 . 2 , 0 . 55 + Math . random () * 0 . 35 ) ; if ( rocket . y >= rocket . targetY ) { rocket . active = false ; fireworks . userData . burst ( new THREE . Vector3 ( rocket . x , rocket . y , rocket . z )) ; } } 5.3 泚æç¹ïŒå ç®ã¯â䜿ãéãããšçœé£ã³âããïŒ AdditiveBlendingã¯èŠæ ããè¯ãåé¢ãèšå®ã«ãã£ãŠã¯çœé£ã³ããããã§ããæ¬¡ãæèãããšã³ã³ãããŒã«ããããããªãšæããŸãã å ç®åæã®çœé£ã³äŸ(ç²ã®æ°ãå€ã倧ããå Žåãç²ãéãªã£ãŠæœ°ããçœã倧ããªå¡ã«èŠãã) çœé£ã³ãæãã調æŽäŸïŒçްããç²åããã£ããèŠããŠãã©ã³ã¹ãããïŒ ç²ã® æ°ãå¢ãããããªã opacity ãš size ãæ§ããã«ãã èæ¯ãçã£é»ã«ãããå°ãè²ãå
¥ããïŒç®ãç²ãã«ããïŒ 5.4 ã«ã¹ã¿ãã€ãºããå Žåã®ãã€ã³ã è±ç«ã¯ãã©ã¡ãŒã¿ã®å¹ãæ¹ãåãããããã調æŽã楜ããã§ãããŸãã¯æ¬¡ã®3ã€ããè§Šãã®ãããããã§ãã targetY : ã©ã®é«ãã§è±ç«ãéãããããã ãã§å°è±¡ãããªãå€ãããŸã ãã¬ã€ã«ã®ç²æ°ãšå¯¿åœ : æã¡äžããã®âçâãèŠãããã©ãããæ±ºããŸã ããŒã¹ãç²ã®æ° : 豪è¯ãã«çŽçµããŸãããçœé£ã³ãšéããå¢ããŸã å人çã«ã¯ãç²ãå¢ãããã ãæã¡äžããéäžãèŠãããã ãå
ã«æŽããã»ãããè±ç«ãããã¯åºããããã§ãã 6.ç§ïŒçŽ
èã®æ¯è²ïŒInstancedMesh + é¢šã®æºãïŒ ç§ã¯ãèœã¡èãçŽ
èã®ãææ°ããéèŠã§ãã æ¬ãµã³ãã«ã§ã¯ InstancedMesh ã䜿ããåããžãªã¡ããªã倧éã«æããŠãèãããã圢ã«ããŸãã ãµã³ãã«äœæã§èŠåŽããããš çŽ
èã®ãããªèãæ£ãèŠçŽ ã¯ãããããèŠçŽ ãå¢ãããæ¹ãé°å²æ°ãåºãŸãããCPUåŽã§æ¯ãã¬ãŒã è¡åæŽæ°ãããŠãããšãæ¥ã«éãæããããšããããŸãã ãã®ãããæ¬ãµã³ãã«ã§ã¯ããŸã âææ°ãåºãâ ã InstancedMesh ã§ç¢ºä¿ãã€ã€ãæŽæ°éšåã¯è»œãã«ããŠãã«ã¯ã«ã¯ããã«åãç¯å²ãã«ããŠããŸãã 6.1 å©çšããäž»ãªæ©èœ InstancedMesh : 1çš®é¡ã®èããè¡åïŒtransformïŒã ãå€ããŠå€§éæç»ããïŒãããŒã³ãŒã«åæžïŒ Matrix4 : setMatrixAt ã§äœçœ®ã»å転ã»ã¹ã±ãŒã«ããŸãšããŠèšå® ç䌌颚 : sin ã§æºããè¶³ãïŒç©çã®ä»£ããïŒ 6.2 泚æç¹ ã€ã³ã¹ã¿ã³ã·ã³ã°ã¯æç»ã匷ãäžæ¹ã æ¯ãã¬ãŒã å
šåäœã®è¡åãæŽæ° ãããšCPUåŽãéããªãããšããããŸãããµã³ãã«ã§ã¯æ°ãæ§ããã«ããæŽæ°ã軜ãåŒã«ããŠããŸããã«ã¹ã¿ãã€ãºããéã¯ãæŽæ°ãå¿
èŠãªåäœã ãæŽæ°ããããªã©ã®å·¥å€«ã广çã§ãã 6.3 ã³ãŒãã®ãã€ã³ãïŒInstancedMeshã®æŽæ°ïŒ çŽ
èã¯ãææ°ããé°å²æ°ã«çŽçµããŸããäžæ¹ã§ãè1æ1æã Mesh ã§äœããšãããŒã³ãŒã«ãå¢ããããã®ã§ã InstancedMesh ã«å¯ããŸãã ããã§ã®ãã€ã³ãã¯ã setMatrixAt(i, dummy.matrix) ã§ ååäœã®è¡åãæŽæ° ããŠããç¹ã§ãã const mesh = new THREE . InstancedMesh ( geo , mat , count ) ; mesh . instanceMatrix . setUsage ( THREE . DynamicDrawUsage ) ; function update ( dt , t ) { for ( let i = 0 ; i < count ; i ++ ) { // d.x / d.y / d.z ãæŽæ°ããŠèœäžããã // sinã§æºããè¶³ããŠé¢šã£ãœããã dummy . position . set ( d . x + sx , d . y , d . z ) ; dummy . rotation . set ( 0 , d . ry + sx * 0 . 7 , d . rz + Math . sin ( t * 1 . 2 + d . phase ) * 0 . 4 ) ; dummy . updateMatrix () ; mesh . setMatrixAt ( i , dummy . matrix ) ; } mesh . instanceMatrix . needsUpdate = true ; } 6.4 ã«ã¹ã¿ãã€ãºããå Žåã®ãã€ã³ã ç§ã¯ãèŠãç®ãå€ãããããéãããããªãã調æŽã倧äºã§ãã count : ãŸãã¯ãããå¢ãããšé°å²æ°ã¯åºãŸãããCPUæŽæ°ã³ã¹ããå¢ããŸã sway : 颚ã®åŒ·ããããã匷ããããšãèœã¡èãšããããçŽå¹éªã£ãœãèŠããŸã æŽæ°é »åºŠ : æ¬çªã§ã¯æ¯ãã¬ãŒã æŽæ°ããªããŠããååãããããèŠããŸã 7.å¬ïŒéªæ¯è²ïŒãã©ã° + ã©ã€ãã£ã³ã° + ããŒãã£ã¯ã«ïŒ å¬ã®è¡šçŸã¯ãéªãã®ãã®ãã 空æ°ïŒçœã£ãœãã»å¥¥è¡ãïŒ ãéèŠã«æããŸããããã§ã¯ãã©ã°ãšã©ã€ãã§ã奥è¡ãããäœããŸãã ãµã³ãã«äœæã§èŠåŽããããš éªã¯ç²ãå¢ããã»ã©å£ç¯æãåºãåé¢ãå¢ããããããšãç»é¢ããããããæ¹åã«å¯ããã¡ã§ãã å人çã«ã¯ãéªãã®ãã®ãã ãã©ã°ã§é æ¯ã溶ãã ã»ããâå¬ã£ãœãâãåºããããç²ã¯æ§ããã«ããŠæºãã匱ãããããããæ±ãããããšæããŠããŸãã 7.1 å©çšããäž»ãªæ©èœ FogïŒãã©ã°ïŒ : é æ¯ãçœã£ãœãïŒéª/é§ã®ç©ºæ°æïŒ DirectionalLight + AmbientLight : 圱ã¯äœ¿ããã軜ãã©ã€ãã§é°å²æ°ãåºã éªããŒãã£ã¯ã« : æ¥ã®ããŒãã£ã¯ã«ãæµçšãã€ã€ãèœäžãšæºãã匱ãã 7.2 ã³ãŒãã®ãã€ã³ãïŒFogãšéªã®èŠãæ¹ïŒ éªæ¯è²ã¯ãéªã®ç²ãã®ãã®ãããé æ¯ãçœã£ãœããã空æ°ãå·ãããå°è±¡ã®ã»ããå¹ãããšãå€ãã§ããããã§æ¬¡ã®2ã€ãçµã¿åãããŸãã ogãã»ãŒå¹ããŠãªãã±ãŒã¹ïŒã»ãŒé§ãæããããé æ¯ããã£ããæ®ãïŒ Fogãå¹ããããŠããã±ãŒã¹ïŒèæ¯è²ã«æº¶ããããŠããã®ãããé§ãããã£ããããªèæ¯ã«ãªãïŒ Fog : é ããã®ã»ã©èæ¯è²ã«æº¶ããïŒçœ/éå¯ãã«ãããšå¬ã£ãœãïŒ éªããŒãã£ã¯ã« : ãµã€ãºãæ§ããã«ããæºãã匱ããïŒå¹éªã«ããªãïŒ // FogïŒå¬ã¯è¿ãããå¹ãããïŒ scene . fog = new THREE . Fog ( 0x0a1322 , 6 , 45 ) ; // éªïŒæ·±åºŠæžã蟌ã¿ã¯ããªãïŒéæã®æ¬ ããé¿ããïŒ const mat = new THREE . PointsMaterial ({ map : circleTexture , transparent : true , depthWrite : false , }) ; 7.3 ã«ã¹ã¿ãã€ãºããå Žåã®ãã€ã³ã å¬ã¯ã掟æããããè¶³ãç®ããããªããã»ããããŸããããŸãã Fogã® near / far : ç©ºæ°æã®äž»åœ¹ã§ããéªç²ã®æ°ããå
ã«ãããè§Šã䟡å€ããããŸã éªç²ã®ãµã€ãº : 倧ãããããããšæ¥ã«äººå·¥çãªèŠãç®ã«å¯ããŸã ã©ã€ãã®åŒ·ã : éå¯ãã®å·ãããåºããããããããããéªæ¯è²ã«ããããã§èª¿æŽããŸã å¬ã®è¡šçŸã¯ãåå£ã®äžã§ããå°ãªãèŠçŽ ã§ãããããèŠãããã®ã§ãWebGLãè§Šãå§ãã人ãæåã«æåäœéšãåŸãããããŒãã ãšæã£ãŠããŸãã 8.ãŸãšã åå£ã®è¡šçŸã¯ãè€éãªã¢ãã«ãé«äŸ¡ãªãã¹ãåŠçããªããŠããæ¬¡ã®çµã¿åããã§ããã£ãœãäœããŸãã æ¥ïŒæ¡ïŒ: éæããŒãã£ã¯ã« ãšæ·±åºŠã®æ±ã å€ïŒè±ç«ïŒ: å ç®åæ ãšãã§ãŒã ç§ïŒçŽ
èïŒ: ã€ã³ã¹ã¿ã³ã·ã³ã° ã§ææ°ãåºã å¬ïŒéªïŒ: ãã©ã°ãšã©ã€ã ã§ç©ºæ°ãäœã æ¬èšäºã®ãµã³ãã«ã¯ãåçŸãããããããšãåªå
ããŠã1ãã¡ã€ã«ã»å€éšç»åãªãã§äœã£ãŠããŸãããããããèæ¯ã®äœã蟌ã¿ãã¢ãã«å·®ãæ¿ãããã¹ãåŠç远å ãªã©ãçšéã«å¿ããŠæ®µéçã«äŒžã°ããŠãã ããã æåŸã«å人çãªæèŠã§ãããåå£ã®ãããªâããããããããŒãâã¯ãWebGLã®æ©èœãçšã㊠ãäœãè¶³ããšäœéšãã©ãå€ãããã ãæŽã¿ããã顿ã ãšæã£ãŠããŸãã ãŸãã¯ãã®ãŸãŸåãããŠãæ°ã«å
¥ã£ãå£ç¯ã®ãã©ã¡ãŒã¿ïŒææ°ãé床ãè²ïŒãªã©ãå°ããã€è§Šã£ãŠã¿ãŠãã ãããèŠãç®ã®å€åãçŽ çŽãªã®ã§ãäœã£ãŠããŠæ¥œãããšããã§ããããŸãã æåã®äžæ©ãšããŠããããã¯ãæ¡ãéªã§ããæ¥ã¯ã圢ãšåããã§å°è±¡ãå€ããé¢çœãããããå¬ã¯å°ãªãèŠçŽ ã§ç©ºæ°æãäœããŸãã ä»é²ïŒãã«ã»ããã®ã³ãŒã 以äžã«ãåå£åãæ¿ãçã®ãã«ã³ãŒããæ²èŒããŸãã <!doctype html> < html lang = "ja" > < head > < meta charset = "utf-8" /> < meta name = "viewport" content = "width=device-width,initial-scale=1" /> < title > Four Seasons (Three.js) </ title > < style > html , body { height : 100% ; } body { margin : 0 ; overflow : hidden ; background : #0b1020 ; font-family : system-ui , -apple-system , "Hiragino Sans" , "Noto Sans JP" , sans-serif ; } canvas { display : block ; } .ui { position : fixed ; top : 12px ; left : 12px ; display : flex ; gap: 8px ; flex-wrap : wrap ; z-index : 2 ; padding : 10px ; border-radius : 14px ; background : rgba( 0 , 0 , 0 , 0.35 ) ; border : 1px solid rgba( 255 , 255 , 255 , 0.10 ) ; backdrop- filter : blur( 10px ) ; color : rgba( 255 , 255 , 255 , 0.85 ) ; } button { appearance : none ; border : 1px solid rgba( 255 , 255 , 255 , 0.14 ) ; background : rgba( 255 , 255 , 255 , 0.06 ) ; color : rgba( 255 , 255 , 255 , 0.9 ) ; padding : 8px 10px ; border-radius : 12px ; font-weight : 650 ; cursor : pointer ; } button [ aria-pressed = "true" ] { background : linear-gradient( 135deg , rgba( 61 , 220 , 151 , .22 ), rgba( 122 , 162 , 255 , .18 )) ; border-color : rgba( 255 , 255 , 255 , 0.18 ) ; } .note { max-width : 420px ; font-size : 12px ; line-height : 1.5 ; opacity : 0.9 ; } </ style > </ head > < body > < div class = "ui" role = "group" aria-label = "season switch" > < button id = "spring" aria-pressed = "true" > æ¥ïŒæ¡ïŒ </ button > < button id = "summer" aria-pressed = "false" > å€ïŒè±ç«ïŒ </ button > < button id = "autumn" aria-pressed = "false" > ç§ïŒçŽ
èïŒ </ button > < button id = "winter" aria-pressed = "false" > å¬ïŒéªïŒ </ button > < div class = "note" > èµ·å㯠Live Server ãªã©ã§ http://localhost ãšããŠéããŠãã ããïŒfile:// ã¯äžå¯ïŒã </ div > </ div > < script type = "importmap" > { "imports" : { "three" : "https://unpkg.com/three@0.164.1/build/three.module.js" } } </ script > < script type = "module" > import * as THREE from 'three' ; const Season = Object . freeze ({ spring : 'spring' , summer : 'summer' , autumn : 'autumn' , winter : 'winter' }) ; let season = Season . spring ; const btns = { spring : document . getElementById ( 'spring' ) , summer : document . getElementById ( 'summer' ) , autumn : document . getElementById ( 'autumn' ) , winter : document . getElementById ( 'winter' ) , } ; function setPressed ( s ) { for ( const [ k , el ] of Object . entries ( btns )) el . setAttribute ( 'aria-pressed' , String ( k === s )) ; } for ( const [ k , el ] of Object . entries ( btns )) { el . addEventListener ( 'click' , () => { season = k ; setPressed ( k ) ; applySeasonLook () ; }) ; } const renderer = new THREE . WebGLRenderer ({ antialias : true , alpha : false , powerPreference : 'high-performance' }) ; renderer . setPixelRatio ( Math . min ( devicePixelRatio , 2 )) ; renderer . setSize ( innerWidth , innerHeight ) ; document . body . appendChild ( renderer . domElement ) ; const scene = new THREE . Scene () ; const camera = new THREE . PerspectiveCamera ( 55 , innerWidth / innerHeight , 0 . 1 , 200 ) ; camera . position . set ( 0 , 3 . 0 , 10 . 5 ) ; const amb = new THREE . AmbientLight ( 0xffffff , 0 . 55 ) ; const dir = new THREE . DirectionalLight ( 0xffffff , 1 . 0 ) ; dir . position . set ( 6 , 10 , 6 ) ; scene . add ( amb , dir ) ; const ground = new THREE . Mesh ( new THREE . PlaneGeometry ( 120 , 120 ) , new THREE . MeshStandardMaterial ({ color : 0x0e1733 , roughness : 1 . 0 , metalness : 0 . 0 }) ) ; ground . rotation . x = - Math . PI / 2 ; ground . position . y = -0 . 6 ; scene . add ( ground ) ; function makeCircleTexture ({ color = '#ffffff' , soft = true } = {}) { const c = document . createElement ( 'canvas' ) ; c . width = 64 ; c . height = 64 ; const g = c . getContext ( '2d' ) ; g . clearRect ( 0 , 0 , c . width , c . height ) ; const r = 26 ; const cx = 32 , cy = 32 ; const grd = g . createRadialGradient ( cx , cy , soft ? 4 : 20 , cx , cy , r ) ; grd . addColorStop ( 0 , color ) ; grd . addColorStop ( 1 , 'rgba(255,255,255,0)' ) ; g . fillStyle = grd ; g . beginPath () ; g . arc ( cx , cy , r , 0 , Math . PI * 2 ) ; g . fill () ; const tex = new THREE . CanvasTexture ( c ) ; tex . needsUpdate = true ; return tex ; } function makePetalTexture () { const c = document . createElement ( 'canvas' ) ; c . width = 64 ; c . height = 64 ; const g = c . getContext ( '2d' ) ; g . clearRect ( 0 , 0 , 64 , 64 ) ; g . translate ( 32 , 32 ) ; g . rotate ( -0 . 25 ) ; // è±ã³ãæãåºãããã«ãå
端ãäžžããæ ¹å
ã«åã蟌ã¿ãããã圢ãæã const base = g . createRadialGradient ( 0 , -6 , 2 , 0 , -6 , 30 ) ; base . addColorStop ( 0 , 'rgba(255,210,225,0.95)' ) ; base . addColorStop ( 0 . 55 , 'rgba(255,175,205,0.75)' ) ; base . addColorStop ( 1 , 'rgba(255,175,205,0.0)' ) ; g . fillStyle = base ; g . beginPath () ; g . moveTo ( 0 , -28 ) ; g . bezierCurveTo ( 18 , -22 , 18 , 10 , 0 , 26 ) ; g . bezierCurveTo ( -18 , 10 , -18 , -22 , 0 , -28 ) ; g . closePath () ; g . fill () ; // æ ¹å
ã®åã蟌ã¿ïŒå°ãã ãéæã«æãïŒ g . globalCompositeOperation = 'destination-out' ; g . fillStyle = 'rgba(0,0,0,0.6)' ; g . beginPath () ; g . ellipse ( 0 , 18 , 7 . 5 , 5 . 5 , 0 , 0 , Math . PI * 2 ) ; g . fill () ; g . globalCompositeOperation = 'source-over' ; // 茪éãã»ãã®å°ãïŒéªã£ãœããæžããïŒ g . strokeStyle = 'rgba(255,120,160,0.28)' ; g . lineWidth = 2 ; g . beginPath () ; g . moveTo ( 0 , -27 ) ; g . bezierCurveTo ( 17 , -21 , 17 , 9 , 0 , 25 ) ; g . bezierCurveTo ( -17 , 9 , -17 , -21 , 0 , -27 ) ; g . closePath () ; g . stroke () ; const tex = new THREE . CanvasTexture ( c ) ; tex . needsUpdate = true ; return tex ; } function makeLeafTexture () { const c = document . createElement ( 'canvas' ) ; c . width = 64 ; c . height = 64 ; const g = c . getContext ( '2d' ) ; g . clearRect ( 0 , 0 , 64 , 64 ) ; g . translate ( 32 , 32 ) ; const grd = g . createLinearGradient ( -10 , -26 , 12 , 26 ) ; grd . addColorStop ( 0 , 'rgba(255,122,48,0.95)' ) ; grd . addColorStop ( 1 , 'rgba(170,40,0,0.0)' ) ; g . fillStyle = grd ; g . beginPath () ; g . ellipse ( 0 , 0 , 14 , 24 , 0 . 6 , 0 , Math . PI * 2 ) ; g . fill () ; g . strokeStyle = 'rgba(255,255,255,0.25)' ; g . lineWidth = 2 ; g . beginPath () ; g . moveTo ( -6 , -18 ) ; g . lineTo ( 8 , 18 ) ; g . stroke () ; const tex = new THREE . CanvasTexture ( c ) ; tex . needsUpdate = true ; return tex ; } function makeFallingPoints ({ count , texture , color , size , area }) { const geo = new THREE . BufferGeometry () ; const pos = new Float32Array ( count * 3 ) ; const vel = new Float32Array ( count * 3 ) ; for ( let i = 0 ; i < count ; i ++ ) { const x = ( Math . random () - 0 . 5 ) * area . x ; const y = Math . random () * area . y ; const z = ( Math . random () - 0 . 5 ) * area . z ; pos [ i * 3 + 0 ] = x ; pos [ i * 3 + 1 ] = y ; pos [ i * 3 + 2 ] = z ; vel [ i * 3 + 0 ] = ( Math . random () - 0 . 5 ) * 0 . 3 ; vel [ i * 3 + 1 ] = - ( 0 . 25 + Math . random () * 0 . 35 ) ; vel [ i * 3 + 2 ] = ( Math . random () - 0 . 5 ) * 0 . 3 ; } geo . setAttribute ( 'position' , new THREE . BufferAttribute ( pos , 3 )) ; geo . setAttribute ( 'velocity' , new THREE . BufferAttribute ( vel , 3 )) ; const mat = new THREE . PointsMaterial ({ color , size , map : texture , transparent : true , depthWrite : false , blending : THREE . NormalBlending , }) ; const pts = new THREE . Points ( geo , mat ) ; // æ¯ãã¬ãŒã getAttribute ãåŒã°ãªããããåç
§ãä¿æããŠãã pts . userData . area = area ; pts . userData . posAttr = geo . getAttribute ( 'position' ) ; pts . userData . velAttr = geo . getAttribute ( 'velocity' ) ; return pts ; } // æ¡ã¯ Points ã ãšãç²ãã«å¯ããããã®ã§ãæ¿ããªïŒInstancedMeshïŒã§è±ã³ãæãåºã function makeSakuraPetals ({ count = 700 , texture }) { const geo = new THREE . PlaneGeometry ( 0 . 28 , 0 . 28 ) ; const mat = new THREE . MeshStandardMaterial ({ color : 0xffffff , map : texture , transparent : true , depthWrite : false , side : THREE . DoubleSide , roughness : 0 . 95 , metalness : 0 . 0 }) ; const mesh = new THREE . InstancedMesh ( geo , mat , count ) ; mesh . instanceMatrix . setUsage ( THREE . DynamicDrawUsage ) ; mesh . frustumCulled = false ; const data = [] ; const dummy = new THREE . Object3D () ; for ( let i = 0 ; i < count ; i ++ ) { data . push ({ x : ( Math . random () - 0 . 5 ) * 18 , y : Math . random () * 10 , z : ( Math . random () - 0 . 5 ) * 18 , fall : 0 . 55 + Math . random () * 0 . 55 , drift : 0 . 35 + Math . random () * 0 . 55 , spin : 1 . 2 + Math . random () * 2 . 6 , wobble : 1 . 0 + Math . random () * 1 . 6 , phase : Math . random () * 10 , ry : Math . random () * Math . PI * 2 , }) ; } function update ( dt , t ) { for ( let i = 0 ; i < count ; i ++ ) { const d = data [ i ] ; d . phase += dt ; d . y -= d . fall * dt ; const wx = Math . sin ( d . phase * d . wobble ) * d . drift ; const wz = Math . cos ( d . phase * ( d . wobble * 0 . 9 )) * d . drift ; d . x += wx * dt ; d . z += wz * dt ; if ( d . y < -0 . 6 ) { d . x = ( Math . random () - 0 . 5 ) * 18 ; d . y = 10 + Math . random () * 4 ; d . z = ( Math . random () - 0 . 5 ) * 18 ; } const tilt = Math . sin ( d . phase * 2 . 1 ) * 0 . 7 ; dummy . position . set ( d . x , d . y , d . z ) ; dummy . rotation . set ( tilt , d . ry + d . phase * 0 . 35 , d . phase * d . spin ) ; const s = 0 . 75 + Math . sin ( d . phase * 1 . 7 ) * 0 . 08 ; dummy . scale . set ( s , s , s ) ; dummy . updateMatrix () ; mesh . setMatrixAt ( i , dummy . matrix ) ; } mesh . instanceMatrix . needsUpdate = true ; } mesh . userData . update = update ; return mesh ; } const sakura = makeSakuraPetals ({ count : 820 , texture : makePetalTexture () }) ; scene . add ( sakura ) ; const snow = makeFallingPoints ({ count : 1600 , texture : makeCircleTexture ({ color : 'rgba(255,255,255,0.9)' , soft : true }) , color : 0xffffff , size : 0 . 08 , area : new THREE . Vector3 ( 18 , 10 , 18 ) }) ; snow . visible = false ; scene . add ( snow ) ; function makeFireworks ({ maxParticles = 1800 }) { const geo = new THREE . BufferGeometry () ; const pos = new Float32Array ( maxParticles * 3 ) ; const vel = new Float32Array ( maxParticles * 3 ) ; const life = new Float32Array ( maxParticles ) ; for ( let i = 0 ; i < maxParticles ; i ++ ) { pos [ i * 3 + 0 ] = 0 ; pos [ i * 3 + 1 ] = -999 ; pos [ i * 3 + 2 ] = 0 ; vel [ i * 3 + 0 ] = 0 ; vel [ i * 3 + 1 ] = 0 ; vel [ i * 3 + 2 ] = 0 ; life [ i ] = 0 ; } geo . setAttribute ( 'position' , new THREE . BufferAttribute ( pos , 3 )) ; geo . setAttribute ( 'velocity' , new THREE . BufferAttribute ( vel , 3 )) ; geo . setAttribute ( 'life' , new THREE . BufferAttribute ( life , 1 )) ; const mat = new THREE . PointsMaterial ({ color : 0xfff4b0 , size : 0 . 12 , map : makeCircleTexture ({ color : 'rgba(255,220,120,0.95)' , soft : true }) , transparent : true , depthWrite : false , blending : THREE . AdditiveBlending }) ; const pts = new THREE . Points ( geo , mat ) ; pts . frustumCulled = false ; // åç
§ããã£ãã·ã¥ïŒupdate/burst ã§é«éåïŒ const posAttr = geo . getAttribute ( 'position' ) ; const velAttr = geo . getAttribute ( 'velocity' ) ; const lifeAttr = geo . getAttribute ( 'life' ) ; function burst ( origin ) { const p = posAttr ; const v = velAttr ; const l = lifeAttr ; const need = 600 ; let spawned = 0 ; for ( let i = 0 ; i < l . count && spawned < need ; i ++ ) { if ( l . array [ i ] > 0 ) continue; const theta = Math . random () * Math . PI * 2 ; const phi = Math . acos ( THREE . MathUtils . randFloat ( -1 , 1 )) ; const sp = 2 . 8 + Math . random () * 2 . 4 ; v . array [ i * 3 + 0 ] = Math . cos ( theta ) * Math . sin ( phi ) * sp ; v . array [ i * 3 + 1 ] = Math . cos ( phi ) * sp ; v . array [ i * 3 + 2 ] = Math . sin ( theta ) * Math . sin ( phi ) * sp ; p . array [ i * 3 + 0 ] = origin . x ; p . array [ i * 3 + 1 ] = origin . y ; p . array [ i * 3 + 2 ] = origin . z ; l . array [ i ] = 1 . 6 + Math . random () * 0 . 8 ; spawned ++; } p . needsUpdate = true ; v . needsUpdate = true ; l . needsUpdate = true ; } pts . userData . burst = burst ; pts . userData . posAttr = posAttr ; pts . userData . velAttr = velAttr ; pts . userData . lifeAttr = lifeAttr ; return pts ; } const fireworks = makeFireworks ({ maxParticles : 2200 }) ; fireworks . visible = false ; scene . add ( fireworks ) ; // æã¡äžãïŒãã±ããïŒïŒè»è·¡ïŒãã¬ã€ã«ïŒ function makeRocketTrail ({ max = 900 }) { const geo = new THREE . BufferGeometry () ; const pos = new Float32Array ( max * 3 ) ; const vel = new Float32Array ( max * 3 ) ; const life = new Float32Array ( max ) ; for ( let i = 0 ; i < max ; i ++ ) { pos [ i * 3 + 0 ] = 0 ; pos [ i * 3 + 1 ] = -999 ; pos [ i * 3 + 2 ] = 0 ; vel [ i * 3 + 0 ] = 0 ; vel [ i * 3 + 1 ] = 0 ; vel [ i * 3 + 2 ] = 0 ; life [ i ] = 0 ; } geo . setAttribute ( 'position' , new THREE . BufferAttribute ( pos , 3 )) ; geo . setAttribute ( 'velocity' , new THREE . BufferAttribute ( vel , 3 )) ; geo . setAttribute ( 'life' , new THREE . BufferAttribute ( life , 1 )) ; const mat = new THREE . PointsMaterial ({ color : 0xfff0c8 , size : 0 . 11 , map : makeCircleTexture ({ color : 'rgba(255,220,160,0.85)' , soft : true }) , transparent : true , depthWrite : false , blending : THREE . AdditiveBlending }) ; const pts = new THREE . Points ( geo , mat ) ; pts . frustumCulled = false ; const posAttr = geo . getAttribute ( 'position' ) ; const velAttr = geo . getAttribute ( 'velocity' ) ; const lifeAttr = geo . getAttribute ( 'life' ) ; function spawnTrail ( x , y , z , vx , vy , vz , ttl ) { const p = posAttr ; const v = velAttr ; const l = lifeAttr ; for ( let i = 0 ; i < l . count ; i ++ ) { if ( l . array [ i ] > 0 ) continue; p . array [ i * 3 + 0 ] = x ; p . array [ i * 3 + 1 ] = y ; p . array [ i * 3 + 2 ] = z ; v . array [ i * 3 + 0 ] = vx ; v . array [ i * 3 + 1 ] = vy ; v . array [ i * 3 + 2 ] = vz ; l . array [ i ] = ttl ; p . needsUpdate = true ; v . needsUpdate = true ; l . needsUpdate = true ; return; } } pts . userData . spawnTrail = spawnTrail ; pts . userData . posAttr = posAttr ; pts . userData . velAttr = velAttr ; pts . userData . lifeAttr = lifeAttr ; return pts ; } const rocketTrail = makeRocketTrail ({ max : 1100 }) ; rocketTrail . visible = false ; scene . add ( rocketTrail ) ; const rocket = { active : false , x : 0 , y : 0 , z : 0 , vx : 0 , vy : 0 , vz : 0 , targetY : 5 . 0 } ; // ãã±ããæ¬äœïŒæããç¹ãåãããŠãæã¡äžãããèŠèªãããïŒ const rocketSprite = new THREE . Sprite ( new THREE . SpriteMaterial ({ map : makeCircleTexture ({ color : 'rgba(255,255,255,0.95)' , soft : true }) , color : 0xfff0d5 , transparent : true , depthWrite : false , blending : THREE . AdditiveBlending }) ) ; rocketSprite . scale . set ( 0 . 6 , 0 . 6 , 0 . 6 ) ; rocketSprite . visible = false ; scene . add ( rocketSprite ) ; function makeLeaves ({ count = 420 , texture }) { const geo = new THREE . PlaneGeometry ( 0 . 5 , 0 . 5 ) ; const mat = new THREE . MeshStandardMaterial ({ color : 0xffffff , map : texture , transparent : true , depthWrite : false , side : THREE . DoubleSide , roughness : 0 . 9 , metalness : 0 . 0 }) ; const mesh = new THREE . InstancedMesh ( geo , mat , count ) ; mesh . instanceMatrix . setUsage ( THREE . DynamicDrawUsage ) ; mesh . frustumCulled = false ; const data = [] ; const dummy = new THREE . Object3D () ; for ( let i = 0 ; i < count ; i ++ ) { data . push ({ x : ( Math . random () - 0 . 5 ) * 16 , y : Math . random () * 8 + 1 , z : ( Math . random () - 0 . 5 ) * 16 , ry : Math . random () * Math . PI * 2 , rz : ( Math . random () - 0 . 5 ) * 0 . 6 , fall : 0 . 4 + Math . random () * 0 . 6 , sway : 0 . 6 + Math . random () * 1 . 4 , phase : Math . random () * 10 }) ; } function update ( dt , t ) { for ( let i = 0 ; i < count ; i ++ ) { const d = data [ i ] ; d . phase += dt ; d . y -= d . fall * dt ; if ( d . y < -0 . 2 ) { d . y = 8 + Math . random () * 3 ; d . x = ( Math . random () - 0 . 5 ) * 16 ; d . z = ( Math . random () - 0 . 5 ) * 16 ; } const sx = Math . sin ( d . phase * d . sway ) * 0 . 35 ; dummy . position . set ( d . x + sx , d . y , d . z ) ; dummy . rotation . set ( 0 , d . ry + sx * 0 . 7 , d . rz + Math . sin ( t * 1 . 2 + d . phase ) * 0 . 4 ) ; const s = 0 . 65 + Math . sin ( d . phase ) * 0 . 08 ; dummy . scale . set ( s , s , s ) ; dummy . updateMatrix () ; mesh . setMatrixAt ( i , dummy . matrix ) ; } mesh . instanceMatrix . needsUpdate = true ; } mesh . userData . update = update ; return mesh ; } const leaves = makeLeaves ({ count : 520 , texture : makeLeafTexture () }) ; leaves . visible = false ; scene . add ( leaves ) ; function resetSummerArtifacts () { // ãã±ããç¶æ
ãæ¢ãã rocket . active = false ; rocketSprite . visible = false ; // trail / fireworks ã®æ®ãç²ã匷å¶çã«æ¶ã for ( const pts of [ rocketTrail , fireworks ]) { const p = pts . userData . posAttr ; const l = pts . userData . lifeAttr ; if ( ! p || ! l ) continue; for ( let i = 0 ; i < l . count ; i ++ ) { l . array [ i ] = 0 ; p . array [ i * 3 + 1 ] = -999 ; } p . needsUpdate = true ; l . needsUpdate = true ; } } function applySeasonLook () { if ( season === Season . spring ) { resetSummerArtifacts () ; renderer . setClearColor ( 0x0b1020 , 1 ) ; scene . fog = new THREE . Fog ( 0x0b1020 , 10 , 60 ) ; ground . material . color . setHex ( 0x0e1733 ) ; amb . intensity = 0 . 55 ; dir . intensity = 0 . 9 ; sakura . visible = true ; snow . visible = false ; fireworks . visible = false ; leaves . visible = false ; } else if ( season === Season . summer ) { renderer . setClearColor ( 0x070a14 , 1 ) ; scene . fog = new THREE . Fog ( 0x070a14 , 14 , 70 ) ; ground . material . color . setHex ( 0x0a0f22 ) ; amb . intensity = 0 . 35 ; dir . intensity = 0 . 55 ; sakura . visible = false ; snow . visible = false ; fireworks . visible = true ; leaves . visible = false ; rocketTrail . visible = true ; rocketSprite . visible = true ; } else if ( season === Season . autumn ) { resetSummerArtifacts () ; renderer . setClearColor ( 0x120a08 , 1 ) ; scene . fog = new THREE . Fog ( 0x120a08 , 9 , 55 ) ; ground . material . color . setHex ( 0x2a160f ) ; amb . intensity = 0 . 6 ; dir . intensity = 1 . 05 ; sakura . visible = false ; snow . visible = false ; fireworks . visible = false ; leaves . visible = true ; } else if ( season === Season . winter ) { resetSummerArtifacts () ; renderer . setClearColor ( 0x0a1322 , 1 ) ; scene . fog = new THREE . Fog ( 0x0a1322 , 6 , 45 ) ; ground . material . color . setHex ( 0x1a2438 ) ; amb . intensity = 0 . 75 ; dir . intensity = 0 . 95 ; sakura . visible = false ; snow . visible = true ; fireworks . visible = false ; leaves . visible = false ; rocketTrail . visible = false ; rocketSprite . visible = false ; } } applySeasonLook () ; function resize () { camera . aspect = innerWidth / innerHeight ; camera . updateProjectionMatrix () ; renderer . setSize ( innerWidth , innerHeight ) ; } addEventListener ( 'resize' , resize ) ; function updateFalling ( points , dt ) { const p = points . userData . posAttr ; const v = points . userData . velAttr ; const a = points . userData . area ; for ( let i = 0 ; i < p . count ; i ++ ) { p . array [ i * 3 + 0 ] += v . array [ i * 3 + 0 ] * dt ; p . array [ i * 3 + 1 ] += v . array [ i * 3 + 1 ] * dt ; p . array [ i * 3 + 2 ] += v . array [ i * 3 + 2 ] * dt ; if ( p . array [ i * 3 + 1 ] < -0 . 2 ) { p . array [ i * 3 + 0 ] = ( Math . random () - 0 . 5 ) * a . x ; p . array [ i * 3 + 1 ] = a . y ; p . array [ i * 3 + 2 ] = ( Math . random () - 0 . 5 ) * a . z ; } v . array [ i * 3 + 0 ] += ( Math . random () - 0 . 5 ) * 0 . 02 * dt ; v . array [ i * 3 + 2 ] += ( Math . random () - 0 . 5 ) * 0 . 02 * dt ; } p . needsUpdate = true ; v . needsUpdate = true ; } function updateFireworks ( dt ) { const p = fireworks . userData . posAttr ; const v = fireworks . userData . velAttr ; const l = fireworks . userData . lifeAttr ; for ( let i = 0 ; i < l . count ; i ++ ) { const life = l . array [ i ] ; if ( life <= 0 ) continue; l . array [ i ] = Math . max ( 0 , life - dt ) ; p . array [ i * 3 + 0 ] += v . array [ i * 3 + 0 ] * dt ; p . array [ i * 3 + 1 ] += v . array [ i * 3 + 1 ] * dt ; p . array [ i * 3 + 2 ] += v . array [ i * 3 + 2 ] * dt ; v . array [ i * 3 + 1 ] -= 3 . 6 * dt ; v . array [ i * 3 + 0 ] *= ( 1 - 0 . 18 * dt ) ; v . array [ i * 3 + 1 ] *= ( 1 - 0 . 18 * dt ) ; v . array [ i * 3 + 2 ] *= ( 1 - 0 . 18 * dt ) ; if ( l . array [ i ] === 0 ) p . array [ i * 3 + 1 ] = -999 ; } p . needsUpdate = true ; v . needsUpdate = true ; l . needsUpdate = true ; } function updateRocket ( dt ) { if ( ! rocket . active ) { rocketSprite . visible = false ; return; } rocket . x += rocket . vx * dt ; rocket . y += rocket . vy * dt ; rocket . z += rocket . vz * dt ; // è»è·¡ïŒå°ãæ£ããïŒ // 1ãã¬ãŒã ã§è€æ°ç²ãåºããŠãçã£ãœãèŠãã for ( let i = 0 ; i < 3 ; i ++ ) { rocketTrail . userData . spawnTrail ( rocket . x + ( Math . random () - 0 . 5 ) * 0 . 05 , rocket . y + ( Math . random () - 0 . 5 ) * 0 . 05 , rocket . z + ( Math . random () - 0 . 5 ) * 0 . 05 , ( Math . random () - 0 . 5 ) * 0 . 22 , -0 . 55 - Math . random () * 0 . 65 , ( Math . random () - 0 . 5 ) * 0 . 22 , 0 . 75 + Math . random () * 0 . 45 ) ; } rocketSprite . position . set ( rocket . x , rocket . y , rocket . z ) ; rocketSprite . visible = true ; // å°éãããççº if ( rocket . y >= rocket . targetY ) { rocket . active = false ; rocketSprite . visible = false ; fireworks . userData . burst ( new THREE . Vector3 ( rocket . x , rocket . y , rocket . z )) ; } } function updateTrailPoints ( points , dt ) { const p = points . userData . posAttr ; const v = points . userData . velAttr ; const l = points . userData . lifeAttr ; for ( let i = 0 ; i < l . count ; i ++ ) { const life = l . array [ i ] ; if ( life <= 0 ) continue; l . array [ i ] = Math . max ( 0 , life - dt ) ; p . array [ i * 3 + 0 ] += v . array [ i * 3 + 0 ] * dt ; p . array [ i * 3 + 1 ] += v . array [ i * 3 + 1 ] * dt ; p . array [ i * 3 + 2 ] += v . array [ i * 3 + 2 ] * dt ; v . array [ i * 3 + 1 ] -= 0 . 9 * dt ; v . array [ i * 3 + 0 ] *= ( 1 - 0 . 35 * dt ) ; v . array [ i * 3 + 1 ] *= ( 1 - 0 . 35 * dt ) ; v . array [ i * 3 + 2 ] *= ( 1 - 0 . 35 * dt ) ; if ( l . array [ i ] === 0 ) p . array [ i * 3 + 1 ] = -999 ; } p . needsUpdate = true ; v . needsUpdate = true ; l . needsUpdate = true ; } let t = 0 ; let last = performance . now () ; let fireTimer = 0 ; function loop ( now ) { const dt = Math . min (( now - last ) / 1000 , 0 . 05 ) ; last = now ; t += dt ; camera . position . x = Math . sin ( t * 0 . 25 ) * 0 . 35 ; camera . lookAt ( 0 , 1 . 0 , 0 ) ; if ( season === Season . spring ) { sakura . userData . update ( dt , t ) ; } else if ( season === Season . winter ) { updateFalling ( snow , dt ) ; snow . material . size = 0 . 075 + Math . sin ( t * 0 . 6 ) * 0 . 01 ; } else if ( season === Season . summer ) { // æã¡äžãâççºâäœé»ïŒè»è·¡ïŒãŸã§èŠãã updateFireworks ( dt ) ; updateTrailPoints ( rocketTrail , dt ) ; updateRocket ( dt ) ; fireTimer -= dt ; if ( fireTimer <= 0 ) { fireTimer = 1 . 0 + Math . random () * 0 . 9 ; // ãã±ãããåçºå°ïŒçºå°äžã¯è¿œå ããªãïŒ if ( ! rocket . active ) { rocket . active = true ; rocket . x = ( Math . random () - 0 . 5 ) * 3 . 2 ; rocket . y = -1 . 2 ; rocket . z = -4 . 0 + ( Math . random () - 0 . 5 ) * 1 . 4 ; rocket . vx = ( Math . random () - 0 . 5 ) * 0 . 18 ; rocket . vy = 6 . 2 + Math . random () * 0 . 8 ; rocket . vz = ( Math . random () - 0 . 5 ) * 0 . 12 ; rocket . targetY = 3 . 9 + Math . random () * 1 . 7 ; } } } else if ( season === Season . autumn ) { leaves . userData . update ( dt , t ) ; } renderer . render ( scene , camera ) ; requestAnimationFrame ( loop ) ; } requestAnimationFrame ( loop ) ; </ script > </ body > </ html > å·çè
äžå· æåïŒNTTè¥¿æ¥æ¬ ããžã¿ã«é©æ°æ¬éš ããžã¿ã«æ¹é©æšé²éšæå±ïŒ NTTè¥¿æ¥æ¬ã®Webã¢ããªã±ãŒã·ã§ã³ã®éçºã»éå¶ã«åŸäºã 奜ããªæè¡ã¹ã¿ãã¯ïŒTypeScript, Vue.js, GraphQL, Laravel åèè³æã»åºå
ž æ¬èšäºãå·çããã«ãããã以äžã®ãµã€ãã»è³æãåèã«ããŸããã Three.js documentationïŒ https://threejs.org/docs/ MDN Web Docs â WebGL APIïŒ https://developer.mozilla.org/ja/docs/Web/API/WebGL_API MDN Web Docs â WebGL best practicesïŒ https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/WebGL_best_practices MDN Web Docs â requestAnimationFrame ïŒ https://developer.mozilla.org/ja/docs/Web/API/Window/requestAnimationFrame åæš ãJavaScriptãã¯ãOracle Corporation ããã³ãã®åäŒç€Ÿã®ç±³åœããã³ãã®ä»ã®åœã«ãããåæšãŸãã¯ç»é²åæšã§ãã ãFirefoxãã¯ãMozilla Foundation ã®åæšã§ãã ãSafariãã¯Apple Inc. ã®åæšã§ãã ãChromiumãã¯Google LLC ã«é¢é£ãããããžã§ã¯ãåã§ãã ãGoogle Chromeãã¯ãGoogle LLC ã®åæšã§ãã ãThree.jsãã¯ãThree.jsãããžã§ã¯ãã«é¢é£ããåç§°ã§ãïŒè©³çްã¯å
¬åŒããã¥ã¡ã³ããåç
§ããŠãã ããïŒã ãVisual Studio Codeãã¯Microsoft Corporation ã®åæšã§ãã ãApache HTTP Serverãããã³ãApacheãã¯ãApache Software Foundation ã®åæšã§ãã èšèŒã®ãã®ä»ã®äŒç€Ÿåã»è£œååã¯ãããããå瀟ã®åæšãããã¯ç»é²åæšã§ãã
åç»
該åœããã³ã³ãã³ããèŠã€ãããŸããã§ãã












