Make light independent from the view in a Phong model - javascript

I'm trying to implement the Phong shading model, but I come across something quite strange. When I change the viewing position, it looks like the light behaves differently, as if it was dependent from the view. Like, if I'm close to the object I only see the effects of the ambient light, while if I go far away from it I start seeing the diffuse's contribution.
These are my shaders:
//Vertex Shader
attribute vec4 vPosition;
attribute vec4 vNormal;
varying vec3 N, L, E;
uniform mat4 modelViewMatrix;
uniform mat4 projectionMatrix;
uniform vec4 lightPosition;
void main()
{
vec3 pos = -(modelViewMatrix * vPosition).xyz;
vec3 light = lightPosition.xyz;
L = normalize(light - pos);
E = -pos;
N = normalize((modelViewMatrix * vNormal).xyz);
gl_Position = projectionMatrix * modelViewMatrix * vPosition;
}
//Fragment Shader
uniform vec4 ambientProduct;
uniform vec4 diffuseProduct;
uniform vec4 specularProduct;
uniform float shininess;
varying vec3 N, L, E;
void main()
{
vec4 fColor;
vec3 H = normalize(L + E);
vec4 ambient = ambientProduct;
float Kd = max(dot(L, N), 0.0);
vec4 diffuse = Kd * diffuseProduct;
float Ks = pow(max(dot(N, H), 0.0), shininess);
vec4 specular = Ks * specularProduct;
if (dot(L, N) < 0.0) {
specular = vec4(0.0, 0.0, 0.0, 1.0);
}
fColor = ambient + diffuse + specular;
fColor.a = 1.0;
gl_FragColor = fColor;
}
What am I doing wrong? How can I make the light behave independently from the viewer position?
Update 1:
After #Rabbid76's answer I edited the vertex shader by adding these lines (as well as passing the separate model and view matrices but I'll omit that for brevity's sake):
vec3 pos = (modelViewMatrix * vPosition).xyz;
vec3 light = (viewMatrix * lightPosition).xyz;
And I also updated the calculation of the N vector as the previous way of doing it seemed to not actually allow a per-fragment shading:
N = normalize(mat3(modelViewMatrix) * vNormal.xyz);
Still, the shade seems to move along with the rotation of the camera. This could be related to the fact that the light is multiplied by the viewMatrix I guess?

The calculation of the light vector is wrong.
L = normalize(light - pos);
While pos is a position in view space, light is a position in world space. light is the position of the light in the world. So light - pos doesn't make any sense at all. Both vectors have to be related to the same reference systems.
Transform the position of the light source by the view matrix, before you set it to the uniform lightPosition, to solve the issue.
Of course the transformation can also be done in shader code:
uniform mat4 viewMatrix;
uniform mat4 modelViewMatrix;
uniform vec4 lightPosition;
void main()
{
vec3 pos = (modelViewMatrix * vPosition).xyz;
vec3 light = (viewMatrix * lightPosition).xyz;
L = normalize(light - pos);
// ...
}
Further note that the position in view space has not to be inverted. It has to be
vec3 pos = (modelViewMatrix * vPosition).xyz;
rather than
vec3 pos = -(modelViewMatrix * vPosition).xyz;

A working snippet in your question is always helpful!
Issues
The light and the position need to be in the same space.
Those could be world space or view space but they need to be the same space.
The code had the position E in view space but the lightPosition in world space
You can't multiply a normal by a modelViewMatrix
You need to remove the translation. You also potentially need to deal
with scaling issues. See this article
The code is computing values in the vertex shader so they will be interpolated as they get passed to the fragment shader. That means they will no longer be unit vectors so you need to re-normalize them.
In computing the half vector you need to add their directions
The code was adding L (the direction from the surface to the light) to the view position of the surface instead of the direction from the surface to the view.
In computing a surface to light direction that would be light - pos but the code was negating pos. Of course you also need pos to be negative for the surface to view direction E
const gl = document.querySelector('canvas').getContext('webgl');
const m4 = twgl.m4;
const vs = `
attribute vec4 vPosition;
attribute vec4 vNormal;
varying vec3 N, L, E;
uniform mat4 viewMatrix;
uniform mat4 modelViewMatrix;
uniform mat4 projectionMatrix;
uniform vec4 lightPosition;
void main()
{
vec3 pos = (modelViewMatrix * vPosition).xyz;
vec3 light = (viewMatrix * lightPosition).xyz;
L = light - pos;
E = -pos;
N = mat3(modelViewMatrix) * vNormal.xyz;
gl_Position = projectionMatrix * modelViewMatrix * vPosition;
}
`;
const fs = `
precision highp float;
uniform vec4 ambientProduct;
uniform vec4 diffuseProduct;
uniform vec4 specularProduct;
uniform float shininess;
varying vec3 N, L, E;
void main()
{
vec4 fColor;
vec3 normal = normalize(N);
vec3 surfaceToLightDir = normalize(L);
vec3 surfaceToViewDir = normalize(E);
vec3 H = normalize(surfaceToLightDir + surfaceToViewDir);
vec4 ambient = ambientProduct;
float Kd = max(dot(surfaceToLightDir, normal), 0.0);
vec4 diffuse = Kd * diffuseProduct;
float Ks = pow(max(dot(normal, H), 0.0), shininess);
vec4 specular = Ks * specularProduct;
if (dot(surfaceToLightDir, normal) < 0.0) {
specular = vec4(0.0, 0.0, 0.0, 1.0);
}
fColor = ambient + diffuse + specular;
fColor.a = 1.0;
gl_FragColor = fColor;
}
`;
// compiles shaders, links program, looks up locations
const programInfo = twgl.createProgramInfo(gl, [vs, fs]);
const vertices = twgl.primitives.createSphereVertices(
2, // radius
8, // subdivision around
6, // subdivisions down
);
// calls gl.createBuffer, gl.bindBuffer, gl.bufferData
const bufferInfo = twgl.createBufferInfoFromArrays(gl, {
vPosition: vertices.position,
vNormal: vertices.normal,
indices: vertices.indices,
});
function render(time) {
time *= 0.001; // convert to seconds
gl.enable(gl.DEPTH_TEST);
gl.enable(gl.CULL_FACE);
gl.useProgram(programInfo.program);
// calls gl.bindBuffer, gl.enableVertexAttribArray, gl.vertexAttribPointer
twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
const projectionMatrix = m4.perspective(
60 * Math.PI / 180, // field of view
gl.canvas.clientWidth / gl.canvas.clientHeight, // aspect
0.1, // znear
100, // zfar
);
const eye = [
Math.sin(time) * 5,
3,
3 + Math.cos(time) * 5,
];
const target = [0, 2, 3];
const up = [0, 1, 0];
const cameraMatrix = m4.lookAt(eye, target, up);
const viewMatrix = m4.inverse(cameraMatrix);
const worldMatrix = m4.translation([0, 2, 3]);
const modelViewMatrix = m4.multiply(viewMatrix, worldMatrix);
const uniforms = {
viewMatrix,
modelViewMatrix,
projectionMatrix,
lightPosition: [4, 3, 1, 1],
ambientProduct: [0, 0, 0, 1],
diffuseProduct: [1, 1, 1, 1],
specularProduct: [1, 1, 1, 1],
shininess: 50,
};
// calls gl.uniformXXX
twgl.setUniforms(programInfo, uniforms);
// calls gl.drawArrays or gl.drawElements
twgl.drawBufferInfo(gl, bufferInfo);
// -- not important to answer --
drawLightAndGrid(uniforms)
requestAnimationFrame(render);
}
requestAnimationFrame(render);
// -- ignore below this line. The only point is to give a frame
// of reference.
let gridBufferInfo;
function drawLightAndGrid(sphereUniforms) {
if (!gridBufferInfo) {
const vPosition = [];
const s = 100;
for (let x = -s; x <= s; x += 2) {
vPosition.push(x, 0, -s);
vPosition.push(x, 0, s);
vPosition.push(-s, 0, x);
vPosition.push( s, 0, x);
}
gridBufferInfo = twgl.createBufferInfoFromArrays(gl, {
vPosition,
vNormal: { value: [0, 0, 0], }
});
}
const worldMatrix = m4.translation(sphereUniforms.lightPosition);
m4.scale(worldMatrix, [0.1, 0.1, 0.1], worldMatrix);
const uniforms = Object.assign({}, sphereUniforms, {
modelViewMatrix: m4.multiply(sphereUniforms.viewMatrix, worldMatrix),
ambientProduct: [1, 0, 0, 1],
diffuseProduct: [0, 0, 0, 0],
specularProduct: [0, 0, 0, 0],
});
twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
twgl.setUniforms(programInfo, uniforms);
twgl.drawBufferInfo(gl, bufferInfo);
twgl.setBuffersAndAttributes(gl, programInfo, gridBufferInfo);
twgl.setUniforms(programInfo, {
modelViewMatrix: sphereUniforms.viewMatrix,
ambientProduct: [0, 0, 1, 1],
});
twgl.drawBufferInfo(gl, gridBufferInfo, gl.LINES);
}
canvas { border: 1px solid black }
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
<canvas></canvas>
For me personally I find short cryptic variable names hard to follow but that's a personal preference.

Related

THREE.js vertexShader color mix based on height

How do I set the color to the mesh only when the height is zero?
As for now, i just mixed the colors:
The problem is that this kind on mixing is not precise. I just want the color blue only when the height is zero (so only inside that red path I made with paint).
I created a custom material for the mesh, like so:
material = new THREE.ShaderMaterial({
uniforms: THREE.UniformsUtils.merge([
THREE.UniformsLib['lights'],
{
lightIntensity: {type: 'f', value: 1.0},
diffuse: {type: 'c', value: new THREE.Color(0x0000ff)},
color0: {
value: new THREE.Color("blue")
},
color1: {
value: new THREE.Color("green")
},
color2: {
value: new THREE.Color("brown")
},
color3: {
value: new THREE.Color("black")
},
bboxMin: {
value: geom.boundingBox.min
},
bboxMax: {
value: geom.boundingBox.max
}
}
]),
vertexShader: `
uniform vec3 bboxMin;
uniform vec3 bboxMax;
varying vec2 vUv;
varying vec3 vPos;
varying vec3 vNormal;
void main() {
vPos = (modelMatrix * vec4(position, 1.0 )).xyz;
vNormal = normalMatrix * normal;
vUv.y = (position.y - bboxMin.y) / (bboxMax.y - bboxMin.y);
gl_Position = projectionMatrix * modelViewMatrix * vec4(position,1.0);
}
`,
fragmentShader: `
uniform vec3 color1;
uniform vec3 color2;
uniform vec3 color3;
uniform vec3 color0;
varying vec2 vUv;
uniform vec3 diffuse;
varying vec3 vPos;
varying vec3 vNormal;
struct PointLight {
vec3 position;
vec3 color;
};
uniform PointLight pointLights[ NUM_POINT_LIGHTS ];
void main() {
vec4 addedLights = vec4(0.1, 0.1, 0.1, 1.0);
for(int l = 0; l < NUM_POINT_LIGHTS; l++) {
vec3 adjustedLight = pointLights[l].position + cameraPosition;
vec3 lightDirection = normalize(vPos - adjustedLight);
addedLights.rgb += clamp(dot(-lightDirection, vNormal), 0.0, 1.0) * pointLights[l].color;
}
gl_FragColor = mix(vec4(mix(mix(mix(color0, color1, vUv.y), color1, vUv.y), mix(color1, color2, vUv.y), vUv.y), 1.0),addedLights, addedLights);
}
`,
lights: true
});
Try using the step() function. Here's a definition to help you understand it. Here's how it works:
float step(float edge, float x)
It takes in a constant to declare the edge, and x, which is your variable.
If x is below the edge, you get 0, and if x is above the edge, you get 1.
Here's a simplified use of it. When height is below 0.2, you'll get blue, and when height is above 0.2, you'll get green.
vec3 green = vec3(0.0, 1.0, 0.0);
vec3 blue = vec3(0.0, 0.0, 1.0);
float edge = 0.2;
float colorMix = step(edge, height);
vec3 finalColor = mix(blue, green, colorMix);
I picked 0.2 to give the blue band some thickness, otherwise it wouldn't be visible.

Make pixel intensity dependant on distance from mouse

I am rendering my scene in a 512x512 canvas, my goal is to have the pixel intensity change according to how far away the pixel is from the mouse cursor. But it doesn't seem to change at all. I try multipling by a large number, then it's all white.
var ico = new THREE.Mesh(new THREE.IcosahedronGeometry(0.3,0), light_mat);
ico.position.x = 0;
ico.position.y = 0;
ico.position.z = -3;
scene.add(ico);
var render = function() {
vec.set(
( mouse_x / 512 ) * 2 - 1,
- ( mouse_y / 512 ) * 2 + 1,
0.5 );
vec.unproject( camera );
vec.sub( camera.position ).normalize();
var distance = (ico.position.z-camera.position.z ) / vec.z;
pos.copy( camera.position ).add( vec.multiplyScalar( distance ) );
console.log(pos.x+":"+pos.y+":"+pos.z);
ico.material.uniforms.mouse.value = pos;
requestAnimationFrame(render);
renderer.render(scene, camera);
};
...
<script type="x-shader/x-vertex" id="light_f">
varying vec3 pos;
varying vec2 uv_pos;
varying vec3 norm;
uniform vec3 mouse;
void main() {
vec4 tex = vec4(1.0,1.0,1.0,1.0);
float ang = dot(mouse,norm);
float dis = 0.1*distance(mouse,pos);
gl_FragColor = tex*dis;
}
</script>
<script type="x-shader/x-vertex" id="light_v">
uniform vec3 mouse;
varying vec2 uv_pos;
varying vec3 norm;
varying vec3 pos;
void main() {
uv_pos = uv;
norm = normal;
pos = position;
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}
</script>
Any help appreciated.

How to draw lines on top of triangle?

Is there a way to draw a gl.LINE_LOOP always on top of gl.TRIANGLES while still using same program and points.
Yes but gl.LINE_LOOP will always connect the first and last points which is probably not what you want.
In any case the default depth function is gl.LESS meaning with depth testing on WebGL will only render pixels if their depth value is less than that pixel's current depth value. So, change it to gl.LEQUAL and you should get what you want.
gl.depthFunc(gl.LEQUAL);
"use strict";
twgl.setDefaults({attribPrefix: "a_"});
const v3 = twgl.v3;
const m4 = twgl.m4;
const gl = twgl.getWebGLContext(document.getElementById("c"));
// compiles shaders, links program, looks up locations
const programInfo = twgl.createProgramInfo(gl, ["vs", "fs"]);
// calls gl.createBuffer, gl.bindBuffer, gl.bufferData
const bufferInfo = twgl.primitives.createSphereBufferInfo(gl, 1, 16, 8);
var uniforms = {
u_lightDir: twgl.v3.normalize([1, 20, -10]),
};
function render(time) {
time *= 0.001;
twgl.resizeCanvasToDisplaySize(gl.canvas);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
gl.enable(gl.CULL_FACE);
gl.enable(gl.DEPTH_TEST);
gl.depthFunc(gl.LEQUAL);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
const fov = 30 * Math.PI / 180;
const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
const zNear = 0.5;
const zFar = 10;
var projection = m4.perspective(fov, aspect, zNear, zFar);
var eye = [1, 3, -5];
var target = [0, 0, 0];
var up = [0, 1, 0];
var camera = m4.lookAt(eye, target, up);
var view = m4.inverse(camera);
var viewProjection = m4.multiply(projection, view);
var world = m4.rotationY(time);
uniforms.u_worldInverseTranspose = m4.transpose(m4.inverse(world));
uniforms.u_worldViewProjection = m4.multiply(viewProjection, world);
gl.useProgram(programInfo.program);
// calls gl.bindBuffer, gl.enableVertexAttribArray, gl.vertexAttribPointer
twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
// calls gl.uniform
twgl.setUniforms(programInfo, uniforms);
twgl.setUniforms(programInfo, {
u_ambient: [0, 0, 0],
u_diffuse: [1, 0, 0],
});
gl.drawElements(gl.TRIANGLES, bufferInfo.numElements, gl.UNSIGNED_SHORT, 0);
// calls gl.uniform
twgl.setUniforms(programInfo, {
u_ambient: [1, 1, 0],
u_diffuse: [0, 0, 0],
});
gl.drawElements(gl.LINE_LOOP, bufferInfo.numElements, gl.UNSIGNED_SHORT, 0);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
body { margin: 0; }
canvas { display: block; width: 100vw; height: 100vh; }
<canvas id="c"></canvas>
<script id="vs" type="notjs">
uniform mat4 u_worldViewProjection;
uniform mat4 u_world;
uniform mat4 u_worldInverseTranspose;
attribute vec4 a_position;
attribute vec3 a_normal;
varying vec3 v_normal;
void main() {
v_normal = (u_worldInverseTranspose * vec4(a_normal, 0)).xyz;
gl_Position = u_worldViewProjection * a_position;
}
</script>
<script id="fs" type="notjs">
precision mediump float;
varying vec3 v_normal;
uniform vec3 u_lightDir;
uniform vec3 u_diffuse;
uniform vec3 u_ambient;
void main() {
vec3 a_normal = normalize(v_normal);
float light = dot(a_normal, u_lightDir) * .5 + .5;
gl_FragColor = vec4(u_diffuse * light + u_ambient, 1);
}
</script>
<script src="https://twgljs.org/dist/3.x/twgl-full.min.js"></script>

GLSL shader to support coloring and texturing

Im trying to write a shader that support both color and texture.
For some reason I can make it work.
No errors threw and each of them work perfect separately,
get location:
shaderProgram.useTextureUniform = gl.getUniformLocation(shaderProgram, "uUseTexture");
when drawing I change the value like this:
var uUseTexture=false;
gl.uniform1f(shaderProgram.useTextureUniform, uUseTexture);
And the GLSL itself:
fragment:
precision mediump float;
uniform sampler2D uSampler;
varying vec2 vTextureCoord;
varying vec4 vColor;
uniform bool uUseTexture;
void main(void) {
vec4 textureColor = texture2D(uSampler, vec2(vTextureCoord.s, vTextureCoord.t));
vec4 texColor = vec4(textureColor.rgb, textureColor.a);
vec4 vertexColor = vColor;
if (!uUseTexture){
gl_FragColor = vertexColor;
}
else{
gl_FragColor = texColor;
}
}
vertex:
attribute vec3 aVertexPosition;
attribute vec3 aVertexNormal;
attribute vec2 aTextureCoord;
attribute vec4 aVertexColor;
uniform mat4 uMVMatrix;
uniform mat4 uPMatrix;
uniform mat3 uNMatrix;
varying vec2 vTextureCoord;
varying vec4 vColor;
void main(void){
vec4 mvPosition = uMVMatrix * vec4(aVertexPosition, 1.0);
gl_Position = uPMatrix * mvPosition;
vTextureCoord = aTextureCoord;
vColor = aVertexColor;}
Before I tell you how to make your shader work you arguably should not do it that way. You should either
Make 2 shaders
Make one shader that uses a texture and a different shader that uses vertex colors. This is what nearly all professional game engines would do.
Make a shader that multiplies both colors and set one to white
If you have
gl_FragColor = vertexColor * textureColor;
Then if textureColor is 1,1,1,1 that means you're multiplying by 1
and so the result is just vertexColor. Similarly if vertexColor
is 1,1,1,1 then you're multiplying by 1 and so the result is just
textureColor
You can get a white texture by making just a single pixel white texture
var tex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, tex);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA,
gl.UNSIGNED_BYTE, new Uint8Array([255, 255, 255, 255]));
Then anytime you just want vertex colors bind that texture to a texture
unit and tell the sampler which unit it you put it in
You might also want to turn off texture coordinates
gl.disableVertexAttribArray(texcoordLocation);
When you want just texture color then you can do this
// turn off the attribute
gl.disableVertexAttribArray(aVertexColorLocation);
// set the attribute's constant value
gl.vertexAttrib4f(aVertexColorLocation, 1, 1, 1, 1);
This method has the added benefit that you can also use both texture colors and vertex colors together to modify the texture color or to tint the texture color. Many game engines would do this as well specifically to take advantage of that ability to blend the colors.
Pauli mentions another option which is to use mix
uniform float u_mixAmount;
gl_FragColor = mix(textureColor, vertexColor, u_mixAmount);
This would also work as you can set u_mixAmount to 0.0 when you want
textureColor and to 1.0 when you want vertexColor but unlike your
boolean example you can also fade between the 2 colors with values
between 0.0 and 1.0. For example 0.3 is 30% of vertexColor and 70%
of textureColor
A few other things
This line
vec4 texColor = vec4(textureColor.rgb, textureColor.a);
Is no different than
vec4 texColor = textureColor;
Just trying your shader it seems to work as is which suggests the issue is not your shader but some other part of your code.
var gl = document.querySelector("canvas").getContext("webgl");
var m4 = twgl.m4;
var arrays = {
aVertexPosition: [
-1, -1, 0,
1, -1, 0,
-1, 1, 0,
1, 1, 0,
],
aVertexNormal: [
0, 0, 1,
0, 0, 1,
0, 0, 1,
0, 0, 1,
],
aTextureCoord: [
0, 0,
1, 0,
0, 1,
1, 1,
],
aVertexColor: [
1, 0, 0, 1,
0, 1, 0, 1,
0, 0, 1, 1,
1, 0, 1, 1,
],
indices: [
0, 1, 2,
2, 1, 3,
],
};
var tex = twgl.createTexture(gl, {
format: gl.LUMINANCE,
mag: gl.NEAREST,
src: [224, 64, 128, 192],
});
var bufferInfo = twgl.createBufferInfoFromArrays(gl, arrays);
var programInfo = twgl.createProgramInfo(gl, ['vs', 'fs']);
gl.useProgram(programInfo.program);
twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
for (var i = 0; i < 2; ++i) {
twgl.setUniforms(programInfo, {
uMVMatrix: m4.identity(),
uPMatrix: m4.scale(m4.translation([i === 0 ? -0.5 : 0.5, 0, 0]), [0.5, 1, 1]),
uNMatrix: m4.identity(),
uSampler: tex,
uUseTexture: i === 1,
});
twgl.drawBufferInfo(gl, bufferInfo);
}
canvas { border: 1px solid black; }
<script src="https://twgljs.org/dist/2.x/twgl-full.min.js"></script>
<script id="fs" type="not-js">
precision mediump float;
uniform sampler2D uSampler;
varying vec2 vTextureCoord;
varying vec4 vColor;
uniform bool uUseTexture;
void main(void) {
vec4 textureColor = texture2D(uSampler, vec2(vTextureCoord.s, vTextureCoord.t));
vec4 texColor = vec4(textureColor.rgb, textureColor.a);
vec4 vertexColor = vColor;
if (!uUseTexture){
gl_FragColor = vertexColor;
}
else{
gl_FragColor = texColor;
}
}
</script>
<script id="vs" type="not-js">
attribute vec3 aVertexPosition;
attribute vec3 aVertexNormal;
attribute vec2 aTextureCoord;
attribute vec4 aVertexColor;
uniform mat4 uMVMatrix;
uniform mat4 uPMatrix;
uniform mat3 uNMatrix;
varying vec2 vTextureCoord;
varying vec4 vColor;
void main(void){
vec4 mvPosition = uMVMatrix * vec4(aVertexPosition, 1.0);
gl_Position = uPMatrix * mvPosition;
vTextureCoord = aTextureCoord;
vColor = aVertexColor;}
</script>
<canvas></canvas>

WebGl memory notation for matrices

Since webGL derives from openGL the coordinate system should be right handed, but as I'm trying to rotate a model by hand-writing the rotation matrix the visual output is the opposite of what I'd expect.
OpenGL needed matrices written in column-major format, and this is the way i'm handwriting a 90° counter-clockwise rotation matrix (assuming we're using a right handed coordinate system) around the y axis:
var rot = new glMatrix.ARRAY_TYPE(16);
//I indexed the array this way to edit those values as if I
were editing rows instead of columns
rot[0] = 0;
rot[4] = 0;
rot[8] = -1;
rot[12] = 0;
rot[1] = 0;
rot[5] = 1;
rot[9] = 0;
rot[13] = 0;
rot[2] = 1;
rot[6] = 0;
rot[10] = 0;
rot[14] = 0;
rot[3] = 0;
rot[7] = 0;
rot[11] = 0;
rot[15] = 1;
gl.uniformMatrix4fv(Program.model, false, rot);
I'd expect a 90° ccw rotation around the y axis but i'm getting a cw one instead. The transpose works as expected which means either the math is wrong or that I'm missing something else
Storage is orthogonal to how matrices are used. You can store them column major or row major. How they get used in multiplication is unrelated to their storage.
To make that clear I could store the matrix like this
// storage format 1
[xaxis-x, xaxis-y, xaxis-z, 0,
yaxis-x, yaxis-y, yaxis-z, 0,
zaxis-x, zazis-y, xaxis-z, 0,
trans-x, trans-y, trans-z, 1]
Or I could store it like this
// storage format 2
[xaxis-x, yaxis-x, zaxis-x, trans-x,
xaxis-y, yaxis-y, zaxis-y, trans-y,
xaxis-z, yazis-z, zaxis-z, trans-z,
0, 0, 0, 1]
I can then write all of these functions
columnMajorMutliplyUsingStorageFormat1(matrix, vector)
columnMajorMutliplyUsingStorageFormat2(matrix, vector)
rowMajorMutliplyUsingStorageFormat1(matrix, vector)
rowMajorMutliplyUsingStorageFormat2(matrix, vector)
You can imagine how those functions would be written. The point is storage format is separate from usage.
In any case pretty much all WebGL and OpenGL programs a matrix almost always takes storage format 1
// storage format 1
[xaxis-x, xaxis-y, xaxis-z, 0,
yaxis-x, yaxis-y, yaxis-z, 0,
zaxis-x, zazis-y, xaxis-z, 0,
trans-x, trans-y, trans-z, 1]
You can verify this in OpenGL 1.0.
glMatrixMode(GL_MODELVIEW);
glTranslatef(1.0f, 2.0f, 3.0f);
float mat[16];
glGetFloatv(GL_MODELVIEW, &mat[0]);
printf("%f %f %f %f\n", mat[0], mat[1], mat[2], mat[3]);
printf("%f %f %f %f\n", mat[4], mat[5], mat[6], mat[7]);
printf("%f %f %f %f\n", mat[8], mat[9], mat[10], mat[11]);
printf("%f %f %f %f\n", mat[12], mat[13], mat[14], mat[15]);
Will print something like
1 0 0 0
0 1 0 0
0 0 1 0
1 2 3 1
In other words if you want to set the translation set elements 12, 13, 14. If you want to set xaxis set elements 0, 1, 2
Here's the typical example with translation in elements 12, 13, 14
The canonical example of usage would be
gl_Position = worldViewProjectionMatrix * position;
"use strict";
twgl.setDefaults({attribPrefix: "a_"});
var m4 = twgl.m4;
var gl = document.getElementById("c").getContext("webgl")
var programInfo = twgl.createProgramInfo(gl, ["vs", "fs"]);
var bufferInfo = twgl.primitives.createCubeBufferInfo(gl, 2);
var tex = twgl.createTexture(gl, {
min: gl.NEAREST,
mag: gl.NEAREST,
src: [
255, 255, 255, 255,
192, 192, 192, 255,
192, 192, 192, 255,
255, 255, 255, 255,
],
});
var uniforms = {
u_lightWorldPos: [1, 8, -10],
u_lightColor: [0.4, 0.8, 0.8, 1],
u_ambient: [0, 0, 0, 1],
u_specular: [1, 1, 1, 1],
u_shininess: 50,
u_specularFactor: 1,
u_diffuse: tex,
};
function render(time) {
time *= 0.001;
twgl.resizeCanvasToDisplaySize(gl.canvas);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
gl.enable(gl.DEPTH_TEST);
gl.enable(gl.CULL_FACE);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
var projection = m4.perspective(30 * Math.PI / 180, gl.canvas.clientWidth / gl.canvas.clientHeight, 0.5, 10);
var eye = [1, 4, -6];
var target = [0, 0, 0];
var up = [0, 1, 0];
var camera = m4.lookAt(eye, target, up);
var view = m4.inverse(camera);
var viewProjection = m4.multiply(view, projection);
var world = m4.rotationY(time);
uniforms.u_viewInverse = camera;
uniforms.u_world = world;
uniforms.u_worldInverseTranspose = m4.transpose(m4.inverse(world));
uniforms.u_worldViewProjection = m4.multiply(world, viewProjection);
gl.useProgram(programInfo.program);
twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
twgl.setUniforms(programInfo, uniforms);
gl.drawElements(gl.TRIANGLES, bufferInfo.numElements, gl.UNSIGNED_SHORT, 0);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
body { margin: 0; }
canvas { width: 100vw; height: 100vh; display: block; }
<script id="vs" type="notjs">
uniform mat4 u_worldViewProjection;
uniform vec3 u_lightWorldPos;
uniform mat4 u_world;
uniform mat4 u_viewInverse;
uniform mat4 u_worldInverseTranspose;
attribute vec4 a_position;
attribute vec3 a_normal;
attribute vec2 a_texcoord;
varying vec4 v_position;
varying vec2 v_texCoord;
varying vec3 v_normal;
varying vec3 v_surfaceToLight;
varying vec3 v_surfaceToView;
void main() {
v_texCoord = a_texcoord;
v_position = (u_worldViewProjection * a_position);
v_normal = (u_worldInverseTranspose * vec4(a_normal, 0)).xyz;
v_surfaceToLight = u_lightWorldPos - (u_world * a_position).xyz;
v_surfaceToView = (u_viewInverse[3] - (u_world * a_position)).xyz;
gl_Position = v_position;
}
</script>
<script id="fs" type="notjs">
precision mediump float;
varying vec4 v_position;
varying vec2 v_texCoord;
varying vec3 v_normal;
varying vec3 v_surfaceToLight;
varying vec3 v_surfaceToView;
uniform vec4 u_lightColor;
uniform vec4 u_ambient;
uniform sampler2D u_diffuse;
uniform vec4 u_specular;
uniform float u_shininess;
uniform float u_specularFactor;
vec4 lit(float l ,float h, float m) {
return vec4(1.0,
max(l, 0.0),
(l > 0.0) ? pow(max(0.0, h), m) : 0.0,
1.0);
}
void main() {
vec4 diffuseColor = texture2D(u_diffuse, v_texCoord);
vec3 a_normal = normalize(v_normal);
vec3 surfaceToLight = normalize(v_surfaceToLight);
vec3 surfaceToView = normalize(v_surfaceToView);
vec3 halfVector = normalize(surfaceToLight + surfaceToView);
vec4 litR = lit(dot(a_normal, surfaceToLight),
dot(a_normal, halfVector), u_shininess);
vec4 outColor = vec4((
u_lightColor * (diffuseColor * litR.y + diffuseColor * u_ambient +
u_specular * litR.z * u_specularFactor)).rgb,
diffuseColor.a);
gl_FragColor = outColor;
}
</script>
<script src="https://twgljs.org/dist/twgl-full.min.js"></script>
<canvas id="c"></canvas>
and here's the opposite example with translation in elements 3, 7, 11
In this case the math in the shader has been changed to
gl_Position = position * worldViewProjectionMatrix;
"use strict";
twgl.setDefaults({attribPrefix: "a_"});
var m4 = twgl.m4;
var gl = document.getElementById("c").getContext("webgl")
var programInfo = twgl.createProgramInfo(gl, ["vs", "fs"]);
var bufferInfo = twgl.primitives.createCubeBufferInfo(gl, 2);
var tex = twgl.createTexture(gl, {
min: gl.NEAREST,
mag: gl.NEAREST,
src: [
255, 255, 255, 255,
192, 192, 192, 255,
192, 192, 192, 255,
255, 255, 255, 255,
],
});
var uniforms = {
u_lightWorldPos: [1, 8, -10],
u_lightColor: [0.4, 0.8, 0.8, 1],
u_ambient: [0, 0, 0, 1],
u_specular: [1, 1, 1, 1],
u_shininess: 50,
u_specularFactor: 1,
u_diffuse: tex,
};
function render(time) {
time *= 0.001;
twgl.resizeCanvasToDisplaySize(gl.canvas);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
gl.enable(gl.DEPTH_TEST);
gl.enable(gl.CULL_FACE);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
var projection = m4.perspective(30 * Math.PI / 180, gl.canvas.clientWidth / gl.canvas.clientHeight, 0.5, 10);
var eye = [1, 4, -6];
var target = [0, 0, 0];
var up = [0, 1, 0];
var camera = m4.lookAt(eye, target, up);
var view = m4.inverse(camera);
var viewProjection = m4.multiply(view, projection);
var world = m4.rotationY(time);
uniforms.u_viewInverse = m4.transpose(camera);
uniforms.u_world = m4.transpose(world);
uniforms.u_worldInverseTranspose = m4.transpose(m4.transpose(m4.inverse(world)));
uniforms.u_worldViewProjection = m4.transpose(m4.multiply(world, viewProjection));
gl.useProgram(programInfo.program);
twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
twgl.setUniforms(programInfo, uniforms);
gl.drawElements(gl.TRIANGLES, bufferInfo.numElements, gl.UNSIGNED_SHORT, 0);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
body { margin: 0; }
canvas { width: 100vw; height: 100vh; display: block; }
<script id="vs" type="notjs">
uniform mat4 u_worldViewProjection;
uniform vec3 u_lightWorldPos;
uniform mat4 u_world;
uniform mat4 u_viewInverse;
uniform mat4 u_worldInverseTranspose;
attribute vec4 a_position;
attribute vec3 a_normal;
attribute vec2 a_texcoord;
varying vec4 v_position;
varying vec2 v_texCoord;
varying vec3 v_normal;
varying vec3 v_surfaceToLight;
varying vec3 v_surfaceToView;
void main() {
v_texCoord = a_texcoord;
v_position = (a_position * u_worldViewProjection);
v_normal = (vec4(a_normal, 0) * u_worldInverseTranspose).xyz;
v_surfaceToLight = u_lightWorldPos - (a_position * u_world).xyz;
v_surfaceToView = (u_viewInverse[3] - (a_position * u_world)).xyz;
gl_Position = v_position;
}
</script>
<script id="fs" type="notjs">
precision mediump float;
varying vec4 v_position;
varying vec2 v_texCoord;
varying vec3 v_normal;
varying vec3 v_surfaceToLight;
varying vec3 v_surfaceToView;
uniform vec4 u_lightColor;
uniform vec4 u_ambient;
uniform sampler2D u_diffuse;
uniform vec4 u_specular;
uniform float u_shininess;
uniform float u_specularFactor;
vec4 lit(float l ,float h, float m) {
return vec4(1.0,
max(l, 0.0),
(l > 0.0) ? pow(max(0.0, h), m) : 0.0,
1.0);
}
void main() {
vec4 diffuseColor = texture2D(u_diffuse, v_texCoord);
vec3 a_normal = normalize(v_normal);
vec3 surfaceToLight = normalize(v_surfaceToLight);
vec3 surfaceToView = normalize(v_surfaceToView);
vec3 halfVector = normalize(surfaceToLight + surfaceToView);
vec4 litR = lit(dot(a_normal, surfaceToLight),
dot(a_normal, halfVector), u_shininess);
vec4 outColor = vec4((
u_lightColor * (diffuseColor * litR.y + diffuseColor * u_ambient +
u_specular * litR.z * u_specularFactor)).rgb,
diffuseColor.a);
gl_FragColor = outColor;
}
</script>
<script src="https://twgljs.org/dist/twgl-full.min.js"></script>
<canvas id="c"></canvas>

Categories

Resources