こんにちは、フルオンチェーンNFTクリエイターのnawooです。
本記事では、以前取り上げたdom氏の制作した3DフルオンチェーンNFT「ROSES」のような、Three.jsを使ったフルオンチェーンNFTを作成する方法について解説します。


一般的には「フルオンチェーンNFT」といえばドット絵やSVGが多い印象ですが、Three.jsを使うと『3Dでの豪華なオンチェーン表現』が可能になります。
Three.jsはとても機能が多いので、今回は簡単なサンプルコードを例に「どうやってJavaScriptのコードをフルオンチェーンNFTにするのか」という点を中心に、解説していきます。
でははじめに、この記事の構成について説明します。










本記事が、「Three.jsを使ったフルオンチェーンNFTの制作方法」などについて理解したいと思われている方にとって、少しでもお役に立てれば幸いです。
※本記事は一般的な情報提供を目的としたものであり、法的または投資上のアドバイスとして解釈されることを意図したものではなく、また解釈されるべきではありません。ゆえに、特定のFT/NFTの購入を推奨するものではございませんので、あくまで勉強の一環としてご活用ください。


Alpha Navigatorは、超アーリーなクリプトプロジェクトに特化して探知し、webサイト, discord, twitterで情報発信を行っています。
ROSESの仕組みをおさらい


ROSESについてはこちらの記事で詳しく解説していますが、要点だけ簡単に説明すると以下の仕組みで作られています。
- メタデータの
animation_url
でHTMLを返す - Three.jsはgzipで圧縮しておき、JavaScriptのfflateライブラリを使って展開する
- データコントラクトを使ってガス代を安くする
ROSESで使われているThree.jsやfflateのデータコントラクト、データコントラクトを扱うためのDataChunkCompiler
コントラクトは、誰でも利用することができます。



今回はこれらのコントラクトを使うことで、dom氏のいう「on-chain npm」を体験してみました。
flowchart explaining the rough process
— dom (@dhof) September 13, 2022
data contracts/sstore2 are well understood at this point so will skip that
main unlock is using compressed data, then decompressing and loading at runtime via an injected helper script in the data uri
“on-chain npm” etc should be possible pic.twitter.com/h8GQnczvLN
コントラクト | アドレス (Goerli) |
---|---|
FFlateDataChunk1 | 0x5707fFa3fE83303342786265a4fddBEAf61B2af4 |
FFlateDataChunk2 | 0xc2217600DeE61b6160cB07e32714CB67e4d3eEe0 |
ThreeDataChunk1 (※1) | 0x6a29761F7913Ae3848BF74df28fb1E9975480FC3 |
ThreeDataChunk2 | 0x42c5521eABdC14C50ED1967edbD424672187CBD4 |
ThreeDataChunk3 | 0xcfbE18E29269B649722174ebD7BDA871383009C3 |
ThreeDataChunk4 | 0xb4550ab1E9507ba5c91a52563c624Ae6463f80B2 |
ThreeDataChunk5 | 0x7e734f6daE766bb5A4dc7F1Ce957e39dC0Cf8f95 |
ThreeDataChunk6 | 0xCb835b9087c57223345D51A960e795f3b251cc0a |
ThreeDataChunk7 | 0xBe8d24fdf071A5586f35bB120c1b8Bf2baD25e81 |
ThreeDataChunk8 | 0xA213524a7a155a422D09797F3F28E120cEC7E11e |
ThreeDataChunk9 | 0x60e82790A1EC642EfDFBbD44ed9441C8cD938095 |
DataChunkCompiler | 0x139A815344FE764921468F2B4B8DA40b3b4b0618 |
DataChunkCompilerV2 (※2) | 0xe4c4400e11Bd8Ab2e4507B5aE4504E60E4A32433 |
- 1) Three.jsのバージョンはr121です。OrbitControlsなどの外部モジュールを使う場合はバージョンに注意してください。
- 2)
DataChunkCompiler.sol
を一部修正してDataChunkCompilerV2.sol
としてデプロイしました。 変更点は次の2点です。- SCRIPT_VAR関数で
omitQuotes=false
の場合にvar "name=value";
となってしまうバグを修正 (正しくはvar name="value";
) - 遅延読み込み用の
<script defer src="...">
の追加 (今回の記事では未使用)
- SCRIPT_VAR関数で
シンプルなNFTを作ってみよう (Sample1)
まずは本章では、物体が回転するだけのシンプルなNFTを作ってみます。


コントラクトの作成
今回は、ROSESのコントラクトを参考にしていますが、ROSESの場合はtokenURI
関数のanimation_url
にHTMLを設定しています。
そしてこのHTMLには、以下5つのscriptとスタイルシートが含まれています。


コントラクトのコードは以下です。
// Sample1.sol
contract Sample1 is ERC721("Sample1", "SAMPLE1"), Ownable {
using Strings for uint256;
IDataChunkCompilerV2 private compiler;
address[9] private threeAddresses;
string private constant STYLE_CODE = "%253Cstyle%253E%252A%257Bmargin (中略) %252Fstyle%253E";
string private constant JS_CODE = "〜"; // ここに script5 (JavaScriptのメインコード)を埋め込む(後述)
function setCompilerAddress(address newAddress) public onlyOwner {
compiler = IDataChunkCompilerV2(newAddress);
}
function setThreeAddress(
address chunk1, address chunk2, address chunk3,
address chunk4, address chunk5, address chunk6,
address chunk7, address chunk8, address chunk9
) public onlyOwner {
threeAddresses[0] = chunk1;
threeAddresses[1] = chunk2;
threeAddresses[2] = chunk3;
threeAddresses[3] = chunk4;
threeAddresses[4] = chunk5;
threeAddresses[5] = chunk6;
threeAddresses[6] = chunk7;
threeAddresses[7] = chunk8;
threeAddresses[8] = chunk9;
}
function mint(uint256 tokenId) public onlyOwner {
_mint(msg.sender, tokenId);
}
function tokenURI(uint256 tokenId) public view override returns (string memory) {
string memory threejs = compiler.compile9(
threeAddresses[0], threeAddresses[1], threeAddresses[2],
threeAddresses[3], threeAddresses[4], threeAddresses[5],
threeAddresses[6], threeAddresses[7], threeAddresses[8]
);
string memory tokenIdStr = tokenId.toString();
return
string.concat(
compiler.BEGIN_JSON(),
string.concat(
compiler.BEGIN_METADATA_VAR("animation_url", false),
compiler.HTML_HEAD(),
STYLE_CODE,
string.concat(
compiler.BEGIN_SCRIPT_DATA_COMPRESSED(),
threejs,
compiler.END_SCRIPT_DATA_COMPRESSED()
),
string.concat(
compiler.BEGIN_SCRIPT(),
compiler.SCRIPT_VAR("tokenId", tokenIdStr, true),
compiler.END_SCRIPT(),
compiler.BEGIN_SCRIPT(),
JS_CODE,
compiler.END_SCRIPT()
),
compiler.END_METADATA_VAR(false)
),
string.concat(compiler.BEGIN_METADATA_VAR("name", false), name(), "%20%23", tokenIdStr, "%22"),
compiler.END_JSON()
);
}
}
DataChunkCompilerV2
コントラクトには、HTMLやメタデータを簡単に作成する関数が用意されています。
例えば、
compiler.BEGIN_SCRIPT(),
compiler.SCRIPT_VAR("tokenId", tokenIdStr, true),
compiler.END_SCRIPT(),
と書くと、
<script>
var tokenId=1;
</script>
のようなHTMLが出力されます。



後で説明しますが、2回URLエンコードされた状態で出力されます。
続いてtokenURI
関数を見てみましょう。ここでscript1〜5とstyleを結合し、animation_url
を作成しています。
compiler.BEGIN_METADATA_VAR("animation_url", false),
compiler.HTML_HEAD(), // script1, 2 を作成
STYLE_CODE, // style を作成
string.concat(
// script3 を作成 (Three.js本体)
compiler.BEGIN_SCRIPT_DATA_COMPRESSED(),
threejs, // データコントラクトから読み込んだ圧縮されたThree.js
compiler.END_SCRIPT_DATA_COMPRESSED()
),
string.concat(
// script4 を作成 (tokenId変数を定義)
compiler.BEGIN_SCRIPT(),
compiler.SCRIPT_VAR("tokenId", tokenIdStr, true),
compiler.END_SCRIPT(),
// script5 を作成 (メインコード)
compiler.BEGIN_SCRIPT(),
JS_CODE,
compiler.END_SCRIPT()
),
compiler.END_METADATA_VAR(false)
Three.jsは、データコントラクトから読み出したものをcompiler.compile9
関数で結合しています。
スタイルシートはSTYLE_CODE
定数(constant)、JavaScriptのメインコード(script5)はJS_CODE
定数で定義しました。
JavaScriptのメインコードを作成したら、後ほどJS_CODE
定数に設定します。
JavaScriptメインコードの作成
続いては、JavaScriptのメインコードです。



ここではThree.jsのチュートリアルを参考にして、物体(TorusKnot)を回転させてみました。
window.onload = () => {
// シーン、カメラ、レンダラーの設定
const size = { width: window.innerWidth, height: window.innerHeight };
const scene = new THREE.Scene();
scene.background = new THREE.Color(0xffffff);
const camera = new THREE.PerspectiveCamera(75, size.width / size.height, 0.1, 1000);
camera.position.z = 4;
camera.lookAt(0, 0, 0);
const renderer = new THREE.WebGLRenderer();
renderer.setSize(size.width, size.height);
renderer.setPixelRatio(window.devicePixelRatio);
document.body.appendChild(renderer.domElement);
// ライトとメッシュの作成と追加
const light = new THREE.HemisphereLight(0xffffff, 0x202020, 1);
const geometry = new THREE.TorusKnotGeometry(1, 0.3, 100, 16, 2, 3);
const material = new THREE.MeshPhongMaterial({ color: 0x00ffff });
const mesh = new THREE.Mesh(geometry, material);
scene.add(light, mesh);
// resizeイベント
window.addEventListener('resize', () => {
size.width = window.innerWidth;
size.height = window.innerHeight;
camera.aspect = size.width / size.height;
camera.updateProjectionMatrix();
renderer.setSize(size.width, size.height);
renderer.setPixelRatio(window.devicePixelRatio);
});
// アニメーション
const clock = new THREE.Clock();
const animate = function () {
requestAnimationFrame(animate);
const delta = clock.getDelta();
mesh.rotation.x += delta;
mesh.rotation.y += delta;
renderer.render(scene, camera);
};
animate();
};
通常のThree.jsプロジェクトであればwindow.onload
に入れる必要はないのですが、 今回は圧縮したThree.jsを展開して<script>
タグを動的に生成しています。



この動的に生成した<script>
が読み込まれてからでないと、Three.jsを使うことができません。
そのためにwindow.onload
を使って、Three.jsが読み込まれた後にメインコードが実行されるようにしています。
また、それ以外はThree.jsのチュートリアルと同じ基本的なコードです。
ライトはHemisphereLight、 メッシュはTorusKnotGeometryを使ったり、resize
イベントでカメラとレンダラーを再調整し、amimate
関数でメッシュを回転させています。



HTMLファイルを開くと、物体が回転しているのが確認できます。 (JSFiddleで見る)


埋め込み用JavaScriptコードの作成
作成したJavaScriptのメインコードは、コントラクトのJS_CODE
にそのまま埋め込むのではなく、まずデータサイズを減らすためにコードを軽量化(Minify)してから、URLエンコードを2回行います。



なぜ2回かというと、メタデータ(JSON)を作成するときにURLエンコードして、さらにtokenURIを作成するときに再度URLエンコードするからです。
A. <script>JavaScriptのコード</script>...
↓ メタデータを作成
B. {animation_url: "data:text/html,(ここにAをURLエンコードしたもの)", name: "..."}
↓ tokenURIを作成
C. data:application/json,(ここにBをURLエンコードしたもの)
では、まずJavaScriptコードを軽量化(Minify)します。
Online JavaScript minifierなどのオンラインツールもありますし、スクリプトやコマンドを使う場合はUglifyJSが便利です。



Minifyしたものが以下です。コメントや改行が削除され、変数名も短くなっています。
window.onload=()=>{const e={width:window.innerWidth,height:window.innerHeight},i=new THREE.Scene,t=...(略)
次に、URLエンコードします。
URIエンコード変換ツールなどのオンラインツールでもいいですし、JavaScriptのencodeURIComponent
関数で変換しても良いです。
window.onload%3D()%3D%3E%7Bconst%20e%3D%7Bwidth%3Awindow.innerWidth%2Cheight%3Awindow.innerHeight%7D...(略)
さらに、もう一度URLエンコードします。
window.onload%253D()%253D%253E%257Bconst%2520e%253D%257Bwidth%253Awindow.innerWidth%252Cheight%253A...(略)



今回は手作業で行いましたが、何度も行う場合は一括変換するスクリプトを作っておくと便利ですよ。
では、作成したコードをコントラクトのJS_CODE
定数に設定します。
// コントラクト (Sample1.sol)
string private constant JS_CODE = "window.onload%253D()%253D%253E%257Bconst%2520e%253D%257Bwidth%253A...(略)";
以上で、コントラクトは完成です。
デプロイ&ミント
デプロイしたら忘れずに、以下をおこなっておきましょう。
setThreeAddress
関数で、Three.jsのデータコントラクトのアドレス設定setDataCompiler
関数で、DataChunkCompilerV2のアドレス設定



そして、実際に筆者がミントしたものがこちらです。


ランダム要素を追加してみよう (Sample2)
さて、先ほどのNFTでは、何枚ミントしても全て同じ内容でした。



でも、どうせなら1枚ごとに別々の内容にしたいですよね。
ということで本章では、先ほどのNFTを修正して、ミントする度にランダムに色や形が変わるジェネラティブNFTを作ってみましょう。


乱数生成について
JavaScriptには、ランダムな値(乱数)を生成するMath.random
関数があるのですが、今回はこの関数は使えません。
なぜなら、JavaScriptはブラウザをリロードするたびに実行されますが、Math.random
関数は実行される度に異なる値を返すため、 同じtokenIDなのにリロードするたびに内容が変化してしまうからです。


ジェネラティブNFTに必要なのは、tokenIDが同じであれば何度実行しても同じ乱数が得られる、「再現性のある乱数を生成する関数」です。





しかし、残念ながらJavaScriptには「再現性のある乱数を生成する関数」が用意されていないので、自分で作る必要があります。
ここで、ROSESではどうやっているのか、実際のコードを見てみましょう。
const o = (o) => (void 0 !== o && (l = o % 2147483647) <= 0 && (l += 2147483646), ((l = (16807 * l) % 2147483647) - 1) / 2147483646);
軽量化(Minify)されているので読みにくいですが、この部分が乱数を生成する関数です。
ここでは、Park&Millerという疑似乱数生成法を使っています。
最初にo(seed)
でseedを設定し、以後はo()
で0.0〜1.0の乱数を生成します。
これにより、同じseedを与えると同じ乱数が得られるので、seedにtokenIDを設定すればブラウザをリロードしても内容が変わりません。
Randomクラスを作成
今回はROSESのコードを参考にして、Randomクラスを作ってみました。



seedを指定して、さまざまなタイプの乱数を取得できます。
const MAX_INT32 = 2147483647;
class Random {
constructor(seed) {
if (seed <= 0) seed += MAX_INT32 - 1;
this._value = seed;
this.int(); // ※1
}
// 0 <= x < 2147483647 の整数を取得
int() {
this._value = (this._value * 48271) % MAX_INT32; // ※2
return this._value;
}
// 0.0 <= x < 1.0 の浮動小数点を取得
float() {
return (this.int() - 1) / (MAX_INT32 - 1);
}
// min <= x < max の整数を取得
intRange(min, max) {
return Math.floor(this.floatRange(min, max));
}
// min <= x < max の浮動小数点を取得
floatRange(min, max) {
return min + (max - min) * this.float();
}
// true or false のブール値を取得
boolean() {
return this.int() % 2 === 0;
}
// 0x000000 <= x <= 0xffffff の色コードを取得
color() {
return this.intRange(0, 0x1000000);
}
}
コンストラクタの引数でseedを指定し、乱数を生成するには int
,float
,intRange
,floatRange
,color
メソッドを使います。color
メソッドでは0x000000〜0xffffffの値が得られるので、Three.jsの色指定に使えます。
// 使用例
const rand = new Random(seed); // seedを指定
const a = rand.int(); // 0 <= a < MAX_INT32
const b = rand.float(); // 0.0 <= b < 1.0
const c = rand.intRange(1, 10); // 1 <= c < 10
const d = rand.intFloat(2.5, 3.5); // 2.5 <= d < 3.5
const e = rand.boolean(); // true or false
const f = rand.color(); // 0x000000 <= f <= 0xffffff
- 1) seedに1,2,3…というtokenIDを渡す場合、Park&Millerの計算式だと最初に生成する乱数は偏りが生じてしまいます。そこで、最初に生成した乱数は使わないように、コンストラクタ内で一度intメソッドを呼び出しておきます。
- 2) ROSESでは乗数に16807を使っていますが、Wikipediaによると48271を使うほうが良いらしいので48271を使いました。
JavaScriptコード
Sample1のコードに、先ほどのRandomクラスを追加しました。
// ランダムな値を取得
const rand = new Random(tokenId);
const bgColor = rand.color(); // 背景色
const geomColor = rand.color(); // 物体の色
const p = rand.intRange(1, 9); // p: 1〜8の整数
const q = rand.intRange(1, 9); // q: 1〜8の整数
// (略)
scene.background = new THREE.Color(bgColor); // 背景色を設定
// (略)
const geometry = new THREE.TorusKnotGeometry(1, 0.2, 200, 32, p, q); // 形を設定
const material = new THREE.MeshPhongMaterial({ color: geomColor }); // 物体の色を設定
Randomクラスを使って、「背景色」「物体の色」「物体(TorusKnotGeometry)の形を決めるp,qの値」をランダムな値に設定します。
これらの値はtokenIDが同じであれば、何度実行しても同じになります。
コントラクトの作成
コントラクトは、Sample1と同じです。
デプロイ&ミント
デプロイして、setThreeAddress
関数とsetDataCompiler
関数でアドレス設定しておきます。
今回は5枚ミントしてみました





ちゃんと1枚ごとに色や形が変化していますね。同じtokenIDであればリロードしても、色や形が変わったりしません。
Etherscanからフリーミントできるので、欲しい方はご自由にどうぞ。
テクスチャを貼ってみよう (Sample3)
せっかく3Dを扱うので、次は、テクスチャを使うサンプルを作ってみましょう。


Three.jsでテクスチャを扱うには、TextureLoader
を使います。
通常は外部の画像ファイルを指定しますが、dataURLを使うことで、JavaScriptコード内に画像を埋め込むことができます。
画像をdataURLに変換する



使用した画像は以下です。某ゲームを参考に自分で描きました。


また、画像ファイルからdataURLへの変換は、こちらのオンラインツールを使用しました。
JavaScriptコード
立方体(BoxGeometry)を作成して、6面にそれぞれテクスチャを指定します。 コードは以下です。
// 画像をdataURLで定義する
const sidePath = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/...(略)';
const topPath = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/...(略)';
const bottomPath = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/...(略)';
window.onload = () => {
(略)
const geometry = new THREE.BoxGeometry();
const loader = new THREE.TextureLoader(); // TextureLoaderを生成
const sideTexture = loader.load(sidePath); // dataURLから画像を読み込む
sideTexture.magFilter = THREE.NearestFilter; // ドットがぼやけないように設定
const topTexture = loader.load(topPath);
topTexture.magFilter = THREE.NearestFilter;
const bottomTexture = loader.load(bottomPath);
bottomTexture.magFilter = THREE.NearestFilter;
const materials = [
new THREE.MeshBasicMaterial({ map: sideTexture }), // マテリアルにテクスチャを設定
new THREE.MeshBasicMaterial({ map: sideTexture }),
new THREE.MeshBasicMaterial({ map: topTexture }),
new THREE.MeshBasicMaterial({ map: bottomTexture }),
new THREE.MeshBasicMaterial({ map: sideTexture }),
new THREE.MeshBasicMaterial({ map: sideTexture }),
];
const cube = new THREE.Mesh(geometry, materials); // メッシュを作成
scene.add(cube);
(略)
// mousemoveイベント
window.addEventListener('mousemove', (event) => {
cube.rotation.y = (event.clientX / size.width - 0.5) * Math.PI; // -90deg ~ +90deg
cube.rotation.x = (event.clientY / size.height - 0.5) * Math.PI;
});
(略)
};
TextureLoader
で画像をテクスチャとして読み込み、マテリアルのmap
に指定します。texture.magFilter = THREE.NearestFilter
とすると、ドットがぼやけないようになります。mousemove
イベントで立方体がマウスカーソルの位置に応じて回転するようにしてみました。
コントラクトの作成
コントラクトはSample1、Sample2と同じです。
JavaScriptコードをMinifyして2回URLエンコードしてから、コントラクトのJS_CODE
定数に埋め込みます。



そして、実際に筆者がミントしたものがこちらです。OpenSea画面でマウスを動かすと、立方体が回転します。


大きなテクスチャを使ってみよう (Sample4)





次は、もっと大きな画像を使ってみます。
以下の画像(パブリックドメイン)を球体に貼り付けて、回転する地球を作ってみましょう。


画像データコントラクトの作成
先ほどのSample3で使った画像は400〜600バイトとファイルサイズが小さかったので、dataURL化してJavaScriptコードに埋め込むことができました。
しかし、今回はファイルサイズが大きいので、データコントラクトを使います。



画像をデータコントラクトに変換してデプロイしておいて、コントラクトのtokenURI
関数で、画像のデータコントラクトを読み込むという方法を使います。
画像ファイルサイズは20,355バイト、dataURL化すると27,162バイトになりましたが、これを分割して2つのデータコントラクトを作成し、デプロイします。
// EarthDataChunk1.sol
pragma solidity ^0.8.1;
contract EarthDataChunk1 {
string public constant data =
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAlgAAAEsBAMAAADtE9ClAAAA...(略)";
}
// EarthDataChunk2.sol
pragma solidity ^0.8.1;
contract EarthDataChunk2 {
string public constant data =
"2r81cvj+U/B4exZjrna30CwatybB5ryJVPlm0SZlKX5S/vftGtfjiRE/bYgX5sPBRnL9phw...(略)";
}
コントラクトの作成
続いて、コントラクトにsetImageAddress
関数を追加します。この関数で画像データコントラクトのアドレスを指定します。
さらに、tokenURI
関数に以下の処理を追加します。
- 2つの画像データコントラクトを読み込んで結合する
imageUrl
変数としてJavaScriptに画像データを渡す
// Sample4.sol
contract Sample4 is ERC721("Sample4", "SAMPLE4"), Ownable {
(略)
IDataChunkCompilerV2 private compiler;
address[2] private imageAddresses;
function setImageAddress(address chunk1, address chunk2) public onlyOwner {
imageAddresses[0] = chunk1;
imageAddresses[1] = chunk2;
}
function tokenURI(uint256 tokenId) public view override returns (string memory) {
(略)
// データコントラクトから画像を読み込む
string memory image = compiler.compile2(imageAddresses[0], imageAddresses[1]);
return
string.concat(
compiler.BEGIN_JSON(),
string.concat(
compiler.BEGIN_METADATA_VAR("animation_url", false),
compiler.HTML_HEAD(),
STYLE_CODE,
string.concat(
// Three.js
compiler.BEGIN_SCRIPT_DATA_COMPRESSED(),
threejs,
compiler.END_SCRIPT_DATA_COMPRESSED()
),
string.concat(
// variables
compiler.BEGIN_SCRIPT(),
compiler.SCRIPT_VAR("tokenId", tokenIdStr, true),
compiler.SCRIPT_VAR("imageUrl", image, false), // ← ここでimageUrl変数を追加
compiler.END_SCRIPT(),
// main script
compiler.BEGIN_SCRIPT(),
JS_CODE,
compiler.END_SCRIPT()
),
compiler.END_METADATA_VAR(false)
),
string.concat(compiler.BEGIN_METADATA_VAR("name", false), name(), "%20%23", tokenIdStr, "%22"),
compiler.END_JSON()
);
}
}



2つのデータコントラクトを結合するには、DataChunkCompilerV2のcompile2
関数を使います。
JavaScriptの変数定義はSCRIPT_VAR
関数が便利です。
compiler.SCRIPT_VAR("imageUrl", image, false);
とすると、
var imageUrl = "data:image/png;base64,...";
というJavaScriptコードが、2回URLエンコードされた状態で出力されます。
JavaScriptコード
imageUrl
変数に画像データ(dataURL)が入っているので、こちらをテクスチャとして読み込みます。
window.onload = () => {
(略)
// メッシュを作成
const geometry = new THREE.SphereGeometry(1, 80, 40);
const texture = new THREE.TextureLoader().load(imageUrl); // テクスチャを読み込み
const material = new THREE.MeshStandardMaterial({ map: texture }); // マテリアルにテクスチャを設定
material.roughness = 0.4;
const sphere = new THREE.Mesh(geometry, material); // メッシュ(球体)を作成
// ライトを設定
const light1 = new THREE.AmbientLight(0x202020);
const light2 = new THREE.PointLight();
light2.position.set(2, 2, 2);
scene.add(sphere, light1, light2);
(略)
}
ライトは、AmbientLight
とPointLight
の2つにしました。
デプロイ&ミント
デプロイして、setThreeAddress
関数とsetDataCompiler
関数でアドレス設定しておきます。 さらに今回はsetImageAddress
関数で画像データコントラクトのアドレスも設定する必要があります。



そして、実際に筆者がミントしたものがこちらです。


まとめ


今回は、Three.jsを使ったフルオンチェーンNFTを4つ作ってみながら、ジェネラティブ・テクスチャ・外部データコントラクトの使用例などについて紹介しました。
本記事が、「Three.jsを使ったフルオンチェーンNFTの制作方法」などについて理解したいと思われている方にとって少しでもお役に立ったのであれば幸いです。
また励みになりますので、参考になったという方はぜひTwitterでのシェア・コメントなどしていただけると嬉しいです。
🆕記事をアップしました🆕
— イーサリアムnavi🧭 (@ethereumnavi) December 28, 2022
今回は、Three.jsを使った『3DフルオンチェーンNFT』の作り方について解説📝
dom氏の制作した「ROSES」のようなオンチェーン表現が可能になります🌹
年末年始の時間に、新たなweb3開発に挑戦してみたい方などは、この機会にぜひご参考ください!https://t.co/ZlYHTWsUQE



Three.jsには多くの機能があるので、アイデア次第でもっと複雑なこともできると思います。ぜひ驚くようなフルオンチェーンNFTを作ってみてください!


イーサリアムnaviを運営するSTILL合同会社では、以下などに関するお問い合わせを受け付けております。
- 広告掲載
- リサーチ代行業務
- アドバイザー業務
- その他(ご依頼・ご提案・ご相談など)
まずはお気軽に、こちらからご連絡ください。
- Webサイト:still-llc.co.jp
- Twitter:@STILL_Corp
- メールアドレス:info@still-llc.com