Hello,
I’m implementing basic flat shading because I want to use the CSS Renderer for a demo. However I ran into a problem.
The problem is that it seems to be working when the cube is not moved, but when a translateX
is applied to it, it still thinks that it’s under the light source directly.
The selected face should be shaded too.
cssObj.parent
is just a THREE.Group
instance.
function shadeFace(cssObj) {
const facePos = cssObj.position.clone();
facePos.applyMatrix4(cssObj.parent.matrixWorld);
const faceNormal = facePos.normalize();
const lightNormal = pointLight.clone().normalize();
const light = clamp(lightNormal.dot(faceNormal), 0, 1);
const color = light * 255;
cssObj.element.style.backgroundColor = `rgb(${color},${color},${color})`;
}
Are you trying to use lights on an HTML element?
The CSS3DRenderer only applies scaling, rotations & translations. (AKA 3D transformations). It does not render shaders. That means that lights, shadows, materials, etc. do not affect the appearance of the CSS3DObject. The colors remain exactly the way you would establish via CSS rules.
Thanks for the reply. Yes, that’s what I want to do, I want to “shade” DOM elements with JS only, not GPU shaders. Maybe I wasn’t too clear about my plan, but with solid color filling it must be ok. It already works to a degree, but I have the problem I stated above.
Made some progress, but something is still fishy
function shadeFace(cssObj) {
const facePos = cssObj.position.clone();
const faceNormal = facePos.normalize();
const lightNormal = pointLight.clone().sub(cssObj.parent.position).normalize();
const light = clamp(lightNormal.dot(faceNormal), 0, 1);
const color = light * 255;
cssObj.element.style.backgroundColor = `rgb(${color},${color},${color})`;
}
1 Like
To state the problem more clearly, how to make the translations taken into account when calculating the dot product from the light source.
I think I need to find the direction vector from the light source and the face normal translated to world space then do the dot product with the face normal.
Update: it works now, I was an idiot, forgot to call the updateLights function for the rest of the cubes
1 Like
Ah! Glad it’s working.
The only problem I see is that you’re assuming the faceNormal
is the same as the object’s position, but you’re not taking its rotation into account. A face looking “up” is going to have a normal of (0, 1, 0)
, but looking “right” is going to be (1, 0, 0)
. You have to consider both its position and its rotation to determine how much the light is going to affect it.
How did you arrive at those calculations? Here’s an 8-minute video I really like that demonstrates how a point light’s position, face normal, and face position all work together to create brightness: https://www.youtube.com/watch?v=8sL3QzQaYl0 it’s written in C++ and GLSL, but it’s really easy to understand the essence of the tut. Shouldn’t be hard to transpose to JS & Three.js. The crux of the tutorial starts at 2:48:
vec3 lightVector = normalize(lightPosition - facePosition);
float brightness = dot(lightVector, faceNormal);
2 Likes
I’ve read quite a few tutorials but this was the most helpful:
http://www.opengl-tutorial.org/beginners-tutorials/tutorial-8-basic-shading/
Thanks for the hint, I’ll definitely watch the video and take the rotation into account as well.
Ah, yes. That’s a good tutorial too. The normal rotations get applied in the “Putting it all together” section:
Normal_cameraspace = ( V * M * vec4(vertexNormal_modelspace,0)).xyz;
It’s multiplying the View matrix * Model matrix * normal, which gives you the result in a very neat one-liner. In JavaScript, it would be a bit more involved:
// Start facing "forward"
vec3 faceNormal = new THREE.Vector3(0, 0, 1);
// Apply rotation by copying object's quaternion
faceNormal.applyQuaternion(cssObj.quaternion);
// Rotated normal!
console.log(faceNormal);
2 Likes