THREE.JS Mouse interaction with shader - three.js

I have simple point cloud shader, which renders points as circles on screen.
vertexShader:
uniform mat4 modelViewMatrix;
uniform mat4 projectionMatrix;
uniform vec3 cameraPosition;
uniform sampler2D texture;
uniform vec2 mouse;
uniform vec2 resolution;
attribute vec3 position;
attribute float radius;
attribute vec3 color;
varying vec3 vColor;
void main() {
vColor = color;
vec3 pos = position;
vec4 projected = projectionMatrix * modelViewMatrix * vec4(pos, 1.0);
gl_Position = projected;
gl_PointSize = radius;
}
fraqShader:
precision mediump float;
varying vec3 vColor;
uniform sampler2D texture;
uniform float useColor;
uniform vec2 mouse;
uniform vec2 resolution;
void main() {
float mx = mouse.x / resolution.x;
float my = mouse.y / resolution.y;
float d = sqrt((gl_PointCoord.x - mx)*(gl_PointCoord.x - mx) + (gl_PointCoord.y - mx)*(gl_PointCoord.y - mx));
if (useColor == 1.) {
gl_FragColor = vec4(vColor, 1.0);
} else {
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
}
gl_FragColor = gl_FragColor * texture2D(texture, gl_PointCoord);
if(d < 0.1) { gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0); }
}
The question is: Is possible to make a mouse interaction with these circles, I mean if the distance from current mouse position is less than point radius then the color would be different?
I have tried to set mouse.x and mouse.y to event_.clientX and event_.clinetY, then pass it to the shader trying to calculate distance:
float mx = mouse.x / resolution.x;
float my = mouse.y / resolution.y;
float d = sqrt((gl_PointCoord.x - mx)*(gl_PointCoord.x - mx) + (gl_PointCoord.y - mx)*(gl_PointCoord.y - mx));
But it doesn't work. Is there any solutions?/

gl_FragCoord.xy contains the window coordinates of the fragment. The lower left is (0,0) and the upper right is the width and height of the viewport in pixels.
Probably you have to flip the y coordinate of the mouse coordinates, because at screen coordinates the upper left is (0, 0) and the bottom right is the width and height of the window:
vec2 mc = vec2(mouse.x, u_resolution.y - mouse.y);
float d = length((mc - gl_FragCoord.xy) / u_resolutuon.xy);

var circularPoint = "data:image/png;base64,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eG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIiB4bWxuczpwaG90b3Nob3A9Imh0dHA6Ly9ucy5hZG9iZS5jb20vcGhvdG9zaG9wLzEuMC8iIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIChNYWNpbnRvc2gpIiB4bXA6Q3JlYXRlRGF0ZT0iMjAxOS0wMS0yMVQxNTozMzo0MSswMzowMCIgeG1wOk1ldGFkYXRhRGF0ZT0iMjAxOS0wMS0yMVQxNTozMzo0MSswMzowMCIgeG1wOk1vZGlmeURhdGU9IjIwMTktMDEtMjFUMTU6MzM6NDErMDM6MDAiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NzI5YjZjYzMtNWNmNy00MzllLWEwNWEtZTkzYTdhY2M4NjlhIiB4bXBNTTpEb2N1bWVudElEPSJhZG9iZTpkb2NpZDpwaG90b3Nob3A6OGM0MWExZjUtMGE3NS1iNjRhLThlY2UtYTI0YjRlNjdlZmE3IiB4bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ9InhtcC5kaWQ6NGU0ODQwM2UtNTYzNS00MmM4LTkxYzQtZjZlODIzMzk0MDg5IiBkYzpmb3JtYXQ9ImltYWdlL3BuZyIgcGhvdG9zaG9wOkNvbG9yTW9kZT0iMyIgcGhvdG9zaG9wOklDQ1Byb2ZpbGU9IkRpc3BsYXkiPiA8eG1wTU06SGlzdG9yeT4gPHJkZjpTZXE+IDxyZGY6bGkgc3RFdnQ6YWN0aW9uPSJjcmVhdGVkIiBzdEV2dDppbnN0YW5jZUlEPSJ4bXAuaWlkOjRlNDg0MDNlLTU2MzUtNDJjOC05MWM0LWY2ZTgyMzM5NDA4OSIgc3RFdnQ6d2hlbj0iMjAxOS0wMS0yMVQxNTozMzo0MSswMzowMCIgc3RFdnQ6c29mdHdhcmVBZ2VudD0iQWRvYmUgUGhvdG9zaG9wIENDIChNYWNpbnRvc2gpIi8+IDxyZGY6bGkgc3RFdnQ6YWN0aW9uPSJzYXZlZCIgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlp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";
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 1000);
camera.position.set(0, 0, 10);
var renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearColor(0xEEEEEE, 1.0);
document.body.appendChild(renderer.domElement);
var controls = new THREE.OrbitControls(camera, renderer.domElement);
var N = 2560;
var pallete = ["#FF6138", "#FFFF9D", "#BEEB9F", "#79BD8F", "#00A388"];
var verts = [], colors = [], rad = [], size = [], id = [], enabled = [];
for (let i = 0; i < N; i++) {
verts.push(getXYZ().multiplyScalar(5));
size.push(0.25 + Math.random() * 1.25);
rad.push(size[size.length - 1] * 1.0E-1 );
colors.push.apply(colors, randomRGB());
enabled.push(1);
var indx = new THREE.Color().setHex((i + 1));
id.push(indx.r, indx.g, indx.b);
}
var geometry = new THREE.BufferGeometry().setFromPoints(verts);
geometry.addAttribute("color", new THREE.BufferAttribute(new Float32Array(colors), 3));
geometry.addAttribute("id", new THREE.BufferAttribute(new Float32Array(id), 3));
geometry.addAttribute("size", new THREE.BufferAttribute(new Float32Array(size), 1));
geometry.addAttribute("rad", new THREE.BufferAttribute(new Float32Array(rad), 1));
geometry.addAttribute("enabled", new THREE.BufferAttribute(new Float32Array(enabled), 1));
var material = new THREE.ShaderMaterial({
uniforms: {
texture: {
value: new THREE.TextureLoader().load(circularPoint)
},
ori: {
value: new THREE.Vector3()
},
dir: {
value: new THREE.Vector3()
},
scale: {
value: window.innerHeight / 2
}
},
vertexShader: document.getElementById('vertexShader').textContent,
fragmentShader: document.getElementById('fragmentShader').textContent,
alphaTest: 0.9
})
var last_id = 0;
material.extensions.fragDepth = true;
material.extensions.drawBuffers = true;
var points = new THREE.Points(geometry, material);
scene.add(points);
var raycaster = new THREE.Raycaster();
var mouse = new THREE.Vector2();
var inverseMatrix = new THREE.Matrix4();
var ray = new THREE.Ray();
pickingScene = new THREE.Scene();
pickingTexture = new THREE.WebGLRenderTarget(window.innerWidth, window.innerHeight);
pickingTexture.texture.minFilter = THREE.LinearFilter;
pickingScene.add(points.clone());
renderer.domElement.addEventListener("mousemove", onMouseMove, false);
function onMouseMove(event) {
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
raycaster.setFromCamera(mouse, camera);
inverseMatrix.getInverse(points.matrixWorld);
ray.copy(raycaster.ray).applyMatrix4(inverseMatrix);
material.uniforms.ori.value = ray.origin;
material.uniforms.dir.value = ray.direction;
renderer.render(pickingScene, camera, pickingTexture);
var pixelBuffer = new Uint8Array(4);
renderer.readRenderTargetPixels(
pickingTexture, event.clientX, pickingTexture.height - event.clientY,
1, 1, pixelBuffer);
var id = (pixelBuffer[0] << 16) | (pixelBuffer[1] << 8) | (pixelBuffer[2]);
if(id < N){
last_id = id;
console.log("highlighted node: " + id);
for(var i = 0; i < N; i++){ if(i != (id)) { enabled[i] = 0.0; } }
points.geometry.attributes.enabled.needsUpdate = true;
}else if(id != last_id){
for(var i = 0; i < N; i++){ enabled[i] = 1.0; }
points.geometry.attributes.enabled.needsUpdate = true;
}
}
renderer.setAnimationLoop(() => { renderer.render(scene, camera) });
function getXYZ(){
var n = 1E1;
var rho = Math.random();
var theta = Math.random() * Math.PI * 2;
var phi = Math.random() * Math.PI * 2;
var x = rho * Math.cos(phi) * Math.sin(theta);
var y = rho * Math.sin(phi) * Math.sin(theta);
var z = rho * Math.cos(theta);
return new THREE.Vector3(x, y, z);
}
function randomRGB() {
var i = Math.floor(Math.random() * 5);
var hex = pallete[i];
var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result ? [
parseInt(result[1], 16) / 255,
parseInt(result[2], 16) / 255,
parseInt(result[3], 16) / 255
] : null;
}
body {
overflow: hidden;
margin: 0;
}
<script src="https://cdn.jsdelivr.net/npm/three#0.98.0/build/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three#0.98.0/examples/js/controls/OrbitControls.js"></script>
<script type='x-shader/x-vertex' id='vertexShader'>
uniform vec3 ori;
uniform vec3 dir;
attribute float rad;
attribute float size;
attribute vec3 color;
uniform float scale;
attribute float enabled;
attribute vec3 id;
varying vec3 vColor;
vec3 closestPointToPoint() {
vec3 target = position - ori;
float distance = dot(target, dir);
return dir * distance + ori;
}
void main() {
vColor = color;
if (length(position - closestPointToPoint()) < rad) if(enabled == 1.) { vColor = id; }
vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );
gl_PointSize = size * ( scale / -mvPosition.z );
gl_Position = projectionMatrix * mvPosition;
}
</script>
<script type='x-shader/x-fragment' id='fragmentShader'>
varying vec3 vColor;
uniform sampler2D texture;
void main() {
gl_FragColor = vec4(vColor, 1.);
gl_FragColor = gl_FragColor * texture2D(texture, gl_PointCoord);
if (gl_FragColor.a < 0.1) discard;
}
</script>
That's the result I was looking for, but any tweaks are welcome.
Thanks to prisoner849, it's mainly his code.

Related

Decompose a GLSL mat4 to original RTS values within vertex shader to calculate a View UV Offset

I need to get the rotation differences between the model and the camera, convert the values to radians/degrees, and pass it to the fragment shader.
For that I need to decompose and the Model rotation matrix and maybe the camera view matrix as well. I cannot seem to find a way to decompose mechanism suitable within a shader.
The rotation details goes into fragment shader to calculate uv offset.
original_rotation + viewing_angles to calculate a final sprite-like offset of the following texture and shown as billboards.
Ultimately UV should offset downwards (ex:H3 to A3) looking from down, upwards looking from up (ex:A3 to H3), left to right looking and viceversa looking from sides (ex: D1 to D8 and viceversa).
const vertex_shader = `
precision highp float;
uniform mat4 modelViewMatrix;
uniform mat4 projectionMatrix;
attribute vec3 position;
attribute vec2 uv;
attribute mat4 instanceMatrix;
attribute float index;
attribute float texture_index;
uniform vec2 rows_cols;
uniform vec3 camera_location;
varying float vTexIndex;
varying vec2 vUv;
varying vec4 transformed_normal;
float normal_to_orbit(vec3 rotation_vector, vec3 view_vector){
rotation_vector = normalize(rotation_vector);
view_vector = normalize(view_vector);
vec3 x_direction = vec3(1.0,0,0);
vec3 y_direction = vec3(0,1.0,0);
vec3 z_direction = vec3(0,0,1.0);
float rotation_x_length = dot(rotation_vector, x_direction);
float rotation_y_length = dot(rotation_vector, y_direction);
float rotation_z_length = dot(rotation_vector, z_direction);
float view_x_length = dot(view_vector, x_direction);
float view_y_length = dot(view_vector, y_direction);
float view_z_length = dot(view_vector, z_direction);
//TOP
float top_rotation = degrees(atan(rotation_x_length, rotation_z_length));
float top_view = degrees(atan(view_x_length, view_z_length));
float top_final = top_view-top_rotation;
float top_idx = floor(top_final/(360.0/rows_cols.x));
//FRONT
float front_rotation = degrees(atan(rotation_x_length, rotation_z_length));
float front_view = degrees(atan(view_x_length, view_z_length));
float front_final = front_view-front_rotation;
float front_idx = floor(front_final/(360.0/rows_cols.y));
return abs((front_idx*rows_cols.x)+top_idx);
}
vec3 extractEulerAngleXYZ(mat4 mat) {
vec3 rotangles = vec3(0,0,0);
rotangles.x = atan(mat[2].z, -mat[1].z);
float cosYangle = sqrt(pow(mat[0].x, 2.0) + pow(mat[0].y, 2.0));
rotangles.y = atan(cosYangle, mat[0].z);
float sinXangle = sin(rotangles.x);
float cosXangle = cos(rotangles.x);
rotangles.z = atan(cosXangle * mat[1].y + sinXangle * mat[2].y, cosXangle * mat[1].x + sinXangle * mat[2].x);
return rotangles;
}
float view_index(vec3 position, mat4 mv_matrix, mat4 rot_matrix){
vec4 posInView = mv_matrix * vec4(0.0, 0.0, 0.0, 1.0);
// posInView /= posInView[3];
vec3 VinView = normalize(-posInView.xyz); // (0, 0, 0) - posInView
// vec4 NinView = normalize(rot_matrix * vec4(0.0, 0.0, 1.0, 1.0));
// float NdotV = dot(NinView, VinView);
vec4 view_normal = rot_matrix * vec4(VinView.xyz, 1.0);
float view_x_length = dot(view_normal.xyz, vec3(1.0,0,0));
float view_y_length = dot(view_normal.xyz, vec3(0,1.0,0));
float view_z_length = dot(view_normal.xyz, vec3(0,0,1.0));
// float radians = atan(-view_x_length, -view_z_length);
float radians = atan(view_x_length, view_z_length);
// float angle = radians/PI*180.0 + 180.0;
float angle = degrees(radians);
if (radians < 0.0) { angle += 360.0; }
if (0.0<=angle && angle<=360.0){
return floor(angle/(360.0/rows_cols.x));
}
return 0.0;
}
void main(){
vec4 original_normal = vec4(0.0, 0.0, 1.0, 1.0);
// transformed_normal = modelViewMatrix * instanceMatrix * original_normal;
vec3 rotangles = extractEulerAngleXYZ(modelViewMatrix * instanceMatrix);
// transformed_normal = vec4(rotangles.xyz, 1.0);
transformed_normal = vec4(camera_location.xyz, 1.0);
vec4 v = (modelViewMatrix* instanceMatrix* vec4(0.0, 0.0, 0.0, 1.0)) + vec4(position.x, position.y, 0.0, 0.0) * vec4(1.0, 1.0, 1.0, 1.0);
vec4 model_center = (modelViewMatrix* instanceMatrix* vec4(0.0, 0.0, 0.0, 1.0));
vec4 model_normal = (modelViewMatrix* instanceMatrix* vec4(0.0, 0.0, 1.0, 1.0));
vec4 cam_loc = vec4(camera_location.xyz, 1.0);
vec4 view_vector = normalize((cam_loc-model_center));
//float findex = normal_to_orbit(model_normal.xyz, view_vector.xyz);
float findex = view_index(position, base_matrix, combined_rot);
vTexIndex = texture_index;
vUv = vec2(mod(findex,rows_cols.x)/rows_cols.x, floor(findex/rows_cols.x)/rows_cols.y) + (uv / rows_cols);
//vUv = vec2(mod(index,rows_cols.x)/rows_cols.x, floor(index/rows_cols.x)/rows_cols.y) + (uv / rows_cols);
gl_Position = projectionMatrix * v;
// gl_Position = projectionMatrix * modelViewMatrix * instanceMatrix * vec4(position, 1.0);
}
`
const fragment_shader = (texture_count) => {
var fragShader = `
precision highp float;
uniform sampler2D textures[${texture_count}];
varying float vTexIndex;
varying vec2 vUv;
varying vec4 transformed_normal;
void main() {
vec4 finalColor;
`;
for (var i = 0; i < texture_count; i++) {
if (i == 0) {
fragShader += `if (vTexIndex < ${i}.5) {
finalColor = texture2D(textures[${i}], vUv);
}
`
} else {
fragShader += `else if (vTexIndex < ${i}.5) {
finalColor = texture2D(textures[${i}], vUv);
}
`
}
}
//fragShader += `gl_FragColor = finalColor * transformed_normal; }`;
fragShader += `gl_FragColor = finalColor; }`;
// fragShader += `gl_FragColor = startColor * finalColor; }`;
// int index = int(v_TexIndex+0.5); //https://stackoverflow.com/questions/60896915/texture-slot-not-getting-picked-properly-in-shader-issue
//console.log('frag shader: ', fragShader)
return fragShader;
}
function reset_instance_positions() {
const dummy = new THREE.Object3D();
const offset = 500*4
for (var i = 0; i < max_instances; i++) {
dummy.position.set(offset-(Math.floor(i % 8)*500), offset-(Math.floor(i / 8)*500), 0);
dummy.updateMatrix();
mesh.setMatrixAt(i, dummy.matrix);
}
mesh.instanceMatrix.needsUpdate = true;
}
function setup_geometry() {
const geometry = new THREE.InstancedBufferGeometry().copy(new THREE.PlaneBufferGeometry(400, 400));
const index = new Float32Array(max_instances * 1); // index
for (let i = 0; i < max_instances; i++) {
index[i] = (i % max_instances) * 1.0 /* index[i] = 0.0 */
}
geometry.setAttribute("index", new THREE.InstancedBufferAttribute(index, 1));
const texture_index = new Float32Array(max_instances * 1); // texture_index
const max_maps = 1
for (let i = 0; i < max_instances; i++) {
texture_index[i] = (Math.floor(i / max_instances) % max_maps) * 1.0 /* index[i] = 0.0 */
}
geometry.setAttribute("texture_index", new THREE.InstancedBufferAttribute(texture_index, 1));
const textures = [texture]
const grid_xy = new THREE.Vector2(8, 8)
mesh = new THREE.InstancedMesh(geometry,
new THREE.RawShaderMaterial({
uniforms: {
textures: {
type: 'tv',
value: textures
},
rows_cols: {
value: new THREE.Vector2(grid_xy.x * 1.0, grid_xy.y * 1.0)
},
camera_location: {
value: camera.position
}
},
vertexShader: vertex_shader,
fragmentShader: fragment_shader(textures.length),
side: THREE.DoubleSide,
// transparent: true,
}), max_instances);
scene.add(mesh);
reset_instance_positions()
}
var camera, scene, mesh, renderer;
const max_instances = 64
function init() {
camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight,1, 10000 );
camera.position.z = 1024;
scene = new THREE.Scene();
scene.background = new THREE.Color(0xffffff);
setup_geometry()
var canvas = document.createElement('canvas');
var context = canvas.getContext('webgl2');
renderer = new THREE.WebGLRenderer({
canvas: canvas,
context: context
});
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
window.addEventListener('resize', onWindowResize, false);
var controls = new THREE.OrbitControls(camera, renderer.domElement);
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
function animate() {
requestAnimationFrame(animate);
renderer.render(scene, camera);
}
var dataurl = "https://i.stack.imgur.com/accaU.png"
var texture;
var imageElement = document.createElement('img');
imageElement.onload = function(e) {
texture = new THREE.Texture(this);
texture.needsUpdate = true;
init();
animate();
};
imageElement.src = dataurl;
JSFiddle of work so far
So You got 4x4 transform matrix M used on xy plane QUAD and want to map its 4 corners (p0,p1,p2,p3) to your texture with "repaeat" like manner (crossing border from left/right/up/down will return right/left/down/up) based on direction of Z axis of the matrix.
You face 2 problems...
M rotation is 3 DOF and you want just 2 DOF (yaw,pitch) so if roll present the result might be questionable
if texture crosses borders you need to handle this in GLSL to avoid seems
so either do this in geometry shader and divide the quad to more if needed or use enlarged texture where you have the needed overlaps ...
Now if I did not miss something the conversion is like this:
const float pi=3.1415926535897932384626433832795;
vec3 d = normalize(z axis from M);
vec2 dd = normalize(d.xy);
u = atan2(dd.y,dd.x);
v = acos(d.z);
u = (u+pi)/(2.0*pi);
v = v/pi
The z axis extraction is just simple copy of 3th column/row (depends on your notation) from your matrix 'M' or transforming (1,0,0,0) by it. For more info see:
Understanding 4x4 homogenous transform matrices
In case of overlapped texture you need to add also this:
const float ov = 1.0/8.0; // overlap size
u = ov + (u/(ov+ov+1.0));
v = ov + (v/(ov+ov+1.0));
And the texture would look like:
In case your quads cover more than 1/8 of your original texture you need to enlarge the overlap ...
Now to handle the corners of QUAD instead of just axis you could translate the quad by distance l in Z+ direction in mesh local coordinates, apply the M on them and use those 4 points as directions to compute u,v in vertex shader. The l will affect how much of the texture area is used for quad ... This approach might even handle roll but did not test any of this yet...
After implementing it my fears was well grounded as any 2 euler angles affect each other so the result is OK on most of the directions but in edge cases the stuff get mirrored and or jumped in one or both axises probably due to area coverage difference between 3 DOF and 2 DOF (unless I made a bug in my code or the math was not computed correctly in vertex which happened to me before due to bug in drivers)
If you going for azimut/elevation that should be fine as its 2 DOF too the equation above shoul dwork for them too +/- some range conversion if needed.

How to Create a Multi-Colored Noise Shader

I need a shader that takes 3 input colors and generates noise as seen below.
It is easy to achieve in Blender with the help of the "Noise Texture" and the "Color Ramp" nodes.
I've found this gist, which might solve my problem. But I wasn't able to configure the colors. And also the noise looks a lot "sharper" than the result in Blender.
Will I need to write my own shader for this or is there a simpler way to achieve this effect with ThreeJS?
Basically, you need just two things:
Noise function in shader
A gradiental texture.
I chose FBM for noise and used .onBeforeCompile() to change a built-in material (Standard):
body{
overflow: hidden;
margin: 0;
}
<canvas id="cnvsGradient" width="300" height="50" style="position: absolute; margin: 10px; border: 1px solid aqua"/>
<script>
// https://github.com/yiwenl/glsl-fbm/blob/master/3d.glsl
const fbm = `
#define NUM_OCTAVES 5
float mod289(float x){return x - floor(x * (1.0 / 289.0)) * 289.0;}
vec4 mod289(vec4 x){return x - floor(x * (1.0 / 289.0)) * 289.0;}
vec4 perm(vec4 x){return mod289(((x * 34.0) + 1.0) * x);}
float noise(vec3 p){
vec3 a = floor(p);
vec3 d = p - a;
d = d * d * (3.0 - 2.0 * d);
vec4 b = a.xxyy + vec4(0.0, 1.0, 0.0, 1.0);
vec4 k1 = perm(b.xyxy);
vec4 k2 = perm(k1.xyxy + b.zzww);
vec4 c = k2 + a.zzzz;
vec4 k3 = perm(c);
vec4 k4 = perm(c + 1.0);
vec4 o1 = fract(k3 * (1.0 / 41.0));
vec4 o2 = fract(k4 * (1.0 / 41.0));
vec4 o3 = o2 * d.z + o1 * (1.0 - d.z);
vec2 o4 = o3.yw * d.x + o3.xz * (1.0 - d.x);
return o4.y * d.y + o4.x * (1.0 - d.y);
}
float fbm(vec3 x) {
float v = 0.0;
float a = 0.5;
vec3 shift = vec3(100);
for (int i = 0; i < NUM_OCTAVES; ++i) {
v += a * noise(x);
x = x * 2.0 + shift;
a *= 0.5;
}
return v;
}
`;
</script>
<script type="module">
console.clear();
import * as THREE from "https://cdn.skypack.dev/three#0.136.0";
import {OrbitControls} from "https://cdn.skypack.dev/three#0.136.0/examples/jsm/controls/OrbitControls.js";
let scene = new THREE.Scene();
let camera = new THREE.PerspectiveCamera(60, innerWidth / innerHeight, 1, 100);
camera.position.set(0, 0, 10);
let renderer = new THREE.WebGLRenderer();
renderer.setSize(innerWidth, innerHeight);
renderer.setClearColor(0x444444);
document.body.appendChild(renderer.domElement);
let controls = new OrbitControls(camera, renderer.domElement);
let light = new THREE.DirectionalLight(0xffffff);
light.position.setScalar(10);
scene.add(light);
scene.add(new THREE.AmbientLight(0xffffff, 0.5));
let g = new THREE.CylinderBufferGeometry(2, 1, 5, 6, 64);
let pos = g.attributes.position;
let v = new THREE.Vector3();
let axis = new THREE.Vector3(0, 1, 0);
for(let i = 0; i < pos.count; i++){
v.fromBufferAttribute(pos, i);
let ratio = (v.y - (-2.5)) / 5;
v.applyAxisAngle(axis, THREE.MathUtils.degToRad(60) * ratio);
pos.setXYZ(i, v.x, v.y, v.z);
}
g.computeVertexNormals();
let uniforms = {
tex: {
value: setGradient()
}
}
let m = new THREE.MeshStandardMaterial({
metalness: 0.25,
roughness: 0.75,
onBeforeCompile: shader => {
shader.uniforms.tex = uniforms.tex;
shader.vertexShader = `
varying vec3 vPos;
${shader.vertexShader}
`.replace(
`#include <begin_vertex>`,
`#include <begin_vertex>
//vPos = (modelMatrix * vec4(position, 1.0)).xyz;
vPos = vec3(position);
`
);
//console.log(shader.vertexShader);
shader.fragmentShader = `
uniform sampler2D tex;
varying vec3 vPos;
${fbm}
${shader.fragmentShader}
`.replace(
`vec4 diffuseColor = vec4( diffuse, opacity );`,
`
float d = fbm(vPos * 0.5);
for(int i = 0; i < 4; i++){
d = fbm(vPos * (float(i) + 1.) * d);
}
vec3 col = texture(tex, vec2(d, 0.5)).rgb;
vec4 diffuseColor = vec4( col, opacity );`
);
//console.log(shader.fragmentShader);
}
});
let o = new THREE.Mesh(g, m);
scene.add(o);
window.addEventListener( 'resize', onWindowResize, false );
renderer.setAnimationLoop(() => {
o.rotation.y += 0.01;
renderer.render(scene, camera);
});
function setGradient(){
let canvas = document.getElementById('cnvsGradient');
let ctx = canvas.getContext('2d');
let gradient = ctx.createLinearGradient(0,0, 300,0);
gradient.addColorStop(0.15, 'yellow');
gradient.addColorStop(.5, 'red');
gradient.addColorStop(0.85, 'blue');
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, canvas.width, canvas.height);
return new THREE.CanvasTexture(canvas);
}
function onWindowResize() {
camera.aspect = innerWidth / innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( innerWidth, innerHeight );
}
</script>

THREE.JS Metaballs animation at fragment shader

I have the code where I am trying to make a metaballs motion transition between particles like Shadertoy sketch https://www.shadertoy.com/view/wtd3Wn
So, in the end, I need something like this:
Eventually, it has to be animated as well.
To do this, I have created another shader [Joint] with width/height equal to the distance between particles and it's already got their position, so if you call
gl_FragColor = vec4(1.0 - circle(fragCoord, startPos, 16.0 ) * circle(fragCoord, endPos, 16.0 ) );
it would overdraw circles at their positions.
But when I am trying to draw metaballs, I've got nothing.
Pretty sure that I am doing something wrong with fragCoord initialization or I have to tweak these parameters:
const float threshold = 3.0;
const float density = 1000.0;
const float norm = 2.0;
and
balls[0] = setMetaball(vec2(0.0, 0.0), 0.2, vec3(0.0, 0.0, 1.0));
balls[1] = setMetaball(startPos, 0.05, vec3(0.0, 0.0, 1.0));
balls[2] = setMetaball(endPos, 0.1, vec3(0.0, 1.0, 0.0));
balls[3] = setMetaball(endPos, 0.05, vec3(0.0, 1.0, 0.0));
positions and radiuses
THREE.TOUCH = {};
var circularPoint = 'data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="128px" height="128px"><circle cx="64" cy="64" r="62" fill="white" /></svg>';
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(35, innerWidth / innerHeight, 1, 1000);
camera.position.set(0, 0, 20);
var renderer = new THREE.WebGLRenderer();
renderer.setSize(innerWidth, innerHeight);
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setClearColor(0xEEEEEE, 1.0);
document.body.appendChild(renderer.domElement);
var controls = new THREE.OrbitControls(camera, renderer.domElement);
var N = 4,
verts = [],
colors = [],
radius = [];
verts = [
new THREE.Vector3(0.0, 0.0, -4.0),
new THREE.Vector3(-4.0, 0.0, 0.0),
new THREE.Vector3(4.0, 0.0, 0.0),
new THREE.Vector3(0.0, 0.0, 4.0)
];
colors.push(1.0, 0., 1.0);
colors.push(1.0, 0., 1.0);
colors.push(0.847, 0.332, 0.347);
colors.push(0.457, 0.695, 0.675);
for (var i = 0; i < N; i++) {
radius.push(0.5);
}
var pointsGeometry = new THREE.BufferGeometry().setFromPoints(verts);
pointsGeometry.addAttribute("color", new THREE.BufferAttribute(new Float32Array(colors), 3));
pointsGeometry.addAttribute("radius", new THREE.BufferAttribute(new Float32Array(radius), 1));
var pointsMaterial = new THREE.ShaderMaterial({
uniforms: {
viewport: {
value: window.innerHeight * window.devicePixelRatio
},
texture: {
value: new THREE.TextureLoader().load(circularPoint)
},
resolution: {
value: [innerWidth * 2, innerHeight * 2]
}
},
vertexShader: document.getElementById("vertexParticle").textContent,
fragmentShader: document.getElementById("fragmentParticle").textContent,
transparent: true
})
var verts2 = [],
colors2 = [],
radius2 = [];
var dist = new THREE.Vector3(-4.0, 0.0, 0.0).distanceTo(new THREE.Vector3(0.0, 0.0, -4.0));
for (var i = 0; i < 1; i++) {
for (var j = i + 1; j < 2; j++) {
var dist = verts[i].distanceTo(verts[j]);
var nv = new THREE.Vector3(verts[i].x, verts[i].y, verts[i].z);
verts2.push(nv.lerp(verts[j], 0.5));
radius2.push(dist / 2.0 + 0.5);
}
}
var jointsGeometry = new THREE.BufferGeometry().setFromPoints(verts2);
jointsGeometry.addAttribute("radius", new THREE.BufferAttribute(new Float32Array(radius2), 1));
var jointsMaterial = new THREE.ShaderMaterial({
uniforms: {
viewport: {
value: innerHeight * devicePixelRatio
},
texture: {
value: new THREE.TextureLoader().load(circularPoint)
},
start: {
value: verts[0]
},
end: {
value: verts[1]
},
resolution: {
value: [innerWidth * 2, innerHeight * 2]
}
},
vertexShader: document.getElementById("vertexJoint").textContent,
fragmentShader: document.getElementById("fragmentJoint").textContent,
transparent: true
})
jointsMaterial.extensions.derivatives = true;
jointsMaterial.extensions.fragDepth = true;
jointsMaterial.extensions.drawBuffers = true;
var points = new THREE.Points(pointsGeometry, pointsMaterial);
scene.add(points);
var joints = new THREE.Points(jointsGeometry, jointsMaterial);
scene.add(joints);
renderer.setAnimationLoop(function() {
renderer.render(scene, camera)
});
body { overflow: hidden; margin: 0; }
<html>
<head>
<meta charset="utf-8">
<title>THREE.JS METABALLS</title>
<meta name="description" content="Ehno based on D3.JS | THREE.JS stack.">
<meta name="keywords" content="HTML,CSS,CSV,JavaScript,D3.JS,THREE.JS">
<meta name="author" content="Vladimir V. KUCHINOV">
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/98/three.min.js"></script>
</head>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/102/three.min.js"></script>
<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>
<script type="x-shader/x-vertex" id="vertexParticle">
#define PI 3.141592
attribute float radius;
attribute vec3 color;
uniform float viewport;
varying vec3 vColor;
void main() {
vColor = color;
vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );
gl_PointSize = viewport * radius * PI / -mvPosition.z;
gl_Position = projectionMatrix * mvPosition;
}
</script>
<script type="x-shader/x-fragment" id="fragmentParticle">
varying vec3 vColor;
uniform sampler2D texture;
uniform vec2 resolution;
void main() {
gl_FragColor = vec4(vColor, 1.) * texture2D(texture, gl_PointCoord);
if (gl_FragColor.a < 0.1) discard;
}
</script>
<script type="x-shader/x-vertex" id="vertexJoint">
#define PI 3.141592
attribute float radius;
uniform float viewport;
uniform vec2 resolution;
uniform vec3 start;
uniform vec3 end;
varying vec2 outStart;
varying vec2 outEnd;
varying vec2 vUv;
vec2 projectWorldCoordinates(vec3 in_){
vec4 p = projectionMatrix * modelViewMatrix * vec4(in_.xy, in_.z, 1.0);
return p.xy / p.w;
}
void main() {
vUv = uv;
outStart = projectWorldCoordinates(start);
outEnd = projectWorldCoordinates(end);
vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );
gl_PointSize = viewport * radius * PI / -mvPosition.z;
gl_Position = projectionMatrix * mvPosition;
}
</script>
<script type="x-shader/x-fragment" id="fragmentJoint">
uniform vec2 resolution;
varying vec2 outStart;
varying vec2 outEnd;
varying vec2 vUv;
const float threshold = 3.0;
const float density = 1000.0;
const float norm = 2.0;
vec2 startPos;
vec2 endPos;
float pixelPower;
vec2 fragCoord;
vec4 mixColor;
struct Metaball{
vec2 position;
float radius;
vec3 color;
float power;
};
Metaball balls[4];
float Norm(float num_) { return pow(num_, norm); }
Metaball setMetaball(vec2 position_, float radius_, vec3 color_){
Metaball m;
m.position = position_;
m.radius = radius_;
m.color = color_;
vec2 pixelPosition = fragCoord.xy / resolution.xy;
pixelPosition.x = pixelPosition.x * resolution.x / resolution.y;
vec2 distanceVector = pixelPosition - position_;
distanceVector = vec2(abs(distanceVector.x), abs(distanceVector.y));
float normDistance = Norm(distanceVector.x) + Norm(distanceVector.y);
m.power = Norm(radius_) / normDistance;
return m;
}
vec4 drawMetaballs(){
vec4 color = vec4(0.);
vec3 val;
int powerMeta = 0;
float maxPower = 0.0;
balls[0] = setMetaball(vec2(0.0, 0.0), 0.2, vec3(0.0, 0.0, 1.0));
balls[1] = setMetaball(startPos, 0.05, vec3(0.0, 0.0, 1.0));
balls[2] = setMetaball(endPos, 0.1, vec3(0.0, 1.0, 0.0));
balls[3] = setMetaball(endPos, 0.05, vec3(0.0, 1.0, 0.0));
for(int i = 0; i < 4; i++){
pixelPower += balls[i].power;
if(maxPower < balls[i].power){
maxPower = balls[i].power;
powerMeta = i;
}
balls[i].power *= balls[i].radius;
}
val = vec3(0.);
for(int i = 0; i < 4; i++){
vec2 pixelPosition = fragCoord / resolution.xy;
pixelPosition.x = pixelPosition.x * resolution.x / resolution.y;
vec2 distanceVector = pixelPosition - balls[i].position;
distanceVector = vec2(abs(distanceVector.x), abs(distanceVector.y));
float normDistance = Norm(distanceVector.x) + Norm(distanceVector.y);
balls[i].power = Norm(balls[i].radius) / normDistance;
val += balls[i].color * (balls[i].power / maxPower);
}
if(pixelPower < threshold || pixelPower > threshold + Norm(density))
{
val = vec3(0.0);
}
color = vec4(val, 1.0);
return color;
}
float circle( vec2 _st, vec2 _center, float _radius ){
const float thickness = 8.0;
float dist = length(_st - _center);
return smoothstep(0.0, thickness / 2.0, abs(_radius - dist));
}
void main() {
vec2 fragCoord = vec2(gl_FragCoord.x, gl_FragCoord.y);
vec2 uv = (fragCoord * 2.0 - resolution.xy) / resolution.y;
startPos = (outStart.xy * 0.5 + 0.5) * resolution.xy;
endPos = (outEnd.xy * 0.5 + 0.5) * resolution.xy;
vec4 mb = drawMetaballs();
gl_FragColor = mb; //vec4(1.0 - circle(fragCoord, startPos, 16.0 ) * circle(fragCoord, endPos, 16.0 ) );
}
</script>
</body>
</html>
Done.
<html>
<head>
<meta charset="utf-8">
<title>THREE.JS METABALLS</title>
<meta name="description" content="Ehno based on D3.JS | THREE.JS stack.">
<meta name="keywords" content="HTML,CSS,CSV,JavaScript,D3.JS,THREE.JS">
<meta name="author" content="Vladimir V. KUCHINOV">
<style>
body { overflow: hidden; margin: 0; }
</style>
</head>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/103/three.min.js"></script>
<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>
<!--
<script src="LineMaterial.js"></script>
<script src="LineSegments2.js"></script>
<script src="LineSegmentsGeometry.js"></script>
<script src="LineGeometry.js"></script>
<script src="Line2.js"></script>
-->
<script type="x-shader/x-vertex" id="vertexParticle">
#define PI 3.141592
attribute float radius;
attribute vec3 color;
uniform float viewport;
varying vec3 vColor;
void main() {
vColor = color;
vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );
gl_PointSize = viewport * radius * PI / -mvPosition.z;
gl_Position = projectionMatrix * mvPosition;
}
</script>
<script type="x-shader/x-fragment" id="fragmentParticle">
varying vec3 vColor;
uniform sampler2D texture;
uniform vec2 resolution;
void main() {
gl_FragColor = vec4(vColor, 1.) * texture2D(texture, gl_PointCoord);
if (gl_FragColor.a < 0.5) discard;
}
</script>
<script type="x-shader/x-vertex" id="vertexJoint">
#define PI 3.141592
attribute float radius;
uniform float viewport;
uniform vec2 resolution;
uniform vec3 start;
uniform vec3 end;
varying vec2 outStart;
varying vec2 outEnd;
varying float size;
vec2 projectWorldCoordinates(vec3 in_){
vec4 p = projectionMatrix * modelViewMatrix * vec4(in_.xy, in_.z, 1.0);
return p.xy / p.w;
}
void main() {
outStart = projectWorldCoordinates(start);
outEnd = projectWorldCoordinates(end);
vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );
gl_PointSize = viewport * radius * PI / -mvPosition.z;
size = viewport * radius * PI / -mvPosition.z;
gl_Position = projectionMatrix * mvPosition;
}
</script>
<script type="x-shader/x-fragment" id="fragmentJoint">
uniform vec2 resolution;
uniform float time;
varying vec2 outStart;
varying vec2 outEnd;
varying float size;
const float threshold = 3.0;
const float density = 1000.0;
const float norm = 2.0;
vec2 startPos;
vec2 endPos;
float pixelPower;
vec2 fragCoord;
vec4 mixColor;
struct Metaball{
vec2 position;
float radius;
vec4 color;
float power;
};
Metaball balls[4];
float Norm(float num_) { return pow(num_, norm); }
Metaball setMetaball(vec2 position_, float radius_, vec4 color_, vec2 uv_){
Metaball m;
m.position = position_;
m.radius = radius_;
m.color = color_;
vec2 distanceVector = uv_ - position_;
distanceVector = vec2(abs(distanceVector.x), abs(distanceVector.y));
float normDistance = Norm(distanceVector.x) + Norm(distanceVector.y);
m.power = Norm(radius_) / normDistance;
return m;
}
vec4 drawMetaballs(vec2 p0_, vec2 p1_, vec2 uv_){
vec4 val;
int powerMeta = 0;
float maxPower = 0.0;
vec2 p0 = startPos;
vec2 p1 = endPos;
vec2 d0 = mix(p0, p1, -(sin(time) - 1.0) / 4.);
vec2 d1 = mix(p1, p0, -(sin(time) - 1.0) / 4.);
float rad = size / 8.0;
balls[0] = setMetaball(p0, rad, vec4(0.0, 1.0, 1.0, 1.0), uv_);
balls[1] = setMetaball(d0, rad * 0.75, vec4(0.0, 1.0, 1.0, 1.0), uv_);
balls[2] = setMetaball(p1, rad, vec4(1.0, 0.0, 1.0, 1.0), uv_);
balls[3] = setMetaball(d1, rad * 0.75, vec4(1.0, 0.0, 1.0, 1.0), uv_);
for(int i = 0; i < 4; i++){
pixelPower += balls[i].power;
if(maxPower < balls[i].power){
maxPower = balls[i].power;
powerMeta = i;
}
balls[i].power *= balls[i].radius;
}
val = vec4(0.);
for(int i = 0; i < 4; i++){
vec2 distanceVector = uv_ - balls[i].position;
distanceVector = vec2(abs(distanceVector.x), abs(distanceVector.y));
float normDistance = Norm(distanceVector.x) + Norm(distanceVector.y);
balls[i].power = Norm(balls[i].radius) / normDistance;
val += balls[i].color * (balls[i].power / maxPower);
}
if(pixelPower < threshold || pixelPower > threshold + Norm(density))
{
val = vec4(0.0);
}
return vec4(val);
}
float DigitBin( const int x_ )
{
return x_==0?480599.0:x_==1?139810.0:x_==2?476951.0:x_==3?476999.0:x_==4?350020.0:x_==5?464711.0:x_==6?464727.0:x_==7?476228.0:x_==8?481111.0:x_==9?481095.0:0.0;
}
float PrintValue( const vec2 position_, const float string_, const float maxDigits_, const float decimal_ )
{
if ((position_.y < 0.0) || (position_.y >= 1.0)) return 0.0;
float fLog10Value = log2(abs(string_)) / log2(10.0);
float fBiggestIndex = max(floor(fLog10Value), 0.0);
float fDigitIndex = maxDigits_ - floor(position_.x);
float fCharBin = 0.0;
if(fDigitIndex > (-decimal_ - 1.01)) {
if(fDigitIndex > fBiggestIndex) {
if((string_ < 0.0) && (fDigitIndex < (fBiggestIndex+1.5))) fCharBin = 1792.0;
} else {
if(fDigitIndex == -1.0) {
if(decimal_ > 0.0) fCharBin = 2.0;
} else {
float fReducedRangeValue = string_;
if(fDigitIndex < 0.0) { fReducedRangeValue = fract( string_ ); fDigitIndex += 1.0; }
float fDigitValue = (abs(fReducedRangeValue / (pow(10.0, fDigitIndex))));
fCharBin = DigitBin(int(floor(mod(fDigitValue, 10.0))));
}
}
}
return floor(mod((fCharBin / pow(2.0, floor(fract(position_.x) * 4.0) + (floor(position_.y * 5.0) * 4.0))), 2.0));
}
float smooth_circle(float st, vec2 pos, float rad, vec2 uv)
{
return 1.0 - smoothstep(rad * rad - st, rad * rad, dot(uv - pos, uv - pos));
}
void main() {
vec2 fontSize = vec2(0.1, -0.1);
fragCoord = -1.0 + 2.0 * gl_PointCoord;
vec2 fragCoord2 = vec2(gl_FragCoord.x, gl_FragCoord.y);
vec2 uv = (fragCoord2 * 2.0 - resolution.xy) / resolution.y;
startPos = (outStart.xy * 0.5 + 0.5) * resolution.xy;
endPos = (outEnd.xy * 0.5 + 0.5) * resolution.xy;
vec3 debug = vec3( PrintValue( (fragCoord + vec2(1.25, -0.75)) / fontSize, gl_FragCoord.x, 5.0, 2.0) );
//startPos, endPos in screen coords (pixels)
vec3 circ = vec3(smooth_circle(1.0, startPos, 10.0, fragCoord2));
vec3 circ2 = vec3(smooth_circle(1.0, endPos, 10.0, fragCoord2));
//local: -1.0...1.0;
vec3 circ3 = vec3(smooth_circle(0.0, vec2(-1.), 0.25, fragCoord));
debug = debug + circ + circ2 + circ3;
gl_FragColor = vec4(drawMetaballs(startPos, endPos, fragCoord2));
}
</script>
<script>
THREE.TOUCH = {};
var joints, uniforms;
var circularPoint = 'data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="128px" height="128px"><circle cx="64" cy="64" r="62" fill="white" /></svg>';
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 1, 1000);
camera.position.set(0, 0, 200);
var renderer = new THREE.WebGLRenderer();
renderer.setSize(innerWidth, innerHeight);
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setClearColor(0xEEEEEE, 1.0);
document.body.appendChild(renderer.domElement);
var controls = new THREE.OrbitControls(camera, renderer.domElement);
var N = 4,
verts = [],
colors = [],
radius = [];
verts = [
new THREE.Vector3(0.0, 0.0, -4.0),
new THREE.Vector3(-4.0, 0.0, 0.0),
new THREE.Vector3(4.0, 0.0, 0.0),
new THREE.Vector3(0.0, 0.0, 4.0)
];
colors.push(0.0, 1.0, 1.0);
colors.push(1.0, 0., 1.0);
colors.push(0.847, 0.332, 0.347);
colors.push(0.457, 0.695, 0.675);
for (var i = 0; i < N; i++) {
radius.push(0.5);
}
var pointsGeometry = new THREE.BufferGeometry().setFromPoints(verts);
pointsGeometry.addAttribute("color", new THREE.BufferAttribute(new Float32Array(colors), 3));
pointsGeometry.addAttribute("radius", new THREE.BufferAttribute(new Float32Array(radius), 1));
var pointsMaterial = new THREE.ShaderMaterial({
uniforms: {
viewport: {
value: window.innerHeight * window.devicePixelRatio
},
texture: {
value: new THREE.TextureLoader().load(circularPoint)
},
resolution: {
value: [innerWidth * window.devicePixelRatio, innerHeight * window.devicePixelRatio]
}
},
vertexShader: document.getElementById("vertexParticle").textContent,
fragmentShader: document.getElementById("fragmentParticle").textContent,
transparent: true
})
var verts2 = [],
colors2 = [],
radius2 = [];
var dist = new THREE.Vector3(-4.0, 0.0, 0.0).distanceTo(new THREE.Vector3(0.0, 0.0, -4.0));
for (var i = 0; i < 1; i++) {
for (var j = i + 1; j < 2; j++) {
var dist = verts[i].distanceTo(verts[j]);
var nv = new THREE.Vector3(verts[i].x, verts[i].y, verts[i].z);
verts2.push(nv.lerp(verts[j], 0.5));
radius2.push(dist / 2.0 + 0.5);
}
}
var jointsGeometry = new THREE.BufferGeometry().setFromPoints(verts2);
jointsGeometry.addAttribute("radius", new THREE.BufferAttribute(new Float32Array(radius2), 1));
uniforms = {
viewport: {
value: innerHeight * devicePixelRatio
},
texture: {
value: new THREE.TextureLoader().load(circularPoint)
},
time: { value: 0.0 },
start: {
value: verts[0]
},
end: {
value: verts[1]
},
resolution: {
value: new THREE.Vector2(window.innerWidth * window.devicePixelRatio, window.innerHeight * window.devicePixelRatio)
}
};
var jointsMaterial = new THREE.ShaderMaterial({
uniforms,
vertexShader: document.getElementById("vertexJoint").textContent,
fragmentShader: document.getElementById("fragmentJoint").textContent,
transparent: true
})
jointsMaterial.extensions.derivatives = true;
jointsMaterial.extensions.fragDepth = true;
jointsMaterial.extensions.drawBuffers = true;
var points = new THREE.Points(pointsGeometry, pointsMaterial);
scene.add(points);
joints = new THREE.Points(jointsGeometry, jointsMaterial);
scene.add(joints);
renderer.setAnimationLoop(function(timestamp) {
uniforms[ "time" ].value = timestamp / 1000;
renderer.render(scene, camera)
});
</script>
</body>
</html>

How can textures with transparent spots be correctly applied to multiple stacked plane instances in threejs?

I'm creating 512 instances of the same 1x1 plane with a texture that has transparent areas. The planes are randomly spread around the origin like the image below.
How can the planes in front be drawn after the planes behind so that the transparency of the planes in front take into account the output of the planes from behind?
(with depthTest disabled)
(with depthTest normal)
For reference, the transparency disabled version of the instanced geometry. This proves that the planes are correctly positioned.
Update:
Adding code as asked:
import {
Mesh,
ShaderMaterial,
Vector3,
PlaneBufferGeometry,
EdgesGeometry,
LineBasicMaterial,
LineSegments,
InstancedBufferAttribute,
UniformsLib,
BufferAttribute,
TextureLoader,
InstancedBufferGeometry,
DoubleSide,
} from 'three'
import path from 'path'
import fs from 'fs'
import {
randomValueBetween,
} from '../../utils'
const vertexShader = fs.readFileSync(path.resolve(__dirname, './assets/vertex.glsl'), 'utf8')
const fragmentShader = fs.readFileSync(path.resolve(__dirname, './assets/fragment.glsl'), 'utf8')
const createInstancedAtrributes = (geometry, instanceCount) => {
const startseed = new InstancedBufferAttribute(new Float32Array(instanceCount * 1), 1)
const scale = new InstancedBufferAttribute(new Float32Array(instanceCount * 3), 3)
const offset = new InstancedBufferAttribute(new Float32Array(instanceCount * 2), 2)
const orientationY = new InstancedBufferAttribute(new Float32Array(instanceCount), 1)
const baseScale = 0.5
for (let i = 0; i < instanceCount; i += 1) {
scale.setXYZ(i,
baseScale * randomValueBetween(0.8, 1.3, 1),
baseScale * randomValueBetween(0.8, 1.3, 1),
baseScale * randomValueBetween(0.8, 1.3, 1),
)
orientationY.setX(i, randomValueBetween(0.0, 1.0, 3))
startseed.setX(i, randomValueBetween(1, 3, 1))
}
for (let i = 0; i < instanceCount / 4; i += 4) {
const randomX = randomValueBetween(-3.5, 3.5, 1)
const randomY = randomValueBetween(-3.5, 3.5, 1)
offset.setXY(i, randomX, randomY)
}
geometry.addAttribute('scale', scale)
geometry.addAttribute('offset', offset)
geometry.addAttribute('startseed', offset)
geometry.addAttribute('orientationY', offset)
return { scale, offset }
}
const createInstancedGeometry = (instancePerUnitCount) => {
const geometry = new InstancedBufferGeometry()
geometry.maxInstancedCount = instancePerUnitCount
const shape = new PlaneBufferGeometry(1, 1, 1, 3)
const data = shape.attributes
geometry.addAttribute('position', new BufferAttribute(new Float32Array(data.position.array), 3))
geometry.addAttribute('uv', new BufferAttribute(new Float32Array(data.uv.array), 2))
geometry.addAttribute('normal', new BufferAttribute(new Float32Array(data.normal.array), 3))
geometry.setIndex(new BufferAttribute(new Uint16Array(shape.index.array), 1))
shape.dispose()
createInstancedAtrributes(geometry, instancePerUnitCount)
return geometry
}
export default class GrassDeform extends Mesh {
constructor() {
const geometry = createInstancedGeometry(8 * 256)
const uniforms = {
uTime: {
type: 'f',
value: 0,
},
uMap: {
type: 't',
value: null,
},
}
const textureLoader = new TextureLoader()
textureLoader.load(path.resolve(__dirname, './assets/grass-texture-01.png'), (t) => {
uniforms.uMap.value = t
})
const material = new ShaderMaterial({
uniforms: Object.assign({},
UniformsLib.ambient,
UniformsLib.lights,
uniforms,
),
vertexShader,
fragmentShader,
lights: true,
transparent: true,
side: DoubleSide,
})
super(geometry, material)
this.geometry = geometry
this.material = material
this.up = new Vector3(0, 0, 1)
const lineGeo = new EdgesGeometry(geometry) // or WireframeGeometry
const mat = new LineBasicMaterial({ color: 0xffffff, linewidth: 2 })
const wireframe = new LineSegments(lineGeo, mat)
this.add(wireframe)
this.frustumCulled = false
}
update({ ellapsedTime }) {
this.material.uniforms.uTime.value = ellapsedTime
}
}
And the object is added to the scene like this:
const grass2 = new GrassDeform2()
grass2.position.set(-1, 0, 0.50)
grass2.rotateX(Math.PI / 2)
scene.add(grass2)
dirLight.target = grass2
const animate = (ellapsedTime = 0) => {
stats.begin()
grass2.update({ ellapsedTime })
/// other scene stuff
renderer.render(scene, playerController.camera)
requestAnimationFrame(animate)
}
animate()
The vertex shader:
#if NUM_DIR_LIGHTS > 0
struct DirectionalLight {
vec3 direction;
vec3 color;
int shadow;
float shadowBias;
float shadowRadius;
vec2 shadowMapSize;
};
uniform DirectionalLight directionalLights[ NUM_DIR_LIGHTS ];
#endif
uniform float uTime;
attribute vec2 offset;
attribute vec3 scale;
attribute float startseed;
varying vec2 vUv;
varying vec3 vPosition;
varying vec3 vDirectionalLightDirection;
varying vec3 vDirectionalLightColor;
varying vec3 uNormal;
void main() {
vec3 pos = position * scale;
pos.x += offset.x;
pos.z += offset.y;
pos.y += (scale.y - 1.0) * 0.5;
pos.y = orientationY
vPosition = pos;
uNormal = normal;
vUv = uv;
uNormal = normal;
vDirectionalLightDirection = directionalLights[0].direction;
vDirectionalLightColor = directionalLights[0].color;
float variation = startseed + uTime * 0.002;
float pass = (0.5 + pos.y) * 0.05;
pos.x += sin(pass + variation) * pass;
pos.z += cos(pass + variation + 0.01) * pass;
pos.y += sin(pass + variation - 0.01) * pass;
gl_Position = projectionMatrix * modelViewMatrix * vec4(pos,1.0);
}
And the fragment shader (has some extra stuff for light, not added for now):
uniform sampler2D uMap;
varying vec2 vUv;
varying vec3 vPosition;
varying vec3 vDirectionalLightDirection;
varying vec3 vDirectionalLightColor;
varying vec3 uNormal;
void main() {
vec4 map = texture2D(uMap, vUv);
vec3 lightVector = normalize((vDirectionalLightDirection) - vPosition);
float dotNL = dot( uNormal, lightVector );
vec3 baseColor = map.rgb;
vec3 lightedColor = vDirectionalLightColor * 0.6 * dotNL;
if ( map.a < 0.5 ) discard; //!!! THIS WAS THE LINE NEEDED TO SOLVE THE ISSUE
gl_FragColor = vec4( map.rgb , 1 );
}
After applying the change from the final result, the scene looks right!
You can solve your problem with alpha testing. Use a pattern like the following in your fragment shader:
vec4 texelColor = texture2D( map, vUv );
if ( texelColor.a < 0.5 ) discard;
Your material will no longer need to have transparent = true, since you appear to be using a cut-out in which the texture alpha is either 0 or 1.
three.js r.88

Three.js camera control not working & WebGL shader

I found nice water simulation from codepen and modified it with help from other thread here (can't find it anymore though).
I have used three.js couple of times before, but now I just can't comprehend why camera positioning/rotation/aspect/etc isn't working. No matter what coordinates or angle I give to camera and use updateProjectionMatrix nothing happens, camera just stays in one place.
I commented out resize events etc, since they don't do anything also.
Entire code:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r70/three.min.js"></script>
<style type="text/css">
body {
overflow: hidden;
margin: 0;
height: 100%;
}
</style>
<title></title>
<script type='text/javascript'>//<![CDATA[
window.onload=function(){
// init camera, scene, renderer
var scene, camera, renderer;
scene = new THREE.Scene();
var fov = 75,
aspect = window.innerWidth / window.innerHeight;
camera = new THREE.PerspectiveCamera(fov, aspect, 0.1, 1000);
camera.position.z = 200;
camera.rotate.z = 1.5707963268;
camera.updateProjectionMatrix();
renderer = new THREE.WebGLRenderer();
renderer.setClearColor(0xc4c4c4);
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
var clock = new THREE.Clock();
var tuniform = {
time: {
type: 'f',
value: 0.1
},
resolution: {
type: 'v2',
value: new THREE.Vector2()
},
mouse: {
type: 'v4',
value: new THREE.Vector2()
}
};
// Mouse position in - 1 to 1
renderer.domElement.addEventListener('mousedown', function(e) {
//var canvas = renderer.domElement;
//var rect = canvas.getBoundingClientRect();
//tuniform.mouse.value.x = (e.clientX - rect.left) / window.innerWidth * 2 - 1;
//tuniform.mouse.value.y = (e.clientY - rect.top) / window.innerHeight * -2 + 1;
});
renderer.domElement.addEventListener('mouseup', function(e) {
//var canvas = renderer.domElement;
//var rect = canvas.getBoundingClientRect();
//tuniform.mouse.value.z = (e.clientX - rect.left) / window.innerWidth * 2 - 1;
//tuniform.mouse.value.w = (e.clientY - rect.top) / window.innerHeight * -2 + 1;
});
// resize canvas function
window.addEventListener('resize',function() {
//camera.aspect = window.innerWidth / window.innerHeight;
//camera.updateProjectionMatrix();
//renderer.setSize(window.innerWidth, window.innerHeight);
});
tuniform.resolution.value.x = window.innerWidth;
tuniform.resolution.value.y = window.innerHeight;
// Create Plane
var material = new THREE.ShaderMaterial({
uniforms: tuniform,
vertexShader: document.getElementById('vertex-shader').textContent,
fragmentShader: document.getElementById('fragment-shader').textContent
});
var mesh = new THREE.Mesh(
new THREE.PlaneBufferGeometry(window.innerWidth, window.innerHeight, 40), material
);
scene.add(mesh);
// draw animation
function render(time) {
tuniform.time.value += clock.getDelta();
requestAnimationFrame(render);
renderer.render(scene, camera);
}
render();
}//]]>
</script>
</head>
<body>
<!-- THIS is OPENGL Shading language scripts -->
<script id="vertex-shader" type="no-js">
void main() {
gl_Position = vec4( position, 1.0 );
}
</script>
<script id="fragment-shader" type="no-js">
#ifdef GL_ES
precision mediump float;
#endif
uniform float time;
uniform vec2 mouse;
uniform vec2 resolution;
varying vec2 surfacePosition;
const int NUM_STEPS = 8;
const float PI = 3.1415;
const float EPSILON = 1e-3;
float EPSILON_NRM = 0.1 / resolution.x;
// sea
const int ITER_GEOMETRY = 3;
const int ITER_FRAGMENT = 5;
const float SEA_HEIGHT = 0.6;
const float SEA_CHOPPY = 2.0;
const float SEA_SPEED = 0.5;
const float SEA_FREQ = 0.16;
const vec3 SEA_BASE = vec3(0.1,0.19,0.22); //meren pohjaväri
const vec3 SEA_WATER_COLOR = vec3(0.8,0.9,0.6);
const float SKY_INTENSITY = 1.0;
#define SEA_TIME time * SEA_SPEED
// math
mat4 fromEuler(vec3 ang) {
vec2 a1 = vec2(sin(ang.x),cos(ang.x));
vec2 a2 = vec2(sin(ang.y),cos(ang.y));
vec2 a3 = vec2(sin(ang.z),cos(ang.z));
mat4 m;
m[0] = vec4(a1.y*a3.y+a1.x*a2.x*a3.x,a1.y*a2.x*a3.x+a3.y*a1.x,-a2.y*a3.x,0.0);
m[1] = vec4(-a2.y*a1.x,a1.y*a2.y,a2.x,0.0);
m[2] = vec4(a3.y*a1.x*a2.x+a1.y*a3.x,a1.x*a3.x-a1.y*a3.y*a2.x,a2.y*a3.y,0.0);
m[3] = vec4(0.0,0.0,0.0,1.0);
return m;
}
vec3 rotate(vec3 v, mat4 m) {
return vec3(dot(v,m[0].xyz),dot(v,m[1].xyz),dot(v,m[2].xyz));
}
float hash( vec2 p ) {
float h = dot(p,vec2(127.1,311.7));
return fract(sin(h)*43758.5453123);
}
float noise( in vec2 p ) {
vec2 i = floor( p );
vec2 f = fract( p );
vec2 u = f*f*(3.0-2.0*f);
return -1.0+2.0*mix( mix( hash( i + vec2(0.0,0.0) ),
hash( i + vec2(1.0,0.0) ), u.x),
mix( hash( i + vec2(0.0,1.0) ),
hash( i + vec2(1.0,1.0) ), u.x), u.y);
}
// lighting
float diffuse(vec3 n,vec3 l,float p) { return pow(dot(n,l) * 0.4 + 0.6,p); }
float specular(vec3 n,vec3 l,vec3 e,float s) {
float nrm = (s + 8.0) / (3.1415 * 8.0);
return pow(max(dot(reflect(e,n),l),0.0),s) * nrm;
}
// sky
vec3 sky_color(vec3 e) {
e.y = max(e.y,0.0);
vec3 ret;
ret.x = pow(1.0-e.y,2.0);
ret.y = 1.0-e.y;
ret.z = 0.6+(1.0-e.y)*0.4;
return ret * SKY_INTENSITY;
}
// sea
float sea_octave(vec2 uv, float choppy) {
uv += noise(uv);
vec2 wv = 1.0-abs(sin(uv));
vec2 swv = abs(cos(uv));
wv = mix(wv,swv,wv);
return pow(1.0-pow(wv.x * wv.y,0.65),choppy);
}
float map(vec3 p) {
float freq = SEA_FREQ;
float amp = SEA_HEIGHT;
float choppy = SEA_CHOPPY;
vec2 uv = p.xz; uv.x *= 0.75;
mat2 m = mat2(1.6,1.2,-1.2,1.6);
float d, h = 0.0;
for(int i = 0; i < ITER_GEOMETRY; i++) {
d = sea_octave((uv+SEA_TIME)*freq,choppy);
d += sea_octave((uv-SEA_TIME)*freq,choppy);
h += d * amp;
uv *= m; freq *= 1.9; amp *= 0.22;
choppy = mix(choppy,1.0,0.2);
}
return p.y - h;
}
float map_detailed(vec3 p) {
float freq = SEA_FREQ;
float amp = SEA_HEIGHT;
float choppy = SEA_CHOPPY;
vec2 uv = p.xz; uv.x *= 0.75;
mat2 m = mat2(1.6,1.2,-1.2,1.6);
float d, h = 0.0;
for(int i = 0; i < ITER_FRAGMENT; i++) {
d = sea_octave((uv+SEA_TIME)*freq,choppy);
d += sea_octave((uv-SEA_TIME)*freq,choppy);
h += d * amp;
uv *= m; freq *= 1.9; amp *= 0.22;
choppy = mix(choppy,1.0,0.2);
}
return p.y - h;
}
vec3 sea_color(in vec3 p, in vec3 n, in vec3 eye, in vec3 dist) {
float fresnel_o = 1.0 - max(dot(n,-eye),0.0);
float fresnel = pow(fresnel_o,3.0) * 0.65;
// reflection
vec3 refl = sky_color(reflect(eye,n));
// color
vec3 ret = SEA_BASE;
ret = mix(ret,refl,fresnel);
// wave peaks
float atten = max(1.0 - dot(dist,dist) * 0.001, 0.0);
ret += SEA_WATER_COLOR * (p.y - SEA_HEIGHT) * 0.18 * atten;
return ret;
}
// tracing
vec3 getNormal(vec3 p, float eps) {
vec3 n;
n.y = map_detailed(p);
n.x = map_detailed(vec3(p.x+eps,p.y,p.z)) - n.y;
n.z = map_detailed(vec3(p.x,p.y,p.z+eps)) - n.y;
n.y = eps;
return normalize(n);
}
float hftracing(vec3 ori, vec3 dir, out vec3 p) {
float tm = 0.0;
float tx = 1000.0;
float hx = map(ori + dir * tx);
if(hx > 0.0) return tx;
float hm = map(ori + dir * tm);
float tmid = 0.0;
for(int i = 0; i < NUM_STEPS; i++) {
tmid = mix(tm,tx, hm/(hm-hx));
p = ori + dir * tmid;
float hmid = map(p);
if(hmid < 0.0) {
tx = tmid;
hx = hmid;
} else {
tm = tmid;
hm = hmid;
}
}
return tmid;
}
// main
void main(void) {
vec2 uv = gl_FragCoord.xy / resolution.xy;
uv = 1.0 - uv * 2.0;
uv.x *= resolution.x / resolution.y;
//uv = (surfacePosition+vec2(0., .5))*17. + 5E-3*(pow(length(surfacePosition+vec2(0. ,0.5)), -2.));
uv.y *= -1.;
//uv.y += -2.;
// ray
vec3 ang = vec3(0.0,0.003, pow(time, 0.6));
ang = vec3(0.0,clamp(2.0-mouse.y*0.01,-0.3,PI),mouse.x*0.01);
vec3 ori = vec3(0.0,3.5,time*.05);
vec3 dir = normalize(vec3(uv.xy,-2.0));
dir.z -= length(uv) * 0.15;
//dir = rotate(normalize(dir),ang);
// tracing
vec3 p;
float dens = hftracing(ori,dir,p);
vec3 dist = p - ori;
vec3 n = getNormal(p, dot(dist,dist)*EPSILON_NRM);
// color
vec3 color = sea_color(p,n,dir,dist);
vec3 light = normalize(vec3(0.0,1.0,0.8));
color += vec3(diffuse(n,light,80.0) * SEA_WATER_COLOR) * 0.12;
color += vec3(specular(n,light,dir,60.0));
// post
color = mix(sky_color(dir),color,pow(smoothstep(0.0,-0.05,dir.y),0.3));
color = pow(color,vec3(0.75));
gl_FragColor = vec4(color,1.0);
}
</script>
<script>
// tell the embed parent frame the height of the content
if (window.parent && window.parent.parent){
window.parent.parent.postMessage(["resultsFrame", {
height: document.body.getBoundingClientRect().height,
slug: "uz6yo2w3"
}], "*")
}
</script>
</body>
</html>
There are so much try in this code that it's not even clear to me what you're trying to do, but I can give some hints :
Change the camera.rotate to camera.rotation l. 30
Your mouse events are commented. If you want to rotate the camera with the mouse, you're gonna have to add a mousemove event ;
By the way, l. 50 you send a vec4 but loads it as a vec2 l. 126 ;
When the window is resized, you may also want to update the new resolution to the shader ;
l. 304, change the Z component of the camera's origin from vec3 ori = vec3(0.0, 3.5, time * 5.0); to vec3 ori = vec3(0.0, 3.5, time * 5.0); so you can see the camera moving along the sea ;
l. 306, instead of dir = rotate(normalize(dir), ang); add the initial dir = normalize(dir) * fromEuler(ang); (ang is the angle of the camera) ;
l. 149, change your mat4 fromEuler(vec3 ang){...} to the initial mat3 fromEuler(vec3 ang){...} function ;
l. 301, just put vec3 ang = vec3(0.0, 0.0, 0.0); and play with it. You may use mouse coordinates in this function, depending on how you want the user interact with the camera.

Resources