ThreeJS Softer/Gradient Shadow Casting With Alpha Texture - javascript

Using ThreeJS I've made a setup that displays a model with a complex material. I'm using an alpha texture to make semi-transparent surfaces, like glass. However when shadows are being cast, the shadow is removed completely instead of using the alpha texture as gradient.
Basically I want a similar effect like this BabylonJS example: https://playground.babylonjs.com/#P1RZV0
When casting shadows I'm using the MeshDepthMaterial in order to let the light cast shadows through transparent surfaces, like this:
var customDepthMaterial = new THREE.MeshDepthMaterial( {
depthPacking: THREE.RGBADepthPacking,
alphaMap: alphaTex,
alphaTest: 1,
skinning: true
})
This works fine however as mentioned, shadows should not disappear completely but rather use the alpha texture as indicator how strong the shadow should be. The more opaque a surface is the stronger the shadow should be and vice versa.
You can take a look at the project here: https://jsfiddle.net/miger/2c7no6mj/
Apparently BabylonJS makes it as simple as using the parameter .transparencyShadow = true; and I've not found anything simmilar in ThreeJS. It seems that there has not been an official solution implemented in ThreeJS yet. Instead the only thing I found was this discussion about it here: https://github.com/mrdoob/three.js/issues/10600
I've already implemented some GLSL code in order to get selective bloom working. I'm guessing to implement the shadow effect I'm looking for I need to do something with the shaders but I do not know how.
These are the shaders I'm using so far:
<script type="x-shader/x-vertex" id="vertexshader">
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}
</script>
<script type="x-shader/x-fragment" id="fragmentshader">
uniform sampler2D baseTexture;
uniform sampler2D bloomTexture;
varying vec2 vUv;
void main() {
gl_FragColor = ( texture2D( baseTexture, vUv ) + vec4( 1.0 ) * texture2D( bloomTexture, vUv ) );
}
</script>
And this is how the selective bloom is implemented using .onBeforeCompile:
child.material.onBeforeCompile = shader => {
shader.uniforms.globalBloom = uniforms.globalBloom;
shader.fragmentShader = `
uniform float globalBloom;
${shader.fragmentShader}
`.replace(
`#include <dithering_fragment>`,
`#include <dithering_fragment>
if (globalBloom > 0.5){
gl_FragColor = texture2D( emissiveMap, vUv );
}
`
);
}
I'm really bad with shaders, there have been some solutions in the above mentioned discussion, however I've just way to little experience with shaders and GLSL to know what I need to do.
How can I implement soft/gradient shadow casting using an alpha texture as strength indicator?

Related

Converting and using Shadertoy's iResolution variable in a Three.js script

I'm converting a Shadertoy to a local Three.js project, and can't get it to render. You can try out the full snippet here.
I think the problem may lie in how I'm converting the iResolution variable. As I understand it, the built-in Shadertoy global variable iResolution contains the pixel dimensions of the window. Here is how iResolution is used in the original Shadertoy:
vec2 uv = fragCoord.xy / iResolution.y;
vec2 ak = abs(fragCoord.xy / iResolution.xy-0.5);
In converting this Shadertoy into a local Three.js-based script I have tried two approaches to converting iResolution:
1) Loading the window dimensions as a Vector2 and sending them into the shader as the uniform vec2 uResolution:
vec2 uv = gl_FragCoord.xy / uResolution.y;
vec2 ak = abs(gl_FragCoord.xy / uResolution.xy-0.5);
This solution sticks closest to the design of the original Shadertoy, but alas nothing renders.
2) The second approach comes from this SO Answer and converts the uv coordinates to xy absolute coordinates:
vec2 uvCustom = -1.0 + 2.0 *vUv;
vec2 ak = abs(gl_FragCoord.xy / uvCustom.xy-0.5);
In this one, I admit I don't fully understand how it works, and my use of the uvCustom in the second line may not be correct.
In the end, nothing renders onscreen except a Three.js CameraHelper I'm using. Otherwise, the screen is black and the console shows no errors for the Javascript or WebGL. Thanks for taking a look!
For starters, you don't need to even do this division. If you are using a full screen quad (PlaneBufferGeometry), you can render it with just the uvs:
vec2 uv = gl_FragCoord.xy / uResolution.y;
vec2 vUv = varyingUV;
uv == vUv; //sort of
your vertex shader can look something like this
varying vec2 varyingUV;
void main(){
varyingUV = uv;
gl_Position = vec4( position.xy , 0. , 1.);
}
If you make a new THREE.PlaneGeometry(2,2,1,1); this should render as a full screen quad

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

WebGL: Every second triangle is missing

It seems that every second triangle of a 3d object is missing. For example when a cube is drawn, there are only 6 triangles, one on each face. Here is a picture of the problem. This problem persists even if only one object is drawn.
Here are parts of my code where I think the problem might be:
//start up
gl = canvas.getContext('webgl');
gl.enable(gl.DEPTH_TEST);
gl.depthFunc(gl.LEQUAL);
gl.clearColor(0.0,0.0,0.0,0.0);
gl.clearDepth(1.0);
//shaders
//vertexShader
'
attribute vec3 vertexPosition;
uniform mat4 transformationMatrix;
attribute vec2 uv;
varying vec2 vUV;
void main()
{
gl_Position = transformationMatrix * vec4(vertexPosition,1);
vUV = uv;
}'
//fragmentShader
//problem is still there even when not using a texture
'
precision highp float;
uniform sampler2D sampler;
varying vec2 vUV;
void main()
{
gl_FragColor = texture2D(sampler,vUV);
}'
//after shaders are compiled and some getAttribLocation/enableVertex/AttribLocation/etc calls are made, models and textures are loaded
//each model has a vertexBuffer, indexBuffer, and nIndices, among some other things
//models are retrieved in a for loop
//models are exported to json format with a vertex format of [X,Y,Z,Nx,Ny,Nz,U,V]
var retrievedModel = retrieveResourceFile(resourcesToLoad.models[m].source);
//make vertex buffer
resourcesToLoad.models[m].path.vertexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER,resourcesToLoad.models[m].path.vertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER,new Float32Array(retrievedModel.vertices),gl.STATIC_DRAW);
//make index buffer
resourcesToLoad.models[m].path.indexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER,resourcesToLoad.models[m].path.indexBuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER,new Uint16Array(retrievedModel.indices),gl.STATIC_DRAW);
resourcesToLoad.models[m].path.nIndices = retrievedModel.indices.length;
//now in draw loop, objects that need to be drawn are stored in Draw.objectsToRender array
gl.bindBuffer(gl.ARRAY_BUFFER,Draw.objectsToRender[i].model.vertexBuffer);
gl.vertexAttribPointer(Shaders.modelShaderProgram.vertexPosition,3,gl.FLOAT,false,4*(3+3+2),0);
gl.vertexAttribPointer(Shaders.modelShaderProgram.uv,2,gl.FLOAT,false,4*(3+3+2),(3+3)*4);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER,Draw.objectsToRender[i].model.indexBuffer);
gl.drawElements(gl.TRIANGLES,Draw.objectsToRender[i].model.nIndices,gl.UNSIGNED_SHORT,0);
I've been comparing my code to a tutorial I used http://www.webglacademy.com/#6, (particularly part 6 section 8), and I can't spot anything different that would cause this problem. The models I am using were exported from Blender using the export script provided in part 6 of the tutorial, but I don't think there is a problem there, as I tried using the dragon model data that works fine in the tutorial, and it has the missing triangles problem when used with my code. Thank you very much.

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.

How to use texture, and color also in WebGL?

I'm learning WebGL, and I would like to make a program, where there are colored, and textured objects also. I tried to modify a bit the fragment shader: If the object don't have texture, then it will be colored.
precision mediump float;
varying vec4 vColor;
varying vec2 vTextureCoord;
uniform sampler2D uSampler;
void main(void) {
gl_FragColor = vColor;
gl_FragColor = texture2D(uSampler, vec2(vTextureCoord.s, vTextureCoord.t));
}
It doesn't works. I get an error: Could not initialize shaders.
This may or may not make sense but whether you have a texture or not requires different shaders. To get around this many people use a single pixel white texture when they don't want textures. That way they only need 1 shader to cover both cases. The shader might look something like this
uniform vec4 u_color;
uniform sampler2D u_texture;
varying vec2 v_texCoord;
void main(void) {
gl_FragColor = texture2D(u_texture, v_texCoord) * u_color;
}
Now, in your code you can draw textured like this
// at init time.
var whiteColor = new Float32Array([1, 1, 1, 1]);
// at draw time
gl.uniform4fv(u_colorLocation, whiteColor); // use white color
gl.bindTexture(gl.TEXTURE_2D, someTexture); // and some texture
... draw ...
And to draw un-textured you do this
// at init time.
var whiteTexture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, whiteTexture);
var whitePixel = new Uint8Array([255, 255, 255, 255]);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0,
gl.RGBA, gl.UNSIGNED_BYTE, whitePixel);
// at draw time
gl.uniform4fv(u_colorLocation, someColor); // use the color I want
gl.bindTexture(gl.TEXTURE_2D, whiteTexture); // use the white texture
... draw ...
That also gives you the ability to tint your textures by using a non white color and a texture together.
I think we may need to see some more code before we can hel you figure out the exact issue. For example, what does your vertex shader look like, and how are you linking them?
In the meantime there are a couple of pointers I can give you about the shader you posted. For one, it will not give you the effect that you described. What will actually happen is that it will always show the texture color or, if no texture is given, display black. The vertex color will never be shown. This is because you are assigning to the final fragment color twice, with the second assignment always overriding the first.
What you might want to try is blending the two values together by either adding or multiplying the color and texture values. Try something like so:
gl_FragColor = vColor + texture2D(uSampler, vTextureCoord);
The only problem there is that if the texture is empty you'll still get black (anything multiplied by 0 is still 0). To avoid that, always use a solid white texture if you don't have an actual texture available.
If you want the exact effect you described (vertex color only if no texture) you may need to use a conditional statement. Those are typically best avoided but I doubt your usage will be anything terribly heavy.
Also, as a style tip, The following lines are equivalent:
vec2(vTextureCoord.s, vTextureCoord.t)
vTextureCoord.st
The second may be faster, however, and should be preferred. Beyond that, however, you could just pass in vTextureCoord directly (like I showed) because it already is a vec2.

Categories

Resources