Blender
イベント
該当するコンテンツが見つかりませんでした
マガジン
該当するコンテンツが見つかりませんでした
技術ブログ
こんにちは。サイオステクノロジーのひろです。 先日OSC福岡、OSC広島でセミナー登壇してきました。 今回は登壇内容についてまとめていきたいと思います。 生成AIの概要を理解できる 生成AI×ツールで新たな価値を生み出せることを理解できる この2点を目的として以下のことについてお話してきました。 生成AIにできることできないこと 生成AIにツールを使わせる技術について 生成AIにロボットを操作させるデモ セミナーに使用した資料を交えつつセミナー内容について解説していきます。 生成AIってなに? 生成AIとはコンテンツを新たに生み出してくれるAIのことを指します。 ユーザがプロンプトを入力することで生成AIは文章や画像や動画、音楽、音声等を出力してくれます。 例としては、テキスト生成であれば Gemini 3.0 GPT-5 Claude Sonnet 4.5 等がありますね。 まず、テキスト生成に焦点をあてて、LLMが文章を生成する過程について説明しました。 テキスト生成を行うLLM(大規模言語モデル)は膨大なテキストデータから言語のパターンを学習したもので、文脈から次に来る確率が高い言葉を予測し、リストアップされた言葉の中からランダムに単語を選択することを繰り返して文章を生成します。 例えば「今日はいい日だ。」という文章を生成する過程を考えてみます。 以下の図のように、まず、「今日は」の次に来る可能性が高い言葉として、「いい」、「天気」、「寒い」がリストアップされ、その中から確率で選択されます。今回は「いい」が選択されていますね。この操作を繰り返します。 ここで一点重要なのは生成AIは最も確率が高い言葉を選択するわけではないという点です。 試しに同じプロンプトを何度か生成AIに投げていただけると異なるレスポンスが来ると思いますので、この仕組みを実感できると思います。 LLMは言葉を理解しているのではなく、パターンを知っており、そのパターンに倣って言葉を連ねているということになります。こちらについては、永田さんの記事「 AIは「1+1って、2になること多いなあ」と思っている!? 」でLLM内部で起こっていることについて解説されてるので気になった方は是非ご覧ください。 次に、生成AIモデルでできることできないことを切り分けるために、まずLLMモデルと生成AIサービスの違いの説明を行いました。 LLMと生成AIサービス。一体どう違うのかというと、機能が異なります。 LLM 生成AIサービス 機能 文章の生成 LLMの機能+履歴保持、web検索、ファイルアップロード,etc 例 GPT-5等 ChatGPT,Gemini等 生成AIサービスにはLLMの機能に加えて様々な機能が追加されています。 例えば表にあるように履歴保持機能。これがあるとこれまでの会話の文脈を生成AIが理解してくれるのでしっかり会話が成り立ちますね。 web検索機能。最近の生成AIサービスは検索機能を持っており、生成AIにリアルタイムの出来事を回答させることもできます。例えば今日の天気等をGeminiに聞いてみると検索結果から回答してくれます。 ChatGPTやGemini等の生成AIサービスは純粋なLLMとは異なる機能を持っているということです。 そして、生成AIモデル単体でできることとできないことを挙げると以下のようになります。 LLM単体でできないことについて解説していきます。 最新の情報に関する回答 LLMモデルはリアルタイムで学習し知識を更新する仕組みはなく、知識のカットオフ日が存在します。 カットオフ日とは、LLMが学習したデータがいつまでのデータかを指し、LLMはその日までのデータしか知り得ません。 そのため、例えば今日の天気を聞いても学習していないので回答することができません。 試しにGPT-4o-miniに対し、カットオフ日について尋ねてみました。 私の知識のカットオフ日は2023年10月です。それ以降の情報については持っていませんので、ご了承ください。何か質問があればお答えします! このLLMは2023年10月までのことしか知らないというわけですね。 試しに「2025年は何が流行しそうか考えてみてください」と尋ねてみました。 2025年に流行しそうなトレンドについて、現在の技術や社会の動向を考慮した上でいくつかの予測を挙げてみます。 持続可能なライフスタイル: 環境意識の高まりに伴い、エコフレンドリーな製品やサービス、サステナブルなファッションが流行する可能性があります。プラスチックの削減やリサイクル製品の人気が高まるでしょう。 ウェアラブルテクノロジー: 健康管理やフィットネスのためのウェアラブルデバイスが進化し、より多機能で使いやすくなることで、一般的なアイテムとして普及するかもしれません。 メタバースとバーチャル体験: メタバース関連のプラットフォームが進化し、仮想空間でのソーシャルインタラクションやエンターテインメントが一般的になる可能性があります。特に、ゲームや仮想イベントが人気を集めるでしょう。 デジタルアートとNFT: デジタルアートやNFT(非代替性トークン)が引き続き注目され、アート市場やエンターテインメント業界で新しい形のクリエイションが流行するかもしれません。 パーソナライズされた医療と健康: 個別化医療や遺伝子ベースの健康管理が進展し、自分に最適な健康法や食事が提案されることで、健康志向のライフスタイルが流行する可能性があります。 リモートワークとフレキシブルな働き方: リモートワークやハイブリッドな働き方が一般化し、職場環境や働き方に関する新しいトレンドが形成されるでしょう。これにより、ワークライフバランスを重視する文化が広がる可能性があります。 エンターテインメントの新形態: ストリーミングサービスやポッドキャスト、バーチャルコンサートなど、デジタルコンテンツの消費スタイルが進化し、新しいエンターテインメントの形が流行するかもしれません。 これらはあくまで予測であり、実際に何が流行するかは様々な要因によって変わるでしょう。未来のトレンドは常に変化し続けるため、柔軟な視点で見ることが重要です。 もう2026年ですが2025年の流行を予想してもらいました。 あたっているかはさておき、このようにカットオフ日以降のことは知らないんですよね。 これから時間が経って、年齢を重ねたころにこのモデルと会話できたら懐かしい気分になれるかもしれません。 独自の情報に関する回答 LLMはもちろん学習したことしか知りません。 そのため、例えば会社の社内規約等のクローズドな情報について聞いても回答することができません。 情報の正確性の保証 LLMにはハルシネーションという、あたかも真実を語るように真っ赤な噓を吐くことがあります。 LLMが文章を生成する過程でもお話しましたが、LLMは、確率で単語を選び、それを繰り返して文章を作成するので、正しいこと以外も出力します。 LLMが本当に正しいことを言っているのか、人間が確認する必要があります。 複雑な計算 何か計算してとLLMに入力したとして、LLMは実際に計算しているわけではなく、学習パターンに基づいて次来る単語を生成しているため、複雑な計算は間違えることがあります。 AIは計算を理解しているわけではなく、 「1+1って、2になること多いなあ」と思っている ということですね。 現実世界やデジタル環境の操作 LLMはテキストを生成するのみで、例えば部屋の電気は消してくれませんし、notionでドキュメントを作成してくれることはありません。 このように生成AIにはできないことがありますが、これはツールと組み合わせることで解消できる場合があります。 生成AI×ツール 生成AIとツールを組み合わせることで多くのことができるようになります。 セミナーでは、RAG、FunctionCalling、MCPについてご紹介しました。 RAG RAGはRetrieval Augmented Generationと呼ばれ、検索拡張生成等と訳される技術です。 生成AI×検索ツールですね。 生成AIが検索ツールを使用してデータを検索し、取得したデータを基に回答を行います。 RAGを活用することで、生成AIはリアルタイムの情報や学習していない独自の情報を手に入れることができます。 また、情報源が明確になるため、根拠のある回答をしてくれますし、根拠をユーザが確認することができるようになります。 前章で挙げた生成AIにできないことのうち以下の項目については解消できそうと思っていただけるのではないでしょうか。 最新の情報に関する回答 独自の情報に関する回答 情報の正確性の保証 Function Calling 次にFunctionCallingです。 FunctionCallingは生成AIに関数を呼び出させる機能です。 関数の実行はアプリケーション側で行うため生成AIのレスポンスを翻訳する部分は実装する必要がありますが、生成AIがどの関数をどんな引数で実行するのか判断してくれます。 例えば検索、計算、外部APIの使用、IoT連携等、様々な機能を生成AIと組み合わせることが可能です。 複雑な計算ができる関数を用意しておけば、生成AIが苦手な計算だけ関数にさせることもできますし、ロボットを動作させる関数なんてのを作成しておけば、生成AIにロボットを操作させることもできるというわけですね。 組み合わせ次第で強力なものが生まれそうな気がします。 Azure OpenAIでFunctionCallingを行う方法については こちら のブログ記事で解説してますので興味がある方はぜひご覧ください。 MCP MCPはAnthropic社が提唱した、生成AIとツールを繋ぐUSB-typeCのような共通規格です。 これまでFunctionCallingを用いたLLMアプリを作成した場合、あるツールを別のアプリでも使用したいとなった場合、アプリ間の言語が異なったり必要なライブラリが異なれば、関数を改修する必要がありました。 また、ツールリストの定義方法はLLMによって異なるため、アプリで使用するLLMが異なれば、その点を改修する必要が出てきます。 MCPを使用した場合、MCPクライアントというものを用意し、LLMアプリと別プロセスで動作するMCPサーバをツールとして扱うようにします。 そうすると、MCPサーバ1つ作成すれば、どのLLMアプリからも使用できるようになるので、アプリ毎に関数を書いたり、ツール定義を行う必要がなくなります。 また、MCPサーバを公開しているサービスは増えており、例えばnotionやblender、googleカレンダー等のMCPサーバを組み込むことが容易です。 公開されているMCPサーバについては こちら をご確認ください。 生成AIとツールを組み合わせる技術であるRAG、FunctionCalling、MCPについて解説を行いました。 続いてデモの解説に移ります。 Qumcum連携 具体的にFunctionCallingでQumcumを生成AIに操作させるデモを行いました。 QumcumはBluetoothによる通信が可能な小型ロボットです。 主な機能は距離センサや音検知、発声等がありますが、今回使用したのは頭、腕、足の回転です。 また、LLMとしてAzure OpenAIのモデルを使用しました。 Azure OpenAIについてはデプロイから実際にAPIを叩くまでをブログ記事にしていますので こちら をご確認ください。 シーケンス図は以下のようになります。 まず、プロンプトの分析をLLMにリクエストし、結果を構造化出力させています。これは分析結果(プロンプトから読み取れる感情、プロンプトに対するロボットの感情、プロンプトの要約等)をUIとして表示するために使用しています。 構造化レスポンスについてもブログにまとめているので、 こちらの記事を ご覧ください。 その後、分析結果とプロンプト本文をLLMに渡し、FunctionCallingを行います。 使用する関数を選択してもらい、アプリケーション側でロボット動作関数を実行しています。 デモ動画はこちらです。 このデモでは、入力したプロンプトからFunctionCallingによって関数が選び取られていることを表しています。 ロボットが万歳をする関数や、足踏み、首振りを行う関数が選び取られ、実行されているのがわかります。 今後の展望 具体的な展望ではないですが、今後できたらおもしろいなと考えていることは以下のようなことです。 テキスト入力から音声入力へ修正 Qumcumの発話機能を活用し、リアルタイム会話機能実装 今までの会話内容を記録し、RAGによって相棒、友人のような会話を可能に RAGを用いて生成AIの相棒を作るはらちゃんのブログは こちら を参照ください。 生成AIとツールを組み合わせることで、某未来から来たネコ型ロボットのような友人を自分の手で作ることができるかもしれませんね。 まとめ 生成AI×ツールによって、生成AI単体ではできなかったことが可能になります。 最新の情報に関する回答 独自の情報に関する回答 現実世界やデジタル環境の操作 等が可能です。 FunctionCallingやMCPを活用して新たな組み合わせによる新たな価値を生み出していきましょう。 閲覧いただきありがとうございました。 セミナーに参加してくださった皆さん、ご清聴ありがとうございました。 わかりやすく伝えられるセミナーを今後も行っていきたいと思います。 ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post OSC福岡&広島2025で生成AI×ツールについてセミナー登壇してきました first appeared on SIOS Tech Lab .
本ブログは、三菱電機株式会社 電力システム製作所 電力ICTセンター 小森様と、アマゾン ウェブ サービス ジャパン合同会社 ソリューションアーキテクト稲田、GitLab 合同会社 ソリューションアーキテクトの小松原様の共著です。三菱電機 電力ICTセンターにおける Kiro と GitLab を組み合わせたソフトウェア開発効率化の取り組みについてご紹介します。 電力ICTセンターについて 三菱電機 電力システム製作所 電力ICTセンターは、再生可能エネルギーの拡大や電力自由化に対応する電力×ICTの専門組織です。電力取引・需給管理から、分散型電源/VPP、蓄電池制御、スマートメーター、託送・精算、環境価値管理、アセットマネジメントまで、電力事業全体をカバーする主力ソリューション「 BLEnDer(ブレンダー) 」を開発しています。BLEnDer を軸に電力の”見える化→判断→計画→制御”を一気通貫で実現する電力デジタルソリューションを提供し、電力会社様、送配電事業者様、小売電気事業者様、発電事業者様など、幅広い事業者様を支えています。 これまでの取り組みと課題 電力ICTセンターでは、ソフトウェア開発の効率化に向けてさまざまな取り組みを行ってきました。 GitLab をベースにした CI/CD 環境の構築 Amazon Bedrock を活用した RAG システムの開発(開発工数見積もりの支援) Amazon Bedrock を活用した設計書レビューの効率化 CI/CD ツールを活用したビルド・テスト・デプロイ作業の効率化 ワークフロー自動化ツールを活用した申請フローの自動化 これらのシステムやツールはそれぞれ開発効率化に寄与していますが、 導入・展開時に共通の課題 がありました。 開発したシステムやツールは、基本的に納入先の開発チームに運用・保守を担っていただきます。そのため、利用方法を示したドキュメントや設計ドキュメントの作成、利用フローの整備が不可欠です。時には勉強会を開催し、必要なスキルを習得していただく必要もありました。しかし、毎回フルセットで伴走支援を行うことは現実的ではなく、ドキュメントや勉強会だけで実践していただくには限界があると感じていました。 「ドキュメントを読まなくても、AI に聞けばワークフローに沿った開発ができる」 ― そんな仕組みを実現するために、Kiro と GitLab を組み合わせた新しいアプローチに取り組んでいます。 Kiro の Steering・Powers・MCP とは 本題に入る前に、今回の取り組みで活用している Kiro の主要機能について説明します。 Steering:プロジェクト横断のグラウンドルール Steering は、Kiro の振る舞いに対して 常に適用されるグラウンドルール を定義する仕組みです。プロジェクトのルートディレクトリに .kiro/steering/ ディレクトリを作成し、マークダウンファイルでルールを記述します。 .kiro/ └── steering/ ├── coding-standards.md # コーディング規約 ├── language.md # 言語設定(日本語で回答する等) └── security.md # セキュリティポリシー Steering に記述したルールは、 どの Power が発動しているかに関わらず常に Kiro のコンテキストに読み込まれます 。そのため、「日本語で回答すること」「機密情報をコードに含めないこと」といった、プロジェクト全体で一貫して守るべきルールの定義に適しています。 電力ICTセンターでは、プロジェクト固有ではない共通のグラウンドルール(言語設定、コーディング規約の基本方針など)を Steering で管理しています。 Powers:動的に呼び出されるワークフロー定義 Powers は、Steering とは異なり、 ユーザーの発話内容に応じて動的に呼び出される ルール定義の仕組みです。Power は POWER.md というメタデータファイルと、 steering/ ディレクトリ配下のステアリングファイル群で構成されます。 .kiro/powers/ └── my-power/ ├── POWER.md # Power のメタデータ(name, description, keywords) ├── mcp.json # この Power 専用の MCP サーバー設定(任意) └── steering/ ├── guide-a.md # ステアリングファイル A └── guide-b.md # ステアリングファイル B POWER.md の frontmatter には keywords を定義でき、ユーザーの発話にこれらのキーワードが含まれると、その Power が動的に発動します。 --- name: "standard-dev-flow" displayName: "Standard Dev Flow" description: "GitLab を使用した開発ワークフローを標準化する Power" keywords: ["イシュー", "ブランチ", "コミット", "MR", "マージリクエスト", "レビュー", "パイプライン", "実装"] --- Steering との使い分けのポイント は、コンテキストウィンドウの効率です。Steering は常に読み込まれるため、大量のルールを定義するとコンテキストを圧迫します。一方、Powers は必要なときだけ動的に読み込まれるため、ワークフロー固有の詳細なルール(数千行に及ぶガイドラインなど)を定義しても、関係ないタスクの際にはコンテキストを消費しません。 電力ICTセンターでは IDE での利用が多く、IDE 上で Power が動的に呼び出されていることを視覚的に確認できる点に安心感を感じています。「いま Kiro がどのルールに従って動いているか」が見えることで、AI の振る舞いを制御できている実感が得られます。 Kiro から Power を呼び出すイメージ MCP(Model Context Protocol):外部ツールとの連携 MCP は、Kiro が外部のツールやサービスと連携するためのプロトコルです。MCP サーバーを定義することで、Kiro が GitLab API の操作や独自のスクリプト実行など、IDE の外にある機能を呼び出せるようになります。 Power ごとに mcp.json を配置できるため、 特定のワークフローでのみ必要な MCP サーバーを、その Power に紐づけて管理 できます。例えば、Standard Dev Flow Power には GitLab 操作用の MCP サーバーを、Playwright Power にはテスト実行用の MCP サーバーを、といった使い分けが可能です。 3 つの機能の関係性 まとめると、以下のような役割分担になります。 機能 適用タイミング 用途 Steering 常時 プロジェクト横断のグラウンドルール Powers キーワードに応じて動的 ワークフロー固有の詳細ルール・手順 MCP Power に紐づけて動的 外部ツール・API との連携 この3層構造により、「常に守るべきルール」「タスクに応じて必要なルール」「外部ツールとの連携」を整理して管理でき、コンテキストウィンドウを効率的に活用しながら AI エージェントの振る舞いを制御できます。 現在の取り組み:Kiro × GitLab による開発プラットフォーム 全体像 現在、CI/CD を中心としたソフトウェア開発サイクル全体の効率化に取り組んでいます。 開発ワークフローの整理(ブランチ戦略・リリース戦略を含む CI/CD 環境の整備) アプリケーションのデプロイ方法の検討、テスト自動化環境の整備 GitLab CI/CD カタログを活用したパイプラインの標準化 ガイドラインの整備 これらの取り組みにおいて、Kiro と GitLab をどのように活用しているかをご紹介します。 Kiro の活用:Powers による開発ワークフローの標準化 Kiro では 2 つの Power (Standard Dev Flow Power, Playwright Power) を利用しています。それぞれについて解説します。 その 1: Standard Dev Flow Power 開発ワークフロー全体を標準化する Power を作成しました。以下のワークフローを Kiro に定義しています。 1. Issue 作成 → 2. ブランチ作成 → 3. 実装 → 4. コミット → 5. MR 作成 → 6. レビュー → 7. マージ この Power には、各フェーズに対応するステアリングファイルが含まれています。 standard-dev-flow/ ├── POWER.md # Power 定義(ワークフロー全体像) ├── mcp.json # MCP サーバー設定 └── steering/ ├── issue-management.md # Issue 作成ガイド ├── branch-commit-mr.md # ブランチ・コミット・MR作成ガイド ├── implementation.md # 実装作業ガイド ├── gitlab-duo-review.md # GitLab Duo レビューワークフロー ├── pipeline-troubleshooting.md # パイプライン失敗解析ガイド └── gitlab-official-mcp-tools.md # GitLab 公式 MCP ツールリファレンス POWER.md にはワークフローの全体像と各ステアリングファイルの参照タイミングを記述しています。例えば、「新しい Issue を作成する際は issue-management.md を参照」「パイプラインが失敗した際は pipeline-troubleshooting.md を参照」といった形で、Kiro がどのフェーズでどのルールを適用すべきかを明示しています。 各ステアリングファイルには、そのフェーズで Kiro が従うべき具体的なルールが記述されています。例えば branch-commit-mr.md には以下のようなルールが含まれます。 ブランチ命名規則: feature/{issue-number}-{brief-description} 形式 Conventional Commits 1.0.0 準拠のコミットメッセージ(type/scope/description/body の構造化、日本語での記述) MR 作成時の squash merge による 1 コミットへの集約 MR テンプレート(変更内容/テスト/レビュー観点/補足)に基づいた記述 開発者は Kiro に「Issue #123 の実装を開始してください」と伝えるだけで、Power に定義されたワークフローに沿って作業が進みます。Kiro は自動的に以下を実行します。 ブランチの確認・切り替え Issue の内容確認と tmp/{タスク名}-issue-details.md の作成 実行計画の作成( tmp/{タスク名}-plan.md ) 実装作業の実施 作業サマリーの作成( tmp/{タスク名}-summary.md ) このように、 作業の追跡ファイルを自動生成する仕組み も Power に組み込んでいます。Issue の詳細理解、チェックリスト形式の実行計画、作業完了後のサマリーを tmp/ ディレクトリに残すことで、作業の透明性を確保しています。 Standard Dev Flow Power には、GitLab 公式 MCP サーバーと独自の MCP サーバーの両方を mcp.json で定義しています。 GitLab 公式 MCP サーバーが提供する主なツール: create_issue / get_issue :Issue の作成・取得 create_merge_request / get_merge_request :MR の作成・取得 search / semantic_code_search :検索機能 独自 MCP サーバー(GitLab MCP Enhancer)で補完しているツール: get_mr_discussions / reply_to_discussion / resolve_all_discussions :MR ディスカッション操作 list_merge_requests / update_merge_request :MR の一覧・編集 list_issues / update_issue / add_issue_note :Issue の一覧・編集 get_latest_pipeline / get_pipeline_jobs / get_job_log :パイプライン解析 この 2 つの MCP サーバーを組み合わせることで、Issue 作成から MR レビュー対応、パイプライン失敗の解析まで、 GitLab の操作をほぼ Kiro 上で完結 できるようになりました。 その 2: Playwright Power 次のステップとして、Playwright を活用した E2E テスト生成のための Power も作成しています。 playwright/ ├── POWER.md # Power定義 └── steering/ ├── playwright-rule.md # コーディング規約・POM変換手順 ├── playwright-cli.md # playwright-cli の使い方 └── test-best-practices.md # テスト実装のベストプラクティス この Power のポイントは、 playwright-cli の出力から Page Object Model(POM)への変換手順 をステアリングファイルに詳細に定義している点です。playwright-cli はコーディングエージェント向けに設計された CLI ベースのブラウザ自動化ツールで、操作ごとに Playwright コードを出力します。 playwright-rule.md には、この出力コードから POM クラスへ変換する 3 ステップの手順が定義されています。 ロケーター抽出 :出力コードから要素を特定 readonly 定義 :コンストラクタでロケーターを宣言 アクションメソッド :操作をメソッドに抽象化 さらに、ロケーター戦略の優先順位も明確に定義しています。 getByRole() – ボタン、リンク等のセマンティック要素 getByLabel() – フォーム要素 getByPlaceholder() – プレースホルダーテキスト getByText() – 表示テキスト getByTestId() – テスト専用属性 CSS/XPath – 最後の手段 test-best-practices.md には、AAA パターン(Arrange/Act/Assert)によるテスト構造化、アサーション強化( toBeVisible() だけでなく toHaveText() で内容確認)、テスト安定化テクニック(スピナー待機、古い DOM の回避)など、実践的なベストプラクティスが記述されています。 これらのルールを Power に落とし込むことで、 誰が Kiro にテスト生成を依頼しても、組織のベストプラクティスに沿った一貫性のあるテストコードが生成されます 。 GitLab の活用 GitLab Pages によるガイドライン公開 プラットフォームを他 BU に展開するにあたり、ガイドラインの整備は不可欠です。GitLab Pages を活用し、マークダウンで記述したガイドを静的 Web サイトに変換して公開しています。ガイドには、GitLab CI/CD カタログを活用した標準パイプラインの説明や、GitLab Duo や Kiro の基本的な使い方や Tips を掲載しています。関連するソースコードはすべて GitLab で管理しており、ソースコードの修正があれば生成 AI を活用して即時にガイドに反映・公開できるため、管理の手間を感じません。 ここで重要なのは、 Powers には基本的にガイドに書いてあることをそのまま落とし込んでいる という点です。これにより、ガイドの内容と AI の振る舞いが常に一致し、「ガイドを読む」か「AI に聞く」かのどちらでも同じ情報にたどり着ける状態を実現しています。 GitLab Pages の利用イメージ CI/CD カタログによるパイプラインの標準化 GitLab CI/CD カタログを作成し、アーティファクトやキャッシュの設定を含むパイプラインテンプレートを配布しています。ユーザー側は環境固有のパラメータ値のみを入力すれば、即座に使用可能な状態です。 GitLab Duo による MR レビュー:AI 同士の協働 Kiro でコーディングを行い、MR を作成した後、GitLab Duo にレビューを依頼するワークフローを構築しています。 生成 AI は自分で生成したコードに対して正しいという認知バイアスがかかる可能性があるため、 コード生成(Kiro)とレビュー(GitLab Duo)を異なる AI に担当させる ことで、より客観的なレビューを実現しています。 GitLab Duo へのレビュー観点は、社内の有識者を交えて mr-review-instructions.yaml に定義しており、レビューコメントに必ず prefix(接頭辞)を付けるルールを設けています。この prefix により、レビュー指摘の重要度が一目で判断できるようになります。 instructions: - name: Common Review Policy fileFilters: - "**/*" instructions: | - **言語**: すべての応答は必ず日本語で行ってください - レビューする際には、以下のprefix(接頭辞)を必ず付けてください - [must] → 必須修正、修正しないのであれば修正しない理由を開発者に求める - [want] → できれば対応してほしい、改善として望ましい提案 - [imo] → 個人的意見、好みベースの提案 - [ask] → 質問、意図・背景の確認 - [nits] → 細かい指摘(nitpicks)、超軽微なスタイル修正など - [info] → 参考情報、共有・補足・関連情報 一方、Kiro 側の Power( gitlab-duo-review.md )には、GitLab Duo のレビュー結果をどう受け取り、どう対応するかのルールを定義しており、GitLab Duo が付与する prefix に対して、Kiro がどのように判断・行動すべきかを明示しています。 ・・・ ### 2. GitLab Duoにレビュー依頼 **重要**: GitLab Duoのレビューは**新規ディスカッション**で`@GitLabDuo`をメンションすることで発動する。MR作成時のdescriptionに記載しても発動しない。 **レビュー依頼のポイント**: - 新規ディスカッションを作成する(これが必須) - `@GitLabDuo`はスペースなしで記載 - 依頼メッセージには以下を含めると効果的: - 実装内容の概要 - 特に確認してほしい点 - 変更理由や背景 - 参考資料: 関連ドキュメント ### 3. レビュー結果確認 **レビューコメントのprefix(重要度の判断基準)**: GitLab Duoのレビューには以下のprefixが付与される。これを基に対応の優先度を判断する。 | Prefix | 意味 | 対応方針 | |--------|------|----------| | `[must]` | 必須修正 | 修正必須。対応しない場合は理由を説明 | | `[want]` | できれば対応 | 改善として望ましい。可能な限り対応 | | `[imo]` | 個人的意見 | 好みベースの提案。採用は任意 | | `[ask]` | 質問 | 意図・背景の確認。回答が必要 | | `[nits]` | 細かい指摘 | 超軽微なスタイル修正。余裕があれば対応 | | `[info]` | 参考情報 | 共有・補足・関連情報。対応不要 | **[ask]への対応**: `[ask]`が付いた質問には、該当ディスカッションに`@GitLabDuo`メンション付きで返信して不明点を解消する。 ・・・ [ask] が付いた質問には、該当ディスカッションに @GitLabDuo メンション付きで返信して不明点を解消するルールも定義しています。 また、GitLab Duo へのレビュー依頼方法についても Power に明記しています。GitLab Duo のレビューは MR の description に記の最後に /assign_reviewer @GitLabDuo を記載することで発動します。こうした「知らないとハマるポイント」も Power に落とし込むことで、開発者が試行錯誤する手間を省いています。 以上を踏まえた、Kiro と GitLab Duo の協働ワークフローは以下の通りです。 Kiro で機能を実装し、MR を作成 GitLab Duo に MR レビューを依頼(MCP 経由) レビュー指摘を Kiro が取得し、対応可否を判断 対応が必要な指摘に対してコード修正を実施 修正をコミット・プッシュし、再度 GitLab Duo にレビューを依頼 承認後、全ディスカッションを一括解決 この AI 同士の協働によるレビューサイクル により、ある程度のクオリティが担保された状態で人間のレビュアーが入ることで、レビュー負担の軽減が期待できます。 この構成のメリット 1. 導入ハードルの大幅な低下 Power を活用して開発フローを定義しているため、開発者は Kiro に質問しながら作業を進めるだけで、ワークフローに沿った開発を行うことができます。分からないことは Kiro に質問して解決できるため、Kiro の基本的な使い方を覚えるだけで開発を始められます。BU への展開時に「AI がサポートしてくれるし、ガイドを特に読む必要はないよ」と伝えるだけでハードルがぐっと下がります。 2. 開発の証跡とコラボレーション Issue や MR で開発の証跡を残すことで、他の開発者とのコラボレーションが容易になります。Issue を追うことで、別の開発者が作業を引き継ぐことも容易になると考えています。 3. レビュー負担の軽減 コード開発の速度が上がる一方で、レビュー者への負担が増大するリスクがあります。GitLab Duo での観点に基づいたレビューと CI/CD パイプラインの実行結果を Kiro にフィードバックさせて改善することで、人間のレビュアーが入る前にある程度のクオリティを担保できます。人間のレビュー疲れやレビュー工数の削減にも寄与することを期待しています。 工夫した点と苦労した点 1 : Power の発動率を上げる工夫 Power に keywords を設定して呼び出しの工夫をしましたが、期待通りに発動しないケースがありました。例えば、Standard Dev Flow Power には ["イシュー", "ブランチ", "コミット", "MR", "マージリクエスト", "レビュー", "パイプライン", "実装"] といったキーワードを設定していますが、ユーザーの発話がこれらのキーワードに完全に一致しない場合、Power が発動しないことがありました。 調査の結果、 Power を積極的に活用するよう促す Steering ファイルを作成する ことで、利用率が向上しました。Steering は常にコンテキストに読み込まれるため、「利用可能な Power がある場合は積極的に活用すること」というルールを Steering に記述することで、Power の発動率を改善できます。AI エージェントツールを組織で活用する際には、こうしたチューニングが重要になります。 2: GitLab 公式 MCP サーバーの補完 GitLabの良さはそのカスタマイズ性の高さにあります。 公式 MCP サーバーを補完する独自の MCP サーバー(GitLab MCP Enhancer)を自作 しました。 gitlab-official-mcp-tools.md というステアリングファイルに 公式 MCP サーバー (Beta 版)の全 14 ツールのパラメータ定義と使用例を記述し、Kiro が正しくツールを使えるようにガイドしています。これによりMR ディスカッションの取得・返信・解決、Issue の一覧・編集、パイプラインのジョブログ取得など、 GitLab の操作が大幅に向上し、ほぼ Kiro 上で操作を完結できるようになりました。 今後の展望 直近では、現在作成中の Playwright Power を完成させ、E2E テスト生成の効率化を推進していきます。また、Playwright Power と並行して、SonarQube の MCP サーバーを活用し、静的解析結果を Kiro にフィードバックする仕組みの構築も検討し始めました。これにより、コードレビュー時だけでなく、実装段階からコード品質を高めるための仕組み作りにも取り組んでいます。このように、組織で活用が見込まれる OSS の活用を推進する Power を順次作成し、AI を活用してソフトウェア開発が楽になる土壌を広げていく方針です。また、GitLab Duo のレビュー観点も継続的に育てていき、適用範囲を広げることでレビュー負担のさらなる軽減を目指します。 1 : 効果の定量化と継続的な改善 AI 活用による開発効率化の効果を可視化するため、定量的な評価指標の測定に取り組んでいきます。Issue からマージまでのリードタイムやレビューイテレーション回数といった開発速度の指標、GitLab Duo のレビュー指摘の精度、Kiro の Playwright Power で生成されるコードの品質などを、実際に各ビジネスユニット (以降、BU)で使用していただきながら評価していきます。これらのフィードバックを基に、Power のルール定義の改善や MCP サーバーの機能拡張など、継続的な改善サイクルを回していきます。 2 : Power とパイプラインのエコシステム構築 Power や CI/CD パイプラインを組織全体で活用していくため、バージョン管理と配布の仕組みを構築していきます。GitLab で Power/MCP や CI/CD パイプラインテンプレートを一元管理し、GitLab Pages で導入方法・アップグレード方法を公開します。バージョン更新時には更新ダッシュボードでユーザーに通知し、各 BU からの要望は Issue やチケットで収集して継続的に改善します。また、既存の管理システムとの連携も視野に入れ、障害報告から修正、リリースまでの開発サイクル全体の効率化を図ります。 3 : 組織全体への展開とコミュニティ形成 中長期的には、電力ICTセンター内の各 BU への展開を加速させていきます。積極的な情報発信を通じて周囲を巻き込みながら、地道に活用の輪を広げていきます。Power やルール整備の取り組みは電力ICTセンターに閉じた話ではないため、ゆくゆくは三菱電機全社的な取り組みとして認知され、活用方法について議論できるコミュニティが形成されることを目指しています。まずは足元の電力ICTセンターのソフトウェア開発効率化につながる活動をどんどん進めていきたいと考えています。 まとめ 三菱電機 電力ICTセンターでは、Kiro の Steering・Powers・MCP と GitLab の各機能を組み合わせることで、 「AI に聞けばワークフローに沿った開発ができる」 プラットフォームの構築を進めています。 この取り組みの本質は、単なるツール導入ではありません。 Steering でプロジェクト横断のグラウンドルールを定義し Powers でワークフロー固有の詳細なルール・手順を動的に呼び出し MCP で GitLab をはじめとする外部ツールとシームレスに連携する この 3 層構造により、ガイドラインの内容を Powers に落とし込み、GitLab Pages で公開するガイドと AI の振る舞いを一致させることで、 ドキュメントを読んでも AI に聞いても同じ結果にたどり着ける 仕組みを作っています。さらに、Kiro によるコード生成と GitLab Duo によるレビューという AI 同士の協働により、人間のレビュー負担を軽減しながら品質を担保するワークフローを実現しています。 ソフトウェア開発の効率化は、ツールの導入だけでは完結しません。開発ワークフローの標準化、ガイドラインの整備、そしてそれらを AI エージェントに落とし込むことで、初めて組織全体の開発効率が向上します。本ブログが、同様の課題を抱える皆様の参考になれば幸いです。 著者 小森 裕之 電力システム製作所 電力デジタルエナジーシステム開発部 システム基盤課所属。 コーヒーと Kiro が大好きです! 電力ICTセンター内の全ビジネスユニットの開発・運用に貢献するため、ITIL4 に準拠した専用プラットフォームの開発に携わっており、主に CI/CD 環境の検討・構築・導入や AI エージェントツールを活用した開発の仕組みづくりを担当しています。 稲田 大陸 – いなりく AWS Japan で働く Kiro をこよなく愛すソリューションアーキテクト。2022 年から三菱電機グループを支援しています。その活動の傍ら、最近は AI 駆動開発ライフサイクル (AI-DLC) の日本のお客様への布教活動もしつつ、 Kiro のブログ などを執筆しています。 小松原 つかさ GitLab 合同会社 シニアパートナーソリューションアーキテクト 長きに渡るソフトウェア開発経験を持ち、データベース、セキュリティ、ビッグデータの領域での深い専門知識を持ちます。2022 年にGitLab に参加し、AI 駆動型開発ツールがもたらす新しい開発パラダイムの構築に取り組んでいます。
はじめに 本記事はSIOS Tech Labアドベントカレンダー23日目の投稿です。 サイオステクノロジーの曽根田です。 普段はデザイン、フロントエンドコーディングや、CMSのセキュリティ保守の一部対応などを行っています。 近年、CopilotやGeminiなどの生成AIの進化により、Photoshop,Illustratorなどでポチポチ作業する手間を省略して、 “フロントエンドのモックアップで直にビジュアルを作る” ということが手軽にできるようになったと感じています。 この記事ではthree.jsでの実例について書きます。 three.jsって? three.js はブラウザ上で3D表現を実装するのに便利なJavaScriptライブラリです。 ブラウザ上でのリッチな演出やゲーム制作までカバーしています。 公式ドキュメント も充実しています。 アイデアの種 美術展が好きでよく行くのですが、六本木にある 21_21 DESIGN SIGHT というギャラリーのショップで購入した、 アスタリスクを3D化して3Dプリンタで出力したオブジェクトようなもの 。直径は5cmくらいです。机に飾っていたこれがアイデアの種になりました。 このオブジェクトとthree.jsを使って作れそうなイメージとして以下が浮かびました。 htmlの一部にビジュアル要素として埋め込めるcanvas要素 鮮やかな大きな色のエリアが複雑なパターンで動く 動きはゆっくり 3Dにも見えるが2D的でもある。全体像ははっきりわからない 見ているだけで飽きない万華鏡のような動き 完成形のビジュアルイメージ 当初は Blender でこの3Dアスタリスクをモデリングを作成し、それを外部ファイルとしてthree.jsに取り込むやり方を検討したのですが、オブジェクトが幾何学的であれば数学的な計算だけで生成できるはずなので、一旦生成AIのプロンプトのみで作る方法を選びました。 いわゆる”Vibe Coding”のフェーズへ 今回の開発プロセスの特徴は以下のようなものです。 AIとの共同作業: GeminiとCopilotという複数の生成AIをプロンプトで制御し、コードを発展させました。 ナレッジベースの雰囲気コーディング: 過去の three.js ライブラリのコーディング経験や、 Blender などの3Dナレッジを活かした「雰囲気で進めるコーディング」です。 開発フェーズ 1: オブジェクトの基本配置と初期プロンプト 難しいことを考えず、ことばのイメージでやりたいことを伝えます。 以下、かなり指示がフランクですが、平日は生成AIと会話している時間が一番多く、”話が通じる仲間”という感覚なので、自然と砕けた口調になります笑 abstractなローポリゴンのオブジェクトが画面いっぱいに表示され、ゆっくりと回転するような、ウェブサイトのファーストビューイメージを作りたい。three.jsを活用。背景は#feeb0bにしてほしい。ライティングはリアルではなくフラットでうすくグラデーションが掛かっている感じ。 ↓ オブジェクトは添付写真のような、”アスタリスクの3D版”みたいにしてほしい(面ごとに色が違う。色の明度と彩度あげる)。正二十面体のような幾何学図形のそれぞれの面を外側にExtrudeしたような形。カメラワークはもっとゴリっと拡大したい。 バイブコーディングなので ”ゴリッと拡大したい” など、雰囲気主体の擬音も直さずそのまま渡してみますが、なんとなく察してくれます。 上記のプロンプト以外にも細かい指示はいくつか与えていますが省略しています。 途中、エラーで上手く描画されないケースもありますが、そのときは都度修正指示を出します。 ほんの3~4ステップのプロンプトでイメージに近いthree.jsの動きを実装してくれました。3D座標空間でオブジェクトが回転しています。 途中経過のキャプチャ。まだ色々と変。 Extrudeという用語について ちなみに上記プロンプトにある”Extrude”というのはBlenderやMayaなどの3D作成ツールのポリゴンモデリングで使われるコマンドで、”押し出し”を意味します。ビジュアル的には以下のようなイメージです。 Extrudeの図解。選択した面が押し出されています 開発フェーズ 2: 形・色・ライティングの調整 面単位ではなく、ポリゴン毎に色が違っていたり、面が重なってチカチカしてたりするのが作りたいゴールのイメージと異なるので、引き続きプロンプトで修正していきます。 カラーリングだが、ポリゴンごとに変えるのではなく、オブジェクトの同一平面の面は同じ色にする。あと、面が重なっていることでちらつきが発生しているようなので解消してほしい。色の彩度と明度をもっと上げる。 修正後が以下。ちらつきとカラーリングは解消されましたが、 形が気に食わない 。 1つのつながったオブジェクトになっていない気がする。アスタリスクの3D版だけど、一つの多面体の各面を個別にExtrudeして作られたアスタリスク、という感じにできない? あとShadingやライティングの感じもフラットすぎて面白くないので、以下のようなプロンプトで改善を加えてみます。 もっと“照明っぽい”陰影にしたい。flatShadingじゃなくていいのでは。vertexColorsも使えば?あと、スポットライト複数使ってオブジェクトを照らせば、それっぽくなるんじゃない? 面にグラデーションが付き、スポットライトによるライティングで陰影が生まれました。 形はガラッと変わり 、”3D版アスタリスク”ではなくトゲトゲのスターのような形になりましたが、数学的計算によって生成できるシームレスな1つのオブジェクトとなったのでOKと判断しました。 欲しかったのはシームレスなオブジェクトと、色のグラデーションの方です。 最後に、カメラを被写体にぐっと近づけて、起動するたびに毎回異なるカラーリングが施されるようなプロンプトを渡します。 完成版のキャプチャ 望んでいた動くキービジュアル要素が作成できました! 最終成果物:three.jsコード 最終版コードは以下です。1枚のhtmlなのでコピペして保存し、開くだけで動きます。three.jsは実際にブラウザで動かさないとよくわからないと思います。 リロードするたびにランダムにカラーが割り当てられます。3Dオブジェクトは数学的に生成されているので、外部読み込みの3Dファイルは不要です(ただし、オンライン上のCDNのthree.jsライブラリを読み込んで使っています) 一応モバイル対応も考慮しているので、モダンなデバイスならば処理落ちなどせずに動くと思います。 <!doctype html> <html lang="ja"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <title>Extruded Poly Star – three.js</title> <style> html, body { height: 100%; margin: 0; } body { background: #feeb0b; /* 指定の背景色 */ overflow: hidden; font-family: system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, "Noto Sans JP", sans-serif; } #hero { position: fixed; inset: 0; } </style> <!-- Import Map(正しい閉じタグ) --> <script type="importmap"> { "imports": { "three": "https://unpkg.com/three@0.160.1/build/three.module.js" } } </script> </head> <body> <canvas id="hero"></canvas> <script type="module"> import * as THREE from 'three'; // ===== ランダム化ユーティリティ(毎回違う画に) ===== // seed は URL ?seed=12345 で固定可能。未指定なら毎回ランダム。 // FIX: URLSearchParams.get('seed') が null のとき Number(null) は 0 になるので、 // 「seed未指定なのに毎回0固定」というバグを避ける。 const params = new URLSearchParams(location.search); const seedParam = params.get('seed'); const urlSeed = (seedParam !== null && seedParam !== '') ? Number(seedParam) : null; const autoSeed = (crypto && crypto.getRandomValues) ? crypto.getRandomValues(new Uint32Array(1))[0] : Math.floor(Math.random() * 1e9); const SEED = (urlSeed !== null && Number.isFinite(urlSeed)) ? Math.floor(urlSeed) : autoSeed; function mulberry32(a){ return function(){ let t = a += 0x6D2B79F5; t = Math.imul(t ^ t >>> 15, t | 1); t ^= t + Math.imul(t ^ t >>> 7, t | 61); return ((t ^ t >>> 14) >>> 0) / 4294967296; } } const rand = mulberry32(SEED); const randRange = (min, max) => min + (max - min) * rand(); console.log('%c[Hero Seed]', 'color:#555', SEED, urlSeed !== null ? '(from URL)' : '(random)'); // ===== 基本セットアップ ===== const canvas = document.getElementById('hero'); const renderer = new THREE.WebGLRenderer({ canvas, antialias: true, alpha: true, powerPreference: 'high-performance' }); // --- DPR 上限(PC/モバイル) + reduced-motion 対応 + 低FPSダウングレード --- const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Windows Phone/i.test(navigator.userAgent) || matchMedia('(pointer: coarse)').matches; const DPR_CAP_DESKTOP = 1.5; const DPR_CAP_MOBILE = 1.25; let dprCap = isMobile ? DPR_CAP_MOBILE : DPR_CAP_DESKTOP; const reduceMotion = matchMedia('(prefers-reduced-motion: reduce)').matches; function applyDPR(cap = dprCap) { const target = Math.min(window.devicePixelRatio || 1, cap); renderer.setPixelRatio(target); renderer.setSize(window.innerWidth, window.innerHeight); return target; } let currentDPR = applyDPR(); const scene = new THREE.Scene(); // カメラ:右側を大きく覆うフレーミング const camera = new THREE.PerspectiveCamera(48, window.innerWidth / window.innerHeight, 0.01, 100); // 構図を毎回微妙に変える(少しだけジッター) const camX = -1.0 + randRange(-0.15, 0.15); const camY = 0.12 + randRange(-0.12, 0.12); const camZ = 2.0 + randRange(-0.2, 0.2); camera.position.set(camX, camY, camZ); const lookX = 0.4 + randRange(-0.08, 0.08); camera.lookAt(lookX, randRange(-0.05,0.05), randRange(-0.05,0.05)); renderer.physicallyCorrectLights = true; // ===== ライティング(複数スポットで“照明っぽい”陰影) ===== const amb = new THREE.AmbientLight(0xffffff, 0.08); scene.add(amb); const keyColor = new THREE.Color().setHSL(0.08 + randRange(-0.03,0.03), 0.9, 0.7); const rimColor = new THREE.Color().setHSL(0.58 + randRange(-0.04,0.04), 0.6, 0.7); const fillColor= new THREE.Color().setHSL(0.9 + randRange(-0.03,0.03), 0.8, 0.7); const spotKey = new THREE.SpotLight(keyColor, 40 + randRange(-8,8), 6.0, Math.PI * (0.26 + randRange(-0.03,0.03)), 0.45, 1.0); spotKey.position.set(2.4 + randRange(-0.3,0.2), 3.2 + randRange(-0.3,0.2), 1.8 + randRange(-0.2,0.2)); scene.add(spotKey); const spotRim = new THREE.SpotLight(rimColor, 20 + randRange(-6,6), 6.0, Math.PI * (0.34 + randRange(-0.04,0.04)), 0.5, 1.0); spotRim.position.set(-2.8 + randRange(-0.2,0.2), 1.6 + randRange(-0.2,0.2), -1.6 + randRange(-0.2,0.2)); scene.add(spotRim); const spotFill = new THREE.SpotLight(fillColor, 12 + randRange(-6,6), 6.0, Math.PI * (0.44 + randRange(-0.05,0.05)), 0.8, 1.0); spotFill.position.set(0.0 + randRange(-0.3,0.3), -2.4 + randRange(-0.3,0.3), 2.2 + randRange(-0.3,0.3)); scene.add(spotFill); // ====== 単一メッシュの「多面体→各面を外向きにエクストルードしたスター」生成 ====== const baseRadius = randRange(0.7, 0.95); const base = new THREE.IcosahedronGeometry(baseRadius, 0); // 20面(すべて三角形) function buildExtrudedStarFromTriPoly(geo, extrude = 1.2) { // 非インデックスでも動作 const pos = geo.attributes.position; const idxArray = (geo.index && geo.index.array) ? geo.index.array : (function(){ const a = new Uint32Array(pos.count); for (let i=0;i<pos.count;i++) a[i]=i; return a; })(); const positions = []; const colors = []; const color = new THREE.Color(); // グローバル色相オフセット&勾配方向もランダム const hueShift = randRange(0, 1); const gradDir = new THREE.Vector3(randRange(-1,1), randRange(0.3,1), randRange(-1,1)).normalize(); for (let f = 0; f < idxArray.length; f += 3) { const ai = idxArray[f], bi = idxArray[f+1], ci = idxArray[f+2]; const a = new THREE.Vector3(pos.getX(ai), pos.getY(ai), pos.getZ(ai)); const b = new THREE.Vector3(pos.getX(bi), pos.getY(bi), pos.getZ(bi)); const c = new THREE.Vector3(pos.getX(ci), pos.getY(ci), pos.getZ(ci)); const ab = new THREE.Vector3().subVectors(b, a); const ac = new THREE.Vector3().subVectors(c, a); const n = new THREE.Vector3().crossVectors(ab, ac).normalize(); const centroid = new THREE.Vector3().addVectors(a, b).add(c).multiplyScalar(1/3); const tip = new THREE.Vector3().copy(centroid).addScaledVector(n, extrude); pushTriWithFaceGradient(a, b, c, f); pushTriWithFaceGradient(a, b, tip, f + 1); pushTriWithFaceGradient(b, c, tip, f + 2); pushTriWithFaceGradient(c, a, tip, f + 3); } function pushTriWithFaceGradient(v1, v2, v3, seed) { const arr = [v1, v2, v3]; let minD = Infinity, maxD = -Infinity; const ds = arr.map(v => { const d = v.dot(gradDir); if (d<minD) minD = d; if (d>maxD) maxD = d; return d; }); const span = Math.max(1e-6, maxD - minD); const baseH = (hueShift + ((seed * 19.23) % 360) / 360) % 1.0; const S = 1.0; const L0 = 0.63; for (let i = 0; i < 3; i++) { const f = (ds[i] - minD) / span; // 0..1 const h = (baseH + f * 1.4) % 1.0; // 虹っぽく周回 const L = THREE.MathUtils.clamp(L0 + (Math.cos((f - 0.5) * Math.PI) * 0.22), 0, 1); color.setHSL(h, S, L); positions.push(arr[i].x, arr[i].y, arr[i].z); colors.push(color.r, color.g, color.b); } } const out = new THREE.BufferGeometry(); out.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3)); out.setAttribute('color', new THREE.Float32BufferAttribute(colors, 3)); out.computeVertexNormals(); return out; } const starGeom = buildExtrudedStarFromTriPoly(base, 1.0); // ===== マテリアル(“照明っぽい”陰影:PBR マット) ===== const mat = new THREE.MeshStandardMaterial({ vertexColors: true, roughness: 0.85, metalness: 0.05, side: THREE.DoubleSide }); const star = new THREE.Mesh(starGeom, mat); // 開始角度&サイズ・位置をランダム化(右寄せは維持) star.scale.setScalar(randRange(2.2, 2.8)); star.position.x = randRange(0.55, 0.9); star.rotation.x = randRange(0, Math.PI*2); star.rotation.y = randRange(0, Math.PI*2); star.rotation.z = randRange(0, Math.PI*2); scene.add(star); // ===== アニメーション(単純なリニア回転) + 低FPSダウングレード ===== const clock = new THREE.Clock(); let rafId = null; // FPS 測定用 let fpsFrames = 0; let fpsStart = performance.now(); let degradeSteps = 0; const MAX_DEGRADE_STEPS = 3; // --- FIX: 乱数は「毎フレーム」じゃなく「起動時に1回だけ」決める(チカチカ防止) --- const rotX0 = star.rotation.x; const rotY0 = star.rotation.y; const rotZ0 = star.rotation.z; const rotXSpeed = randRange(0.015, 0.028); // x軸 [rad/s] const rotZSpeed = -randRange(0.012, 0.020); // z軸 [rad/s](逆方向) function maybeDowngrade() { const now = performance.now(); const elapsed = now - fpsStart; if (elapsed >= 1000) { const fps = fpsFrames * 1000 / elapsed; fpsFrames = 0; fpsStart = now; // 45fpsを下回ったら DPR を 0.25 刻みで下げる(最小 1.0) if (fps < 45 && degradeSteps < MAX_DEGRADE_STEPS && currentDPR > 1.0) { dprCap = Math.max(1.0, +(dprCap - 0.25).toFixed(2)); currentDPR = applyDPR(dprCap); degradeSteps++; // 30fps を著しく下回るならライトも少し弱める if (fps < 30) { spotKey.intensity *= 0.9; spotFill.intensity *= 0.85; } } } } function renderFrame() { renderer.render(scene, camera); } function tick() { if (reduceMotion) { // 動きに弱いユーザーは静止 renderFrame(); return; } const t = clock.getElapsedTime(); star.rotation.x = rotX0 + t * rotXSpeed; star.rotation.y = rotY0; // Yは固定(開始角度は保持) star.rotation.z = rotZ0 + t * rotZSpeed; renderFrame(); fpsFrames++; maybeDowngrade(); rafId = requestAnimationFrame(tick); } // ===== リサイズ対応(DPR も再適用) ===== function onResize() { currentDPR = applyDPR(); camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); } window.addEventListener('resize', onResize); document.addEventListener('visibilitychange', () => { if (document.visibilityState === 'hidden') { if (rafId) cancelAnimationFrame(rafId); } else { tick(); } }); onResize(); tick(); // ====== テスト(DPR上限・reduced-motion・低FPSダウングレードの土台を検証) ====== (function runTests(){ console.groupCollapsed('%c[Hero Tests] DPR cap + reduced-motion + perf', 'color:#111'); try { console.assert(renderer instanceof THREE.WebGLRenderer, 'renderer created'); console.assert(scene && camera && star, 'scene/camera/star exist'); console.assert(star.material instanceof THREE.MeshStandardMaterial, 'using MeshStandardMaterial'); console.assert(star.material.vertexColors === true, 'vertexColors enabled'); // DPR 上限が PC:1.5 / モバイル:1.25 を超えていない const capExpected = isMobile ? DPR_CAP_MOBILE : DPR_CAP_DESKTOP; console.assert(renderer.getPixelRatio() <= capExpected + 1e-6, 'pixelRatio is capped'); // reduced-motion フラグが boolean console.assert(typeof reduceMotion === 'boolean', 'reduceMotion flag present'); // 【追加】seed未指定時に urlSeed が null になる(= 0 固定バグが無い) const hasSeed = new URLSearchParams(location.search).has('seed'); if (!hasSeed) console.assert(seedParam === null, 'no-seed should not default to 0'); // エクストルード済み(頂点数増加) const vcount = star.geometry.getAttribute('position').count; console.assert(vcount > 60, 'geometry extruded (vertex count increased)'); // 回転が進行(reduced-motion でない環境のみチェック) const rx0 = star.rotation.x, rz0 = star.rotation.z; setTimeout(() => { const rx1 = star.rotation.x, rz1 = star.rotation.z; if (!reduceMotion) console.assert(rx1 !== rx0 || rz1 !== rz0, 'rotation progressing over time'); console.log('All tests passed ✅'); console.groupEnd(); }, 350); } catch (e) { console.error('Test failure:', e); console.groupEnd(); } })(); </script> </body> </html> 補足 今回、tech-lab.sios.jpのデザインリニューアルを担当させていただいたのですが、トップページの一部背景に上記のビジュアルを画像として使用しています。 元々、キーのキャラクターであるサイをポリゴン化する、というのもこのthree.jsの実験から着想したアイデアでした。 結局、こちらのサイがメインのキービジュアルという形になりました。 ポリゴン化したサイ デザイン案のバリエーション検討(Adobe DXのキャプチャ) さいごに three.jsはリッチコンテンツ向きである分、敷居が高く、使いどころが難しいように見えるかもしれませんが、ビジュアルイメージ検討のためのツールの1つとして気軽に使うと面白いと思います。 ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post Three.jsのVibe Codingでビジュアルイメージを検討する first appeared on SIOS Tech Lab .
動画
該当するコンテンツが見つかりませんでした







