kokomi.js - a powerful assistant for web 3D creation

foreword

Hello everyone, this is the CSS and WebGL magician - alphardex. In this article, we will meet a new friend - kokomi.js , who can bring you an amazing 3D creation experience~

kokomi.js Kosuke

I have created a lot of three.js related works before , but later found that there are too few things that can be reused, and the project structure is also chaotic. In order to solve these two problems, the author decided to write a wheel to encapsulate some of the most commonly used functions of three.js, and to make the structure of the project clearer, so there is kokomi.js

Origin of her name: Sangonomiya Kokomi

logo.jpg

Her Github address: github.com/alphardex/k…

Ready to work

In this article we will use the codesandbox platform to do all the coding work. The account can be registered directly with the Github account

Platform address: codesandbox.io

basic scene

Create ts template

First, we click on the upper right corner Create Sandbox, find it from the list, Vanilla Typescriptand create the simplest ts template

qGpOfJ.png

The address of this step: codesandbox.io/s/typescript…

Anso kokomi.js

DependenciesIn the lower input box on the left , enter kokomi.jsit to install kokomi.js

qG9Bh4.png

Since kokomi.js depends on three.js, we also need to install it and its type: threeand@types/three

Scene construction

In index.ts, modify our canvas container id to #sketch, and introduce a createSketchfunction (implemented below)

index.ts

import "./styles.css";

import createSketch from "./app";

document.getElementById("app").innerHTML = `
<div id="sketch"></div>`;

createSketch();
复制代码

在style.css中,给容器设置一定的样式,使其铺满屏幕

style.css

body {
  margin: 0;
}

#sketch {
  width: 100vw;
  height: 100vh;
  overflow: hidden;
  background: black;
}
复制代码

新建文件app.ts,在里面输入如下代码

app.ts

import * as kokomi from "kokomi.js";

class Sketch extends kokomi.Base {
  create() {}
}

const createSketch = () => {
  const sketch = new Sketch();
  sketch.create();
  return sketch;
};

export default createSketch;
复制代码

右边画面会报错,提示我们先要配置下babel

新建.babalrc文件,在里面拷贝如下代码

.babalrc

{
  "presets": [
    "env"
  ],
  "plugins": [
    "transform-runtime",
    "@babel/plugin-proposal-class-properties"
  ],
  "parserOpts": {
    "plugins": [
      "dynamicImport"
    ]
  }
}
复制代码

这时我们能看到一片黑屏,表明kokomi.js已经被顺利引入了

接下来让我们创建一个3D世界的Hello World——一个可爱的白色方块

class Sketch extends kokomi.Base {
  create() {
    const box = new kokomi.Box(this);
    box.addExisting();
  }
}
复制代码

可以看到屏幕中心已经出现了一个白色的方块

qGCQDx.png

接下来让我们添加轨道视角

class Sketch extends kokomi.Base {
  create() {
    const box = new kokomi.Box(this);
    box.addExisting();

    new kokomi.OrbitControls(this);
  }
}
复制代码

这样我们就能自由地拖拽画面了

qGC8UO.gif

让方块旋转起来吧

class Sketch extends kokomi.Base {
  create() {
    const box = new kokomi.Box(this);
    box.addExisting();

    new kokomi.OrbitControls(this);

    this.update((time: number) => {
      box.spin(time);
    });
  }
}
复制代码

qGCt8H.gif

恭喜,此刻你已经完成了最基础的3D场景的搭建

该步骤地址:codesandbox.io/s/kokomi-js…

素材管理

kokomi.js提供了AssetManager类,用来统一管理素材的加载

定义素材

首先,创建一个resources.ts文件,里面定义好素材列表

素材列表对象有3个字段:

  • name:素材名
  • type:素材类型,目前支持texture(2D贴图)、cubeTexture(3D贴图)、gltfModel(模型)、font(字体)
  • path:素材路径

resources.ts

import type * as kokomi from "kokomi.js";

import foxModel from "/public/models/Fox/glTF/Fox.gltf";

const resourceList: kokomi.ResourceItem[] = [
  {
    name: "foxModel",
    type: "gltfModel",
    path: foxModel
  }
];

export default resourceList;
复制代码

我们引入了一个神子,啊不狐狸的模型,这个模型是从gltf样例模型中取出来的,也可以替换成自己喜爱的其他模型

加载素材

实例化AssetManager类,并将素材resourceList作为参数传入

监听emitterready事件(素材加载完毕事件),读取items里的foxModel,将其加入场景中,即完成了素材的加载

app.ts

import * as THREE from "three";
import * as kokomi from "kokomi.js";
import resourceList from "./resources";

class Sketch extends kokomi.Base {
  create() {
    new kokomi.OrbitControls(this);

    this.camera.position.copy(new THREE.Vector3(6, 4, 3));

    const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
    this.scene.add(ambientLight);

    const dirLight = new THREE.DirectionalLight(0xffffff, 0.6);
    dirLight.position.copy(new THREE.Vector3(1, 2, 3));
    this.scene.add(dirLight);

    const assetManager = new kokomi.AssetManager(this, resourceList);
    assetManager.emitter.on("ready", () => {
      const foxModel = assetManager.items.foxModel;
      foxModel.scene.scale.set(0.02, 0.02, 0.02);
      this.scene.add(foxModel.scene);
    });
  }
}
复制代码

qGmJmQ.gif

该步骤地址:codesandbox.io/s/kokomi-js…

这里有个优化点:可以将Fox模型抽成一个class组件,这样可以维护一个属于自己的组件逻辑

组件化

新建文件夹 components,新建 fox.ts,编写狐狸的模型组件

The basic idea of ​​​​writing components: inherit the Componentclass, write the state logic of the component in it, the addExistingfunction is responsible for adding the component to the scene, the updatefunction is the animation frame, and will be synchronized to the global animation of the scene

fox.ts

import * as THREE from "three";
import * as kokomi from "kokomi.js";
import type * as STDLIB from "three-stdlib";

type ActionName = "idle" | "walk" | "run";

class Fox extends kokomi.Component {
  gltf: STDLIB.GLTF;
  mixer: THREE.AnimationMixer;
  actions: Record<string, THREE.AnimationAction>;
  constructor(base: kokomi.Base, gltf: STDLIB.GLTF) {
    super(base);

    this.gltf = gltf;

    const mixer = new THREE.AnimationMixer(this.gltf.scene);
    this.mixer = mixer;

    this.actions = {};
    this.setActions();
  }
  addExisting(): void {
    this.gltf.scene.scale.set(0.02, 0.02, 0.02);
    this.base.scene.add(this.gltf.scene);
  }
  update(time: number): void {
    const delta = this.base.clock.getDelta();
    this.mixer.update(delta);
  }
  setActions() {
    this.actions.idle = this.mixer.clipAction(this.gltf.animations[0]);
    this.actions.walk = this.mixer.clipAction(this.gltf.animations[1]);
    this.actions.run = this.mixer.clipAction(this.gltf.animations[2]);
  }
  playAction(name: ActionName = "idle") {
    const prevAction = this.actions.current;
    const nextAction = this.actions[name];

    nextAction.reset();
    nextAction.play();
    if (prevAction) {
      nextAction.crossFadeFrom(prevAction, 1, true);
    }

    this.actions.current = nextAction;
  }
}

export default Fox;
复制代码

Apply the component to the main scene

app.ts

import Fox from "./components/fox";

class Sketch extends kokomi.Base {
   ...
  create() {
    ...
    this.assetManager.emitter.on("ready", () => {
      const fox = new Fox(this, this.assetManager.items.foxModel);
      fox.addExisting();
      fox.playAction("idle");
    });
  }
}
复制代码

qGc80S.gif

The fox instance here has its own function and state, so that when we create others class, there will be no duplicate conflicts in functionality

The address of this step: codesandbox.io/s/kokomi-js…

special effects creation

Create a new shader folder, and create a new fragment.glslfragment shader in it, copy the following code inside

void mainImage(out vec4 fragColor,in vec2 fragCoord){
    vec2 p=fragCoord/iResolution.xy;
    vec3 color=vec3(p,0.);
    fragColor=vec4(color,1.);
}
复制代码

In the main scene, instantiate the ScreenQuadclass, open shadertoyModeit, and introduce the fragment shader

app.ts

import fragmentShader from "./shaders/fragment.glsl";

class Sketch extends kokomi.Base {
  create() {
    const screenQuad = new kokomi.ScreenQuad(this, {
      shadertoyMode: true,
      fragmentShader
    });
    screenQuad.addExisting();
  }
}
复制代码

qGMUDU.png

You can see that the uv plane is displayed, and we can create Shader on it.

The address of this step: codesandbox.io/s/kokomi-js…

For example, the most common Raymarching on Shadertoy

qG3PMt.png

The demo address: codesandbox.io/s/raymarchi…

finally

This article takes you to a preliminary understanding of kokomi.js. The author will continue to improve her in the future, so stay tuned.

Guess you like

Origin juejin.im/post/7078578317053394975