THREE.JS GLSL sprite always front to camera - three.js

I'm creating a glow effect for car stop lights and found a shader that makes it possible to always face the camera:
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 );
}
This solution is quite simple and almost works. It reacts to camera movement and it would be great. BUT this element is a child of a car. The car itself is moving around and when it rotates the material stops pointing directly at the camera.
I don't want to use SpritePlugin or LensFlarePlugin because they slow down my game by 20fps so I'll stick to this lightweight solution.
I found a solution for Direct 3d that you have to remove rotation data from tranformation matrix, but I don't know how to do this in THREE.js
I guess that instead of adding calculations with car transformation there must be a way to simplify this shader instead.
How to simplify this shader so the material always faces the camera?
From the link below: "To do spherical billboarding, just remove all rotations by setting the identity matrix". How to do it ShaderMaterial in THREE.js?
http://www.geeks3d.com/20140807/billboarding-vertex-shader-glsl/
The problem here I think is intercepting transformation matrix from ShaderMaterial before it's passed to the shader, but I'm not sure.
Probably irrelevant but here's also fragment shader:
uniform vec3 glowColor;
varying float intensity;
void main() {
vec3 glow = glowColor * intensity;
gl_FragColor = vec4( glow, 1.0 );
}
edit: for now I found a workaround which is eliminating parent's rotation influence by setting opposite quaternion. Not perfect and it's happening in CPU not GPU
this.quaternion._x = -this.parent.quaternion._x;
this.quaternion._y = -this.parent.quaternion._y;
this.quaternion._z = -this.parent.quaternion._z;
this.quaternion._w = -this.parent.quaternion._w;

Are you looking for an implementation of billboarding? (make a 2D sprite always face camera) If so, all you need to do is this:
"vec3 billboard(vec2 v, mat4 view){",
" vec3 up = vec3(view[0][1], view[1][1], view[2][1]);",
" vec3 right = vec3(view[0][0], view[1][0], view[2][0]);",
" vec3 p = right * v.x + up * v.y;",
" return p;",
"}"
v is the offset from the center, basically the 4 vertices in a plane that faces the z-axis. Eg. (1.0, 1.0), (1.0, -1.0), (-1.0, 1.0), and (-1.0, -1.0).
Use it like so:
"vec3 worldPos = billboard(a_offset, u_view);"
// then do whatever else.

Related

How to convert a square texture into a trapezoid texture with progressive distortion in GLSL

Im in a Three.js project and Im trying to convert a square with a square texture inside into a trapezoid.
I manage to create the shape but the texture inside, although it fits/cover the shape it do it with an undesired distorsiĆ³n.
Im using a PlaneBufferGeometry with ShaderMaterial and im trying to obtain this distorsion in the shader part (although it would be ok if it is done in the threejs geometry part).
This is my vertex:
uniform sampler2D uTexture;
varying vec2 vUv;
void main(){
float scaleTOP = 0.5;
float scaleBOTTOM = 1.0;
float scaleLEFT = 1.0;
float scaleRIGHT = 1.0;
float scaleX = mix(scaleBOTTOM, scaleTOP, uv.y);
float posX = position.x*scaleX;
float scaleY = mix(scaleLEFT, scaleRIGHT, uv.x);
float posY = position.y*scaleY;
vec3 finalPosition = vec3(posX, posY);
gl_Position = projectionMatrix * modelViewMatrix * vec4( finalPosition, 1.0 );
// Varyings:
vUv = uv;
}
And this is my fragment:
uniform sampler2D uTexture;
varying vec2 vUv;
void main() {
vec4 tex = texture2D ( uTexture, vUv );
gl_FragColor = vec4(tex.r, tex.g, tex.b, 1.0);
}
Unfortunately I manage to distort the square into the trapezoid but the texture is not distorted in the way I want. See figure to see the intended result:
Figure:
My vertex and fragment were ok.
The problem was that the Threejs geometry I was using had only 2 polygons. I was using:
this.bg_geometry = new THREE.PlaneBufferGeometry(width, height, 1, 1)
Thats it... with only one division which only created two triangles which actually can be seen in the figure I posted.
I changed the geometry to:
this.bg_geometry = new THREE.PlaneBufferGeometry(width, height, 100, 100)
...and now the texture is distorted as desired.
Anyway many thanks to #prisoner849 as he put me in the track to pass 4 points as uniforms uPoints in this order: TL,TR,BL,BR to set the shape of the plane.
My vertex shader looks now like this:
uniform vec3 uPoints[4];
varying vec2 vUv;
void main(){
vec3 baselineBottom = (uPoints[3] - uPoints[2]) * uv.x + uPoints[2];
vec3 baselineTop = (uPoints[1] - uPoints[0]) * uv.x + uPoints[0];
vec3 finalPosition = (baselineTop - baselineBottom) * uv.y + baselineBottom;
gl_Position = projectionMatrix * modelViewMatrix * vec4( finalPosition, 1.0 );
vUv = uv;
}

Raycasting with InstancedMesh, InstancedBufferGeometry, custom shader

Basic, I can't get raycasting to work with them. My guess is my matrix coordinate calculation method is wrong. Don't know how to do it right.
I set vertex position and offset in vertexShader, and in InstancedMesh, I set the same offset, expecting the the raycast can get the an instanceID, but nothing intersects. You can find my entire code here.
I tried to adapt an official raycasting example here, but can't figure out where I did wrong. My hodgepodge uses: InstancedMesh, InstancedBufferGeometry, custom shader together. My objective is to learn how it works.
My question is where I did wrong?
My vertex shader:
precision highp float;
uniform mat4 modelViewMatrix;
uniform mat4 projectionMatrix;
attribute vec3 position;
attribute vec4 color;
attribute vec3 offset;
varying vec3 vPosition;
varying vec4 vColor;
void main() {
vColor = vec4(color);
vPosition = offset*1.0 + position;
gl_Position = projectionMatrix * modelViewMatrix * vec4( vPosition, 1.0 );
// if gl_Position not set, nothing is shown
}
My InstancedMesh matrix setting:
for(let i = 0; i < SQUARE_COUNT; i++) {
transform.position.set(offsets[i], offsets[i+1], offsets[i+2] )
transform.updateMatrix()
mesh.setMatrixAt(i, transform.matrix)
}
The offsets is set before as following:
for(let i = 0; i < SQUARE_COUNT; i++ ) {
offsets.push( 0 + i*0.05, 0 + i*0.05, 0 + i*0.05); // same is set in InstancedMesh
colors.push( Math.random(), Math.random(), Math.random(), Math.random() );
}
The raycaster has no awareness of any nonstandard transformation that you do in your vertex shader. It's just the way it works. It has no way of knowing that you are doing:
vPosition = offset*1.0 + position;
in your shader.
It works by assuming that you are running the bog standard vertex shader with no additional transforms. It assumes that every object you are casting against has a well defined/computed bounding box as well.
If you are going to use raycasting, you may have to make a non-rendered scene that represents your objects in their final rendered positions, and cast against that.

How to color only the faces where the normals are perpendicular to the camera

I am trying to do the math for a shader that needs to darken on the faces that have normals perpendicular to the camera (dot product is 0). So basically how do I get this dot product?
How do I fix the following?
uniform float time;
uniform vec3 eye_dir;
varying float darkening;
void main(){
float product=dot(normalize(eye_dir),normalize(normal.xyz));
darkening=product;
gl_Position=
projectionMatrix*
modelViewMatrix*
vec4(position,1.);
}
// in THREE.js
this.camera.getWorldDirection(this.eyeDir);
...
cell.material.uniforms.eye_dir = new Uniform(this.eyeDir);
To do what you want you've to calculate the vector from the fragment to the camera. The easiest way to do this, is to do it in view space (camera space), because in view space the position of the camera is (0, 0, 0).
Transform the position by the modelViewMatrix from model space to view space and the normal by the normalMatrix from model space to view space. See WebGLProgram.
Since the result of the dot product is 1.0 when the vectors are orientated in the same direction, the darkening is 1.0 - abs(dotproduct).
varying float darkening;
void main(){
vec4 view_pos = modelViewMatrix * vec4(position, 1.0);
vec3 view_dir = normalize(-view_pos.xyz); // vec3(0.0) - view_pos;
vec3 view_nv = normalize(normalMatrix * normal.xyz);
float NdotV = dot(view_dir, view_nv);
darkening = 1.0 - abs(NdotV);
gl_Position = projectionMatrix * view_pos;
}
Note, the Dot product of eye_dir and normal doesn't make any sense at all, because eye_dir is a vector in world space and normal is a vector in model (object) space.

Partially transparent shader occluding objects in THREE.js

I am making a game with a fog of war layer covering the board. I want to have a cursor that shows up when the player mouses over a tile, and I'm implementing this with a glow effect around the tile, also implemented using a shader.
I'm running into a strange issue: the glow effect works fine for positive x values (when the camera is set at x = -250, y = 250) but I can't see it for negative x values unless the camera gets rotated to almost completely vertical (or I move the camera underneath the fog of war layer).
It's hard to explain, so I've made a CodePen demonstrating the problem: https://codepen.io/jakedluhy/pen/QqzajN?editors=0010
I'm pretty new to custom shaders, so any insight or help would be appreciated. Here's the shaders for the fog of war:
// Vertex
varying vec4 vColor;
void main() {
vec3 cRel = cameraPosition - position;
float dx = (20.0 * cRel.x) / cRel.y;
float dz = (20.0 * cRel.z) / cRel.y;
gl_Position = projectionMatrix *
modelViewMatrix *
vec4(
position.x + dx,
position.y,
position.z + dz,
1.0
);
vColor = vec4(0.0, 0.0, 0.0, 0.7);
}
// Fragment
varying vec4 vColor;
void main() {
gl_FragColor = vColor;
}
And the shaders for the "glow":
// Vertex
varying vec4 vColor;
attribute float alpha;
void main() {
vColor = vec4(color, alpha);
gl_Position = projectionMatrix *
modelViewMatrix *
vec4(position, 1.0);
}
// Fragment
varying vec4 vColor;
void main() {
gl_FragColor = vColor;
}
The math in the vertex shader for the fog of war is to keep the fog in a relative position to the game board.
Tagging THREE.js and glsl because I'm not sure whether this is a THREE.js exclusive problem or not...
Edit: version 0.87.1
Your example looks pretty weird. By setting depthWrite:false on your fog material the two boxes render.
version 0.87.1

Interior Mapping shader self shadowing

I'm tinkering with Joost van Dongen's Interior mapping shader and I'm trying to implement self-shadowing. Still I couldn't quite figure out what coordinates shadow casting light vectors need to be in. You can see somewhat working demo at here I've attached the light position with an offset to the camera position just to see whats happening but obviously it doesn't look right either.
Shader code is below. Look for SHADOWS DEV in fragment shader. Vectors in question are: shad_E and shad_I.
vertex shader:
varying vec3 oP; // surface position in object space
varying vec3 oE; // position of the eye in object space
varying vec3 oI; // incident ray direction in object space
varying vec3 shad_E; // shadow light position
varying vec3 shad_I; // shadow direction
uniform vec3 lightPosition;
void main() {
// inverse veiw matrix
mat4 modelViewMatrixInverse = InverseMatrix( modelViewMatrix );
// surface position in object space
oP = position;
// position of the eye in object space
oE = modelViewMatrixInverse[3].xyz;
// incident ray direction in object space
oI = oP - oE;
// link the light position to camera for testing
// need to find a way for world space directional light to work
shad_E = oE - lightPosition;
// light vector
shad_I = oP - shad_E;
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}
fragment shader:
varying vec3 oP; // surface position in object space
varying vec3 oE; // position of the eye in object space
varying vec3 oI; // incident ray direction in object space
varying vec3 shad_E; // shadow light position
varying vec3 shad_I; // shadow direction
uniform vec3 wallFreq;
uniform float wallsBias;
uniform vec3 wallCeilingColor;
uniform vec3 wallFloorColor;
uniform vec3 wallXYColor;
uniform vec3 wallZYColor;
float checker(vec2 uv, float checkSize) {
float fmodResult = mod( floor(checkSize * uv.x) + floor(checkSize * uv.y), 2.0);
if (fmodResult < 1.0) {
return 1.0;
} else {
return 0.85;
}
}
void main() {
// INTERIOR MAPPING by Joost van Dongen
// http://interiormapping.oogst3d.net/
// email: joost#ronimo-games.com
// Twitter: #JoostDevBlog
vec3 wallFrequencies = wallFreq / 2.0 - wallsBias;
//calculate wall locations
vec3 walls = ( floor( oP * wallFrequencies) + step( vec3( 0.0 ), oI )) / wallFrequencies;
//how much of the ray is needed to get from the oE to each of the walls
vec3 rayFractions = ( walls - oE) / oI;
//texture-coordinates of intersections
vec2 intersectionXY = (oE + rayFractions.z * oI).xy;
vec2 intersectionXZ = (oE + rayFractions.y * oI).xz;
vec2 intersectionZY = (oE + rayFractions.x * oI).zy;
//use the intersection as the texture coordinates for the ceiling
vec3 ceilingColour = wallCeilingColor * checker( intersectionXZ, 2.0 );
vec3 floorColour = wallFloorColor * checker( intersectionXZ, 2.0 );
vec3 verticalColour = mix(floorColour, ceilingColour, step(0.0, oI.y));
vec3 wallXYColour = wallXYColor * checker( intersectionXY, 2.0 );
vec3 wallZYColour = wallZYColor * checker( intersectionZY, 2.0 );
// SHADOWS DEV // SHADOWS DEV // SHADOWS DEV // SHADOWS DEV //
vec3 shad_P = oP; // just surface position in object space
vec3 shad_walls = ( floor( shad_P * wallFrequencies) + step( vec3( 0.0 ), shad_I )) / wallFrequencies;
vec3 shad_rayFr = ( shad_walls - shad_E ) / shad_I;
// Cast shadow from ceiling planes (intersectionXZ)
wallZYColour *= mix( 0.3, 1.0, step( shad_rayFr.x, shad_rayFr.y ));
verticalColour *= mix( 0.3, 1.0, step( rayFractions.y, shad_rayFr.y ));
wallXYColour *= mix( 0.3, 1.0, step( shad_rayFr.z, shad_rayFr.y ));
// SHADOWS DEV // SHADOWS DEV // SHADOWS DEV // SHADOWS DEV //
// intersect walls
float xVSz = step(rayFractions.x, rayFractions.z);
vec3 interiorColour = mix(wallXYColour, wallZYColour, xVSz);
float rayFraction_xVSz = mix(rayFractions.z, rayFractions.x, xVSz);
float xzVSy = step(rayFraction_xVSz, rayFractions.y);
interiorColour = mix(verticalColour, interiorColour, xzVSy);
gl_FragColor.xyz = interiorColour;
}
Based on my very limited understanding of what you're trying to implement, it seems you would need to take the location of the intersection between the eye vector and the interior plane it hits, then trace it back to the light.
To trace back to the light, you would first have to check if the interior plane intersected by the eye vector is back-facing from the light's perspective, which would make it in shadow. If it's front-facing then you have to ray cast from within the room to the light and check for an intersection with any of the other interior planes.

Resources