Three.js: Update geometry attributes when using RawShaderMaterial - javascript

I have a Three.js scene that passes a few attributes to a RawShaderMaterial. After the initial render, I'd like to update some attributes, but haven't been able to figure out how to do so.
Here's a sample scene (fiddle):
/**
* Generate a scene object with a background color
**/
function getScene() {
var scene = new THREE.Scene();
scene.background = new THREE.Color(0xaaaaaa);
return scene;
}
/**
* Generate the camera to be used in the scene
**/
function getCamera() {
var aspectRatio = window.innerWidth / window.innerHeight;
var camera = new THREE.PerspectiveCamera(75, aspectRatio, 0.1, 100000);
camera.position.set(0, 1, -6000);
return camera;
}
/**
* Generate the renderer to be used in the scene
**/
function getRenderer() {
// Create the canvas with a renderer
var renderer = new THREE.WebGLRenderer({antialias: true});
// Add support for retina displays
renderer.setPixelRatio(window.devicePixelRatio);
// Specify the size of the canvas
renderer.setSize(window.innerWidth, window.innerHeight);
// Add the canvas to the DOM
document.body.appendChild(renderer.domElement);
return renderer;
}
/**
* Generate the controls to be used in the scene
**/
function getControls(camera, renderer) {
var controls = new THREE.TrackballControls(camera, renderer.domElement);
controls.zoomSpeed = 0.4;
controls.panSpeed = 0.4;
return controls;
}
/**
* Generate the points for the scene
**/
function addPoints(scene) {
var geometry = new THREE.InstancedBufferGeometry();
geometry.addAttribute( 'position',
new THREE.BufferAttribute( new Float32Array( [0, 0, 0] ), 3));
// add data for each observation
var n = 10000; // number of observations
var rootN = n**(1/2);
var cellSize = 20;
var translation = new Float32Array( n * 3 );
var texIdx = new Float32Array( n );
var translationIterator = 0;
var texIterator = 0;
for (var i=0; i<n*3; i++) {
var x = Math.random() * n - (n/2);
var y = Math.random() * n - (n/2);
translation[translationIterator++] = x;
translation[translationIterator++] = y;
translation[translationIterator++] = Math.random() * n - (n/2);
texIdx[texIterator++] = (x + y) > (n/8) ? 1 : 0;
}
var IBA = THREE.InstancedBufferAttribute;
geometry.addAttribute('translation', new IBA(translation, 3, 1));
geometry.addAttribute('texIdx', new IBA(texIdx, 1, 1));
var canvases = [
getElem('canvas', { width: 16384, height: 16384, }),
getElem('canvas', { width: 16384, height: 16384, }),
];
for (var i=0; i<canvases.length; i++) {
var canvas = canvases[i];
var ctx = canvas.getContext('2d');
ctx.fillStyle = i == 0 ? 'red' : 'blue';
ctx.rect(0, 0, 16384, 16384);
ctx.fill();
}
window.canvases = canvases;
var material = new THREE.RawShaderMaterial({
uniforms: {
a: {
type: 't',
value: getTexture(canvases[0]),
},
b: {
type: 't',
value: getTexture(canvases[1]),
}
},
vertexShader: document.getElementById('vertex-shader').textContent,
fragmentShader: document.getElementById('fragment-shader').textContent,
});
var mesh = new THREE.Points(geometry, material);
mesh.frustumCulled = false; // prevent the mesh from being clipped on drag
scene.add(mesh);
}
function getTexture(canvas) {
var tex = new THREE.Texture(canvas);
tex.needsUpdate = true;
tex.flipY = false;
return tex;
}
/**
* Create an element
**/
function getElem(tag, obj) {
var obj = obj || {};
var elem = document.createElement(tag);
Object.keys(obj).forEach(function(attr) {
elem[attr] = obj[attr];
})
return elem;
}
/**
* Render!
**/
function render() {
requestAnimationFrame(render);
renderer.render(scene, camera);
controls.update();
};
/**
* Main
**/
var scene = getScene();
var camera = getCamera();
var renderer = getRenderer();
var controls = getControls(camera, renderer);
addPoints(scene);
render();
<html>
<head>
<style>
html, body { width: 100%; height: 100%; background: #000; }
body { margin: 0; overflow: hidden; }
canvas { width: 100%; height: 100%; }
</style>
</head>
<body>
<script src='https://cdnjs.cloudflare.com/ajax/libs/three.js/88/three.min.js'></script>
<script src='https://rawgit.com/YaleDHLab/pix-plot/master/assets/js/trackball-controls.js'></script>
<script type='x-shader/x-vertex' id='vertex-shader'>
precision highp float;
uniform mat4 modelViewMatrix;
uniform mat4 projectionMatrix;
uniform vec3 cameraPosition;
attribute vec3 position; // sets the blueprint's vertex positions
attribute vec3 translation; // x y translation offsets for an instance
attribute float texIdx; // the texture index to access
varying float vTexIdx;
void main() {
// set point position
vec3 pos = position + translation;
vec4 projected = projectionMatrix * modelViewMatrix * vec4(pos, 1.0);
gl_Position = projected;
// assign the varyings
vTexIdx = texIdx;
// use the delta between the point position and camera position to size point
float xDelta = pow(projected[0] - cameraPosition[0], 2.0);
float yDelta = pow(projected[1] - cameraPosition[1], 2.0);
float zDelta = pow(projected[2] - cameraPosition[2], 2.0);
float delta = pow(xDelta + yDelta + zDelta, 0.5);
gl_PointSize = 40000.0 / delta;
}
</script>
<script type='x-shader/x-fragment' id='fragment-shader'>
precision highp float;
uniform sampler2D a;
uniform sampler2D b;
varying float vTexIdx;
void main() {
int textureIndex = int(vTexIdx);
vec2 uv = vec2(gl_PointCoord.x, gl_PointCoord.y);
if (textureIndex == 0) {
gl_FragColor = texture2D(a, uv);
} else if (textureIndex == 1) {
gl_FragColor = texture2D(b, uv);
}
}
</script>
</body>
</html>
After building this scene, I try to make all points red by running the following lines in the console:
var texIdxAttr = scene.children[0].geometry.attributes.texIdx.array;
for (var i=0; i<texIdxAttr.length; i++) {
scene.children[0].geometry.attributes.texIdx.array[i] = 0;
}
scene.children[0].geometry.verticesNeedUpdate = true;
scene.children[0].geometry.elementsNeedUpdate = true;
scene.children[0].geometry.uvsNeedUpdate = true;
But this appears to change nothing. Does anyone know what I can do to update the texIdx attribute values and re-render the scene after the initial render? I'd be grateful for any help others can offer with this question.

It seems to update a buffer attribute (or indexed buffer attribute) one can set the .dynamic property of that buffer to true, manually mutate the buffer, then set the .needsUpdate attribute of the buffer to true.
In this updated demo, after clicking the scene, the buffer attribute texIdx is updated such that all points have texIdx == 0:
<html>
<head>
<style>
html, body { width: 100%; height: 100%; background: #000; }
body { margin: 0; overflow: hidden; }
canvas { width: 100%; height: 100%; }
</style>
</head>
<body>
<script src='https://cdnjs.cloudflare.com/ajax/libs/three.js/88/three.min.js'></script>
<script src='https://rawgit.com/YaleDHLab/pix-plot/master/assets/js/trackball-controls.js'></script>
<script type='x-shader/x-vertex' id='vertex-shader'>
precision highp float;
uniform mat4 modelViewMatrix;
uniform mat4 projectionMatrix;
uniform vec3 cameraPosition;
attribute vec3 position; // sets the blueprint's vertex positions
attribute vec3 translation; // x y translation offsets for an instance
attribute float texIdx; // the texture index to access
varying float vTexIdx;
void main() {
// set point position
vec3 pos = position + translation;
vec4 projected = projectionMatrix * modelViewMatrix * vec4(pos, 1.0);
gl_Position = projected;
// assign the varyings
vTexIdx = texIdx;
// use the delta between the point position and camera position to size point
float xDelta = pow(projected[0] - cameraPosition[0], 2.0);
float yDelta = pow(projected[1] - cameraPosition[1], 2.0);
float zDelta = pow(projected[2] - cameraPosition[2], 2.0);
float delta = pow(xDelta + yDelta + zDelta, 0.5);
gl_PointSize = 40000.0 / delta;
}
</script>
<script type='x-shader/x-fragment' id='fragment-shader'>
precision highp float;
uniform sampler2D a;
uniform sampler2D b;
varying float vTexIdx;
void main() {
int textureIndex = int(vTexIdx);
vec2 uv = vec2(gl_PointCoord.x, gl_PointCoord.y);
if (textureIndex == 0) {
gl_FragColor = texture2D(a, uv);
} else if (textureIndex == 1) {
gl_FragColor = texture2D(b, uv);
}
}
</script>
<script>
/**
* Generate a scene object with a background color
**/
function getScene() {
var scene = new THREE.Scene();
scene.background = new THREE.Color(0xaaaaaa);
return scene;
}
/**
* Generate the camera to be used in the scene
**/
function getCamera() {
var aspectRatio = window.innerWidth / window.innerHeight;
var camera = new THREE.PerspectiveCamera(75, aspectRatio, 0.1, 100000);
camera.position.set(0, 1, -6000);
return camera;
}
/**
* Generate the renderer to be used in the scene
**/
function getRenderer() {
// Create the canvas with a renderer
var renderer = new THREE.WebGLRenderer({antialias: true});
// Add support for retina displays
renderer.setPixelRatio(window.devicePixelRatio);
// Specify the size of the canvas
renderer.setSize(window.innerWidth, window.innerHeight);
// Add the canvas to the DOM
document.body.appendChild(renderer.domElement);
return renderer;
}
/**
* Generate the controls to be used in the scene
**/
function getControls(camera, renderer) {
var controls = new THREE.TrackballControls(camera, renderer.domElement);
controls.zoomSpeed = 0.4;
controls.panSpeed = 0.4;
return controls;
}
/**
* Generate the points for the scene
**/
function addPoints(scene) {
var BA = THREE.BufferAttribute;
var IBA = THREE.InstancedBufferAttribute;
var geometry = new THREE.InstancedBufferGeometry();
// add data for each observation
var n = 10000; // number of observations
var rootN = n**(1/2);
var cellSize = 20;
var translation = new Float32Array( n * 3 );
var texIdx = new Float32Array( n );
var translationIterator = 0;
var texIterator = 0;
for (var i=0; i<n*3; i++) {
var x = Math.random() * n - (n/2);
var y = Math.random() * n - (n/2);
translation[translationIterator++] = x;
translation[translationIterator++] = y;
translation[translationIterator++] = Math.random() * n - (n/2);
texIdx[texIterator++] = (x + y) > (n/8) ? 1 : 0;
}
var positionAttr = new BA(new Float32Array( [0, 0, 0] ), 3);
var translationAttr = new IBA(translation, 3, 1);
var texIdxAttr = new IBA(texIdx, 1, 1);
positionAttr.dynamic = true;
translationAttr.dynamic = true;
texIdxAttr.dynamic = true;
geometry.addAttribute('position', positionAttr);
geometry.addAttribute('translation', translationAttr);
geometry.addAttribute('texIdx', texIdxAttr);
var canvases = [
getElem('canvas', { width: 16384, height: 16384, }),
getElem('canvas', { width: 16384, height: 16384, }),
];
for (var i=0; i<canvases.length; i++) {
var canvas = canvases[i];
var ctx = canvas.getContext('2d');
ctx.fillStyle = i == 0 ? 'red' : 'blue';
ctx.rect(0, 0, 16384, 16384);
ctx.fill();
}
var material = new THREE.RawShaderMaterial({
uniforms: {
a: {
type: 't',
value: getTexture(canvases[0]),
},
b: {
type: 't',
value: getTexture(canvases[1]),
}
},
vertexShader: document.getElementById('vertex-shader').textContent,
fragmentShader: document.getElementById('fragment-shader').textContent,
});
var mesh = new THREE.Points(geometry, material);
mesh.frustumCulled = false; // prevent the mesh from being clipped on drag
scene.add(mesh);
window.onclick = function() {
// paint it red
for (var i=0; i<texIdxAttr.count; i++) {
scene.children[0].geometry.attributes.texIdx.array[i] = 0;
}
scene.children[0].geometry.attributes.texIdx.needsUpdate = true;
}
}
function getTexture(canvas) {
var tex = new THREE.Texture(canvas);
tex.needsUpdate = true;
tex.flipY = false;
return tex;
}
/**
* Create an element
**/
function getElem(tag, obj) {
var obj = obj || {};
var elem = document.createElement(tag);
Object.keys(obj).forEach(function(attr) {
elem[attr] = obj[attr];
})
return elem;
}
/**
* Render!
**/
function render() {
requestAnimationFrame(render);
renderer.render(scene, camera);
controls.update();
};
/**
* Main
**/
var scene = getScene();
var camera = getCamera();
var renderer = getRenderer();
var controls = getControls(camera, renderer);
addPoints(scene);
render();
</script>
</body>
</html>

Related

I'm trying to make a checkerboard texture in glsl shader using three js but it is getting aliased or pixelated, how to make it smooth?

I'm trying to make a checkerboard texture in glsl shader using three js but it is getting aliased or pixelated, how to make it smooth?
I tried to blur the edges with smoothstep function but it is also not working and cubes of the checkerboard are getting stretched also.
How can I make a smooth and seamless checkerboard texture?
//vertex shader
const vShader = `
varying vec2 v_uv;
varying float v_size_factor;
varying vec3 v_position;
uniform float u_time;
void main(){
v_uv = uv;
v_position = position;
v_size_factor = 0.45;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position * v_size_factor, 1.0);
}
`;
//fragment shader
const fShader = `
varying vec2 v_uv;
uniform vec2 u_resolution;
uniform float u_time;
varying vec3 v_position;
float Rect(vec2 pt, vec2 size, vec2 offset, float blur)
{
vec2 p = pt - offset;
vec2 halfSize = size * 0.5;
float a = smoothstep(-halfSize.x, -halfSize.x + blur, p.x)
- smoothstep(halfSize.x, halfSize.x + blur, p.x);
float b = smoothstep(-halfSize.y, -halfSize.y + blur, p.y)
- smoothstep(halfSize.x, halfSize.y + blur, p.y);
return a * b;
}
void main() {
vec2 uv = v_uv * vec2(0.999999);
uv = fract(uv * 40.0);
float blur = 0.001;
float shape = Rect(uv, vec2(1.0), vec2(0.0), blur) ;
shape += Rect(uv, vec2(1.0), vec2(1.0), blur) ;
vec3 color = vec3(1.0) * shape;
gl_FragColor = vec4(color, 1.0);
}
`;
const scene = new THREE.Scene();
const camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0.1, 1000);
const renderer = new THREE.WebGLRenderer({ antialias: true });
const clock = new THREE.Clock();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// const geometry = new THREE.PlaneGeometry(2, 2, 100, 100);
const geometry = new THREE.TorusKnotGeometry(0.4, 0.2, 200, 64);
const uniform = {
u_color: { value: new THREE.Color(0x00ff00) },
u_time: { value: 0.0 },
u_mouse: { value: { x: 0.0, y: 0.0 } },
u_resolution: { value: { x: 0.0, y: 0.0 } },
};
const material = new THREE.ShaderMaterial({
vertexShader: vShader,
fragmentShader: fShader,
uniforms: uniform,
opacity: true,
});
const plane = new THREE.Mesh(geometry, material);
scene.add(plane);
camera.position.z = 1;
onWindowResize();
animate();
function animate() {
requestAnimationFrame(animate);
renderer.render(scene, camera);
onWindowResize();
plane.rotation.y += 0.01;
uniform.u_time.value = clock.getElapsedTime();
}
//set mouse coordinates
function move(e) {
uniform.u_mouse.value.x = e.touches ? e.touches[0].clientX : e.clientX;
uniform.u_mouse.value.y = e.touches ? e.touches[0].clientY : e.clientY;
}
if ("ontouchStart" in window) {
document.addEventListener("touchmove", move);
} else {
document.addEventListener("resize", onWindowResize, false);
document.addEventListener("mousemove", move);
}
function onWindowResize(event) {
const aspectRatio = window.innerWidth / window.innerHeight;
let width, height;
if (aspectRatio >= 1) {
width = 1;
height = (window.innerHeight / window.innerWidth) * width;
} else {
width = aspectRatio;
height = 1;
}
camera.left = -width;
camera.right = width;
camera.top = height;
camera.bottom = -height;
if (uniform.u_resolution !== undefined) {
uniform.u_resolution.value.x = window.innerWidth;
uniform.u_resolution.value.y = window.innerHeight;
}
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
overflow: hidden;
}
canvas {
position: absolute;
width: 100%;
height: 100vh;
z-index: -1;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Shader - webgl</title>
</head>
<body>
<canvas id="canvas"></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/102/three.min.js"></script>
<script src="script.js"></script>
</body>
</html>

Improve UV line quality of threejs/shader geometry

I'm using the uv output of threejs's torus to create moving lines across the torus. It works, but doesn't look crisp.
How can I improve the line quality?
I've tried making the material two-sided, and increasing the width of the lines, but the quality isn't improving much.
I haven't tried completely reproducing the torus outside of threejs, but that's out of my comfort zone.
I'm hoping there's way to change the logic of the fragment shader to produce clearer lines. I'd be greatful for any suggestions.
Codepen
/* Scene Initialization */
var startTime = Date.now();
var scene = new THREE.Scene();
var width = window.innerWidth;
var height = window.innerHeight;
var canvas = document.getElementById('canvas');
var camera = new THREE.PerspectiveCamera(75, 1, 1, 1200);
// var camera = new THREE.OrthographicCamera( window.innerWidth / - 2, window.innerWidth / 2, window.innerHeight / 2, window.innerHeight / - 2, 1, 1200 );
camera.position.set(0, -420, 600);
camera.lookAt(new THREE.Vector3(0, 0, 0));
var renderer = new THREE.WebGLRenderer({antialias: true});
renderer.setSize(window.innerWidth * .2, window.innerWidth * .2);
renderer.setClearColor( 0xffffff, 1);
canvas.appendChild(renderer.domElement);
var geometry = new THREE.TorusGeometry(200, 200, 260, 260);
material = new THREE.ShaderMaterial( {
uniforms: {time: { type: "f", value: Date.now() - startTime}, },
vertexShader: `attribute vec3 center;
varying vec3 vCenter;
varying vec2 vUv;
void main() {
vCenter = center;
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}`,
fragmentShader: `varying vec3 vCenter;
varying vec2 vUv;
uniform float time;
uniform sampler2D tDiffuse;
void main() {
float sh = 0.005;
float PI = 3.1415926535897932384626433832795;
// float linesX = mod(time + vUv.x, 0.03);
float linesX = sin((time + vUv.x) * PI * 30.)/30.;
// float linesY = mod(time + vUv.y, 0.05);
float linesY = sin((time + vUv.y) * PI * 20.)/20.;
float smoothX =
smoothstep( 0.0 - sh, 0.0, linesX) -
smoothstep( 0.0, 0.0 + sh, linesX);
float smoothY =
smoothstep( 0.0 - sh, 0.0, linesY) -
smoothstep( 0.0, 0.0 + sh, linesY);
float uvOutput = smoothX + smoothY;
gl_FragColor.rgb = vec3(1.0, 0, 0);
gl_FragColor.a = uvOutput;
// gl_FragColor = vec4(1.,0,0,1.)
}`
} );
//material.extensions.derivatives = true;
material.side = THREE.DoubleSide;
material.transparent = true;
//material.blending = THREE.Add;
material.depthTest = false;
var torus = new THREE.Mesh(geometry, material);
var geom = torus.geometry;
geometry.sortFacesByMaterialIndex();
torus.position.x = 0;
scene.add(torus);
/* Request Animation Frame */
function animation() {
camera.lookAt(new THREE.Vector3(0, 0, 0));
renderer.render(scene, camera);
material.uniforms.time.value = (Date.now() - startTime)/20000;
requestAnimationFrame(animation);
}
animation();
setupDraggableEvents();
function setupDraggableEvents() {
var hammer = new Hammer(document.getElementsByTagName('canvas')[0]);
hammer.on('pan', function(event) {
torus.rotation.y += event.velocityX / 10;
torus.rotation.x += event.velocityY / 10;
});
}
I recommend to define the number of lines for both directions and to calculate the distance to a line in terms of UV coordinates:
float t = time;
vec2 noLines = vec2(30.0, 20.0);
vec2 floorUV = floor((t + vUv) * noLines);
vec2 distUV = t + vUv - (floorUV+0.5) / noLines;
Smoothly interpolate between the thickness and the half thickness of a line (smoothstep), to calculate the "saturation". This causes that the line always has the full "strength" in the middle (of course you can experiment with this e.g. sh*0.66, sh*0.33):
float sh = 0.005;
vec2 lineUV = smoothstep(sh, sh*0.5, abs(distUV));
The alpha channel is the maximum "saturation" value of both directions:
float uvOutput = max(lineUV.x, lineUV.y);
gl_FragColor = vec4(1.0, 0.0, 0.0, uvOutput);
See the example, where I applied the suggested changes to your original code:
/* Scene Initialization */
var startTime = Date.now();
var scene = new THREE.Scene();
var width = window.innerWidth;
var height = window.innerHeight;
var canvas = document.getElementById('canvas');
var camera = new THREE.PerspectiveCamera(75, 1, 1, 1200);
camera.position.set(0, -420, 600);
camera.lookAt(new THREE.Vector3(0, 0, 0));
orbitControls = new THREE.OrbitControls(camera);
var renderer = new THREE.WebGLRenderer({antialias: true});
renderer.setSize(window.innerWidth * .2, window.innerWidth * .2);
renderer.setClearColor( 0xffffff, 1);
canvas.appendChild(renderer.domElement);
var geometry = new THREE.TorusGeometry(200, 200, 260, 260);
material = new THREE.ShaderMaterial( {
uniforms: {time: { type: "f", value: Date.now() - startTime}, },
vertexShader: `attribute vec3 center;
varying vec3 vCenter;
varying vec2 vUv;
void main() {
vCenter = center;
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}`,
fragmentShader: `varying vec3 vCenter;
varying vec2 vUv;
uniform float time;
uniform sampler2D tDiffuse;
void main() {
float t = time;
vec2 noLines = vec2(30.0, 20.0);
vec2 floorUV = floor((t + vUv) * noLines);
vec2 distUV = t + vUv - (floorUV+0.5) / noLines;
float sh = 0.005;
vec2 lineUV = smoothstep(sh, sh*0.5, abs(distUV));
float uvOutput = max(lineUV.x, lineUV.y);
gl_FragColor = vec4(1.0, 0.0, 0.0, uvOutput);
}`,
transparent: true
} );
//material.extensions.derivatives = true;
material.side = THREE.DoubleSide;
material.transparent = true;
//material.blending = THREE.Add;
material.depthTest = false;
var torus = new THREE.Mesh(geometry, material);
var geom = torus.geometry;
geometry.sortFacesByMaterialIndex();
torus.position.x = 0;
scene.add(torus);
/* Request Animation Frame */
function animation() {
camera.lookAt(new THREE.Vector3(0, 0, 0));
renderer.render(scene, camera);
material.uniforms.time.value = (Date.now() - startTime)/20000;
requestAnimationFrame(animation);
}
resize();
window.onresize = resize;
animation();
setupDraggableEvents();
function setupDraggableEvents() {
var hammer = new Hammer(document.getElementsByTagName('canvas')[0]);
hammer.on('pan', function(event) {
torus.rotation.y += event.velocityX / 10;
torus.rotation.x += event.velocityY / 10;
});
}
function resize() {
var aspect = window.innerWidth / window.innerHeight;
renderer.setSize(window.innerWidth, window.innerHeight);
camera.aspect = aspect;
camera.updateProjectionMatrix();
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/100/three.min.js"></script>
<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>
<div id="canvas"></div>

Three.js: Custom sizes for each quad using InstancedBufferGeometry + ShaderMaterial

I am trying to create a scene full of points, each with unique width and heights. However, I can't figure out how to get into the vertex shader to move the vertices to create the custom sizes. Here's the scene:
// generate a scene object
var scene = new THREE.Scene();
scene.background = new THREE.Color(0xaaaaaa);
// generate a camera
var aspectRatio = window.innerWidth / window.innerHeight;
var camera = new THREE.PerspectiveCamera(75, aspectRatio, 0.1, 10000);
camera.position.set(0, 1, -10);
// generate a renderer
var renderer = new THREE.WebGLRenderer({antialias: true});
renderer.setPixelRatio(window.devicePixelRatio); // <3 retina
renderer.setSize(window.innerWidth, window.innerHeight); // canvas size
document.body.appendChild(renderer.domElement);
// generate controls
var controls = new THREE.TrackballControls(camera, renderer.domElement);
// generate some lights
var ambientLight = new THREE.AmbientLight(0xeeeeee);
scene.add(ambientLight);
/**
* Add the points
**/
var BA = THREE.BufferAttribute;
var IBA = THREE.InstancedBufferAttribute;
var geometry = new THREE.InstancedBufferGeometry();
var n = 10000, // number of observations
rootN = n**(1/2),
cellSize = 20,
translations = new Float32Array(n * 3),
widths = new Float32Array(n),
heights = new Float32Array(n),
translationIterator = 0,
widthIterator = 0,
heightIterator = 0;
for (var i=0; i<n*3; i++) {
translations[translationIterator++] = (Math.random() * n) - (Math.random() * n);
translations[translationIterator++] = (Math.random() * n) - (Math.random() * n);
translations[translationIterator++] = (Math.random() * n) - (Math.random() * n);
widths[widthIterator++] = Math.random() * 20;
heights[heightIterator++] = Math.random() * 20;
}
// coordinates for template box
var size = 10,
verts = [
0, 0, 0, // lower left
size, 0, 0, // lower right
size, size, 0, // upper right
0, size, 0, // upper left
]
var positionAttr = new BA(new Float32Array(verts), 3),
translationAttr = new IBA(translations, 3, true, 1),
widthAttr = new IBA(widths, 1, true, 1),
heightAttr = new IBA(heights, 1, true, 1);
// we make two triangles but only use 4 distinct vertices in the object
// the second argument to THREE.BufferAttribute is the number of elements
// in the first argument per vertex
geometry.setIndex([0,1,2, 2,3,0])
geometry.addAttribute('position', positionAttr);
geometry.addAttribute('translation', translationAttr);
geometry.addAttribute('width', widthAttr);
geometry.addAttribute('height', heightAttr);
var material = new THREE.RawShaderMaterial({
vertexShader: document.getElementById('vertex-shader').textContent,
fragmentShader: document.getElementById('fragment-shader').textContent,
});
material.side = THREE.DoubleSide;
var mesh = new THREE.Mesh(geometry, material);
mesh.frustumCulled = false; // prevent the mesh from being clipped on drag
scene.add(mesh);
// render loop
function render() {
requestAnimationFrame(render);
renderer.render(scene, camera);
controls.update();
};
// draw some geometries
var geometry = new THREE.TorusGeometry(10, 3, 16, 100);
var material = new THREE.MeshNormalMaterial({ color: 0xffff00 });
render();
html, body { width: 100vw; height: 100vh; background: #000; }
body { margin: 0; overflow: hidden; }
canvas { width: 100vw; height: 100vh; }
<script src='https://cdnjs.cloudflare.com/ajax/libs/three.js/97/three.min.js'></script>
<script src='https://threejs.org/examples/js/controls/TrackballControls.js'></script>
<script type='x-shader/x-vertex' id='vertex-shader'>
precision highp float;
uniform mat4 modelViewMatrix;
uniform mat4 projectionMatrix;
uniform vec3 cameraPosition;
attribute vec3 position; // sets the blueprint's vertex positions
attribute vec3 translation; // x y translation offsets for an instance
attribute float width;
attribute float height;
void main() {
// set point position
vec3 pos = position + translation;
vec4 projected = projectionMatrix * modelViewMatrix * vec4(pos, 1.0);
gl_Position = projected;
}
</script>
<script type='x-shader/x-fragment' id='fragment-shader'>
precision highp float;
void main() {
gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
}
</script>
My question is: how can I use the width and height attributes to make each point have the given (relative) proportions specified by its width and height values? Can I query the index of the vertex being drawn within its given instance? Any suggestions would be very helpful!
Ah, it seems one can do the following in the vertex shader:
void main() {
// set point position
vec3 pos = position + translation;
pos.x = pos.x * width;
pos.y = pos.y * height;
vec4 projected = projectionMatrix * modelViewMatrix * vec4(pos, 1.0);
gl_Position = projected;
}
Full snippet:
// generate a scene object
var scene = new THREE.Scene();
scene.background = new THREE.Color(0xaaaaaa);
// generate a camera
var aspectRatio = window.innerWidth / window.innerHeight;
var camera = new THREE.PerspectiveCamera(75, aspectRatio, 0.1, 10000);
camera.position.set(0, 1, -10);
// generate a renderer
var renderer = new THREE.WebGLRenderer({antialias: true});
renderer.setPixelRatio(window.devicePixelRatio); // <3 retina
renderer.setSize(window.innerWidth, window.innerHeight); // canvas size
document.body.appendChild(renderer.domElement);
// generate controls
var controls = new THREE.TrackballControls(camera, renderer.domElement);
// generate some lights
var ambientLight = new THREE.AmbientLight(0xeeeeee);
scene.add(ambientLight);
/**
* Add the points
**/
var BA = THREE.BufferAttribute;
var IBA = THREE.InstancedBufferAttribute;
var geometry = new THREE.InstancedBufferGeometry();
var n = 10000, // number of observations
rootN = n**(1/2),
cellSize = 20,
translations = new Float32Array(n * 3),
widths = new Float32Array(n),
heights = new Float32Array(n),
translationIterator = 0,
widthIterator = 0,
heightIterator = 0;
for (var i=0; i<n*3; i++) {
translations[translationIterator++] = (Math.random() * n) - (Math.random() * n);
translations[translationIterator++] = (Math.random() * n) - (Math.random() * n);
translations[translationIterator++] = (Math.random() * n) - (Math.random() * n);
widths[widthIterator++] = Math.random() * 20;
heights[heightIterator++] = Math.random() * 20;
}
// coordinates for template box
var size = 10,
verts = [
0, 0, 0, // lower left
size, 0, 0, // lower right
size, size, 0, // upper right
0, size, 0, // upper left
]
var positionAttr = new BA(new Float32Array(verts), 3),
translationAttr = new IBA(translations, 3, true, 1),
widthAttr = new IBA(widths, 1, true, 1),
heightAttr = new IBA(heights, 1, true, 1);
// we make two triangles but only use 4 distinct vertices in the object
// the second argument to THREE.BufferAttribute is the number of elements
// in the first argument per vertex
geometry.setIndex([0,1,2, 2,3,0])
geometry.addAttribute('position', positionAttr);
geometry.addAttribute('translation', translationAttr);
geometry.addAttribute('width', widthAttr);
geometry.addAttribute('height', heightAttr);
var material = new THREE.RawShaderMaterial({
vertexShader: document.getElementById('vertex-shader').textContent,
fragmentShader: document.getElementById('fragment-shader').textContent,
});
material.side = THREE.DoubleSide;
var mesh = new THREE.Mesh(geometry, material);
mesh.frustumCulled = false; // prevent the mesh from being clipped on drag
scene.add(mesh);
// render loop
function render() {
requestAnimationFrame(render);
renderer.render(scene, camera);
controls.update();
};
// draw some geometries
var geometry = new THREE.TorusGeometry(10, 3, 16, 100);
var material = new THREE.MeshNormalMaterial({ color: 0xffff00 });
render();
html, body { width: 100vw; height: 100vh; background: #000; }
body { margin: 0; overflow: hidden; }
canvas { width: 100vw; height: 100vh; }
<script src='https://cdnjs.cloudflare.com/ajax/libs/three.js/97/three.min.js'></script>
<script src='https://threejs.org/examples/js/controls/TrackballControls.js'></script>
<script type='x-shader/x-vertex' id='vertex-shader'>
precision highp float;
uniform mat4 modelViewMatrix;
uniform mat4 projectionMatrix;
uniform vec3 cameraPosition;
attribute vec3 position; // sets the blueprint's vertex positions
attribute vec3 translation; // x y translation offsets for an instance
attribute float width;
attribute float height;
void main() {
// set point position
vec3 pos = position + translation;
vec4 projected = projectionMatrix * modelViewMatrix * vec4(pos, 1.0);
gl_Position = projected;
}
</script>
<script type='x-shader/x-fragment' id='fragment-shader'>
precision highp float;
void main() {
gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
}
</script>

Load textures from Base64 in Three.js

I am currently loading textures from URLs but since my back-end code is generating planets I need them to be displayed using Base64.
(I'm playing around with procedural generation so I'd prefer not to save the image and then load it via URL)
Here's the code;
<!DOCTYPE html><html class=''>
<head>
<style>body {
background: black;
text-align: center;
}
</style></head><body>
<script id="vertexShader" type="x-shader/x-vertex">
uniform vec3 viewVector;
uniform float c;
uniform float p;
varying float intensity;
void main({
vec3 vNormal = normalize( normalMatrix * normal );
vec3 vNormel = normalize( normalMatrix * viewVector );
intensity = pow( c - dot(vNormal, vNormel), p );
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}
</script>
<script id="fragmentShader" type="x-shader/x-fragment">
uniform vec3 glowColor;
varying float intensity;
void main() {
vec3 glow = glowColor * intensity;
gl_FragColor = vec4( glow, 1.0 );
}
</script>
<script src='http://cdnjs.cloudflare.com/ajax/libs/three.js/r63/three.min.js'></script><script src='https://s3-us-west-2.amazonaws.com/s.cdpn.io/123941/orbitcontrols.js'></script>
<script>var container, controls, camera, renderer, scene, light,
rotationSpeed = 0.02,
clock = new THREE.Clock(),
WIDTH = window.innerWidth - 30,
HEIGHT = window.innerHeight - 30;
//cam vars
var angle = 45,
aspect = WIDTH / HEIGHT,
near = 0.1,
far = 10000;
//mesh vars
var earthMesh, Atmos, AtmosMat;
container = document.createElement('div');
document.body.appendChild(container);
//cam
camera = new THREE.PerspectiveCamera(angle, aspect, near, far);
camera.position.set(1380, -17, 394);
//scene
scene = new THREE.Scene();
camera.lookAt(scene.position);
//light
light = new THREE.SpotLight(0xFFFFFF, 1, 0, Math.PI / 2, 1);
light.position.set(4000, 4000, 1500);
light.target.position.set (1000, 3800, 1000);
light.castShadow = true;
//light.shadowCameraNear = 1;
//light.shadowCameraFar = 10000;
//light.shadowCameraFov = 50;
scene.add(light);
//EARTH
var earthGeo = new THREE.SphereGeometry (200, 400, 400),
earthMat = new THREE.MeshPhongMaterial();
earthMesh = new THREE.Mesh(earthGeo, earthMat);
earthMesh.position.set(-100, 0, 0);
earthMesh.rotation.y=5;
scene.add(earthMesh);
//diffuse
earthMat.map = THREE.ImageUtils.loadTexture('https://s3-us-west-2.amazonaws.com/s.cdpn.io/123941/earthmap.jpg');
//bump
earthMat.bumpMap = THREE.ImageUtils.loadTexture('https://s3-us-west-2.amazonaws.com/s.cdpn.io/123941/bump-map.jpg');
earthMat.bumpScale = 8;
//specular
earthMat.specularMap = THREE.ImageUtils.loadTexture('https://s3-us-west-2.amazonaws.com/s.cdpn.io/123941/earthspec1k.jpg');
earthMat.specular = new THREE.Color('#2e2e2e');
earthMesh.castShadow = true;
earthMesh.receiveShadow = true;
//Atmosphere
AtmosMat = new THREE.ShaderMaterial({
uniforms:{
"c": { type: "f", value: 0.3 },
"p": { type: "f", value: 5.2},
glowColor: { type: "c", value: new THREE.Color(0x00dbdb)},
viewVector: { type: "v3", value: camera.position}
},
vertexShader: document.getElementById('vertexShader').textContent,
fragmentShader: document.getElementById('fragmentShader').textContent,
side: THREE.BackSide,
blending: THREE.AdditiveBlending,
transparent: true
});
Atmos = new THREE.Mesh(earthGeo, AtmosMat);
Atmos.position = earthMesh.position;
Atmos.scale.multiplyScalar(1.2);
scene.add(Atmos);
//STARS
var starGeo = new THREE.SphereGeometry (3000, 10, 100),
starMat = new THREE.MeshBasicMaterial();
starMat.map = THREE.ImageUtils.loadTexture('https://s3-us-west-2.amazonaws.com/s.cdpn.io/123941/star-field.png');
starMat.side = THREE.BackSide;
var starMesh = new THREE.Mesh(starGeo, starMat);
scene.add(starMesh);
//renderer
renderer = new THREE.WebGLRenderer({antialiasing : true});
renderer.setSize(WIDTH, HEIGHT);
container.appendChild(renderer.domElement);
//controls
controls = new THREE.OrbitControls( camera, renderer.domElement);
controls.addEventListener( 'change', render );
function animate(){
requestAnimationFrame(animate);
controls.update();
render();
}
function render(){
var delta = clock.getDelta();
earthMesh.rotation.y += rotationSpeed * delta;
renderer.clear();
renderer.render(scene, camera);
}
animate();
//# sourceURL=pen.js
</script>
</body></html>
I have tried;
image = document.createElement( 'img' );
document.body.appendChild( image );
earthMat.map = new THREE.Texture( image );
image.addEventListener( 'load', function ( event ) { texture.needsUpdate = true; } );
image.src = 'data:image/png;base64,<?php echo $image_data_base64 ?>';
But it doesn't seem to be working correctly.
Any help would be greatly appreciated, thanks.
Turns out I had to do;
earthMat.map = THREE.ImageUtils.loadTexture( image.src );
Instead of;
earthMat.map = new THREE.Texture( image );
new event listener;
image.addEventListener( 'load', function ( event ) {
earthMat.map = THREE.ImageUtils.loadTexture( image.src );
earthMat.needsUpdate = true;
});
Perhaps this does not meet the needs of the original question with the base64 string coming from a PHP script, but in our case the solution was much simpler (THREE.js r130):
const manager = new THREE.LoadingManager()
const texture = new THREE.TextureLoader(manager).load('data:image/png;base64,...')

No output in canvas when integrating Shadertoy

I stumbled upon this SO post describing a way to port shadertoy examples into threejs.
I tried following along and ended up with this plunk. The fragment shader looks like this
#ifdef GL_ES
precision highp float;
#endif
uniform float iGlobalTime;
uniform sampler2D iChannel0;
uniform sampler2D iChannel1;
varying vec2 vUv;
void main(void)
{
vec2 p = -1.0 + 2.0 *vUv;
vec2 q = p - vec2(0.5, 0.5);
q.x += sin(iGlobalTime* 0.6) * 0.2;
q.y += cos(iGlobalTime* 0.4) * 0.3;
float len = length(q);
float a = atan(q.y, q.x) + iGlobalTime * 0.3;
float b = atan(q.y, q.x) + iGlobalTime * 0.3;
float r1 = 0.3 / len + iGlobalTime * 0.5;
float r2 = 0.2 / len + iGlobalTime * 0.5;
float m = (1.0 + sin(iGlobalTime * 0.5)) / 2.0;
vec4 tex1 = texture2D(iChannel0, vec2(a + 0.1 / len, r1 ));
vec4 tex2 = texture2D(iChannel1, vec2(b + 0.1 / len, r2 ));
vec3 col = vec3(mix(tex1, tex2, m));
gl_FragColor = vec4(col * len * 1.5, 1.0);
}
and the threejs code like this
window.addEventListener("load", function () {
// Camera variables.
var width = window.innerWidth;
var height = window.innerHeight;
var aspect = width/height;
var fov = 65;
var clipPlaneNear = 0.1;
var clipPlaneFar = 1000;
var clearColor = 0x221f26;
var clearAlpha = 1.0;
// main container.
var $container = $('#container');
// Clock
var clock = new THREE.Clock();
// Set up uniform.
var tuniform = {
iGlobalTime: { type: 'f', value: 0.1 },
iChannel0: { type: 't', value: THREE.ImageUtils.loadTexture( 'images/tex07.jpg') },
iChannel1: { type: 't', value: THREE.ImageUtils.loadTexture( 'images/tex03.jpg' ) },
};
tuniform.iChannel0.value.wrapS = tuniform.iChannel0.value.wrapT = THREE.RepeatWrapping;
tuniform.iChannel1.value.wrapS = tuniform.iChannel1.value.wrapT = THREE.RepeatWrapping;
// Set up our scene.
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(fov, aspect, clipPlaneNear, clipPlaneFar);
//camera.position.z = 10;
var renderer = new THREE.WebGLRenderer({antialias: true});
renderer.setSize(width, height);
renderer.setClearColor(new THREE.Color(clearColor, clearAlpha));
$container.append(renderer.domElement);
var mat = new THREE.ShaderMaterial( {
uniforms: tuniform,
vertexShader: $('#vertexshader').text(),
fragmentShader: $('#fragmentshader').text(),
side:THREE.DoubleSide
} );
tuniform.iGlobalTime.value += clock.getDelta();
var tobject = new THREE.Mesh( new THREE.PlaneGeometry(700, 394,1,1), mat);
//var tobject = new THREE.Mesh( new THREE.PlaneBufferGeometry (700, 394,1,1), mat);
scene.add(tobject);
// Keep updating our scene.
var loop = function loop() {
renderer.render(scene, camera);
};
loop();
}, false);
As you can see from the plunk, there is no output in the page. What have I done wrong in this case, and how can I successfully implement the shader in threejs?
Your camera should be at camera.position.z = 1000; and
Your loop code should be:
var loop = function loop() {
requestAnimationFrame(loop);
tuniform.iGlobalTime.value += clock.getDelta();
renderer.render(scene, camera);
};
loop();
Updated plunker:

Categories

Resources