ここ1年くらいp5.jsを使ってcreative codingをしてきた。直感的だし使いやすく惚れ込んでいる。ただ、creative coding界隈の様々なアーティストの作品を見ていると、p5.js・processingの他、three.js, TouchDesigner, openFrameworks, blender, Cinema4d等の様々なツールが使われていて、p5.jsではどうやったら実現できるのか見当もつかないものも多くあった。特に、shaderの活用、3D描写、物理演算を用いた素材のリアルさの表現はとても美しいと思っていた。p5.jsを使ってもdrawingContextを使ってshadowを付けて疑似的な奥行きで3D感と影は出せるけども、花を描写したときに全部こっち側を向いているような描写になってしまう。そこで同じjavascriptであるthree.jsを使って表現の幅を増やしたいと思った。といっても、p5.jsとthree.jsは違いすぎる。p5.jsのnoiseやrandomを使いたいし、オフスクリーンレンダリングした画像から色を取得してthree.jsで使いたい。そのためにasyncとawaitを勉強する必要があって実現するために相当時間がかかったので、完成版をメモしておく。

p5.jsの描写をthree.jsのplaneに張り付けている。また、threejsのコードブロックの中でも、pという変数の後ろに、例えばp.noise()を付けることでp5.jsのnoiseの機能を使うことができる。

p5.js version 2.0, three.js r178, viteを使っている

import p5 from 'p5';
import * as THREE from "three";
import { OrbitControls } from "three/addons/controls/OrbitControls.js";

async function initP5() {
    return new Promise(resolve => {
        const sketch = p => {
            p.setup = async function () {
                const p5canvas = p.createCanvas(p.windowWidth, p.windowHeight)
                p.background("white")
                p5canvas.hide()
                p.mypic = await p.loadImage("mypicture.png")
                resolve(p)
            }
            p.draw = function () {
                p.push()
                    let gradientStroke = p.drawingContext.createLinearGradient(0,0,0,p.height);
                    gradientStroke.addColorStop(0, p.color(255, 0, 255)); //upper
                    gradientStroke.addColorStop(1, p.color(0, 255, 255)); //lower
                    p.drawingContext.fillStyle = gradientStroke;
                    p.circle(p.random(p.width),p.random(p.height),50)
                p.pop()
            }
        }
        new p5(sketch);
    })
}

async function initThree() {
    let p = await initP5()
    p.print(p.mypic.get(10,20))
    p.print(p.random())

    var scene = new THREE.Scene();
    scene.background = new THREE.Color().setRGB(0.5, 0.5, 1.0)
    var camera = new THREE.PerspectiveCamera(
        45,
        innerWidth / innerHeight,
        0.1,
        1000)
    camera.position.set(0, 0, 100)

    var renderer = new THREE.WebGLRenderer({ antialias: true });
    renderer.setPixelRatio(devicePixelRatio);
    renderer.setSize(innerWidth, innerHeight);

    const geoPlane = new THREE.PlaneGeometry(50, 50, p.width, p.height)
    const ptexture = new THREE.CanvasTexture(p.canvas);
   
    const geoMaterial = new THREE.MeshStandardMaterial({
        map: ptexture 
    });
    const plane = new THREE.Mesh(geoPlane, geoMaterial)
    scene.add(plane)

    const light = new THREE.DirectionalLight(0xffffff, 5);
    light.position.set(0, 0, 10);
    scene.add(light);

    var controls = new OrbitControls(camera, renderer.domElement);
    document.body.appendChild(renderer.domElement);

    function animate() {
        ptexture.needsUpdate = true;
        camera.lookAt(scene.position);
        controls.update();
        renderer.render(scene, camera);
        requestAnimationFrame(animate);
    }
    animate();

    addEventListener("resize", () => {
    camera.aspect = innerWidth / innerHeight;
    camera.updateProjectionMatrix();
    renderer.setSize(innerWidth, innerHeight);
    });
    
}
initThree();
category