How to use Matrix4.setLookAt to move the camera around the scene
Trying to make a first person camera controller in webGL. I'm trying to do this using the Matrix4.setLookAt () function, but I'm not sure how to calculate (and frankly, a bit vague about which parameters I need to change and when) how to navigate. As I currently implement for looking left and right, it seems to work fine at first, but as soon as the value approaches 1.0 or -1.0 for g_eyeX values, it starts to move the camera (in the negative x direction towards) from the cube to the scene. can't find much documentation on how to use this function to move the "camera" around a scene as most of it links to three.js (which I'm trying to find out how it works and doesn't want to use the library for it).Can anyone help or point me in the right direction? It would be very helpful
My code is below and this is what the setLookAt function does as an argument to Matrix4.setLookAt (eyeX, eyeY, eyeZ, atX, atY, atZ, upX, upY, upZ)
'eyeX, Y, Z' - Specify the position of the eye point 'atX, atY, atZ' - Specify the position of the eye point 'upX, upY, upZ' - Specify the upward direction in the scene
JS:
// sceneWalker.js
// modified from RotatingTriangle.js (c) 2012 matsuda
// uses a non-indexed cube - 2 triangles per side - 36 vertices
// Vertex shader program
var VSHADER_SOURCE =
'attribute vec4 a_Position;\n' +
'uniform mat4 u_ViewMatrix;\n' +
'uniform mat4 u_ModelMatrix;\n' +
'uniform mat4 u_ProjMatrix;\n' +
'void main() {\n' +
' gl_Position = u_ProjMatrix * u_ViewMatrix * u_ModelMatrix * a_Position;\n' +
'}\n';
// Fragment shader program
var FSHADER_SOURCE =
'void main() {\n' +
' gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);\n' +
'}\n';
// Rotation angle (degrees/second)
var ANGLE_STEP = 0.0;
var MOVE_AMOUNT = 0.0;
var g_eyeX = 0.0, g_eyeY = 0.0, g_eyeZ = 0.25; // Eye position
var g_curX = 0.0, g_curZ = -3.0;
function main() {
// Retrieve <canvas> element
var canvas = document.getElementById('webgl');
// Get the rendering context for WebGL
var gl = getWebGLContext(canvas);
if (!gl) {
console.log('Failed to get the rendering context for WebGL');
return;
}
// Initialize shaders
if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
console.log('Failed to intialize shaders.');
return;
}
// Write the positions of vertices to a vertex shader
var n = initVertexBuffers(gl);
if (n < 0) {
console.log('Failed to set the positions of the vertices');
return;
}
// Specify the color for clearing <canvas>
gl.clearColor(0.0, 0.0, 0.0, 1.0);
// Get storage location of u_ViewMatrix
var u_ViewMatrix = gl.getUniformLocation(gl.program, 'u_ViewMatrix');
if (!u_ViewMatrix) {
console.log('Failed to get the storage location of u_ViewMatrix');
return;
}
var u_ModelMatrix = gl.getUniformLocation(gl.program, 'u_ModelMatrix');
if (!u_ModelMatrix) {
console.log('Failed to get the storage location of u_ModelMatrix');
return;
}
var u_ProjMatrix = gl.getUniformLocation(gl.program, 'u_ProjMatrix');
if (!u_ModelMatrix) {
console.log('Failed to get the storage location of u_ProjMatrix');
return;
}
// Current rotation angle
var currentAngle = 0.0;
// Model matrix
var modelMatrix = new Matrix4();
var viewMatrix = new Matrix4();
var projMatrix = new Matrix4();
modelMatrix.setTranslate(0, 0, 100);
viewMatrix.setLookAt(g_eyeX, g_eyeY, g_eyeZ, 0, 0, 0, 0, 1, 0);
projMatrix.setPerspective(45, (canvas.width)/(canvas.height), 0.1, 10000000);
gl.uniformMatrix4fv(u_ModelMatrix, false, modelMatrix.elements);
gl.uniformMatrix4fv(u_ViewMatrix, false, viewMatrix.elements);
gl.uniformMatrix4fv(u_ProjMatrix, false, projMatrix.elements);
document.onkeydown = function(ev){ keydown(ev, gl, n, u_ViewMatrix, viewMatrix); };
// Start drawing
var tick = function() {
//currentAngle = animate(currentAngle); // Update the rotation angle
draw(gl, n, currentAngle, modelMatrix, viewMatrix, u_ModelMatrix, u_ViewMatrix); // Draw the triangle
requestAnimationFrame(tick, canvas); // Request that the browser calls tick
};
tick();
}
function keydown(ev, gl, n, u_ViewMatrix, viewMatrix) {
console.log(ev.keyCode);
if(ev.keyCode == 39) { // The right arrow key was pressed
g_eyeX -= 0.01;
console.log(g_eyeX);
} else
if (ev.keyCode == 37) { // The left arrow key was pressed
g_eyeX += 0.01;
console.log(g_eyeX);
}
if(ev.keyCode == 38){
g_eyeY += 0.01;
}
if(ev.keyCode == 40){
g_eyeY -= 0.01;
}
if(ev.keyCode == 68){
g_curX -= 0.01;
}
if(ev.keyCode == 65){
g_curX += 0.01;
}
if(ev.keyCode == 87){
g_curZ += 0.01;
}
if(ev.keyCode == 83){
g_curZ -= 0.01;
}
else { return; }
}
function initVertexBuffers(gl) {
var vertices = new Float32Array ([
-0.5, 0.5, 0.5,
-0.5, -0.5, 0.5,
0.5, -0.5, 0.5,
-0.5, 0.5, 0.5,
0.5, -0.5, 0.5,
0.5, 0.5, 0.5,
0.5, 0.5, 0.5,
0.5, -0.5, 0.5,
0.5, -0.5, -0.5,
0.5, 0.5, 0.5,
0.5, -0.5, -0.5,
0.5, 0.5, -0.5,
0.5, -0.5, 0.5,
-0.5, -0.5, 0.5,
-0.5, -0.5, -0.5,
0.5, -0.5, 0.5,
-0.5, -0.5, -0.5,
0.5, -0.5, -0.5,
0.5, 0.5, -0.5,
-0.5, 0.5, -0.5,
-0.5, 0.5, 0.5,
0.5, 0.5, -0.5,
-0.5, 0.5, 0.5,
0.5, 0.5, 0.5,
-0.5, -0.5, -0.5,
-0.5, 0.5, -0.5,
0.5, 0.5, -0.5,
-0.5, -0.5, -0.5,
0.5, 0.5, -0.5,
0.5, -0.5, -0.5,
-0.5, 0.5, -0.5,
-0.5, -0.5, -0.5,
-0.5, -0.5, 0.5,
-0.5, 0.5, -0.5,
-0.5, -0.5, 0.5,
-0.5, 0.5, 0.5
]);
var n = 36; // The number of vertices
// Create a buffer object
var vertexBuffer = gl.createBuffer();
if (!vertexBuffer) {
console.log('Failed to create the buffer object');
return -1;
}
// Bind the buffer object to target
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
// Write date into the buffer object
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
// Assign the buffer object to a_Position variable
var a_Position = gl.getAttribLocation(gl.program, 'a_Position');
if(a_Position < 0) {
console.log('Failed to get the storage location of a_Position');
return -1;
}
gl.vertexAttribPointer(a_Position, 3, gl.FLOAT, false, 0, 0);
// Enable the assignment to a_Position variable
gl.enableVertexAttribArray(a_Position);
return n;
}
function draw(gl, n, currentAngle, modelMatrix, viewMatrix, u_ModelMatrix, u_ViewMatrix) {
// Set the rotation matrix
modelMatrix.setRotate(currentAngle, 1, 1, 1);
modelMatrix.setTranslate(g_curX, 0, g_curZ);
viewMatrix.setLookAt(g_eyeX, g_eyeY, g_eyeZ, 0, 0, 0, 0, 1, 0);
// Pass the rotation matrix to the vertex shader
gl.uniformMatrix4fv(u_ViewMatrix, false, viewMatrix.elements);
gl.uniformMatrix4fv(u_ModelMatrix, false, modelMatrix.elements);
// Clear <canvas>
gl.clear(gl.COLOR_BUFFER_BIT);
// Draw the rectangle
gl.drawArrays(gl.TRIANGLES, 0, n);
}
// Last time that this function was called
var g_last = Date.now();
function animate(angle) {
// Calculate the elapsed time
var now = Date.now();
var elapsed = now - g_last;
g_last = now;
// Update the current rotation angle (adjusted by the elapsed time)
var newAngle = angle + (ANGLE_STEP * elapsed) / 1000.0;
return newAngle %= 360;
}
HMTL:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Cube</title>
</head>
<body onload="main()">
<canvas id="webgl" width="400" height="400">
Please use a browser that supports "canvas"
</canvas>
<script src="../lib/webgl-utils.js"></script>
<script src="../lib/webgl-debug.js"></script>
<script src="../lib/cuon-utils.js"></script>
<script src="../lib/cuon-matrix.js"></script>
<script src="sceneWalker.js"></script>
</body>
</html>
setLookAt is typically implemented to take 3 vectors (or 9 distinct values) as arguments. The first argument is the position of the eye / camera / you. The second argument is the position in the direction you want to look. The third argument is up the axis. This is used to set the orientation of the camera. This is necessary because there are infinitely many landmarks that point in the same direction. Think of a plane, the direction of the plane tells you how it moves, but the orientation of the plane indicates whether it flies normally or is upside down.
SetLookAt usually returns a matrix of the view (or its inverse), which you then pass to the GPU.
So instead of the three 0's that you have as the gaze position, you probably want to use your model's position instead? Also, note that your implementation of motion is currently moving you in absolute coordinates, not based on the current orientation of the camera. +/- 1.0 to eye_x cannot make you move left in camera space.
Here's a simple pseudo FPSCamera implementation for reference that should get you started. The main thing to note is that the camera should track its orientation through the front, front and side vectors. They are in world coordinates and are normalized to unit length.
/** @constructor */
function FPSCamera(){
this.pos = [0.0, 0.0, 0.0];
this.dir = [0.0, 0.0, -1.0]; // or forward
this.up = [0.0, 1.0, 0.0];
this.side = [1.0, 0.0, 0.0]; // or right
}
FPSCamera.prototype.forward = function(dist){
this.pos[0] += this.dir[0] * dist;
this.pos[1] += this.dir[1] * dist;
this.pos[2] += this.dir[2] * dist;
};
// do the same for other 2 directions, strife and fly
// looks to left/right
FPSCamera.prototype.yaw = function(radians){
var orientationChange = ORIENTATION.fromAxisAngle(this.up, radians);
this.dir = VEC3.rotatedByOrientation(this.dir, orientationChange);
this.side = VEC3.cross(this.dir, this.up);
this.side = VEC3.normalize(this.side);
};
// same for pitch... except the rotation axis is this.side and you need to ensure the pitch is within +/- 90 degrees
FPSCamera.prototype.getViewMatrix = function(){
// matrix can be extracted from the 3 direction vectors, but lets use lookAt here;
return MAT4.lookAt(this.pos, VEC3.plus(this.pos, this.dir), this.up);
};