Shader Materials and GL Framebuffers in THREE.js - javascript

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

Related

Threejs: make custom shader match the standard rendering

For performance reasons I need to display hundred of moving tetrahedrons in a scene. There for I use instancedbuffergeometry which requires a custom shader.
The scene also contains objects with regular geometry (non-buffer) and some lights (I prepared boiled-down snippet: https://jsfiddle.net/negan/xs3k1yz4/ ).
My problem is that the tetrahedrons are not shaded such that their lighting plausibly fits to the rest of the scene. The reason is probably the primitive shader I built:
<script id="vertexShader" type="x-shader/x-vertex">
attribute vec3 offset;
attribute vec4 color;
varying vec4 vColor;
varying vec3 vNormal;
void main() {
vColor = color;
vNormal = normalMatrix * vec3(normal);
gl_Position = projectionMatrix *
modelViewMatrix *
vec4(position*1.0+offset,1.0);
}
</script>
<script id="fragmentShader" type="x-shader/x-fragment">
varying vec4 vColor;
varying vec3 vNormal;
void main() {
float di = 0.4*dot(vNormal,normalize(vec3(1.,1.,0.)))+0.4*dot(vNormal,normalize(vec3(1.,0.,1.)));
di = di+0.2;
vec4 vColor2= vColor*vec4(1.0,1.,1.,0.2)*di;
gl_FragColor = vColor2;// adjust the alpha
}
</script>
Is there a way to make the custom shader fit the lights I defined in the scene? The shader also renders the faces in such a way which does not make the impression of directed light. I'd rather like to have single faces lit evenly rather that have the color interpolated from the vertices but I was unable to achieve that.
Any pointer or help is appreciated.
Here's a gist of the full fragment and vertex shader source for a Three.js MeshPhongMaterial shader, as of r83.
Three.js builds shaders using a string concatenation system, so figuring out the source of a shader from looking at the Three.js source will be almost impossible.
The above Gist was generated by installing the shader editor Chrome extension, going to a Three.js example page that has a MeshPhongMaterial like this one and using the shader editor to inspect the full source of a running shader program:
Three.js passes all default uniforms, like lighting data, to all shader programs, so if you create a custom shader with the above Gist code, lights, bones, etc, will all work automatically.
I would take the full source code and add your logic in manually, literally adding the result of your calculations to the existing gl_FragColor = ...
In the future, my tool ShaderFrog will let you automatically add any Three.js shader feature (lights, bones, morph targets, etc) to any custom shader. ShaderFrog can already combine any shaders together automatically, but I haven't done the manual work needed to fully support Three features yet.
There's InstancedMesh module for three.js . It allows mesh instancing without using shader materials. It patches the existing material.
https://github.com/pailhead/three-instanced-mesh

THREE.js - Shifting a texture on a Mesh constructed from a TubeGeometry

I have a problem that I suspect has a very simple answer (one or two lines), but nothing I've tried so far is working. Many thanks for any suggestions.
What I have is a TubeGeometry and a Texture that I've made via canvas drawing. I then make a mesh (multi-material object if that makes a difference), and one of the materials has the map property specified as my texture. I'll call this material Mat.
The problem is that I need a way to continuously rotate the texture around the tube (in torus terms, around the meridians, not the equators). Kind of like kneading circular bread. Previously I was specifying an offset (0-1) and had a function createTexture(offset) for redrawing the texture on the canvas to mimic wraparound, constantly using the code
Mat.map = new THREE.Texture( createTexture(offset) );
Mat.map.needsUpdate = true;
Mat.needsUpdate = true;
Aesthetically, this works fine... except that that the canvas drawing is way too expensive and my performance suffers massively for it. So it's not a reasonable solution.
I also tried messing around with the Mat.map.offset property, but that isn't working at all. Seems to be leaving the original texture present and overwriting only parts of it. I can't discern exactly what's going on there, and wonder if it's a problem with using TubeGeometry because a related stackExchange question about Spheres was solved by this method.
A third thought was to go into TubeGeometry.faceVertexUvs and shift all the face coordinates around, modding by one, as in:
function transform(n){
var faceVertexUvs = tubeGeometry.faceVertexUvs[0];
for (var i =0; i < faceVertexUvs.length; i++){
for (var j=0; j< 3; j++){
faceVertexUvs[i][j].y = ((faceVertexUvs[i][j].y + n) % 1);
}
}
tubeGeometry.uvsNeedUpdate = true;
}
This comes sooo close to working, but always leaves one equatorial line where everything goes wrong. The texture looks terrible like it were having a great crisis of indecision, and the ray-casting I'm doing goes nuts at this spot too. 99% of it works just fine tho... Maybe there's a way to salvage this last attempt?
I'm not attached to any one method, though. maximum efficiency is extra appreciated!
Thanks again for any help!
You can do this with a fragment shader:
<script id="fs" type="x-shader/x-fragment">
uniform float iGlobalTime;
uniform sampler2D iChannel0;
varying vec2 vUv;
void main() {
vec2 uv = vUv;
uv.y = uv.y + iGlobalTime;
if (uv.y > 1.)
uv.y = uv.y - 1.;
gl_FragColor = texture2D( iChannel0, vec2(uv.x, uv.y));
}
</script>
Define your shader material:
_uniforms = {
iGlobalTime: { type: 'f', value: 0.1 },
iChannel0: { type: 't', value: THREE.ImageUtils.loadTexture( 'textures/cat.png') },
};
newMaterial = new THREE.ShaderMaterial( {
uniforms: _uniforms,
vertexShader: document.getElementById( 'vs' ).textContent,
fragmentShader: document.getElementById( 'fs' ).textContent,
} );
Then set iGlobalTime in your render loop:
_uniforms.iGlobalTime.value += clock.getDelta();
while (_uniforms.iGlobalTime.value > 1.)
_uniforms.iGlobalTime.value -= 1.;
Complete working example here: http://rwoodley.org/MyContent/SO/01/
The seam is created by the % 1. Remove the % 1 and enable wrapping on the texture (map.wrapS = THREE.RepeatWrapping and/or map.wrapT). This will allow you to keep incrementing the UVs and the texture will wrap on itself. You could also try changing map.offset which may just offset the texture for you.

Passing PointLight Info to a custom Shader with three.js

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

Complex shape character outline

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.

Double sided transparent shader looks buggy

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.

Categories

Resources