I want to create an effect like the undulating sphere described in the Aerotwist Tutorial. However, in the tutorial Paul creates a fake GLSL hard-coded light in the fragment shader - instead I want to pass info from a three.js PointLight instance to my shaders, manipulate vertices/normals, then perform Phong shading.
My understanding of the various levels of GPU consideration when shading a scene in three.js is as follows (sticking with Phong, for example):
No GPU consideration: Use a MeshPhongMaterial and don't worry about shaders. This is super easy but doesn't let you mess around on the GPU side.
Some GPU consideration: Use a ShaderLib Phong shader. This allows you to push shading calculations to the GPU, but since they're pre-written you can't do any custom modification of vertex positions, normals, or illumination calculations.
Full GPU management: Use a ShaderMesh and write your shaders from scratch. This gives you full customization, but also forces you to explicitly pass the attributes and uniforms your shaders will need.
Q1: Is the above understanding accurate?
Q2: Is there a way to do something between levels 2 and 3? I want the ability to customize the shaders to mess with vertex positions/normals, but I don't want to write my own Phong shader when a perfectly good one is included with three.js.
Q3: If there is no such middle ground between levels 2 and 3, and I need to just go for level 3, whats the best way to go about it? Do I pass the light's position, intensity, etc. as uniforms, do my vertex/normal modifications, then finally explicitly write the Phong shading calculations?
It's very straightforward to do what you are asking with three.js
I'm not sure where it falls in your Q[]
Q1
You are still using the shaders, someone else wrote them for you. You only have access to the interface. Under the hood, calling something like MeshBasicMaterial can actually compile a different shader based on what you feed into it. Like, it may not process any UVS and not include them in the shader if there is no map called etc. You still have the power to impact the GPU depending on what you call.
If you are referring to the shader chunks, it's possible to hack stuff here, but it's pretty cumbersome. My advice is to study the code, for example the phong shading and start building your own piece by piece, using the chunks. Look at what goes in, what goes out.
No need to pass attributes. THREE.ShaderMaterial is not entirely built from scratch. It still provides you with quite a bit of stuff, and has a bunch of properties that you can set to get more. The basic attributes for one, are setup for you ie. you don't declare "attribute vec3 position". You can get an array containing all the lights in the scene if you tick the lighting flag as West illustrated, but you can ignore this if for example, you are building a particle shader, or some screen effect. Pretty much every shader is set up to read some basic attributes like 'position' 'uv' 'normal'. You can easily add your own on a procedural mesh, but on an actual model it's not trivial. You get some uniforms by default, you get the entire set of MVP matrices, 'cameraPosition' etc. Writing a phong shader from there is straightforward.
Now for how would you do this. Say that you are following this tutorial and you have this shader:
// same name and type as VS
varying vec3 vNormal;
void main() {
//this is hardcoded you want to pass it from your environment
vec3 light = vec3(0.5, 0.2, 1.0);//it needs to be a uniform
// ensure it's normalized
light = normalize(light);//you can normalize it outside of the shader, since it's a directional light
// calculate the dot product of
// the light to the vertex normal
float dProd = max(0.0,
dot(vNormal, light));
// feed into our frag colour
gl_FragColor = vec4(dProd, // R
dProd, // G
dProd, // B
1.0); // A
}
Here's what you need to do:
GLSL
uniform vec3 myLightPos;//comes in
void main(){
vec3 light = normalize(myLightPos);//but you better do this in javascript and just pass the normalized vec3
}
Javascript
new THREE.ShaderMaterial({
uniforms:{
myLightPos:{
type:"v3",
value: new THREE.Vector3()
}
},
vertexShader: yourVertShader,
fragmentShader: yourFragmentShader
});
Q1: Correct. Although, some users on this board have posted work-arounds for hacking MeshPhongMaterial, but that is not the original intent.
Q2 and Q3: Look at ShaderLib.js and you will see the "Normal Map Shader". This is a perfect template for you. Yes, you can duplicate/rename it and modify it to your liking.
It uses a Phong-based lighting model, and even accesses the scene lights for you. You call it like so:
var shader = THREE.ShaderLib[ "normalmap" ];
var uniforms = THREE.UniformsUtils.clone( shader.uniforms );
. . .
var parameters = {
fragmentShader: shader.fragmentShader,
vertexShader: shader.vertexShader,
uniforms: uniforms,
lights: true // set this flag and you have access to scene lights
};
var material = new THREE.ShaderMaterial( parameters );
See these examples: http://threejs.org/examples/webgl_materials_normalmap.html and http://threejs.org/examples/webgl_materials_normalmap2.html.
For coding patterns to follow, see ShaderLib.js and ShaderChunk.js.
three.js r.67
Related
I've managed to make a webgl example all in one file with no included libraries, and only functions that are being used: https://jsfiddle.net/vmLab6jr/
I'm drawing a square made of 2 triangles and I'm making it move farther away and closer to the camera. I want to understand how this part works:
// Now move the drawing position a bit to where we want to start
// drawing the square.
mvMatrix = [
[1,0,0,0],
[0,1,0,0],
[0,0,1,-12+Math.sin(g.loops/6)*4],
[0,0,0,1]
];
var mvUniform = gl.getUniformLocation(g.shaderProgram, "uMVMatrix");
gl.uniformMatrix4fv(mvUniform, false, g.float32(mvMatrix));
Why does webgl want a 4x4 matrix to set the position for drawing an object? Or is there a way to use 1x3, like [x,y,z]? Is it because the shaders I'm using we're arbitrarily set to 4x4?
I cannot find information on what uniformMatrix4fv() does and when and why it's used and what the alternatives are.
Why does the element [2][3] control the z of the object?
I know it has something to do with the frustum matrix being 4x4. And that same spot in the frustum matrix has D, where var D = -2*zfar*znear/(zfar-znear); But to change the x of the object I'm drawing I need to change [0][3] but that slot in the frustum matrix just has a 0.
function makeFrustum(left, right, bottom, top, znear, zfar)
{
var X = 2*znear/(right-left);
var Y = 2*znear/(top-bottom);
var A = (right+left)/(right-left);
var B = (top+bottom)/(top-bottom);
var C = -(zfar+znear)/(zfar-znear);
var D = -2*zfar*znear/(zfar-znear);
return [
[X, 0, A, 0],
[0, Y, B, 0],
[0, 0, C, D],
[0, 0, -1, 0]
];
}
I've been using this tutorial: https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/Tutorial/Getting_started_with_WebGL
WebGL does not want a 4x4 matrix. WebGL is just a rasterization library
All it cares about is you provide a vertex shader that fills in a special variable called gl_Position with a clip space coordinate and then you also provide a fragment shader that sets the special variable gl_FragColor with a color.
No matrices are required to do that. Any matrices you use are yours, provided by you to code you supply. There are no required matrices in WebGL.
That said if you follow these tutorials they will eventually lead you to how to use matrices and how the frustum function works
There's also this Q&A: Trying to understand the math behind the perspective matrix in WebGL
As for your multiple questions
Why does webgl want a 4x4 matrix to set the position for drawing an object?
It doesn't. The shader you provided does.
Or is there a way to use 1x3, like [x,y,z]?
Yes, provide a shader that uses 1x3 math
Is it because the shaders I'm using we're arbitrarily set to 4x4?
Yes
I cannot find information on what uniformMatrix4fv() does and when and why it's used and what the alternatives are.
WebGL 1.0 is based on OpenGL ES 2.0 and so the WebGL spec basically says "look at the OpenGL ES 2.0 spec". Specifically it says
1.1 Conventions
...
The remaining sections of this document are intended to be read in conjunction with the OpenGL ES 2.0 specification (2.0.25 at the time of this writing, available from the Khronos OpenGL ES API Registry). Unless otherwise specified, the behavior of each method is defined by the OpenGL ES 2.0 specification.
As for uniformMatrix4fv the various uniform functions are used to set global variables you declared inside the shaders you provided. These global variables are called uniforms because they keep a uniform value from iteration to iteration of your shaders. That's in contrast to 2 other kinds of shader inputs. One called attributes which generally pull the next set of values out of buffers during each iteration of your vertex shader. The other type are called varyings which you set in your vertex shader and are interpolated for each iteration of your fragment shader.
Say I have this character and I want allow user to select it, so when it s selected I want to show an outline around it.
the character is an object3D with some meshes.
I tried to clone and set a backside material, but it did NOT work, the problem was each cube in the shape was render with backside separately so the outline was wrong.
do I need to create another mesh for the outline, is there an easier way?
What #spassvolgel wrote is correct;
What I suspect needs to be done is something like this: 1. First the background needs to be rendered 2. Then, on a separate transparent layer, the character model with a flat color, slightly bigger than the original, 3. On another transparent layer the character with its normal material / texture 4. Finally, the character layer needs to go on top of the outline layer and them combined need to be placed in the bg
You just create multiple scenes and combine them with sequential render passes:
renderer.autoClear = false;
. . .
renderer.render(scene, camera); // the entire scene
renderer.clearDepth();
renderer.render(scene2, camera); // just the selected item, larger, in a flat color
renderer.render(scene3, camera); // the selected item again
three.js.r.129
An generic solution that applies to geometries of any complexity might be to apply a fragment shader via the ShaderMaterial class in three.js. Not sure what your experience level is at, but if you need it an introduction to shaders can be found here.
A good example where shaders are used to highlight geometries can be found here. In their vertex shader, they calculate the normal for a vertex and a parameter used to express intensity of a glow effect:
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 );
}
These parameters are passed to the fragment shader where they are used to modify the color values of pixels surrounding the geometry:
uniform vec3 glowColor;
varying float intensity;
void main()
{
vec3 glow = glowColor * intensity;
gl_FragColor = vec4( glow, 1.0 );
}
I found something on gamedev.stackexchange.com/ that could be useful. They talk of a stencil buffer. I have no idea on how to apply this to THREE.js though..
https://gamedev.stackexchange.com/questions/59361/opengl-get-the-outline-of-multiple-overlapping-objects
You can get good results by rendering your outlined object(s) to a texture that is (ideally) the size of your destination framebuffer, then render a framebuffer-sized quad using that texture and have the fragment shader blur or do other image transforms. I have an example here that uses raw WebGL, but you can make a custom ShaderMaterial without too much trouble.
I haven't found the answer yet but I wanted to demonstrate what happens when I create multiple meshes, and put another mesh behind each of these meshes with
side: THREE.BackSide
http://jsfiddle.net/GwS9c/8/
as you can see, it's not the desired effect. I would like a clean outline behind ALL three meshes, that doesn't overlap. My level of programming shaders is really non-existent, but on most online resources people say to use this approach of cloning the meshes.
I'm trying to use an FBO in a material in THREE.js. I have a GPU-based fluid simulation which outputs its final visualisation to a framebuffer object, which I would like to use to texture a mesh. Here's my simple fragment shader:
varying vec2 vUv;
uniform sampler2D tDiffuse;
void main() {
gl_FragColor = texture2D( tDiffuse, vUv );
}
I am then trying to use a simple THREE.ShaderMaterial:
var material = new THREE.ShaderMaterial( {
uniforms: { tDiffuse: { type: "t", value: outputFBO } },
//other stuff... which shaders to use etc
} );
But my mesh just appears black, albeit with no errors to the console. If I use the same shader and shader material, but supply the result of THREE.ImageUtils.loadTexture("someImageOrOther") as the uniform to the shader, it renders correctly, so I assume the problem is with my FBO. Is there some convenient way of converting from an FBO to a Texture2D in WebGL?
EDIT:
After some more experimentation it would appear that this isn't the problem. If I pass the FBO to a different shader I wrote that just outputs the texture to the screen then it displays fine. Could my material appear black because of something like lighting/normals?
EDIT 2:
The UVs and normals are coming straight from THREE, so I don't think it can be that. Part of the problem is that most shader errors aren't reported so I have difficulty in that regard. If I could just map the WebGLTexture somehow that would make everything easier, perhaps like this
var newMaterial = new THREE.MeshLambertMaterial({ map : outputFBO.texture });
but of course that doesn't work. I haven't been able to find any documentation that suggests THREE can read directly from WebGLTextures.
By poking a little into the sources of WebGLRenderer (look at https://github.com/mrdoob/three.js/blob/master/src/renderers/WebGLRenderer.js#L6643 and after), you may try to create a three js texture with a dummy picture, then change the data member __webglTexture of this texture by putting your own webgltexture.
Also, you may need to set to true the __webglInit data member of the texture object so that init code is not executed (because then __webglTexture is overwritten by a call to _gl.createTexture();)
If you don't mind using the Three.js data structures, here's how you do it:
Three.js use framebuffer as texture
I have made a little test that allows you to experiment with shaders in a 3D environment using three.js.
There's a sphere in the scene that shows the shader.
The demo shader I have created is a very simple shader that uses a 2D noise implementation. A big part of the sphere remains black, which I made transparent. I want the other side of the sphere to be visible too. So I have enabled transparency and set rendering side to double-sided.
material = new THREE.ShaderMaterial({
'uniforms': uniforms,
'fragmentShader': $('textarea#input-fragment').val(),
'vertexShader': $('textarea#input-vertex').val()
});
material.side = THREE.DoubleSide;
material.transparent = true;
On this example, the buggyness is easier to notice.
When the sphere is viewed from the top, you only see the shader from the outer side. When viewed from the side there seems to be a bit choppyness, and when viewed from the bottom it seems to be working.
These are the different angles (top - side - bottom):
Here's the important bit of my fragment shader:
void main() {
float r = cnoise(vNormal.yz * 2.0 + t);
float g = cnoise(vNormal.xz * -1.0 + t);
float b = cnoise(vNormal.xy * -2.0 + t);
// opacity ranges assumable from 0 - 3, which is OK
gl_FragColor = vec4(r, g, b, r + g + b);
}
So why am I seeing the choppy edges and why does the viewing angle matters?
There is nothing wrong with your shader. You can also see the effect if you set:
gl_FragColor = vec4( 1.0, 1.0, 1.0, 0.5 );
Self-transparency is tricky in three.js.
For performance reasons in WebGLRenderer, depth sorting works only between objects (based on their position), not within a single object.
The rendering order of the individual faces within an object cannot be controlled.
This is why from some viewing angles your scene looks better than from others.
One work-around is to explode the geometry into individual meshes of one face each.
Another work-around (your best bet, IMO) is to replace your transparent, double-sided sphere with two transparent spheres in the same location -- a front-sided one and a back-sided one.
three.js r.56
Very similar to what I ran into. The WHY to understand this is best explained on Three.js Transparency fundamentals.
Without more details on your code or goals, here is an alternate solution as of version r128. Just add one more line to your material:
material.depthTest: false,
in a nutshell, your shader is fine as #WestLangley mentioned, but during rendering transparency, the depth of pixels in relation to one another is taken into account as well - ending up in certain pixels not rendering. This is where your "buggy-ness" came from. Not really a bug, but the way your scene is rendered by default until told to do otherwise. There are a lot of *issues you can run into that compete with your expectations so I recommend reading up on the link I posted.
*One such issue: If there are other objects in your scene, then of course since you turned off depthTest you can get the incorrect object placement as an object that should be in the background can get rendered in the foreground.
I'm trying to break up the repetition in my texture by applying a bump map which repeats much less frequently. Unfortunately, it seems to take on the repeat value of 'landTexture' below (64), instead of the value I set it to (1).
landTexture.wrapS = landTexture.wrapT = THREE.RepeatWrapping;
landTexture.repeat.set(64, 64);
bumpTexture.wrapS = bumpTexture.wrapT = THREE.RepeatWrapping;
bumpTexture.repeat.set(1, 1);
var m = new THREE.MeshPhongMaterial({map:landTexture,
ambient: 0x552811,
specular: 0x333333,
shininess: 25,
bumpMap: bumpTexture,
bumpScale: 1,
metal: false });
If I comment out map:landTexture, then the bump map scale is 1. Can I mix these two repeat values somehow?
No. The offset and repeat values default to one of them:
// uv repeat and offset setting priorities
// 1. color map
// 2. specular map
// 3. displacement map
// 4. normal map
// 5. bump map
// 5. roughness map
// 5. metalness map
// 6. alpha map
// 7. emissive map
In your case, that would be the landTexture settings.
The workaround is to modify your textures, or create a custom ShaderMaterial.
EDIT: The exception is light map and ambient occlusion map, which each use the second set of UVs. This allows the other textures to be of higher detail than the light/AO map.
three.js r.84
Yes. In recent versions three.js r90^ has an API that can be used to change the behavior of built-in materials with GLSL.
It's not easy to do, but an example has been made:
https://github.com/pailhead/three.js/blob/aa72250835b82f7dde2e8375775a4b039cb719c6/examples/webgl_materials_extended_multiple_uvs.html
https://github.com/mrdoob/three.js/pull/14174
Basically the built in materials are based on shader templates, which is just an ordered list of #include <some_chunk> statements.
Some of these "chunks" contain some code that looks like this
/*...*/ texture2D( foo, vUv ) /*...*/
Where foo is alphaMap,map, specularMap etc. This means that a texture lookup is done on that sampler, at the interpolated uv attribute. You don't really care what precedes this code, or what follows it (it could be just a semi-colon ; or some mask .xy).
So what you want to do is apply some offset, or the way three.js does it, apply a mat3 transform.
The GLSL thus needs to look like this
texture2D( foo, foo_transform * vUv )
The problem then becomes supplying the shader with this uniform. The example does a bit of brute force by first compiling the shader, and then searching through the entire thing (otherwise you have to know in which chunks to look for this texture lookup).
This is a much better solution than modifying textures, and should actually be simpler than writing a custom ShaderMaterial.
Disclaimer - three is not really meant to be used like this but can be. So for example, while every map is prefixed somethingMap the albedo map is not and it's just called map, if it were an albedoMap the regular expression in this example would be simpler.