filling shader attributes from webgl context in threejs visualization - three.js

I'm learning about shaders using a number of existing webgl tutorials, and I was hoping there would be a way to attach a compiled shader program to a threejs shadermaterial, but I'm getting stuck. If possible it would be very nice to set attributes and uniforms using the gl methods, and then set the shader program on the material. Here's what I've tried.
<!doctype html>
<html>
<head>
<script src="http://threejs.org/build/three.min.js"></script>
<meta charset="utf-8" />
<title>Sample Three.js</title>
<style>
#container {
background: #000;
width: 400px;
height: 300px;
}
</style>
</head>
<body>
</body>
<script type="x-shader/x-vertex" id="vertexshader">
// switch on high precision floats
#ifdef GL_ES
precision highp float;
#endif
uniform mat4 projectionmyMatrix;
attribute vec3 vertexPos;
attribute float displacement;
uniform float amplitude;
void main()
{
vec3 newPos = vertexPos;
gl_Position = projectionMatrix * modelViewMatrix * vec4(newPos,1.0);
}
</script>
<script type="x-shader/x-fragment" id="fragmentshader">
#ifdef GL_ES
precision highp float;
#endif
void main( void ) {
gl_FragColor = vec4( 1.0,1.0,1.0,1.0);
}
</script>
<!-- End Shaders -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.5.1/jquery.min.js"></script>
<script type="text/javascript">
// set the scene size
var WIDTH = 800,
HEIGHT = 600;
// set some camera attributes
var VIEW_ANGLE = 45,
ASPECT = WIDTH / HEIGHT,
NEAR = 1,
FAR = 1000;
// get the DOM element to attach to
// - assume we've got jQuery to hand
var $container = $('body');
// create a WebGL renderer, camera
// and a scene
var renderer = new THREE.WebGLRenderer();
var camera = new THREE.PerspectiveCamera(
VIEW_ANGLE,
ASPECT,
NEAR,
FAR );
var scene = new THREE.Scene();
// the camera starts at 0,0,0 so pull it back
camera.position.z = 300;
// start the renderer
renderer.setSize(WIDTH, HEIGHT);
// attach the render-supplied DOM element
$container.append(renderer.domElement);
// set up the sphere vars
var radius = 50, segments = 16, rings = 16;
// create the sphere's material
var shaderMaterial = new THREE.ShaderMaterial({
vertexShader: $('#vertexshader').text(),
fragmentShader: $('#fragmentshader').text()
});
// create a new mesh with sphere geometry -
// we will cover the sphereMaterial next!
var sphere = new THREE.Mesh(
new THREE.SphereGeometry(radius, segments, rings),
shaderMaterial);
//filling the attribute vertex array
// add the sphere and camera to the scene
scene.add(sphere);
scene.add(camera);
renderer.compile(scene,camera)
var gl = renderer.getContext()
var sq = createSquare(gl)
var prg = shaderMaterial.program.program
var posAttr = gl.getAttribLocation(prg,'vertexPos')
// set the vertex buffer to be drawn
gl.bindBuffer(gl.ARRAY_BUFFER, sq.buffer);
// set the shader to use
gl.useProgram(prg);
// connect up the shader parameters: vertex position and projection/model matrices
gl.vertexAttribPointer(posAttr, sq.vertSize, gl.FLOAT, false, 0, 0);
renderer.compile(scene,camera)
// create a rendering loop
var frame = 0;
function update() {
frame += .01
renderer.render(scene, camera);
requestAnimationFrame(update)
}
requestAnimationFrame(update)
</script>
</html>
I would prefer not to have to translate from the tutorials into the uniforms, attributes syntax used by three.js denoted below
```
var attributes = {
displacement: {
type: 'f', // a float
value: [] // an empty array
}
};
var uniforms = {
amplitude: {
type: 'f', // a float
value: 1
}
};
var vShader = $('#vertexshader');
var fShader = $('#fragmentshader');
// create the final material
var shaderMaterial =
new THREE.MeshShaderMaterial({
uniforms: uniforms,
attributes: attributes,
vertexShader: vShader.text(),
fragmentShader: fShader.text()
});
...

No, this approach is neither recommended nor supported. Instead of using the raw WebGL context, you have two options:
You can use THREE.ShaderMaterial for a custom shader definition. three.js automatically provides some built-in attributes and uniforms (e.g modelViewMatrix or projectionMatrix) which are frequently used by vertex and fragment shaders. The official doc page provides a lot of information.
THREE.RawShaderMaterial is a more lightweight option since three.js does not provide the mentioned built-in uniforms and attributes.
The following two basic examples show the usage of both materials with the latest version of three.js (R91):
https://threejs.org/examples/#webgl_shader
https://threejs.org/examples/#webgl_buffergeometry_rawshader
I recommend to work with these examples and not with potentially outdated tutorials. For example attributes are no parameter of ShaderMaterial anymore. Instead, attribute data are part of the geometry.

Related

How to prevent interpolation of vertex colors in THREE.js shader?

I am trying to write a shader that draws contour plots on meshes.
Here is an example of contour plot.
My first aim is visualizing one triangle face with different colors.
You can find the code that I am using in here.
<html lang="en">
<head>
<title>Face Contour Example</title>
</head>
<body>
<script src="http://threejs.org/build/three.min.js"></script>
<script src="http://threejs.org/examples/js/controls/OrbitControls.js"></script>
<script id="vertexShader" type="x-shader/x-vertex">
varying vec3 vColor;
void main(){
vColor = color;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
</script>
<script id="fragmentShader" type="x-shader/x-fragment">
varying vec3 vColor;
void main(){
gl_FragColor = vec4( vColor.rgb, 1.0 );
}
</script>
<script type="text/javascript">
var camera, scene, renderer, mesh, material, controls;
init();
animate();
function init() {
// Renderer.
renderer = new THREE.WebGLRenderer();
//renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
// Add renderer to page
document.body.appendChild(renderer.domElement);
// Create camera.
camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 1, 1000);
camera.position.z = -400;
// Create scene.
scene = new THREE.Scene();
var colors = {
"color1" : {
type : "c",
value : new THREE.Color(0xff0000) //r
},
"color2" : {
type : "c",
value : new THREE.Color(0x00ff00) //b
},
"color3" : {
type : "c",
value : new THREE.Color(0x0000ff) //g
},
};
var fShader = document.getElementById('fragmentShader').text;
var vShader = document.getElementById('vertexShader').text;
// Create material
var material = new THREE.ShaderMaterial({
vertexShader: vShader,
fragmentShader: fShader,
vertexColors: THREE.VertexColors,
});
// var material = new THREE.MeshBasicMaterial( { vertexColors: THREE.VertexColors } );
// Create cube and add to scene.
var geometry = new THREE.Geometry();
geometry.vertices=[
new THREE.Vector3(100,0,0),
new THREE.Vector3(-100,0,0),
new THREE.Vector3(50,100,0)
]
var face=new THREE.Face3();
face.a=0;
face.b=1;
face.c=2;
face.vertexColors[ 0 ] = colors["color1"].value;
face.vertexColors[ 1 ] = colors["color2"].value;
face.vertexColors[ 2 ] = colors["color3"].value;
geometry.faces=[face]
mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
function addWireFrame(){
//Create wireframe helper for mesh with same geometry
var wireframeMesh=new THREE.WireframeGeometry(geometry);
var line = new THREE.LineSegments( wireframeMesh );
line.material.depthTest = false;
line.material.opacity = 0.75;
line.material.transparent = true;
mesh.add( line );
}
addWireFrame();
//Orbit controls
controls = new THREE.OrbitControls( camera );
// Create ambient light and add to scene.
var light = new THREE.AmbientLight(0x404040); // soft white light
scene.add(light);
// Create directional light and add to scene.
var directionalLight = new THREE.DirectionalLight(0xffffff);
directionalLight.position.set(1, 1, 1).normalize();
scene.add(directionalLight);
// Add listener for window resize.
window.addEventListener('resize', onWindowResize, false);
}
function animate() {
requestAnimationFrame(animate);
controls.update();
renderer.render(scene, camera);
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
</script>
</body>
</html>
In the code I assigned red, green and blue colors to each vertices of a face.
In vertex shader, I redirected those colors to fragment shader. And In fragment shader, I am planning to use my own formula to decide which color will be used for that instance of the fragment. (My formula will depend on the position on the face.)
However, I couldn't manage to prevent interpolation of vertex colors. Is there a way to pick vertex color from an array directly without interpolation in three.js?
Also, I appreciate alternative solutions that may be suitable for my problem.
You don't want to disable interpolation. You want, instead, to use the interpolated coordinates as an index. The interpolated color value tells you how close you are to each of the vertices. You can then quantize this interpolated value into ranges, or indexes into a color array, to produce the end color.
I modified your fiddle to show the color of the closest vertex using the following pixel shader:
void main(){
vec3 c = vColor;
gl_FragColor = vec4(c.r > c.g && c.r > c.b ? 1.0 : 0.0,
c.g > c.r && c.g > c.b ? 1.0 : 0.0,
c.b > c.r && c.b > c.g ? 1.0 : 0.0,
1.0 );
}
The result looks like this:
You will need a more complex quantization method to show a contour map, but I hope this approach gives you a good start.

ThreeJS - Material which shows surface structure in a scene without light

In my scene I render complex objects, which have a complex surface structure. Furthermore I am not using light in my scene and I am trying to avoid it.
For now I am using the MeshNormalMaterial which shows perfectly the surface structures of my objects.
object with MeshNormalMaterial:
But I want to render certain objects with a unique color (e.g. from dark red to light red based on the surface structure/ similar to the MeshNormalMaterial).
I tried the MeshDepthMaterial for one object, but it rendered the whole object in almost color (no/ sparse color gradation) and not as expected like in this example. Independent of the camera position.
Same object from above with: MeshDepthMaterial
I am using a THREE.PerspectiveCamera with THREE.OrbitControls. Camera properties:
//camera attributes
public fieldOfView: number = 60;
public nearClippingPane: number = 0.1;
public farClippingPane: number = 50000;
Does the MeshNormalMaterial require light or why is this the case? Can I somehow amplify the depth effect of MeshNormalMaterial?
Is ist possible to restrict the RGB Colors of MeshNormalMaterial or do I have to use another Material for my purpose?
I just slightly modified the code of the fragment shader from this SO answer, so all credits to Rabbid76:
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.01, 1000);
camera.position.set(0, 0, 10);
var renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
var controls = new THREE.OrbitControls(camera, renderer.domElement);
var colors = {
color1: "#ff0000",
color2: "#ffaaaa"
}
var geometry = new THREE.TorusKnotBufferGeometry(2, 0.5, 100, 16);
var material = new THREE.ShaderMaterial({
uniforms: {
color1: {
value: new THREE.Color(colors.color1)
},
color2: {
value: new THREE.Color(colors.color2)
}
},
vertexShader: vertShader,
fragmentShader: fragShader
});
var mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
var gui = new dat.GUI();
gui.addColor(colors, "color1").onChange(function(value) {
material.uniforms.color1.value.set(value);
});
gui.addColor(colors, "color2").onChange(function(value) {
material.uniforms.color2.value.set(value);
});
render();
function render() {
requestAnimationFrame(render);
renderer.render(scene, camera);
}
body {
overflow: hidden;
margin: 0;
}
<script src="https://threejs.org/build/three.min.js"></script>
<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>
<script src="https://threejs.org/examples/js/libs/dat.gui.min.js"></script>
<script>
var vertShader = `
varying vec3 vNormal;
void main(void)
{
vNormal = normalMatrix * normalize(normal);
gl_Position = projectionMatrix * modelViewMatrix * vec4(position,1.0);
}
`;
var fragShader = `
uniform vec3 color1;
uniform vec3 color2;
varying vec3 vNormal;
void main(void)
{
vec3 view_nv = normalize(vNormal);
vec3 nv_color = view_nv * 0.5 + 0.5;
vec3 c = mix(color1, color2, nv_color.r);
gl_FragColor = vec4(c, 1.0);
}
`;
</script>

Interpolated Colous with Fragment-and VertexShader in GLSL

i want to draw a cube an a square in a scene, both with interpolated colours. I want to use just the fragment-and vertexshader!
i can draw it with just simple one colour.
The code for that is the following
<!DOCTYPE html>
[enter image description here][1]<html>
<head>
<meta charset="utf-8">
<title>GLSL - Texturen</title>
</head>
<body>
<h1>Texturen</h1>
<!-- three.js einbinden -->
<script src="js/three.min.js"></script>
<!-- Einbinden der OrbitControls, um die Darstellung mit der Maus rotieren zu können. -->
<script src="js/OrbitControls.js"></script>
<script type="x-shader/x-vertex" id="vertexshader">
// switch on high precision floats
#ifdef GL_ES
precision highp float;
#endif
// transmit uv coordinates to fragment shader
void main()
{
/* set fragment position */
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
// transmit uv coordinates to fragment shader
}
</script>
<script type="x-shader/x-fragment" id="fragmentshader">
#ifdef GL_ES
precision highp float;
#endif
// transmit uv coordinates to fragment shader
void main()
{
// set color based on uv coordinates
// gl_FragColor =
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
}
</script>
<!-- TODO: separate Shader für spätere getrennte Behandlung von Kugel und Würfel -->
<script>
/* Copyright world image: By NASA/Goddard Space Flight Center [Public domain], via Wikimedia Commons */
/* Scene */
var scene = new THREE.Scene();
/* Camera */
var camera = new THREE.PerspectiveCamera(45,window.innerWidth/(window.innerHeight-150),1,1000);
camera.position.x = 0;
camera.position.y = 0;
camera.position.z = 20;
camera.lookAt(new THREE.Vector3(0,0,-10));
scene.add(camera);
/* Renderer */
var renderer = new THREE.WebGLRenderer({antialias: true});
renderer.setSize(window.innerWidth, (window.innerHeight-150));
renderer.setClearColor( 0xeeeeee);
renderer.clear();
/* Append document to HTML */
document.body.appendChild(renderer.domElement);
// load vertex shader by using the inner content of the element
// 'vertexshader'
var vShader = document.getElementById('vertexshader').textContent;
// load fragement shader by using the inner content of the element
// 'fragmentshader'
var fShader = document.getElementById('fragmentshader').textContent;
// create the shader material to use the custom vertex and fragment shaders
var shaderMaterial =
new THREE.ShaderMaterial({
vertexShader: vShader,
fragmentShader: fShader,
vertexColors: THREE.FaceColors,
});
/* definition of a sphere */
var sphereGeometry = new THREE.SphereGeometry(5, 60, 60);
// create the mesh
sphereMesh = new THREE.Mesh(sphereGeometry, shaderMaterial);
// location
sphereMesh.position.set(-8, 0, -5);
// ... and add it to the scene
scene.add(sphereMesh);
// TODO (1d): Add your code so that only the right part of the texture is shown
// for each face
// END TODO
/* definition of a cube */
var cubeGeometry = new THREE.BoxGeometry(8, 8, 8);
// create the mesh
cubeMesh = new THREE.Mesh(cubeGeometry, shaderMaterial);
// location
cubeMesh.position.set(8, 0, -5);
// ... and add it to the scene
scene.add(cubeMesh);
// OrbitControls erzeugen, um mit der Maus beliebig rotieren zu können
var controls = new THREE.OrbitControls(camera, renderer.domElement);
// initialize the system
init();
function render() {
requestAnimationFrame( render );
controls.update();
renderer.render( scene, camera );
}
function init() {
render();
}
</script>
and the result looks like that:
It is the cube and the square just in red!
But i want it to be interpolated colours.
Can anyone tell me how that works with the just adding something in the fragment-and vertex-shader?
The result i want to get should look like that:
You can pass the UV coordinates from your vertex shader to your fragment shader, using a varying, and then use its x and y values to make gl_FragColor, like so:
var vShader = `
varying vec2 vUv;
void main()
{
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}`;
var fShader = `
varying vec2 vUv;
void main()
{
gl_FragColor = vec4(vUv.x, vUv.y, 0., 1.0);
}
`;
jsfiddle example r86.
PS Bonus: if you want to know more about fragment shaders, then you can visit https://www.shadertoy.com/

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.

Animating custom shader in webgl / three.js

I am currently learning OpenGL and stumbled across this tutorial:
http://patriciogonzalezvivo.com/2015/thebookofshaders/03/
I tried to use the snipped in webgl in order to further understand the mechanism but somehow it doesnt work and I am honestly not sure why. I am sure there must be some syntax error but what could it be? If not then how can I make this work?
To be honest im trying to understand how to implement u_time. I thought the GPU automatically has an in built timer which causes the color transition animation.
// set the scene size
var WIDTH = 400,
HEIGHT = 300;
// set some camera attributes
var VIEW_ANGLE = 45,
ASPECT = WIDTH / HEIGHT,
NEAR = 0.1,
FAR = 10000;
// get the DOM element to attach to
// - assume we've got jQuery to hand
var $container = $('#container');
// create a WebGL renderer, camera
// and a scene
var renderer = new THREE.WebGLRenderer();
var camera = new THREE.Camera( VIEW_ANGLE,
ASPECT,
NEAR,
FAR );
var scene = new THREE.Scene();
// the camera starts at 0,0,0 so pull it back
camera.position.z = 300;
// start the renderer
renderer.setSize(WIDTH, HEIGHT);
// attach the render-supplied DOM element
$container.append(renderer.domElement);
// create the sphere's material
var shaderMaterial = new THREE.MeshShaderMaterial({
vertexShader: $('#vertexshader').text(),
fragmentShader: $('#fragmentshader').text()
});
// set up the sphere vars
var radius = 50, segments = 16, rings = 16;
// create a new mesh with sphere geometry -
// we will cover the sphereMaterial next!
var sphere = new THREE.Mesh(
new THREE.Sphere(radius, segments, rings),
shaderMaterial);
// add the sphere to the scene
scene.addChild(sphere);
// draw!
renderer.render(scene, camera);
<div id="container"></div>
<script type="x-shader/x-vertex" id="vertexshader">
// switch on high precision floats
#ifdef GL_ES
precision highp float;
#endif
void main()
{
gl_Position = projectionMatrix * modelViewMatrix * vec4(position,1.0);
}
</script>
<script type="x-shader/x-fragment" id="fragmentshader">
uniform float u_time;
void main() {
gl_FragColor = vec4(sin(u_time),0.0,0.0,1.0);
}
</script>
<script src="https://aerotwist.com/static/tutorials/an-introduction-to-shaders-part-1/demo/js/Three.js"></script>
<script src="https://code.jquery.com/jquery-2.2.4.js"></script>
You were right that you need to bind/update the value in javascript. To do that, you need to do two things:
Declare the u_time uniform ( including the type and initial value ) that is in the shader when you create the shader material.
var shaderMaterial = new THREE.MeshShaderMaterial({
uniforms: { // <- This is an object with your uniforms as keys
u_time: { type: "f", value: 0 }
},
vertexShader: $('#vertexshader').text(),
fragmentShader: $('#fragmentshader').text()
});
You need to have a render loop where you continuously update the uniform's value. Here is a basic example of a render loop which uses requestAnimationFrame() to call itself once the browser is ready to render another frame:
function draw () {
requestAnimationFrame(draw);
// Update shader's time
sphere.materials[0].uniforms.u_time.value += 0.01;
// draw!
renderer.render(scene, camera);
}
draw();
Note that you update uniforms.u_time.value not uniforms.u_time. This is because a uniform holds both it's type and it's current value.
Working jsFiddle with changes
Also know that you are using a very old version of three.js in your fiddle. Version r40 is from 2011 and we are up to r76 currently. There are some niceties in recent versions that make this simpler.

Resources