How to use u_time in mapbox GL JS custom style layer - opengl-es

I want to add time to mapbox shader in fragmentSource, but when I add uniform float u_time; the triangle does not render. Here is a fiddle https://jsfiddle.net/benderlio/o4xc5hw7/33/
var fragmentSource =`
uniform float u_time;
void main() {
gl_FragColor = vec4(sin(1.0*u_time),0.0,1.0,1);
}`

See OpenGL ES Shading Language 1.00 Specification - 4.5.3 Default Precision Qualifiers:
The fragment language has no default precision qualifier for floating point types. Hence for float, floating
point vector and matrix variable declarations, either the declaration must include a precision qualifier or
the default float precision must have been previously declared.
Since the uniform variable has a floating point type you have to add a precision qualifier to the fragment shader. If you don't specify a precision qualifier, that will cause a compile error (e.g. _No precision qualifier for (float)).
Either add a default precision qualifier:
precision mediump float;
uniform float u_time;
Or add an explicit precision qualifier to the uniform variable:
uniform mediump float u_time;
I recommend to validate if the shaders have been compiled successfully (gl.getShaderParameter/gl.getShaderInfoLog) and the program has been linked (gl.getProgramParameter/gl.getProgramInfoLog). For instance:
// create a vertex shader
var vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertexShader, vertexSource);
gl.compileShader(vertexShader);
if (!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS))
alert(gl.getShaderInfoLog(vertexShader));
// create a fragment shader
var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragmentShader, fragmentSource);
gl.compileShader(fragmentShader);
if (!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS))
alert(gl.getShaderInfoLog(fragmentShader));
// link the two shaders into a WebGL program
this.program = gl.createProgram();
gl.attachShader(this.program, vertexShader);
gl.attachShader(this.program, fragmentShader);
gl.linkProgram(this.program);
if (!gl.getProgramParameter(this.program, gl.LINK_STATUS))
alert(gl.getProgramInfoLog(this.program));
mapboxgl.accessToken = 'pk.eyJ1IjoiYmVuZGVybGlkemUiLCJhIjoiY2pud3c0MnN1MDdraTN4cXBraDR3MHdyaCJ9.OsQLWGIWOutuIXCHgT8coQ';
var map = (window.map = new mapboxgl.Map({
container: 'map',
zoom: 3,
center: [7.5, 58],
style: 'mapbox://styles/mapbox/light-v10',
antialias: true // create the gl context with MSAA antialiasing, so custom layers are antialiased
}));
// create a custom style layer to implement the WebGL content
var highlightLayer = {
id: 'highlight',
type: 'custom',
// method called when the layer is added to the map
// https://docs.mapbox.com/mapbox-gl-js/api/#styleimageinterface#onadd
onAdd: function(map, gl) {
// create GLSL source for vertex shader
var vertexSource =
`
uniform mat4 u_matrix;
attribute vec2 a_pos;
void main() {
gl_Position = u_matrix * vec4(a_pos, 0.0, 1);
}`;
// create GLSL source for fragment shader
var fragmentSource =
`
precision mediump float;
uniform float u_time;
void main() {
gl_FragColor = vec4(1.0, 0.0, 1.0, 1.0);
}`;
// create a vertex shader
var vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertexShader, vertexSource);
gl.compileShader(vertexShader);
if (!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS))
alert(gl.getShaderInfoLog(vertexShader));
// create a fragment shader
var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragmentShader, fragmentSource);
gl.compileShader(fragmentShader);
if (!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS))
alert(gl.getShaderInfoLog(fragmentShader));
// link the two shaders into a WebGL program
this.program = gl.createProgram();
gl.attachShader(this.program, vertexShader);
gl.attachShader(this.program, fragmentShader);
gl.linkProgram(this.program);
if (!gl.getProgramParameter(this.program, gl.LINK_STATUS))
alert(gl.getProgramInfoLog(this.program));
this.aPos = gl.getAttribLocation(this.program, 'a_pos');
// define vertices of the triangle to be rendered in the custom style layer
var helsinki = mapboxgl.MercatorCoordinate.fromLngLat({
lng: 25.004,
lat: 60.239
});
var berlin = mapboxgl.MercatorCoordinate.fromLngLat({
lng: 13.403,
lat: 52.562
});
var kyiv = mapboxgl.MercatorCoordinate.fromLngLat({
lng: 30.498,
lat: 50.541
});
// create and initialize a WebGLBuffer to store vertex and color data
this.buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer);
gl.bufferData(
gl.ARRAY_BUFFER,
new Float32Array([
helsinki.x,
helsinki.y,
berlin.x,
berlin.y,
kyiv.x,
kyiv.y
]),
gl.STATIC_DRAW
);
},
// method fired on each animation frame
// https://docs.mapbox.com/mapbox-gl-js/api/#map.event:render
render: function(gl, matrix) {
gl.useProgram(this.program);
gl.uniformMatrix4fv(
gl.getUniformLocation(this.program, 'u_matrix'),
false,
matrix
);
gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer);
gl.enableVertexAttribArray(this.aPos);
gl.vertexAttribPointer(this.aPos, 2, gl.FLOAT, false, 0, 0);
gl.enable(gl.BLEND);
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 3);
}
};
// add the custom style layer to the map
map.on('load', function() {
map.addLayer(highlightLayer);
});
body { margin: 0; padding: 0; }
#map { position: absolute; top: 0; bottom: 0; width: 100%; }
<script src='https://api.tiles.mapbox.com/mapbox-gl-js/v1.4.0/mapbox-gl.js'></script>
<div id='map'></div>

Related

Three js postProcessing solution with transparency

I discovered that solution to create postporcessing effects with three.js :
https://medium.com/#luruke/simple-postprocessing-in-three-js-91936ecadfb7
(made by Luigi De Rosa)
It's a great way to do it. Unfortunately I can't manage to add transparency in my final renderer. Should I add a transparency component inside my postprocessing fragment shader ?
const fragmentShader = `precision highp float;
uniform sampler2D uScene;
uniform vec2 uResolution;
uniform float uTime;
void main() {
vec2 uv = gl_FragCoord.xy / uResolution.xy;
vec3 color = vec3(uv, 1.0);
//simple distortion effect
uv.y += sin(uv.x*30.0+uTime*10.0)/40.0;
uv.x -= sin(uv.y*10.0-uTime)/40.0;
color = texture2D(uScene, uv).rgb;
gl_FragColor = vec4(color, 1.0);
}
`;
Thank you
EDIT 1 :
I added the attribute transparent:true to the RawShaderMaterial.
I changed the format of the new THREE.WebGLRenderTarget by THREE.RGBAFormat instead of THREE.RGBFormat.
I also added those lines at the end of my fragment shader :
gl_FragColor = vec4(color, 1.0);
vec4 tex = texture2D( uScene, uv );
if(tex.a < 0.0) {
gl_FragColor.a = 1.0;
}
But I still doesn't see through my canvas
EDIT 2 :
Here's a snippet with the postProcessing class
let renderer, camera, scene, W = window.innerWidth, H = window.innerHeight, geometry, material, mesh;
initWebgl();
function initWebgl(){
renderer = new THREE.WebGLRenderer( { alpha: true, antialias: true } );
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( W, H );
document.querySelector('.innerCanvas').appendChild( renderer.domElement );
camera = new THREE.OrthographicCamera(-W/H/2, W/H/2, 1/2, -1/2, -0.1, 0.1);
scene = new THREE.Scene();
geometry = new THREE.PlaneBufferGeometry(0.5, 0.5);
material = new THREE.MeshNormalMaterial();
mesh = new THREE.Mesh( geometry , material );
scene.add(mesh);
}
function rafP(){
requestAnimationFrame(rafP);
// renderer.render(scene, camera);
post.render(scene, camera);
}
const vertexShader = `precision highp float;
attribute vec2 position;
void main() {
// Look ma! no projection matrix multiplication,
// because we pass the values directly in clip space coordinates.
gl_Position = vec4(position, 1.0, 1.0);
}`;
const fragmentShader = `precision highp float;
uniform sampler2D uScene;
uniform vec2 uResolution;
uniform float uTime;
void main() {
vec2 uv = gl_FragCoord.xy / uResolution.xy;
vec3 color = vec3(uv, 1.0);
uv.y += sin(uv.x*20.0)/10.0;
color = texture2D(uScene, uv).rgb;
gl_FragColor = vec4(color, 1.0);
vec4 tex = texture2D( uScene, uv );
// if(tex.a - percent < 0.0) {
if(tex.a < 0.0) {
gl_FragColor.a = 1.0;
//or without transparent = true use
// discard;
}
}`;
//PostProcessing
class PostFX {
constructor(renderer) {
this.renderer = renderer;
this.scene = new THREE.Scene();
// three.js for .render() wants a camera, even if we're not using it :(
this.dummyCamera = new THREE.OrthographicCamera();
this.geometry = new THREE.BufferGeometry();
// Triangle expressed in clip space coordinates
const vertices = new Float32Array([
-1.0, -1.0,
3.0, -1.0,
-1.0, 3.0
]);
this.geometry.addAttribute('position', new THREE.BufferAttribute(vertices, 2));
this.resolution = new THREE.Vector2();
this.renderer.getDrawingBufferSize(this.resolution);
this.target = new THREE.WebGLRenderTarget(this.resolution.x, this.resolution.y, {
format: THREE.RGBAFormat, //THREE.RGBFormat
stencilBuffer: false,
depthBuffer: true
});
this.material = new THREE.RawShaderMaterial({
fragmentShader,
vertexShader,
uniforms: {
uScene: { value: this.target.texture },
uResolution: { value: this.resolution }
},
transparent:true
});
// TODO: handle the resize -> update uResolution uniform and this.target.setSize()
this.triangle = new THREE.Mesh(this.geometry, this.material);
// Our triangle will be always on screen, so avoid frustum culling checking
this.triangle.frustumCulled = false;
this.scene.add(this.triangle);
}
render(scene, camera) {
this.renderer.setRenderTarget(this.target);
this.renderer.render(scene, camera);
this.renderer.setRenderTarget(null);
this.renderer.render(this.scene, this.dummyCamera);
console.log(this.renderer);
}
}
post = new PostFX(renderer);
rafP();
body{
margin:0;
padding:0;
background:#00F;
}
.innerCanvas{
position:fixed;
top:0;
left:0;
width:100%;
height:100%;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/105/three.js"></script>
<div class="innerCanvas"></div>
On the Alpha channel, 0 means fully transparent and 1 means fully opaque.
The only thing you need, in this case, is to pass gl_FragColor the result from your texture sample. You don't even need to worry about its value.
gl_FragColor = texture2D(uScene, uv);
JSFiddle

FBO Particles with Cumulative Movement

Link to thread threejs discourse: https://discourse.threejs.org/t/fbo-particles-with-cumulative-movement/7221
This is difficult for me to explain because of my limited knowledge on the subject, but I'm gonna do my best..
At this point, I have a basic FBO particle system in place that works. The following is how it's set up:
var FBO = function( exports ){
var scene, orthoCamera, rtt;
exports.init = function( width, height, renderer, simulationMaterial, renderMaterial ){
var gl = renderer.getContext();
//1 we need FLOAT Textures to store positions
//https://github.com/KhronosGroup/WebGL/blob/master/sdk/tests/conformance/extensions/oes-texture-float.html
if (!gl.getExtension("OES_texture_float")){
throw new Error( "float textures not supported" );
}
//2 we need to access textures from within the vertex shader
//https://github.com/KhronosGroup/WebGL/blob/90ceaac0c4546b1aad634a6a5c4d2dfae9f4d124/conformance-suites/1.0.0/extra/webgl-info.html
if( gl.getParameter(gl.MAX_VERTEX_TEXTURE_IMAGE_UNITS) == 0 ) {
throw new Error( "vertex shader cannot read textures" );
}
//3 rtt setup
scene = new THREE.Scene();
orthoCamera = new THREE.OrthographicCamera(-1,1,1,-1,1/Math.pow( 2, 53 ),1 );
//4 create a target texture
var options = {
minFilter: THREE.NearestFilter,//important as we want to sample square pixels
magFilter: THREE.NearestFilter,//
format: THREE.RGBAFormat,//180407 changed to RGBAFormat
type:THREE.FloatType//important as we need precise coordinates (not ints)
};
rtt = new THREE.WebGLRenderTarget( width,height, options);
//5 the simulation:
//create a bi-unit quadrilateral and uses the simulation material to update the Float Texture
var geom = new THREE.BufferGeometry();
geom.addAttribute( 'position', new THREE.BufferAttribute( new Float32Array([ -1,-1,0, 1,-1,0, 1,1,0, -1,-1, 0, 1, 1, 0, -1,1,0 ]), 3 ) );
geom.addAttribute( 'uv', new THREE.BufferAttribute( new Float32Array([ 0,1, 1,1, 1,0, 0,1, 1,0, 0,0 ]), 2 ) );
scene.add( new THREE.Mesh( geom, simulationMaterial ) );
//6 the particles:
//create a vertex buffer of size width * height with normalized coordinates
var l = (width * height );
var vertices = new Float32Array( l * 3 );
for ( var i = 0; i < l; i++ ) {
var i3 = i * 3;
vertices[ i3 ] = ( i % width ) / width ;
vertices[ i3 + 1 ] = ( i / width ) / height;
}
//create the particles geometry
var geometry = new THREE.BufferGeometry();
geometry.addAttribute( 'position', new THREE.BufferAttribute( vertices, 3 ) );
//the rendermaterial is used to render the particles
exports.particles = new THREE.Points( geometry, renderMaterial );
exports.particles.frustumCulled = false;
exports.renderer = renderer;
};
//7 update loop
exports.update = function(){
//1 update the simulation and render the result in a target texture
// exports.renderer.render( scene, orthoCamera, rtt, true );
exports.renderer.setRenderTarget( rtt );
exports.renderer.render( scene, orthoCamera );
exports.renderer.setRenderTarget( null );
//2 use the result of the swap as the new position for the particles' renderer
// had to add .texture on the end of rtt for r103
exports.particles.material.uniforms.positions.value = rtt.texture;
};
return exports;
}({});
The following are the shaders it uses:
<script type="x-shader/x-vertex" id="simulation_vs">
//vertex shader
varying vec2 vUv;
void main() {
vUv = vec2(uv.x, uv.y);
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}
</script>
<script type="x-shader/x-fragment" id="simulation_fs">
//fragment Shader
uniform sampler2D positions;//DATA Texture containing original positions
varying vec2 vUv;
void main() {
//basic simulation: displays the particles in place.
vec3 pos = texture2D( positions, vUv ).rgb;
// we can move the particle here
gl_FragColor = vec4( pos,1.0 );
}
</script>
<script type="x-shader/x-vertex" id="render_vs">
//vertex shader
uniform sampler2D positions;//RenderTarget containing the transformed positions
uniform float pointSize;//size
void main() {
//the mesh is a nomrliazed square so the uvs = the xy positions of the vertices
vec3 pos = texture2D( positions, position.xy ).xyz;
//pos now contains a 3D position in space, we can use it as a regular vertex
//regular projection of our position
gl_Position = projectionMatrix * modelViewMatrix * vec4( pos, 1.0 );
//sets the point size
gl_PointSize = pointSize;
}
</script>
<script type="x-shader/x-fragment" id="render_fs">
//fragment shader
void main()
{
gl_FragColor = vec4( vec3( 1. ), .25 );
}
</script>
I understand that I would move the particles in the "simulation_fs", but if I move a particle in that shader, if I try to do something like this,
pos.x += 1.0;
it will still only shift it one unit from the original texture position. I want the movement to be cumulative.
Would using a 2nd set of simulation shaders allow me to move the particles in a cumulative way? Is that a practical solution?
For cumulative movement, you need to use uniforms:
Look into passing a uniform named time to your vertex shader. Then you can update the time once per frame, and you can use that to animate your vertex positions. For example:
position.x = 2.0 * time; // Increment linearly
position.x = sin(time); // Sin wave back-forth animation
Without a changing variable, your vertex animations will be static from one frame to the next.
I needed to accomplish something like this, and set up an absolutely minimal example that I can tweak in the future. You'll see the positional changes are cumulative.
The following was simplified from a wonderful discussion of FBO's by Nicolas Barradeau (a webgl wizard):
// specify the container where we'll render the scene
var elem = document.querySelector('body'),
elemW = elem.clientWidth,
elemH = elem.clientHeight
// generate a scene object
var scene = new THREE.Scene();
// generate a camera
var camera = new THREE.PerspectiveCamera(75, elemW/elemH, 0.001, 100);
// generate a renderer
var renderer = new THREE.WebGLRenderer({antialias: true, alpha: true});
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(elemW, elemH);
elem.appendChild(renderer.domElement);
// generate controls
var controls = new THREE.TrackballControls(camera, renderer.domElement);
// position camera and controls
camera.position.set(0.5, 0.5, -5);
controls.target = new THREE.Vector3(0.5, 0.5, 0);
/**
* FBO
**/
// verify browser agent supports "frame buffer object" features
gl = renderer.getContext();
if (!gl.getExtension('OES_texture_float') ||
gl.getParameter(gl.MAX_VERTEX_TEXTURE_IMAGE_UNITS) == 0) {
alert(' * Cannot create FBO :(');
}
// set initial positions of `w*h` particles
var w = h = 256,
i = 0,
data = new Float32Array(w*h*3);
for (var x=0; x<w; x++) {
for (var y=0; y<h; y++) {
data[i++] = x/w;
data[i++] = y/h;
data[i++] = 0;
}
}
// feed those positions into a data texture
var dataTex = new THREE.DataTexture(data, w, h, THREE.RGBFormat, THREE.FloatType);
dataTex.minFilter = THREE.NearestFilter;
dataTex.magFilter = THREE.NearestFilter;
dataTex.needsUpdate = true;
// add the data texture with positions to a material for the simulation
var simMaterial = new THREE.RawShaderMaterial({
uniforms: { posTex: { type: 't', value: dataTex }, },
vertexShader: document.querySelector('#sim-vs').textContent,
fragmentShader: document.querySelector('#sim-fs').textContent,
});
// delete dataTex; it isn't used after initializing point positions
delete dataTex;
THREE.FBO = function(w, simMat) {
this.scene = new THREE.Scene();
this.camera = new THREE.OrthographicCamera(-w/2, w/2, w/2, -w/2, -1, 1);
this.scene.add(new THREE.Mesh(new THREE.PlaneGeometry(w, w), simMat));
};
// create a scene where we'll render the positional attributes
var fbo = new THREE.FBO(w, simMaterial);
// create render targets a + b to which the simulation will be rendered
var renderTargetA = new THREE.WebGLRenderTarget(w, h, {
wrapS: THREE.RepeatWrapping,
wrapT: THREE.RepeatWrapping,
minFilter: THREE.NearestFilter,
magFilter: THREE.NearestFilter,
format: THREE.RGBFormat,
type: THREE.FloatType,
stencilBuffer: false,
});
// a second render target lets us store input + output positional states
renderTargetB = renderTargetA.clone();
// render the positions to the render targets
renderer.render(fbo.scene, fbo.camera, renderTargetA, false);
renderer.render(fbo.scene, fbo.camera, renderTargetB, false);
// store the uv attrs; each is x,y and identifies a given point's
// position data within the positional texture; must be scaled 0:1!
var geo = new THREE.BufferGeometry(),
arr = new Float32Array(w*h*3);
for (var i=0; i<arr.length; i++) {
arr[i++] = (i%w)/w;
arr[i++] = Math.floor(i/w)/h;
arr[i++] = 0;
}
geo.addAttribute('position', new THREE.BufferAttribute(arr, 3, true))
// create material the user sees
var material = new THREE.RawShaderMaterial({
uniforms: {
posMap: { type: 't', value: null }, // `posMap` is set each render
},
vertexShader: document.querySelector('#ui-vert').textContent,
fragmentShader: document.querySelector('#ui-frag').textContent,
transparent: true,
});
// add the points the user sees to the scene
var mesh = new THREE.Points(geo, material);
scene.add(mesh);
function render() {
// at the start of the render block, A is one frame behind B
var oldA = renderTargetA; // store A, the penultimate state
renderTargetA = renderTargetB; // advance A to the updated state
renderTargetB = oldA; // set B to the penultimate state
// pass the updated positional values to the simulation
simMaterial.uniforms.posTex.value = renderTargetA.texture;
// run a frame and store the new positional values in renderTargetB
renderer.render(fbo.scene, fbo.camera, renderTargetB, false);
// pass the new positional values to the scene users see
material.uniforms.posMap.value = renderTargetB.texture;
// render the scene users see as normal
renderer.render(scene, camera);
controls.update();
requestAnimationFrame(render);
};
render();
html, body { width: 100%; height: 100%; background: #000; }
body { margin: 0; overflow: hidden; }
canvas { width: 100%; height: 100%; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/101/three.min.js"></script>
<script src="https://threejs.org/examples/js/controls/TrackballControls.js"></script>
<!-- The simulation shaders update positional attributes -->
<script id='sim-vs' type='x-shader/x-vert'>
precision mediump float;
uniform mat4 projectionMatrix;
uniform mat4 modelViewMatrix;
attribute vec2 uv; // x,y offsets of each point in texture
attribute vec3 position;
varying vec2 vUv;
void main() {
vUv = vec2(uv.x, 1.0 - uv.y);
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
</script>
<script id='sim-fs' type='x-shader/x-frag'>
precision mediump float;
uniform sampler2D posTex;
varying vec2 vUv;
void main() {
// read the supplied x,y,z vert positions
vec3 pos = texture2D(posTex, vUv).xyz;
// update the positional attributes here!
pos.x += cos(pos.y) / 100.0;
pos.y += tan(pos.x) / 100.0;
// render the new positional attributes
gl_FragColor = vec4(pos, 1.0);
}
</script>
<!-- The ui shaders render what the user sees -->
<script id='ui-vert' type='x-shader/x-vert'>
precision mediump float;
uniform sampler2D posMap; // contains positional data read from sim-fs
uniform mat4 projectionMatrix;
uniform mat4 modelViewMatrix;
attribute vec2 position;
void main() {
// read this particle's position, which is stored as a pixel color
vec3 pos = texture2D(posMap, position.xy).xyz;
// project this particle
vec4 mvPosition = modelViewMatrix * vec4(pos, 1.0);
gl_Position = projectionMatrix * mvPosition;
// set the size of each particle
gl_PointSize = 0.3 / -mvPosition.z;
}
</script>
<script id='ui-frag' type='x-shader/x-frag'>
precision mediump float;
void main() {
gl_FragColor = vec4(0.0, 0.5, 1.5, 1.0);
}
</script>

THREE.JS ShaderMaterial exceeds of my mesh

I have a classic mesh composed by a THREE.PlaneGeometry and a material. If I add a THREE.MeshNormalMaterial() here's the result I get :
So far, so good. But when I call my THREE.ShaderMaterial(), using an external texture, the dimension of my mesh completely changes :
I always get that weird ratio even if - like in the screenshot - my texture is a square (512x512). I just want my MaterialShader to fit inside my geometry.
Here is the code of my MaterialShader :
var material = new THREE.ShaderMaterial( {
uniforms: uniforms,
vertexShader: document.getElementById( 'vertexShader' ).textContent,
fragmentShader: document.getElementById( 'fragmentShader' ).textContent,
} );
var mesh = new THREE.Mesh( geometry, material );
scene.add( mesh );
I don't see what I'm missing. Does anyone has an idea ? Thank you very much.
UPDATE :
Here's the fully code of my ShaderMaterial :
material = new THREE.ShaderMaterial({
uniforms:{
u_time: { type: "f", value: 1.0 },
u_resolution: { type: "v2", value: new THREE.Vector2() },
u_mouse: { type: "v2", value: new THREE.Vector2() },
texture1: { type: "t", value: texture }
},
vertexShader:`
void main() {
gl_Position = vec4( position, 1.0 );
}
`,
fragmentShader:`
#ifdef GL_ES
precision highp float;
precision highp int;
#endif
uniform vec2 u_resolution;
uniform vec2 u_mouse;
uniform float u_time;
uniform sampler2D texture1;
void main(){
float pyt=3.1415926*2./3.;
float m=-1e10;
vec4 mv= vec4(0.);
vec2 xy = gl_FragCoord.xy/u_resolution.xy;
int ic=0;
for (int i=0;i<30;i++){
vec2 np=vec2(xy.x+float(i)/u_resolution.x*sin(3.14/2.) * 4.,xy.y+float(i)/u_resolution.y*cos(3.14/2.) * 4.);
float jTime = u_time*1.618;
vec4 tk=texture2D(texture1,np);
float t=tk.r*sin(jTime)+tk.g*sin(jTime+pyt)+tk.b*sin(jTime+2.*pyt)-.01*float(i);
if (t>m){m=t; mv=tk;ic=i;}
}
float sc=float(ic)/30.;
vec4 tk=texture2D(texture1,xy);
mv=sc*tk+(1.-sc)*mv;
gl_FragColor = vec4(mv.r,mv.g,mv.b,1.0);
}
`
});
UPDATE2 :
I changed my vertex shader but nothing has changed.
I might have a lead : I think this is related to my camera settings. I changed them and I've a better result. Now my texture fits into my square mesh.
Unfortunately, the scale isn't good. Since my texture is a square too, I want it to have exactly the same size than my mesh, for now it's zoomed.
How can I manage the size of my texture ? Should I do it inside my vertexShader ?
Here's my texture settings for now :
texture = new THREE.TextureLoader().load( "test5.jpg");
texture.wrapS = THREE.RepeatWrapping;
texture.wrapT = THREE.RepeatWrapping;
UPDATE 3 :
I found that code to apply a texture and make it fit to my mesh :
https://bl.ocks.org/duhaime/c8375f1c313587ac629e04e0253481f9
It's working but as soon as I change the example fragement shader by mine, I've no errors but the shaders become one unique color. I don't understand what I'm missing...
Try this code of the vertex shader:
void main() {
gl_Position = projectionMatrix *
modelViewMatrix *
vec4(position,1.0);
}
Reference
Simply pass the uv coordinates from the vertex shader to the fragment shader and use them there.
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 1000);
camera.position.set(0, 0, 5);
camera.lookAt(scene.position);
var renderer = new THREE.WebGLRenderer();
renderer.setClearColor(0x404040);
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
var iResolution = new THREE.Vector2();
var planeGeo = new THREE.PlaneBufferGeometry(5, 5);
var planeMat = new THREE.ShaderMaterial({
uniforms: {
texture: {
value: null
},
iResolution: {
value: iResolution
},
iTime: {
value: 0
}
},
vertexShader: `
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix *
modelViewMatrix *
vec4(position,1.0);
}
`,
fragmentShader: `
uniform sampler2D texture;
uniform float iTime;
uniform vec2 iResolution;
varying vec2 vUv;
void main() {
float pyt=3.1415926*2./3.;
float m=-1e10;//very negitive start value for maximisation algorithm.
vec4 mv= vec4(0.);//lank starting value of max so far
vec2 xy = vUv;
int ic=0;//stores smear distance
for (int i=0;i<30;i++){
//point offset on a circle
vec2 np=vec2(xy.x+float(i)/iResolution.x*sin(iTime),xy.y+float(i)/iResolution.y*cos(iTime));
//colour cycles faster than position
float jTime = iTime*1.618;
//get neerby point
vec4 tk=texture2D(texture,np);
// and if its colourfull enough, use that
float t=tk.r*sin(jTime)+tk.g*sin(jTime+pyt)+tk.b*sin(jTime+2.*pyt)-.01*float(i);
if (t>m){m=t; mv=tk;ic=i;}
}
//mix smeared with background depending ondistance
float sc=float(ic)/30.;
vec4 tk=texture2D(texture,xy);
mv=sc*tk+(1.-sc)*mv;
gl_FragColor = vec4(mv.rgb,1.0);
}
`
});
var textureLoader = new THREE.TextureLoader();
textureLoader.load("https://threejs.org/examples/textures/UV_Grid_Sm.jpg", tex => {
planeMat.uniforms.texture.value = tex;
planeMat.uniforms.texture.value.needsUpdate = true;
iResolution.set(tex.image.width, tex.image.height);
planeMat.needsUpdate = true;
console.log(texture);
});
var plane = new THREE.Mesh(planeGeo, planeMat);
scene.add(plane);
var clock = new THREE.Clock();
var time = 0;
render();
function render() {
requestAnimationFrame(render);
time += clock.getDelta();
planeMat.uniforms.iTime.value = time;
renderer.render(scene, camera);
}
body {
overflow: hidden;
margin: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/96/three.min.js"></script>

ThreeJS draggable CSS3DObject

Been using THREE JS CSS3DRenderer to render a panorama view based in this example.
I have added another CSS3DObject image which represents a GPS marker with the following code below:
var marker = document.createElement( 'img' );
marker.classList.add('gps_marker');
marker.src = 'gps_marker.jpg';
marker.width = 5;
marker.style.border = 'solid 1px red';
var object = new THREE.CSS3DObject( marker );
object.position.set( 0, 0, 30 );
scene.add( object );
Now I'd like to make the gps markers draggable around the scene around the scene. Dragcontrols doesn't seem to work with CSS3DObject. Any help would be very appreciated.
You need to use "textured" point cloud (THREE.Points) object with shader material. Use uniform to pass texture to fragment shader. Not sure that dragcontrols will work with it (will move all points at ones if so). But this is IMHO the best way to draw icons. Example:
let uniforms = {
iconSize: {
value: self.iconSize
},
color: {
value: new THREE.Color(0xffffff)
},
texture: {
value: self.textureLoader.load(url, texture => {
texture.flipY = false;
texture.needsUpdate = true;
})
}
};
let material = new THREE.ShaderMaterial({
uniforms: uniforms,
vertexShader: document.getElementById('point-cloud-vertex-shader').textContent,
fragmentShader: document.getElementById('point-cloud-fragment-shader').textContent,
side: THREE.DoubleSide,
transparent: true,
opacity: 0.7,
depthTest: false,
clipping: true,
clippingPlanes: self.clippingPlanes,
});
//... create THREE.Points object with this material
shaders:
<script type="x-shader/x-vertex" id="point-cloud-vertex-shader">
uniform vec3 color;
uniform float iconSize;
uniform sampler2D texture;
void main() {
vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );
gl_PointSize = iconSize;
gl_Position = projectionMatrix * mvPosition;
}
</script>
<script type="x-shader/x-fragment" id="point-cloud-fragment-shader">
uniform vec3 color;
uniform float iconSize;
uniform sampler2D texture;
void main() {
gl_FragColor = texture2D( texture, gl_PointCoord );
}
</script>

Is there a way of handling Framebuffer Objects (FBO's) in Three.js?

I'm learning to manipulate postion values in the GPU using textures a.k.a., Framebuffer Objects (FBO's), while using Three.js. I've been using this question as a starting place, and this example by #mrdoob and #zz85, as well as this old thread.
However, the examples are quite dated (examples use three.js rev.55 vs. current rev.80), so I'm needing make a fair number of revisions and reworks to the code. Before I get in too deep I wanted to pause and ask if any way of handling FBO's has already been written into the Three.js code base, or if I've overlooked an updated script somewhere. Thanks!
If not, I'll do my darnedest and perhaps post the result here if it seems generally useful.
In case you like to "pop the hood", I wanted to share an absolutely minimal example of a FBO scene in THREE.js. Hopefully the inline comments help spell out how this comes together:
// specify the container where we'll render the scene
var elem = document.querySelector('body'),
elemW = elem.clientWidth,
elemH = elem.clientHeight
// generate a scene object
var scene = new THREE.Scene();
// generate a camera
var camera = new THREE.PerspectiveCamera(75, elemW/elemH, 0.001, 100);
// generate a renderer
var renderer = new THREE.WebGLRenderer({antialias: true, alpha: true});
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(elemW, elemH);
elem.appendChild(renderer.domElement);
// generate controls
var controls = new THREE.TrackballControls(camera, renderer.domElement);
// position camera and controls
camera.position.set(0.5, 0.5, -5);
controls.target = new THREE.Vector3(0.5, 0.5, 0);
/**
* FBO
**/
// verify browser agent supports "frame buffer object" features
gl = renderer.getContext();
if (!gl.getExtension('OES_texture_float') ||
gl.getParameter(gl.MAX_VERTEX_TEXTURE_IMAGE_UNITS) == 0) {
alert(' * Cannot create FBO :(');
}
// set initial positions of `w*h` particles
var w = h = 256,
i = 0,
data = new Float32Array(w*h*3);
for (var x=0; x<w; x++) {
for (var y=0; y<h; y++) {
data[i++] = x/w;
data[i++] = y/h;
data[i++] = 0;
}
}
// feed those positions into a data texture
var dataTex = new THREE.DataTexture(data, w, h, THREE.RGBFormat, THREE.FloatType);
dataTex.minFilter = THREE.NearestFilter;
dataTex.magFilter = THREE.NearestFilter;
dataTex.needsUpdate = true;
// add the data texture with positions to a material for the simulation
var simMaterial = new THREE.RawShaderMaterial({
uniforms: { posTex: { type: 't', value: dataTex }, },
vertexShader: document.querySelector('#sim-vs').textContent,
fragmentShader: document.querySelector('#sim-fs').textContent,
});
// delete dataTex; it isn't used after initializing point positions
delete dataTex;
THREE.FBO = function(w, simMat) {
this.scene = new THREE.Scene();
this.camera = new THREE.OrthographicCamera(-w/2, w/2, w/2, -w/2, -1, 1);
this.scene.add(new THREE.Mesh(new THREE.PlaneGeometry(w, w), simMat));
};
// create a scene where we'll render the positional attributes
var fbo = new THREE.FBO(w, simMaterial);
// create render targets a + b to which the simulation will be rendered
var renderTargetA = new THREE.WebGLRenderTarget(w, h, {
wrapS: THREE.RepeatWrapping,
wrapT: THREE.RepeatWrapping,
minFilter: THREE.NearestFilter,
magFilter: THREE.NearestFilter,
format: THREE.RGBFormat,
type: THREE.FloatType,
stencilBuffer: false,
});
// a second render target lets us store input + output positional states
renderTargetB = renderTargetA.clone();
// render the positions to the render targets
renderer.render(fbo.scene, fbo.camera, renderTargetA, false);
renderer.render(fbo.scene, fbo.camera, renderTargetB, false);
// store the uv attrs; each is x,y and identifies a given point's
// position data within the positional texture; must be scaled 0:1!
var geo = new THREE.BufferGeometry(),
arr = new Float32Array(w*h*3);
for (var i=0; i<arr.length; i++) {
arr[i++] = (i%w)/w;
arr[i++] = Math.floor(i/w)/h;
arr[i++] = 0;
}
geo.addAttribute('position', new THREE.BufferAttribute(arr, 3, true))
// create material the user sees
var material = new THREE.RawShaderMaterial({
uniforms: {
posMap: { type: 't', value: null }, // `posMap` is set each render
},
vertexShader: document.querySelector('#ui-vert').textContent,
fragmentShader: document.querySelector('#ui-frag').textContent,
transparent: true,
});
// add the points the user sees to the scene
var mesh = new THREE.Points(geo, material);
scene.add(mesh);
function render() {
// at the start of the render block, A is one frame behind B
var oldA = renderTargetA; // store A, the penultimate state
renderTargetA = renderTargetB; // advance A to the updated state
renderTargetB = oldA; // set B to the penultimate state
// pass the updated positional values to the simulation
simMaterial.uniforms.posTex.value = renderTargetA.texture;
// run a frame and store the new positional values in renderTargetB
renderer.render(fbo.scene, fbo.camera, renderTargetB, false);
// pass the new positional values to the scene users see
material.uniforms.posMap.value = renderTargetB.texture;
// render the scene users see as normal
renderer.render(scene, camera);
controls.update();
requestAnimationFrame(render);
};
render();
html, body { width: 100%; height: 100%; background: #000; }
body { margin: 0; overflow: hidden; }
canvas { width: 100%; height: 100%; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/101/three.min.js"></script>
<script src="https://threejs.org/examples/js/controls/TrackballControls.js"></script>
<!-- The simulation shaders update positional attributes -->
<script id='sim-vs' type='x-shader/x-vert'>
precision mediump float;
uniform mat4 projectionMatrix;
uniform mat4 modelViewMatrix;
attribute vec2 uv; // x,y offsets of each point in texture
attribute vec3 position;
varying vec2 vUv;
void main() {
vUv = vec2(uv.x, 1.0 - uv.y);
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
</script>
<script id='sim-fs' type='x-shader/x-frag'>
precision mediump float;
uniform sampler2D posTex;
varying vec2 vUv;
void main() {
// read the supplied x,y,z vert positions
vec3 pos = texture2D(posTex, vUv).xyz;
// update the positional attributes here!
pos.x += cos(pos.y) / 100.0;
pos.y += tan(pos.x) / 100.0;
// render the new positional attributes
gl_FragColor = vec4(pos, 1.0);
}
</script>
<!-- The ui shaders render what the user sees -->
<script id='ui-vert' type='x-shader/x-vert'>
precision mediump float;
uniform sampler2D posMap; // contains positional data read from sim-fs
uniform mat4 projectionMatrix;
uniform mat4 modelViewMatrix;
attribute vec2 position;
void main() {
// read this particle's position, which is stored as a pixel color
vec3 pos = texture2D(posMap, position.xy).xyz;
// project this particle
vec4 mvPosition = modelViewMatrix * vec4(pos, 1.0);
gl_Position = projectionMatrix * mvPosition;
// set the size of each particle
gl_PointSize = 0.3 / -mvPosition.z;
}
</script>
<script id='ui-frag' type='x-shader/x-frag'>
precision mediump float;
void main() {
gl_FragColor = vec4(0.0, 0.5, 1.5, 1.0);
}
</script>
I just discovered that there is a way in Three.js to handle Frame Buffer Objects (FBOs) to calculate things like changing position data using the GPU; it is called the THREE.GPUComputationRenderer. There is an excellent flock of birds example here that demonstrates how to pass a number of variables by rendering their values into textures to be used in the final shader.

Resources