Animating custom shader in webgl / three.js - 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.

Related

Three.js. How do I use a custom material for a scene background rather then color or texture?

The docs for scene say a color or texture can be used for scene.background. I would like to use a ShaderMaterial with my own custom shaders. How can I do this?
Specifically, I want to paint a color ramp behind the foreground elements. Here is the fragment shader:
uniform vec2 uXYPixel;
void main() {
vec2 xy = vec2(gl_FragCoord.x/uXYPixel.x, gl_FragCoord.y/uXYPixel.y);
gl_FragColor.rgb = vec3(xy.x, xy.y, 0);
gl_FragColor.a = 1.0;
}
uXYPixel is a uniform vec2 with the values window.innerWidth, window.innerHeight
You'd need to manually create two render passes. One that renders the background plane with a simple Camera, and the second one that renders the rest of the scene. You can use the most basic Camera class since you won't be using transformation or projection matrices:
// Set renderer with no autoclear
var renderer = new THREE.WebGLRenderer();
renderer.autoClear = false;
document.body.append(renderer.domElement);
renderer.setSize(window.innerWidth, window.innerHeight);
// Set up background scene
var bgScene = new THREE.Scene();
var bgCam = new THREE.Camera();
var bgGeom = new THREE.PlaneBufferGeometry(2, 2);
var bgMat = new THREE.ShaderMaterial({
// Add shader stuff in here
// ..
// Disable depth so it doesn't interfere with foreground scene
depthTest: false,
depthWrite: false
});
var bgMesh = new THREE.Mesh(bgGeom, bgMat);
bgScene.add(bgMesh);
// Set up regular scene
var scene = new THREE.Scene();
var cam = new THREE.PerspectiveCamera(45, w/h, 1, 100);
function update() {
// Clear previous frame
renderer.clear();
// Background render pass
renderer.render(bgScene, bgCam);
// Foreground render pass
renderer.render(scene, cam);
requestAnimationFrame(update);
}
update();
Here you can see a working example.
Notice that the renderer's autoClear = false attribute makes sure it doesn't clear the buffer in between each render() call; you'll have to clear it manually at the beginning of each frame.

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.

filling shader attributes from webgl context in threejs visualization

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.

Three.js custom Shader with Texture

I want to write a custom shader which manipulates my image with three.js.
For that I want to create a plane with the image as a texture. Afterwards I want to move vertices around to distort the image.
(If that an absolute wrong way to do this, please tell me).
First I have my shaders:
<script type="x-shader/x-vertex" id="vertexshader">
attribute vec2 a_texCoord;
varying vec2 v_texCoord;
void main() {
// Pass the texcoord to the fragment shader.
v_texCoord = a_texCoord;
gl_Position = projectionMatrix *
modelViewMatrix *
vec4(position,1.0);
}
</script>
<script type="x-shader/x-fragment" id="fragmentshader">
uniform sampler2D u_texture;
varying vec2 v_texCoord;
void main() {
vec4 color = texture2D(u_texture, v_texCoord);
gl_FragColor = color;
}
</script>
Where I don't really understand what the texture2D is doing, but I found that in other code fragments.
What I want with this sample: Just color the vertex (gl_FracColor) with the color from the «underlying» image (=texture).
In my code I have setup a normal three scene with a plane:
// set some camera attributes
var VIEW_ANGLE = 45,
ASPECT = window.innerWidth/window.innerHeight,
NEAR = 0.1,
FAR = 1000;
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(VIEW_ANGLE, ASPECT, NEAR, FAR);
camera.position.set(0, 0, 15);
var vertShader = document.getElementById('vertexshader').innerHTML;
var fragShader = document.getElementById('fragmentshader').innerHTML;
var texloader = new THREE.TextureLoader();
var texture = texloader.load("img/color.jpeg");
var uniforms = {
u_texture: {type: 't', value: 0, texture: texture},
};
var attributes = {
a_texCoord: {type: 'v2', value: new THREE.Vector2()}
};
// create the final material
var shaderMaterial = new THREE.ShaderMaterial({
uniforms: uniforms,
vertexShader: vertShader,
fragmentShader: fragShader
});
var renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild(renderer.domElement);
var plane = {
width: 5,
height: 5,
widthSegments: 10,
heightSegments: 15
}
var geometry = new THREE.PlaneBufferGeometry(plane.width, plane.height, plane.widthSegments, plane.heightSegments)
var material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } );
var plane = new THREE.Mesh( geometry, shaderMaterial );
scene.add(plane);
plane.rotation.y += 0.2;
var render = function () {
requestAnimationFrame(render);
// plane.rotation.x += 0.1;
renderer.render(scene, camera);
};
render();
Unfortunately, after running that code I just see a black window. Although I know that if I use the material as material when creating the mesh, I can see it clearly.
So it must be the shaderMaterial or the shaders.
Questions:
do I have to define the uniform u_texture and the attribute
a_texCoord in my shader Material uniforms and attributes? And do
they have to have the exact same name?
How many vertices are there anyway? Will I get a vertices for every pixel in the image? Or is it just 4 for each corner of the plane?
What value does a_texCoord have? Nothing happens if I write:
var attributes = {
a_texCoord: {type: 'v2', value: new THREE.Vector2(1,1)}
};
Or do I have to use some mapping (built in map stuff from three)? But how would I then change vertex positions?
Could someone shed some light on that matter?
I got it to work by changing this:
var uniforms = {
u_texture: {type: 't', value: 0, texture: texture},
};
To this:
var uniforms = {
u_texture: {type: 't', value: texture},
};
Anyway all other questions are still open and answers highly appreciated.
(btw: why the downgrade of someone?)
do I have to define the uniform u_texture and the attribute a_texCoord
in my shader Material uniforms and attributes? And do they have to
have the exact same name?
Yes and yes. The uniforms are defined as part of the shader-material while the attributes haven been moved from shader-material to the BufferGeometry-class in version 72 (i'm assuming you are using an up to date version, so here is how you do this today):
var geometry = new THREE.PlaneBufferGeometry(...);
// first, create an array to hold the a_texCoord-values per vertex
var numVertices = (plane.widthSegments + 1) * (plane.heightSegments + 1);
var texCoordBuffer = new Float32Array(2 * numVertices);
// now register it as a new attribute (the 2 here indicates that there are
// two values per element (vec2))
geometry.addAttribute('a_texCoord', new THREE.BufferAttribute(texCoordBuffer, 2));
As you can see, the attribute will only work if it has the exact same name as specified in your shader-code.
I don't know exactly what you are planning to use this for, but it sounds suspiciously like you want to have the uv-coordinates. If that is the case, you can save yourself a lot of work if you have a look at the THREE.PlaneBufferGeometry-class. It already provides an attribute named uv that is probably exactly what you are looking for. So you just need to change the attribute-name in your shader-code to
attribute vec2 uv;
How many vertices are there anyway? Will I get a vertices for every
pixel in the image? Or is it just 4 for each corner of the plane?
The vertices are created according to the heightSegments and widthSegments parameters. So if you set both to 5, there will be (5 + 1) * (5 + 1) = 36 vertices (+1 because a line with only 1 segment has two vertices etc.) and 5 * 5 * 2 = 50 triangles (with 150 indices) in total.
Another thing to note is that the PlaneBufferGeometry is an indexed geometry. This means that every vertex (and every other attribute-value) is stored only once, although it is used by multiple triangles. There is a special index-attribute that contains the information which vertices are used to create which triangles.
What value does a_texCoord have? Nothing happens if I write: ...
I hope the above helps to answer that.
Or do I have to use some mapping (built in map stuff from three)?
I would suggest you use the uv attribute as described above. But you absolutely don't have to.
But how would I then change vertex positions?
There are at least two ways to do this: in the vertex-shader or via javascript. The latter can be seen here: http://codepen.io/usefulthink/pen/vKzRKr?editors=1010
(the relevant part for updating the geometry starts in line 84).

Weld edge vertices of BoxBufferGeometry

I am trying to create terrain in the shape of a cube which will allow for vertex displacement along the y‑axis of those on the top plane. All vertices adjacent to those of the top plane need to be connected.
In a performant manner, user input from either desktop or mobile would move them up or down.
From what I have read it is better to offload expensive operations to the GPU. I thought achieving the vertex displacement in a ShaderMaterial with a displacement attribute seemed like a perfect fit until I read the following:
As of THREE r72, directly assigning attributes in a ShaderMaterial is no longer supported. A BufferGeometry instance (instead of a Geometry instance) must be used instead.
So it seems that using attribute for my Geometry is out of the question?
My attempt at displacing the vertices along the top plane using BufferGeometry in the ShaderMaterial however results in the following:
The top plane's vertices of the BufferGeometry are not connected to the other planes, contrary to those of the Geometry, which are connected by using its mergeVertices method. To my knowledge that method is not available for BufferGeometry objects?
Basically what started my fear, uncertainty and doubt concerning Geometry was a post I read by mrdoob.
Summary
I already have this working for Geometry, but would like to make use of the GPU with ShaderMaterial's attributes, seemingly only supported by BufferGeometry, if it offers performance benefits for mobile and if Geometry might be deprecated in the future.
Here is a small snippet illustrating the issue:
let winX = window.innerWidth;
let winY = window.innerHeight;
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(50, winX / winY, 0.1, 100);
camera.position.set(2, 1, 2);
camera.lookAt(scene.position);
const renderer = new THREE.WebGLRenderer();
renderer.setSize(winX, winY);
document.body.appendChild(renderer.domElement);
const terrainGeo = new THREE.BoxBufferGeometry(1, 1, 1);
const terrainMat = new THREE.ShaderMaterial({
vertexShader: `
attribute float displacement;
varying vec3 dPosition;
void main() {
dPosition = position;
dPosition.y += displacement;
gl_Position = projectionMatrix * modelViewMatrix * vec4(dPosition, 1.0);
}
`,
fragmentShader: `
void main() {
gl_FragColor = vec4(1.0, 0.0, 1.0, 1.0);
}
`
});
const terrainObj = new THREE.Mesh(terrainGeo, terrainMat);
let displacement = new Float32Array(terrainObj.geometry.attributes.position.count);
displacement.forEach((elem, index) => {
// Select vertex 8 - 11, the top of the cube
if (index >= 8 && index <= 11) {
displacement[index] = Math.random() * 0.1 + 0.25;
}
});
terrainObj.geometry.addAttribute('displacement',
new THREE.BufferAttribute(displacement, 1)
);
scene.add(camera);
scene.add(terrainObj);
const render = () => {
requestAnimationFrame(render);
renderer.render(scene, camera);
}
render();
const gui = new dat.GUI();
const updateBufferAttribute = () => {
terrainObj.geometry.attributes.displacement.needsUpdate = true;
};
gui.add(displacement, 8).min(0).max(2).step(0.05).onChange(updateBufferAttribute);
gui.add(displacement, 9).min(0).max(2).step(0.05).onChange(updateBufferAttribute);
gui.add(displacement, 10).min(0).max(2).step(0.05).onChange(updateBufferAttribute);
gui.add(displacement, 11).min(0).max(2).step(0.05).onChange(updateBufferAttribute);
<script src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.5.1/dat.gui.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r76/three.min.js"></script>
<style type="text/css">body { margin: 0 } canvas { display: block }</style>

Resources