如何使用Three.js 绘制NFT蘑菇窗体顶端
#第 1 部分:简介
在本文中,我将尝试简要而完整地概述如何创造艺术品,并将你的艺术品发布到NFT(Non-Fungible Token非同质化代币)网站进行售卖。以及如何在区块链上制作艺术品。艺术品:蘑菇的设计将会使用到JavaScript以及Three.js,本文将告诉大家在虚拟世界如何生成和发布艺术品。
#背景
出于兴趣笔者会编写一些不寻常的东西。就在新年期间,关于 NFT 的消息让笔者感到震惊,这也是为什么会尝试在这种模式下创造一些有创意的东西。虽说将 JPEG 上传到区块链的想法并不新奇,但是能够将艺术品上链的可能性却让人满怀期待。
简而言之,它背后的想法是通过代币生成器,每次你“铸造”代币的时候会售卖给你一个独特的艺术品(实际上,调用区块链方法花费你的钱购买艺术家创造的艺术品)。毫无疑问,这笔交易会产生一个独特的对象,并永远存储在区块链中,不可否认这种交易具备一种魔力,不是吗?
由此有一些艺术平台利用了这个想法,其中最著名的是artblocks.io。但它建立在太坊区块链的基础上,它仍然使用proof-of-work(工作量证明)的模式并且Gas(手续费)非常高,于是笔者决定尝试一个更民主、更便宜、环保平台 - fxhash.xyz
#什么是生成式 NFT 艺术品?
所有的生成 NFT (NFT,全称为Non-Fungible Token,指非同质化代币,包括jpg和视频剪辑形式,是用于表示数字资产,的唯一加密货币令牌,可以买卖。)NFT的艺术品基本上都以网页的方式呈现,并使用 vanilla JavaScript或一些第三方库在画布上绘制艺术品。NFT艺术品可以分为 3 类:抽象数学艺术品、有形程序艺术品和可变手绘艺术品。
如图1所示,
第一类,抽象数学,利用一些数学概念来生成抽象图像:可能有一些分形(fractals 一种复杂的几何形状)、吸引子(attractors,一个系统有朝某个稳态发展的趋势,这个稳态就叫做吸引子)、元胞自动机(cellular automata,是一种时间、空间、状态都离散,空间相互作用和时间因果关系为局部的网格动力学模型,具有模拟复杂系统时空演化过程的能力。
)等。
第二类,有形程序艺术品,试图使用参数化的方式来描述一些具体的事物。
第三类,可变手绘艺术品,是对图像预先绘制的部分进行简单随机化处理。
图1
此外,还有一些实验性和互动性的作品,甚至还有模块化合成器和游戏之类的艺术品,不过比较少见。
文章描述一个蘑菇(艺术品)的创作过程,并使用事务哈希对其进行随机化。结合艺术视野、构图和风格化等手段,创作了这个“生成式 NFT 艺术品”。
#第 2 部分:画蘑菇
Otinium caseubbacula — 生殖蘑菇标本之一
在介绍王理论知识之后,让我们进入技术环节。本项目完全使用Three.js库,它有一个使用简单的JavaScript库,在网上很容易查找到它的API使用方式。
#生成菌柄
首先要绘制出菌柄的样条线(我们称其为基本样条线,样条线的作用是辅助生成实体),针对该样条线进行参数化形成菌柄的轮廓。为了创建基本样条线,使用了 Three.js中的CatmullRomCurve3类。然后,通过沿基本样条线的移动,创造闭合形状来构建对应的几何图形,最后将这些图形连接起来,如图2所示。为此使用了Three.js中的 BufferGeometry组件。
代码入如下:
stipe_vSegments =30;// vertical resolution
stipe_rSegments =20;// angular resolution
stipe_points =[];// vertices
stipe_indices =[];// face indices
stipe_shape =new THREE.CatmullRomCurve3(..., closed=false);
functionstipe_radius(a, t){...}
for(var t =0; t <1; t +=1/ stipe_vSegments){
// stipe profile curve
var curve =new THREE.CatmullRomCurve3([
new THREE.Vector3(0,0,stipe_radius(0, t)),
new THREE.Vector3(stipe_radius(Math.PI /2, t),0,0),
new THREE.Vector3(0,0,-stipe_radius(Math.PI, t)),
new THREE.Vector3(-stipe_radius(Math.PI *1.5, t),0,0),
], closed=true, curveType='catmullrom', tension=0.75);
var profile_points = curve.getPoints( stipe_rSegments );
for(var i =0; i < profile_points.length; i++){
stipe_points.push(profile_points[i].x, profile_points[i].y, profile_points[i].z);
}
}
//
// and then create a BufferGeometry
var stipe =new THREE.BufferGeometry();
stipe.setAttribute('position',new THREE.BufferAttribute(new Float32Array(stipe_points),3));
stipe.setIndex(stipe_indices);
stipe.computeVertexNormals();
菌柄生成的阶段:样条、顶点、面
图2
#菌柄噪声
为了让菌柄看上去更自然,其表面会随着高度而发生变化,我们定义了噪声函数,该函数对半径进行定义,通过改变基本样条曲线上点的角度和相对高度两个参数的方式生成不一样的半径信息。如下代码所示。
base_radius =1;// mean radius
noise_c =2;// higher this - higher the deformations
// stipe radius as a function of angle and relative position
functionstipe_radius(a, t){
return base_radius +(1- t)*(1+ Math.random())*noise_c;
}
菌柄噪声变化
图3
如图3所示,通过半径的噪声函数,根据角度和高度生成不同的半径。
#菌帽
菌帽的生成方式,也可以通过在菌柄顶部加入旋转的样条曲线,然后再对曲线进行参数化来完成。这里可以可以将旋转产生的表面命名为基础表面,然后定义基础表面在基础样条上的位置再加入围绕菌柄顶部旋转的函数。这种参数化的编程方式将方便后面加入噪声函数。代码如下:
adial resolution
cap_cSegments =20;// angular resolution
cap_points =[];
cap_indices =[];
// cap surface as a function of polar coordinates
functioncap_surface(a0, t0){
// 1. compute (a,t) from (a0,t0), e.g apply noise
// 2. compute spline value in t
// 3. rotate it by angle a around stipe end
// 4. apply some other noises/transformations
...
return surface_point;
}
// spawn surface vertices with resolution
// cap_rSegments * cap_cSegments
for(var i =1; i cap_rSegments; i++){
var t0 = i / cap_rSegments;
for(var j =0; j < cap_cSegments; j++){
var a0 = Math.PI *2/ cap_cSegments * j;
var surface_point =cap_surface(a0, t0);
cap_points.push(surface_point.x, surface_point.y, surface_point.z);
}
}
//
// and then create a BufferGeometry
var cap =new THREE.BufferGeometry();
cap.setAttribute('position',new THREE.BufferAttribute(new Float32Array(cap_points),3));
cap.setIndex(cap_indices);
cap.computeVertexNormals();
帽生成阶段:样条、顶点、面
图4
如图4所示,通过基础样条以及顶点生成基础平面,这个平明就是菌帽。
#菌帽噪音
同样为了让艺术品看上去更加真实,菌帽也需要加入一些噪音。我将帽噪声分为 3类:径向噪声、角度噪声和法线噪声。径向噪声会影响顶点在基本样条上的相对位置。角噪声改变了围绕柄顶部基本样条旋转的角度。
最后,法线噪声会改变顶点沿基面的位置。在坐标系中定义帽表面时,会对扭曲应用 2d Perlin 噪声,因此这里会使用noisejs库,来完成上述功能。代码如下:
functionradnoise(a, t){
return-Math.abs(NOISE.perlin2(t * Math.cos(a), t * Math.sin(a))*0.5);
}
functionangnoise(a, t){
return NOISE.perlin2(t * Math.cos(a), t * Math.sin(a))*0.2;
}
functionnormnoise(a, t){
return NOISE.perlin2(t * Math.cos(a), t * Math.sin(a))* t;
}
functioncap_surface(a0, t0){
// t0 -> t by adding radial noise
var t = t0 *(1+radnoise(a, t0));
// compute normal vector in t
var shape_point = cap_shape.getPointAt(t);
var tangent = cap_shape.getTangentAt(t);
var norm =new THREE.Vector3(0,0,0);
const z1 =new THREE.Vector3(0,0,1);
norm.crossVectors(z1, tangent);
// a0 -> a by adding angular noise
var a =angnoise(a0, t);
var surface_point =new THREE.Vector3(
Math.cos(a)* shape_point.x,
shape_point.y,
Math.sin(a)* shape_point.x
);
// normal noise coefficient
var surfnoise_val =normnoise(a, t);
// finally surface point
surface_point.x += norm.x * Math.cos(a)* surfnoise_val;
surface_point.y += norm.y * surfnoise_val;
surface_point.z += norm.x * Math.sin(a)* surfnoise_val;
return surface_point;
}
从左到右的噪声分量:径向、角度、法线
图5
如图5所示,从左到右分别给菌帽径内噪声、角度噪声和法线噪声。
#蘑菇的其余部分:鳞片、鳃、环
鳃和环的几何形状与帽的几何形状非常相似。可以在帽表面上的一些随机锚点周围生成嘈杂的顶点,然后基于它们创建ConvexGeometry 。代码如下:
bufgeoms =[];
scales_num =20;
n_vertices =10;
scale_radius =2;
for(var i =0; i < scales_num; i++){
var scale_points =[];
// choose a random center of the scale on the cap
var a = Math.random()* Math.PI *2;
var t = Math.random();
var scale_center =cap_surface(a, t);
// spawn a random point cloud around the scale_center
for(var j =0; j < n_vertices; j++){
scale_points.push(new THREE.Vector3(
scale_center.x +(1- Math.random()*2)* scale_radius,
scale_center.y +(1- Math.random()*2)* scale_radius,
scale_center.z +(1- Math.random()*2)* scale_radius
);
}
// create convex geometry using these points
var scale_geometry =new THREE.ConvexGeometry( scale_points );
bufgeoms.push(scale_geometry);
}
// join all these geometries into one BufferGeometry
var scales = THREE.BufferGeometryUtils.mergeBufferGeometries(bufgeoms);
鳞片、鳃、环和蘑菇的完整几何形状
图6
如图6所示,绘制出鳞片、鳃、环和蘑菇的完整几何形状。
#碰撞检查
由于在同一个场景中会生成多个蘑菇,此时蘑菇之间会产生交叉层叠的情况,所以需要检查它们之间的碰撞关系。这里使用一个代码片段来自检测每个网格点的光线投射,从而达到检查蘑菇碰撞的目的。
为了减少计算时间,生成了蘑菇的低多边形双胞胎以及蘑菇本身。然后使用这个低多边形模型来检查与其他蘑菇的碰撞。
代码如下:
for(var vertexIndex =0; vertexIndex < Player.geometry.attributes.position.array.length; vertexIndex++)
{
var localVertex =new THREE.Vector3().fromBufferAttribute(Player.geometry.attributes.position, vertexIndex).clone();
var globalVertex = localVertex.applyMatrix4(Player.matrix);
var directionVector = globalVertex.sub( Player.position );
var ray =new THREE.Raycaster( Player.position, directionVector.clone().normalize());
var collisionResults = ray.intersectObjects( collidableMeshList );
if( collisionResults.length >0&& collisionResults[0].distance < directionVector.length())
{
// a collision occurred... do something...
}
}
用于更快碰撞检查的简化模型
图7
如图7所示,检查蘑菇的碰撞效果
#渲染和风格化
最初,想实现 2d 绘图的效果,尽管所有的生成都是用 3d 制作的。在风格化的背景下,首先想到的是轮廓效果。由于我们不是着色器方面的专家,所以只是从这个例子中获取了轮廓效果,并得到蘑菇轮廓的铅笔画风格。
三个js轮廓效果
图8
如图8所示,显示蘑菇的铅笔画风格。
接着就是着色了,蘑菇的纹理应该有点嘈杂并且有一些柔和的阴影。这里有一个简便的方式就是使用BufferGeometry API定义对象的顶点颜色。使用这种方法还可以将顶点的颜色进行参数化,也就是加入角度和位置作为参数从而改变颜色,因此噪声纹理的生成变得稍微容易一些了。
添加一些顶点颜色
图9
如图9所示,通过角度和位置给顶点添加颜色。
最后,使用EffectComposer添加了一些全局噪声和类似膜颗粒效果(Film Grain是模拟照相膜的随机光学纹理)。代码如下:
var renderer =new THREE.WebGLRenderer({antialias:true});
outline =new THREE.OutlineEffect( renderer ,{thickness:0.01, alpha:1, defaultColor:[0.1,0.1,0.1]});
var composer =new THREE.EffectComposer(outline);
//
var renderPass =new THREE.RenderPass( scene, camera );
composer.addPass( renderPass );
var filmPass =new THREE.FilmPass(
0.20,// noise intensity
0.025,// scanline intensity
648,// scanline count
false,// grayscale
);
composer.addPass(filmPass);
composer.render();
几乎准备好了,彩色和嘈杂的蘑菇
图10
如图10 所示,即将要完成的蘑菇。
#起个名字
对于起名字这件事,使用了一个简单的马尔可夫链,它接受了 1k 个蘑菇名称的训练。为了预处理和标记这些名称,使用了 python 库YouTokenToMe。有了它,可以将所有名称拆分为 200 个唯一标记,并将它们的转换概率写入JavaScript字典。代码的 JS 端只读取这些概率并堆叠标记,直到它生成几个单词。
以下是使用这种方法生成的一些蘑菇名称示例:
Stricosphaete cinus
Fusarium sium confsisomyc
Etiformansum poonic
Hellatatum bataticola
Armillanata gossypina mortic
Chosporium anniiffact
Fla po sporthrina
#第 3 部分:完成
图11
如图11所示,在 fxhash 上铸造的 15 个蘑菇
#准备发布
首先,要准备一个项目在 fxhash 上发布,只需将代码中的所有随机调用更改为fxrand()方法。主要思想是必须为每个哈希生成唯一的输出,需要对相同的哈希生成完全相同的输出。然后在沙箱中测试令牌,最后在打开铸币时铸币。而已!
这将我们带到了蘑菇地图集(我将这个集合命名为)。您可以在此处查看并查看其变化。虽然它不像笔者之前的一些作品那样售罄,但笔者认为这是他艺术生涯中最先进和最具挑战性的作品。希望铸造这个代币的人也能在NFT的世界中享受他们的真菌!
原文地址:https://hackernoon.com/how-to-draw-generative-nft-mushrooms-with-threejs