I've attempted to port this Shadertoy code into p5.js WebGL, everything works nicely. However instead of passing xs
, ys
, colorRs
, etc, I'd like to pass vec2's and vec3s directly into the uniforms somehow, e.g. uniform vec2 positions[]
, and uniform vec3 colors[]
. I've attempted mapping positions and colors just like done with radii, but nothing has worked so far, just a black screen.
Here's what I've tried in sketch.js
: metaballShader.setUniform('positions', balls.map(b => [b.pos.x, b.pos.y]));
, and etc for colors.
sketch.js:
let metaballShader;
let balls = [];
function preload() {
metaballShader = loadShader("metaball.vert", "metaball.frag");
}
function setup() {
createCanvas(680, 530, WEBGL);
shader(metaballShader);
balls.push(new Ball(random(width), random(height), [1, 0, 0]));
balls.push(new Ball(random(width), random(height), [0, 1, 0]));
balls.push(new Ball(random(width), random(height), [0, 0, 1]));
metaballShader.setUniform('width', width);
metaballShader.setUniform('height', height);
metaballShader.setUniform('rs', balls.map(b => b.r));
metaballShader.setUniform('colorRs', balls.map(b => b.color[0]));
metaballShader.setUniform('colorGs', balls.map(b => b.color[1]));
metaballShader.setUniform('colorBs', balls.map(b => b.color[2]));
}
function draw() {
metaballShader.setUniform('xs', balls.map(b => b.pos.x));
metaballShader.setUniform('ys', balls.map(b => b.pos.y));
quad(-1, -1, 1, -1, 1, 1, -1, 1);
for (const ball of balls) {
ball.update(balls);
}
}
ball.js:
const BASE_SPEED = 2.5;
class Ball {
constructor(x, y, color) {
this.pos = {
x: x,
y: y
}
this.angle = random(0, 2 * Math.PI);
this.vel = {
x: BASE_SPEED * cos(this.angle),
y: BASE_SPEED * sin(this.angle)
}
this.r = random(0.4, 0.9);
this.color = color;
}
update() {
this.pos.x += this.vel.x;
this.pos.y += this.vel.y;
if (this.pos.x < 0 || this.pos.x > width) this.vel.x *= -1;
if (this.pos.y < 0 || this.pos.y > height) this.vel.y *= -1;
}
}
metaball.vert:
attribute vec3 aPosition;
uniform float width;
uniform float height;
varying highp vec2 vPos;
void main() {
gl_Position = vec4(aPosition, 1.0);
vPos = vec2(
(gl_Position.x + 1.) / 2. * width,
(gl_Position.y + 1.) / 2. * height
);
}
metaball.frag:
precision highp float;
#define BALLS 3
uniform float xs[BALLS];
uniform float ys[BALLS];
uniform float rs[BALLS];
uniform float colorRs[BALLS];
uniform float colorGs[BALLS];
uniform float colorBs[BALLS];
varying highp vec2 vPos;
struct Metaball {
float r;
vec2 pos;
vec3 col;
};
vec4 getDist(Metaball ball) {
float dist = float(ball.r) / length(vPos - ball.pos);
return vec4(ball.col * dist, dist);
}
Metaball balls[BALLS];
void main() {
for (int i = 0; i < BALLS; i++) {
Metaball ball;
ball.pos = vec2(xs[i], ys[i]);
ball.r = rs[i];
ball.col = vec3(colorRs[i], colorGs[i], colorBs[i]);
balls[i] = ball;
}
float total;
vec3 color;
for (int i = 0; i < BALLS; i++) {
vec4 colorValue = getDist(balls[i]);
total += colorValue.a;
color += colorValue.rgb;
}
float threshold = total > 0.016 ? 1. : 0.;
color /= total;
gl_FragColor = vec4(color * threshold, 1.);
}
Any ideas? Thanks!
You need to use flatMap
or something equivalent to flatten your arrays. For example if you were specifying the value for a uniform vec2 example[3]
you would want to use something like setUniform('example', [1, 2, 3, 4, 5, 6])
as opposed to setUniform('example', [[1, 2], [3, 4], [5, 6]])
. Below is a working example.
let metaballShader;
let balls = [];
function setup() {
createCanvas(680, 530, WEBGL);
metaballShader = createShader(
`
attribute vec3 aPosition;
uniform float width;
uniform float height;
varying highp vec2 vPos;
void main() {
gl_Position = vec4(aPosition, 1.0);
vPos = vec2(
(gl_Position.x + 1.) / 2. * width,
(gl_Position.y + 1.) / 2. * height
);
}`,
`
precision highp float;
#define BALLS 3
uniform highp vec2 positions[BALLS];
uniform float rs[BALLS];
uniform vec3 colors[BALLS];
varying highp vec2 vPos;
struct Metaball {
float r;
highp vec2 pos;
vec3 col;
};
vec4 getDist(Metaball ball) {
float dist = float(ball.r) / length(vPos - ball.pos);
return vec4(ball.col * dist, dist);
}
Metaball balls[BALLS];
void main() {
for (int i = 0; i < BALLS; i++) {
Metaball ball;
ball.pos = positions[i];
ball.r = rs[i];
ball.col = colors[i];
balls[i] = ball;
}
float total;
vec3 color;
for (int i = 0; i < BALLS; i++) {
vec4 colorValue = getDist(balls[i]);
total += colorValue.a;
color += colorValue.rgb;
}
float threshold = total > 0.016 ? 1. : 0.;
color /= total;
gl_FragColor = vec4(color * threshold, 1.);
}`
);
shader(metaballShader);
balls.push(new Ball(random(width), random(height), [1, 0, 0]));
balls.push(new Ball(random(width), random(height), [0, 1, 0]));
balls.push(new Ball(random(width), random(height), [0, 0, 1]));
metaballShader.setUniform('width', width);
metaballShader.setUniform('height', height);
metaballShader.setUniform('rs', balls.map(b => b.r));
metaballShader.setUniform('colors', balls.flatMap(b => b.color));
}
function draw() {
metaballShader.setUniform('positions', balls.flatMap(b => [b.pos.x, b.pos.y]));
quad(-1, -1, 1, -1, 1, 1, -1, 1);
for (const ball of balls) {
ball.update(balls);
}
}
const BASE_SPEED = 2.5;
class Ball {
constructor(x, y, color) {
this.pos = {
x: x,
y: y
}
this.angle = random(0, 2 * Math.PI);
this.vel = {
x: BASE_SPEED * cos(this.angle),
y: BASE_SPEED * sin(this.angle)
}
this.r = random(0.4, 0.9);
this.color = color;
}
update() {
this.pos.x += this.vel.x;
this.pos.y += this.vel.y;
if (this.pos.x < 0 || this.pos.x > width) this.vel.x *= -1;
if (this.pos.y < 0 || this.pos.y > height) this.vel.y *= -1;
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.6.0/p5.js"></script>