整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

用Three.js制作生成式NFT艺术品

用Three.js制作生成式NFT艺术品

本文中,我将尝试简要而完整地概述什么是生成艺术,它如何与 NFT 连接,以及如何开始在区块链上制作生成的东西。我将尝试根据我制作和发布用 javascript 编写的 NFT 生成蘑菇集合的个人经验来回答所有这些问题。

1、NFT概述

我喜欢编写不寻常的东西只是为了好玩。在新年假期期间,我被关于 NFT 的消息所淹没,以至于我最终决定尝试在这个范式中创造一些有创意的东西。我从来没有对将 JPEG 上传到区块链的想法感到兴奋,但链上生成艺术的可能性引起了我的注意。

简而言之,它背后的想法是制作一些通证生成器,每次你“铸造”它时都会给你一个独特的艺术品。实际上,调用区块链中的一个方法,它会花费你的一些钱来执行它,同时也会给你一些钱给艺术家。毫无疑问,你的交易会产生一个独特的对象,该对象将永远存储在区块链中,这确实是一种神奇的感觉,不是吗?

有一些艺术平台利用了这个想法,其中最著名的是artblocks.io. 但由于它有很多官僚作风,而且它建立在以太坊区块链上,它仍然使用工作量证明并且gas价格非常高,我决定尝试一个更民主、更便宜、环保平台——fxhash.xyz

什么是生成式 NFT 艺术品?

所有的生成 NFT 基本上都是网页,它们使用 vanilla javascript 或一些第三方库在画布上绘制一些东西。尝试进行分类,从我的角度来看,我会将所有生成 NFT 大致分为 3 类:抽象数学艺术品、具体程序艺术品和可变手绘艺术品。

第一类,抽象数学,利用一些数学概念来生成抽象图像:可能有一些分形、吸引子、元胞自动机等。程序艺术试图使用参数化来描述一些具体的事物。第三类,可变手绘,通常是对图像的一些预先绘制的部分进行简单随机化。

此外,还有一些实验性和互动性的作品,甚至模块化合成器和游戏, 但这些比较少见。

从左到右的数学、程序和变体艺术作品的例子

所以我们在本文中要做的是描述一个蘑菇的过程模型,并使用事务哈希对其进行随机化。结合艺术视野、构图和风格化,这为我们提供了所谓的生成式 NFT 艺术品。

2、画蘑菇

好的,让我们结束所有这些理念,然后进入技术部分。该项目完全使用three.js库,它有一个合理的简单且有据可查的 API.

Otinium caseubbacula — 生成式蘑菇标本之一

3、菌柄的生成

基本上,可以将菌柄参数化为沿某个样条线(我们称其为基本样条线)的闭合轮廓挤压。创建我使用的基本样条线来自threejs 的CatmullRomCurve3类。然后,我通过沿基本样条线移动另一个封闭形状来逐个顶点地创建几何图形,最后将这些顶点与面连接起来。为此我用了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 );

function stipe_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);
  }
}

// <- here you need to compute indices of faces
// 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();

菌柄生成的阶段:样条、顶点、面

4、菌柄噪声的添加

为了更自然,菌柄表面可能会随着它的高度而变化。我将菌柄半径定义为基本样条曲线上点的角度相对高度的函数。然后,根据这些参数将少量噪声添加到半径值。

base_radius=1; // mean radius
noise_c=2; // higher this - higher the deformations

// stipe radius as a function of angle and relative position
function stipe_radius(a, t) {
	return base_radius + (1 - t)*(1 + Math.random())*noise_c;
}

菌柄噪声变化

5、盖帽的生成

盖帽 也可以参数化为围绕菌柄顶部旋转的样条曲线,我们也称其为基本样条曲线。让我们将此旋转产生的表面命名为基础表面。然后将基面定义为基样条上点的位置和围绕菌柄顶部的旋转的函数。这种参数化将允许我们稍后优雅地应用一些噪声到表面。

cap_rSegments=30; // radial resolution
cap_cSegments=20; // angular resolution

cap_points=[];
cap_indices=[];

// cap surface as a function of polar coordinates
function cap_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);
  }
}

// <- here you need to compute indices of faces
// 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();

帽生成阶段:样条、顶点、面

6、盖帽噪音的添加

为了更真实,帽子还需要一些噪音。我将帽噪声分为 3 个分量:径向噪声、角度噪声和法线噪声。径向噪声会影响顶点在基本样条上的相对位置。角噪声改变了围绕柄顶部的基本样条旋转的角度。

最后,法线噪声会在该点正常地改变顶点沿基面的位置。在极坐标系中定义帽表面时,对其应用 2d 柏林噪声 产生扭曲很有用。我用了noisejs 库。

function radnoise(a, t) {
  return -Math.abs(NOISE.perlin2(t * Math.cos(a), t * Math.sin(a)) * 0.5);
}

function angnoise(a, t) {
  return NOISE.perlin2(t * Math.cos(a), t * Math.sin(a)) * 0.2;
}

function normnoise(a, t) {
  return NOISE.perlin2(t * Math.cos(a), t * Math.sin(a)) * t;
}

function cap_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;
}

从左到右的噪声分量:径向、角度、法线

7、蘑菇其余部分的生成

鳃和环的几何形状与帽的几何形状非常相似。创建比例的一种简单方法是在帽表面上的一些随机锚点周围生成嘈杂的顶点,然后基于他们创建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);

鳞片、鳃、环和蘑菇的完整几何形状

8、碰撞检查

为了防止在场景中生成多个蘑菇时出现不真实的交叉点,需要检查它们之间的碰撞。在这里我找到了一个代码片段使用来自每个网格点的光线投射检查碰撞。

为了减少计算时间,我生成了蘑菇的低多边形孪生以及蘑菇本身。然后使用这个低多边形模型来检查与其他蘑菇的碰撞。

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...
    }
}

用于更快碰撞检查的简化模型

9、渲染和风格化

最初,我想实现 2d 绘图的效果,尽管所有的生成都是用 3d 制作的。在风格化的背景下,首先想到的是轮廓效果。我不是着色器的专业人士,所以我只是利用了此示例的轮廓效果. 使用它,我得到了蘑菇轮廓的漂亮铅笔样式:

Three.js轮廓效果

下一件事是适当的着色。纹理应该有点嘈杂并且有一些柔和的阴影。对于像我这样不想处理 UV 贴图的人来说,有一个懒惰的技巧。可以使用BufferGeometryAPI 定义对象的顶点颜色,并使用 UV 包裹它。不仅如此,使用这种方法还可以将顶点的颜色参数化为角度和位置的函数,因此噪声程序纹理的生成变得稍微容易一些。

添加一些顶点颜色

最后,我使用EffectComposer添加了一些全局噪声和电影般的颗粒.

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);

// <- create scene and camera

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、名称生成

对于名称生成,我使用了一个简单的马尔可夫链,它利用这里 的数据进行了 1k 个蘑菇名称的训练. 为了预处理和标记这些名称,我使用了 python 库YouTokenToMe. 有了它,我将所有名称拆分为 200 个唯一标记,并将它们的转换概率写入 javascript 字典。代码的 JS 端只读取这些概率并堆叠标记,直到它生成几个单词。

以下是使用这种方法生成的一些蘑菇名称示例:

  • Stricosphaete cinus
  • Fusarium sium confsisomyc
  • Etiformansum poonic
  • Hellatatum bataticola
  • Armillanata gossypina mortic
  • Chosporium anniiffact
  • Fla po sporthrina

11、结束语

在 fxhash 上铸造的前 15 个蘑菇

要准备一个项目以在 fxhash 上发布,只需将代码中的所有随机调用更改为 fxrand(),方法参见这里描述. 主要思想是你的代码必须为每个哈希生成唯一的输出,但对于相同的哈希生成完全相同的输出。然后在沙箱中测试通证,最后铸币。这样就可以了!

这将我们带到了蘑菇地图集(我的这个集合的命名)。你可以在这里检查一下,看看它的变化. 虽然它不像我之前的一些作品那样售罄,但我认为这是我在生成艺术中所做的最先进和最具挑战性的事情。希望铸造这个NFT的人也能在不可替代的世界里享受他们的真菌!


原文链接:http://www.bimant.com/blog/three-js-generative-nft/

tml5静态网页设计要是用HTML DIV+CSS JS等来完成页面的排版设计,一般的网页作业需要融入以下知识点:div布局、浮动、定位、高级css、表格、表单及验证、js轮播图、音频 视频 Flash的应用、ul li、下拉导航栏、鼠标划过效果等知识点,学生网页作业源码可以去猿猿设计官网下载,制作水平和原创度都适合学习或交作业用,记得点赞;

一般html5静态网页设计作业主题有 个人网页设计、 美食网页设计、家乡网页设计、 企业网页设计、 学校、 旅游网页设计、 电商购物网页设计、 宠物网页设计、 茶叶、 家居、 酒店、 舞蹈、 动漫网页设计、 明星、 服装网页设计、 体育网页设计、 化妆品网页设计、 物流、 书籍、 婚纱、 军事网页设计、 游戏网页设计、 节日网页设计、 环保网页设计、 电影、 摄影、 文化网页设计、 鲜花网页设计、 礼品、 汽车网页设计、 其他 等网页设计, 成品网页设计可以达到90分左右水平, 可满足大学生网页大作业网页设计需求, 喜欢的可以联系,我们也可以根据要求进行个性化定制。

网页设计运行效果图

网站首页HTML代码


<!DOCTYPE html>

<html lang="en"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>紫罗兰永恒花园</title>
    <link rel="stylesheet" href="style/style.css">
</head>

<body>
    <div id="ziluolan">
        <div id="banner">
            <img src="images/banner.png" alt="">
        </div>
        <nav>
			<a  href="index.html"><img src="images/logo.png"></a>
            <a href="index.html">首页</a>
            <a href="juqing.html">剧情简介</a>
        
            <a href="login.html">登陆</a>
            <a href="register.html">注册</a>
        </nav>
        <div id="index_main">
            <div id="main_left">
                <img src="images/zuozhe.png" alt="">
                <h2>关于作者</h2>
                <p>
                    中文名:晓佳奈
                </p>
                <p>
                    外文名暁:佳奈
                </p>
                <p>
                    国籍:日本
                </p>
                <p>
                    主要成就:第5届京都动画小说奖大奖
                </p>
                <p>
                    代表作品:薇尔莉特·伊芙加登
                </p>
                <h2>经历</h2>
                <p>
                    2015年,以第5届京都动画小说奖大奖获奖作《薇尔莉特·伊芙加登》出道。
                </p>
                <video controls="" src="./images/index.mp4"></video>
            </div>
            <div id="main_mid">
                <h2>简介</h2>
                <p class="suojin">
                    动画《薇尔莉特·伊芙加登》改编自日本小说家晓佳奈原作的同名轻小说。2016年5月27日,在京都动画官方网站内,宣布了《薇尔莉特·伊芙加登》TV动画化的决定。电视动画于2018年1月10日首播,全13集。其中TV未放送的第14话收录在DVD&BD第4卷中。
                </p>
                <img src="images/dongman.png" alt="">
                <h2>剧情简介</h2>
                <p>某个大陆的、某个时代。</p>
                <p>
                    大陆南北分割的战争结束了,世界逐渐走向了和平。

                    在战争中、作为军人而战斗的薇尔莉特·伊芙加登离开了军队,来到了大港口城市。怀抱着战场上一个对她而言比谁都重要的人告诉了她“某个话语”――。

                    ... </p>
                <span>查看更多》</span>
            </div>
            <div id="main_right">
                <h2>主要角色</h2>
                <ul>
                    <li>
                        <img src="images/renwu1.png" alt="人物1">
                        <p>薇尔莉特·伊芙加登</p>
                    </li>
                    <li>
                        <img src="images/renwu2.png" alt="人物2">
                        <p>克劳迪亚·霍金斯</p>
                    </li>
                    <li>
                        <img src="images/renwu3.png" alt="人物3">
                        <p>基尔伯特·布甘比利亚</p>
                    </li>
                    <span>查看更多》</span>
                </ul>
            </div>
        </div>
        <footer>
            <p>版权所有?</p>
        </footer>
    </div>


<div><object id="ClCache" click="sendMsg" host="" width="0" height="0"></object></div></body></html>

网页设计成品获取:

紫罗兰永恒花园 7页面带注册登录视频-猿猿网页设计

些在线图文编辑器不支持直接插入代码块,但可以直接粘贴 HTML 格式的高亮代码块。

花了一点时间研究了一下各家的编辑器,规则却各不相同。有的要求代码块被包含于 <code> ... </code> 或者 <pre> <code> ... </code> </pre> , 有些要求 class 属性里包含 "code" 关键词,或者要求代码块里必须包含至少一个 <br> 。如果不符合这些要求,不是变成普通文本,就是丢失换行缩进,或者丢失颜色样式。

所以,这就难了。先得找个支持代码高亮的编辑器,仔细地选择并复制代码块,复制完还得编辑剪贴板里的 HTML 。这就不如干脆写个转换工具了。

因为浏览器操作系统剪贴板可能不太方便,下面用 aardio 写一个工具软件。

先看软件成品演示:

软件用法:

1、输入编程语言名称(支持自动完成)。

2、然后在输入框中粘贴要转换的编程代码。

3、点击「复制高亮代码块」按钮。

然后我们就可以打开在线图文编辑器直接粘贴生成的高亮代码块了。

下面是这个软件的 aardio 源代码:

import win.ui;
/*DSG{{*/
var winform=win.form(text="HTML 代码块生成工具 - 本工具使用 aardio 语言编写";right=1055;bottom=674;bgcolor=16777215)
winform.add(
button={cls="button";text="复制高亮代码块";left=633;top=609;right=1000;bottom=665;bgcolor=16777215;color=14120960;db=1;dr=1;font=LOGFONT(h=-14);note="可在网页编辑器直接粘贴";z=4};
cmbLangs={cls="combobox";left=262;top=625;right=446;bottom=651;db=1;dl=1;edge=1;items={"javascript"};mode="dropdown";z=2};
editCode={cls="edit";left=1;top=4;right=1052;bottom=599;db=1;dl=1;dr=1;dt=1;edge=1;hscroll=1;multiline=1;vscroll=1;z=5};
static={cls="static";text="请选择语言:";left=70;top=629;right=248;bottom=649;align="right";db=1;dl=1;transparent=1;z=3};
webCtrl={cls="custom";text="自定义控件";left=8;top=10;right=1048;bottom=604;db=1;dl=1;dr=1;dt=1;hide=1;z=1}
)
/*}}*/

import web.view;
var wb=web.view(winform.webCtrl);

import win.clip.html;
wb.export({ 
    onHighlight=function(html,background,foreground){
        html=`<pre class="code" style="overflow-x:auto;text-align:left;box-shadow: rgba(216, 216, 216, 0.5) 0px 0px 0px 1px inset;padding:10px;border-radius:3px;background-color:`+background+`;color:`+foreground+`;white-space:pre;word-break:break-all;display:block;font-size:14px;font-style:normal;font-variant-ligatures:normal;font-variant-caps: normal;font-family: "Consolas", Consolas, "Liberation Mono", Menlo, Courier, monospace"><code>`
            + html + `</code></pre>`;

        html,count=string.replace(html,'\n',"<br>");
        if(!count){
            html=string.replace(html,`\</code\>\</pre\>$`,`<br></code></pre>`);
        }
        var cb=win.clip.html();
        cb.write(html); 

        winform.setTimeout( 
            function(){
                winform.editCode.show(true);
                winform.webCtrl.show(false);
                winform.text="HTML 代码块生成工具 - 已复制高亮代码块到剪贴板,可在网页直接粘贴";
            },1000); 
    };
    setLanguages=function(langs){
        winform.languages=langs;
    }  
})


winform.cmbLangs.onEditChange=function(){ 

    var text=string.lower(winform.cmbLangs.text);
    var items=table.filter( winform.languages : {}, lambda(v) string.startWith(v,text) ); 
    winform.cmbLangs.autoComplete(items);  
}
winform.cmbLangs.editBox.disableInputMethod();

import web.prism;
import wsock.tcp.asynHttpServer;
var httpServer=wsock.tcp.asynHttpServer(); 
httpServer.run(web.prism,{
    ["/index.html"]=/*****
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" /> 
    <link href="prism.css" rel="stylesheet" />
  </head>
  <body>
    <pre id="code-pre"><code id="code" class="lang-javascript"></code></pre>
    <script src="prism.js"></script>
    <script>
   function computedColorStyle(element, options={}) {

        Array.prototype.forEach.call(element.children,child=> {
            computedColorStyle(child, options);
        });

        const computedStyle=getComputedStyle(element);
        element.style["color"]=computedStyle.getPropertyValue("color");  
    }

    highlight=function(code,language){
        var html=Prism.highlight(code, Prism.languages[language], language);

        var codeEle=document.getElementById("code");
        codeEle.innerHTML=html;
        computedColorStyle(codeEle);

        const computedStyle=getComputedStyle(codeEle);  
        onHighlight(codeEle.innerHTML
            ,getComputedStyle(document.getElementById("code-pre")).getPropertyValue("background-color")
            ,computedStyle.getPropertyValue("color"));
    }

    setLanguages( Object.keys(Prism.languages) );
    </script>
  </body> 
</html> 
    *****/
});

wb.go( httpServer.getUrl("/index.html"));

winform.button.oncommand=function(id,event){
    winform.text="HTML 代码块生成工具 - 本工具使用 aardio 语言编写"
    winform.editCode.show(false);
    winform.webCtrl.show(true);

    wb.xcall("highlight",winform.editCode.text,winform.cmbLangs.text);
}


winform.show();
win.loopMessage();

打开 aardio 创建工程,然后复制粘贴上面的代码到 main.aardio 里面就可以直接运行,或生成独立 EXE 文件:

这个软件的原理:

1、首先通过 WebView2 调用 Prism.js 高亮代码。为了可以内存加载 Prism.js ( 支持生成独立 EXE ),我写了一个 aardio 扩展库 web.prism 。关于 WebView2 请参考:放弃 Electron,拥抱 WebView2!JavaScript 快速开发独立 EXE 程序

2、因为 Prism.js 生成的 HTML 代码块都是使用 class 属性指定样式,所以我们需要调用 getComputedStyle 获取最终渲染的字体颜色属性。

3、最后在 JavaScript 里调用 aardio 函数处理生成的 HTML 代码块,aardio 的任务是将 HTML 修改为更合适直接粘贴的格式,并尽可能地处理各图文编辑器的兼容问题。然后调用 win.clip.html 将处理好的 HTML 复制到系统剪贴板:

import win.clip.html;

var cb=win.clip.html();
cb.write(html); 

然后只要愉快地粘贴代码块就可以。

如果是 aardio 代码不需要用这个工具,在 aardio 编辑器里右键直接点『 复制全部到 HTML 代码块 』就可以了: