Jump to content

Create OGL Shader Polyline


rasander
 Share

Recommended Posts

Hi, I am currently trying to generate the polyline example of OGL (v0.0.90) (https://oframe.github.io/ogl/examples/?src=polylines.html) with PIXI.js (v6.3.0). I have created a new PIXI.Mesh and created the appropriate shaders and buffers. Only at some point I'm stuck and don't know why no object is visible. Is it because of the change of the buffer values or does PIXI use a different coordinate reference? For the mathematical operations I use the Vec3 class from OGL, but I could change that later. Here is the code I wrote.

import * as PIXI from 'pixi.js';
import * as OGL from "ogl";

const canvas = document.createElement('canvas');
canvas.id = 'stage';
canvas.width  = window.innerWidth;
canvas.height = window.innerHeight;
const rendererOptions = {
    width: canvas.width,
    height: canvas.height,
    view: canvas,
};
const stage = new PIXI.Container();
const renderer = new PIXI.Renderer(rendererOptions);
renderer.backgroundColor = 0xff0000;
document.body.appendChild(canvas);

const vertexLines = `
attribute vec3 position;
attribute vec3 next;
attribute vec3 prev;
attribute vec2 uv;
attribute float side;

uniform vec2 uResolution;
uniform float uDPR;
uniform float uThickness;

vec4 getPosition() {
    vec2 aspect = vec2(uResolution.x / uResolution.y, 1);
    vec2 nextScreen = next.xy * aspect;
    vec2 prevScreen = prev.xy * aspect;

    vec2 tangent = normalize(nextScreen - prevScreen);
    vec2 normal = vec2(-tangent.y, tangent.x);
    normal /= aspect;
    normal *= 1.0 - pow(abs(uv.y - 0.5) * 1.9, 2.0);

    float pixelWidth = 1.0 / (uResolution.y / uDPR);
    normal *= pixelWidth * uThickness;

    // When the points are on top of each other, shrink the line to avoid artifacts.
    float dist = length(nextScreen - prevScreen);
    normal *= smoothstep(0.0, 0.02, dist);

    vec4 current = vec4(position, 1);
    current.xy -= normal * side;
    return current;
}

void main() {
    gl_Position = getPosition();
}
`;

const fragmentLines = /* glsl */ `
precision highp float;
uniform vec3 uColor;
varying vec2 vUv;
void main() {
    gl_FragColor.rgb = uColor;
    gl_FragColor.a = 1.0;
}
`;

const countPoints = 20;
const polylines = [{
    spring: 0.05,
    friction: 0.7,
    mouseVelocity: new OGL.Vec3(),
    mouseOffset: new OGL.Vec3(0.01),
    position: new Float32Array(countPoints * 3 * 2),
    prev: new Float32Array(countPoints * 3 * 2),
    next: new Float32Array(countPoints * 3 * 2),
    side: new Float32Array(countPoints * 1 * 2),
    uv: new Float32Array(countPoints * 2 * 2),
    index: new Uint16Array((countPoints - 1) * 3 * 2),
    tmp: new OGL.Vec3(),
    points: [],
}];

polylines.forEach((polyline) => {
    // set static buffer values
    for (let i = 0; i < countPoints; i++) {
        polyline.side.set([-1, 1], i * 2);
        const v = i / (countPoints - 1);
        polyline.uv.set([0, v, 1, v], i * 4);

        if (i === countPoints - 1) continue;
        const ind = i * 2;
        polyline.index.set([ind + 0, ind + 1, ind + 2], (ind + 0) * 3);
        polyline.index.set([ind + 2, ind + 1, ind + 3], (ind + 1) * 3);
    }

    // set empty points
    for (let i = 0; i < countPoints; i++) polyline.points.push(new OGL.Vec3());

    let geometry = new PIXI.Geometry();
    geometry.addAttribute('position', new PIXI.Buffer(polyline.position, false, false), 3, false);
    geometry.addAttribute('prev', new PIXI.Buffer(polyline.prev, false, false), 3, false);
    geometry.addAttribute('next', new PIXI.Buffer(polyline.next, false, false), 3, false);
    geometry.addAttribute('side', polyline.side, 1, false);
    geometry.addAttribute('uv', polyline.uv, 2, false);
    geometry.addIndex(polyline.index);

    let shader = PIXI.Shader.from(vertexLines, fragmentLines, {
        uColor: { value: new OGL.Color('#fff') },
        uThickness: { value: 50 },
        uResolution: { value: new OGL.Vec2() },
        uDPR: { value: 1 },
        uMiter: { value: 1 }
    });

    let mesh = new PIXI.Mesh(
        geometry,
        shader
    );
    polyline.mesh = mesh;
    stage.addChild(mesh);
});


// Add handlers to get mouse position
const mouse = new OGL.Vec3();
if ('ontouchstart' in window) {
    window.addEventListener('touchstart', updateMouse, false);
    window.addEventListener('touchmove', updateMouse, false);
} else {
    window.addEventListener('mousemove', updateMouse, false);
}

function updateGeometry(polyline) {
    console.log(mouse);
    polyline.points.forEach((p, i) => {
        p.toArray(polyline.position, i * 3 * 2);
        p.toArray(polyline.position, i * 3 * 2 + 3);

        if (!i) {
            // If first point, calculate prev using the distance to 2nd point
            polyline.tmp.copy(p).sub(polyline.points[i + 1]).add(p);
            polyline.tmp.toArray(polyline.prev, i * 3 * 2);
            polyline.tmp.toArray(polyline.prev, i * 3 * 2 + 3);
        } else {
            p.toArray(polyline.next, (i - 1) * 3 * 2);
            p.toArray(polyline.next, (i - 1) * 3 * 2 + 3);
        }

        if (i === polyline.points.length - 1) {
            // If last point, calculate next using distance to 2nd last point
            polyline.tmp.copy(p).sub(polyline.points[i - 1]).add(p);
            polyline.tmp.toArray(polyline.next, i * 3 * 2);
            polyline.tmp.toArray(polyline.next, i * 3 * 2 + 3);
        } else {
            p.toArray(polyline.prev, (i + 1) * 3 * 2);
            p.toArray(polyline.prev, (i + 1) * 3 * 2 + 3);
        }
    });

    polyline.mesh.geometry.getBuffer('position').update(polyline.position);
    polyline.mesh.geometry.getBuffer('prev').update(polyline.prev);
    polyline.mesh.geometry.getBuffer('next').update(polyline.next);
}

function updateMouse(e) {
    if (e.changedTouches && e.changedTouches.length) {
        e.x = e.changedTouches[0].pageX;
        e.y = e.changedTouches[0].pageY;
    }
    if (e.x === undefined) {
        e.x = e.pageX;
        e.y = e.pageY;
    }

    // Get mouse value in -1 to 1 range, with y flipped
    mouse.set((e.x / renderer.width) * 2 - 1, (e.y / renderer.height) * -2 + 1, 0);
}

function update() {
    updateId = requestAnimationFrame(update.bind(this));

    polylines.forEach((polyline) => {
        for (let i = polyline.points.length - 1; i >= 0; i--) {
            if (!i) {
                // For the first point, spring ease it to the mouse position
                polyline.tmp.copy(mouse).add(polyline.mouseOffset).sub(polyline.points[i]).multiply(polyline.spring);
                polyline.mouseVelocity.add(polyline.tmp).multiply(polyline.friction);
                polyline.points[i].add(polyline.mouseVelocity);
            } else {
                // The rest of the points ease to the point in front of them, making a line
                polyline.points[i].lerp(polyline.points[i - 1], 0.9);
            }
        }
        updateGeometry(polyline);
    });

    renderer.render(stage);
};

function resize() {
    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;
    renderer.resize(canvas.width, canvas.height);

    polylines.forEach((polyline) => {
        if (polyline.resolution) polyline.resolution.value.set(canvas.width, canvas.height);
        if (polyline.dpr) polyline.dpr.value = renderer.resolution;
    });
}

resize();
window.addEventListener('resize', resize.bind(this));

let updateId = 0;
update();

 

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...