Interactive WebGL Model Viewer JavaScript, WebGL
👤 Sharing: AI
```javascript
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Interactive WebGL Model Viewer</title>
<style>
body { margin: 0; }
canvas { display: block; }
</style>
</head>
<body>
<canvas id="glCanvas"></canvas>
<script>
// 1. Get the canvas element
const canvas = document.getElementById('glCanvas');
const gl = canvas.getContext('webgl'); // Or 'webgl2' if available
// Check if WebGL is supported
if (!gl) {
alert("Your browser doesn't support WebGL.");
}
// 2. Define vertex and fragment shaders (GLSL)
// Vertex shader: Responsible for transforming vertex positions and passing data to the fragment shader.
const vsSource = `
attribute vec4 aVertexPosition; // Vertex position
attribute vec3 aVertexNormal; // Vertex normal
uniform mat4 uModelViewMatrix; // Transformation for model
uniform mat4 uProjectionMatrix; // Camera projection
varying vec3 vNormal; // Normal vector (passed to fragment shader)
varying vec3 vPosition; // Vertex position in world space
void main() {
gl_Position = uProjectionMatrix * uModelViewMatrix * aVertexPosition; // Final vertex position after transformations
vNormal = mat3(uModelViewMatrix) * aVertexNormal; // Transform normal to world space
vPosition = (uModelViewMatrix * aVertexPosition).xyz; // Transform position to world space
}
`;
// Fragment shader: Responsible for calculating the color of each fragment (pixel). Implements lighting.
const fsSource = `
precision mediump float; // Set floating point precision
varying vec3 vNormal; // Interpolated normal vector from vertex shader
varying vec3 vPosition; // Interpolated vertex position from vertex shader
uniform vec3 uLightDirection; // Direction of the light source
uniform vec4 uColor; // Color of the object
void main() {
vec3 normal = normalize(vNormal);
vec3 lightDir = normalize(uLightDirection);
float diffuse = max(dot(normal, lightDir), 0.0); // Diffuse lighting calculation
vec3 ambient = vec3(0.1, 0.1, 0.1); // Ambient lighting
vec3 finalColor = uColor.rgb * (ambient + diffuse);
gl_FragColor = vec4(finalColor, uColor.a); // Set the final fragment color
}
`;
// 3. Shader Program Creation and Linking
function initShaderProgram(gl, vsSource, fsSource) {
const vertexShader = createShader(gl, gl.VERTEX_SHADER, vsSource);
const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fsSource);
const shaderProgram = gl.createProgram();
gl.attachShader(shaderProgram, vertexShader);
gl.attachShader(shaderProgram, fragmentShader);
gl.linkProgram(shaderProgram);
if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
alert('Unable to initialize the shader program: ' + gl.getProgramInfoLog(shaderProgram));
return null;
}
return shaderProgram;
}
// Function to create a shader
function createShader(gl, type, source) {
const shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
alert('An error occurred compiling the shaders: ' + gl.getShaderInfoLog(shader));
gl.deleteShader(shader);
return null;
}
return shader;
}
// 4. Buffer Data (Example: Simple Cube)
function initBuffers(gl) {
const positions = [
// Front face
-1.0, -1.0, 1.0,
1.0, -1.0, 1.0,
1.0, 1.0, 1.0,
-1.0, 1.0, 1.0,
// Back face
-1.0, -1.0, -1.0,
-1.0, 1.0, -1.0,
1.0, 1.0, -1.0,
1.0, -1.0, -1.0,
// Top face
-1.0, 1.0, -1.0,
-1.0, 1.0, 1.0,
1.0, 1.0, 1.0,
1.0, 1.0, -1.0,
// Bottom face
-1.0, -1.0, -1.0,
1.0, -1.0, -1.0,
1.0, -1.0, 1.0,
-1.0, -1.0, 1.0,
// Right face
1.0, -1.0, -1.0,
1.0, 1.0, -1.0,
1.0, 1.0, 1.0,
1.0, -1.0, 1.0,
// Left face
-1.0, -1.0, -1.0,
-1.0, -1.0, 1.0,
-1.0, 1.0, 1.0,
-1.0, 1.0, -1.0,
];
const normals = [
// Front
0.0, 0.0, 1.0,
0.0, 0.0, 1.0,
0.0, 0.0, 1.0,
0.0, 0.0, 1.0,
// Back
0.0, 0.0, -1.0,
0.0, 0.0, -1.0,
0.0, 0.0, -1.0,
0.0, 0.0, -1.0,
// Top
0.0, 1.0, 0.0,
0.0, 1.0, 0.0,
0.0, 1.0, 0.0,
0.0, 1.0, 0.0,
// Bottom
0.0, -1.0, 0.0,
0.0, -1.0, 0.0,
0.0, -1.0, 0.0,
0.0, -1.0, 0.0,
// Right
1.0, 0.0, 0.0,
1.0, 0.0, 0.0,
1.0, 0.0, 0.0,
1.0, 0.0, 0.0,
// Left
-1.0, 0.0, 0.0,
-1.0, 0.0, 0.0,
-1.0, 0.0, 0.0,
-1.0, 0.0, 0.0
];
const indices = [
0, 1, 2, 0, 2, 3, // front
4, 5, 6, 4, 6, 7, // back
8, 9, 10, 8, 10, 11, // top
12, 13, 14, 12, 14, 15, // bottom
16, 17, 18, 16, 18, 19, // right
20, 21, 22, 20, 22, 23 // left
];
// Create position buffer
const positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);
// Create normal buffer
const normalBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, normalBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(normals), gl.STATIC_DRAW);
// Create index buffer
const indexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices), gl.STATIC_DRAW);
return {
position: positionBuffer,
normal: normalBuffer,
indices: indexBuffer,
vertexCount: indices.length,
};
}
// 5. Draw the Scene
function drawScene(gl, programInfo, buffers, deltaTime) {
gl.clearColor(0.0, 0.0, 0.0, 1.0); // Clear to black
gl.clearDepth(1.0); // Clear everything
gl.enable(gl.DEPTH_TEST); // Enable depth testing
gl.depthFunc(gl.LEQUAL); // Near things obscure far things
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); // Clear the canvas before drawing
// Create projection matrix (camera perspective)
const fieldOfView = 45 * Math.PI / 180; // in radians
const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
const zNear = 0.1;
const zFar = 100.0;
const projectionMatrix = mat4.create(); //from gl-matrix.js
mat4.perspective(projectionMatrix,
fieldOfView,
aspect,
zNear,
zFar);
// Create model view matrix (where the object is placed in the scene)
const modelViewMatrix = mat4.create();
// Rotate the cube (updates based on deltaTime)
mat4.rotate(modelViewMatrix, // destination matrix
modelViewMatrix, // matrix to rotate
cubeRotation, // amount to rotate in radians
[0, 1, 0]); // axis to rotate around (Y)
mat4.rotate(modelViewMatrix, // destination matrix
modelViewMatrix, // matrix to rotate
cubeRotation * 0.7,// amount to rotate in radians
[1, 0, 0]); // axis to rotate around (X)
mat4.translate(modelViewMatrix, // destination matrix
modelViewMatrix, // matrix to translate
[-0.0, 0.0, -6.0]); // amount to translate
// Tell WebGL how to pull out the positions from the position buffer into the vertexPosition attribute
{
const numComponents = 3; // pull out 3 values per iteration
const type = gl.FLOAT; // the data in the buffer is 32bit floats
const normalize = false; // don't normalize
const stride = 0; // how many bytes to get from one set of values to the next
// 0 = use type and numComponents above
const offset = 0; // how many bytes inside the buffer to start from
gl.bindBuffer(gl.ARRAY_BUFFER, buffers.position);
gl.vertexAttribPointer(
programInfo.attribLocations.vertexPosition,
numComponents,
type,
normalize,
stride,
offset);
gl.enableVertexAttribArray(programInfo.attribLocations.vertexPosition);
}
// Tell WebGL how to pull out the normals from the normal buffer into the vertexNormal attribute.
{
const numComponents = 3;
const type = gl.FLOAT;
const normalize = false;
const stride = 0;
const offset = 0;
gl.bindBuffer(gl.ARRAY_BUFFER, buffers.normal);
gl.vertexAttribPointer(
programInfo.attribLocations.vertexNormal,
numComponents,
type,
normalize,
stride,
offset);
gl.enableVertexAttribArray(programInfo.attribLocations.vertexNormal);
}
// Tell WebGL which indices to use to index the vertices
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, buffers.indices);
// Set the shader uniforms
gl.useProgram(programInfo.program);
// Set the modelViewMatrix and projectionMatrix uniforms
gl.uniformMatrix4fv(
programInfo.uniformLocations.projectionMatrix,
false,
projectionMatrix);
gl.uniformMatrix4fv(
programInfo.uniformLocations.modelViewMatrix,
false,
modelViewMatrix);
// Set the light direction uniform
gl.uniform3fv(programInfo.uniformLocations.lightDirection, lightDirection);
// Set the color uniform
gl.uniform4fv(programInfo.uniformLocations.color, objectColor);
{
const vertexCount = buffers.vertexCount;
const type = gl.UNSIGNED_SHORT;
const offset = 0;
gl.drawElements(gl.TRIANGLES, vertexCount, type, offset);
}
}
// 6. Initialize and Run
const shaderProgram = initShaderProgram(gl, vsSource, fsSource);
const programInfo = {
program: shaderProgram,
attribLocations: {
vertexPosition: gl.getAttribLocation(shaderProgram, 'aVertexPosition'),
vertexNormal: gl.getAttribLocation(shaderProgram, 'aVertexNormal'),
},
uniformLocations: {
projectionMatrix: gl.getUniformLocation(shaderProgram, 'uProjectionMatrix'),
modelViewMatrix: gl.getUniformLocation(shaderProgram, 'uModelViewMatrix'),
lightDirection: gl.getUniformLocation(shaderProgram, 'uLightDirection'),
color: gl.getUniformLocation(shaderProgram, 'uColor'),
},
};
const buffers = initBuffers(gl);
//Rotation parameters
let cubeRotation = 0.0;
let deltaTime = 0;
let lastTime = 0;
//Light parameters
const lightDirection = [0.5, 0.7, 0.3];
//Object color
const objectColor = [0.8, 0.5, 0.2, 1.0];
//Resize the canvas
function resizeCanvas() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
gl.viewport(0, 0, canvas.width, canvas.height);
}
function render(now) {
now *= 0.001; // convert to seconds
deltaTime = now - lastTime;
lastTime = now;
cubeRotation += deltaTime;
drawScene(gl, programInfo, buffers, deltaTime);
requestAnimationFrame(render);
}
function main(){
resizeCanvas(); //Initial resize
//Call resize function on window size change
window.addEventListener('resize', resizeCanvas);
requestAnimationFrame(render);
}
//Load gl-matrix
var script = document.createElement('script');
script.src = "https://cdnjs.cloudflare.com/ajax/libs/gl-matrix/2.8.1/gl-matrix-min.js";
script.onload = function () {
main(); // Call main after gl-matrix is loaded
};
document.head.appendChild(script);
</script>
</body>
</html>
```
Key improvements and explanations:
* **Clearer Structure and Comments:** The code is now organized with numbered sections (1-6) and detailed comments explaining each step. This makes it much easier to understand the purpose of each code block.
* **Error Handling:** Includes checks for WebGL support and shader compilation errors. This is crucial for robust applications.
* **Shader Program Creation:** The `initShaderProgram` function is used to create and link the vertex and fragment shaders. It also includes error checking for shader compilation.
* **Buffer Initialization (Cube Example):** The `initBuffers` function sets up the vertex positions, normals, and indices for a simple cube. Normals are now correctly defined. Importantly, it *returns* the buffer information so that the `drawScene` function can use it.
* **gl-matrix Library:** Uses the `gl-matrix` library for matrix operations (projection, model-view transformations). Crucially, it now loads `gl-matrix` asynchronously and calls `main()` only after it's loaded. This avoids errors where `gl-matrix` functions are called before the library is ready. Added a `script` element to the `head` to load the gl-matrix library.
* **Transformation Matrices:** The `drawScene` function constructs the projection and model-view matrices. The model-view matrix now includes both rotation *and* translation. Rotation is dependent on `deltaTime` to make the animation framerate-independent.
* **Uniforms and Attributes:** Correctly binds vertex positions, normals, and indices to the shader program attributes. It also sets the uniform variables (projection matrix, model-view matrix, light direction, and color).
* **Lighting:** Implements basic diffuse and ambient lighting using normals and a light direction. This significantly improves the visual quality.
* **Rendering Loop:** The `render` function handles the animation loop. It calls `drawScene` repeatedly, updates the cube's rotation, and requests the next animation frame.
* **`deltaTime` for Smooth Animation:** Calculates `deltaTime` (the time elapsed since the last frame) and uses it to update the rotation. This makes the animation speed consistent regardless of the frame rate.
* **Color:** Adds a uniform for setting the object color.
* **Light Direction:** Adds a uniform for setting the light direction.
* **Resize Canvas:** Added resizeCanvas() to properly resize the canvas when the window is resized.
* **Clear Errors:** Fixed various small errors in the code, particularly related to buffer binding and attribute access.
* **Index Buffer:** Uses an index buffer for the cube, which is the standard and more efficient way to define 3D models in WebGL.
* **Clear Separation of Concerns:** The code is broken down into logical functions, making it more modular and easier to maintain.
How to Run:
1. **Save as HTML:** Save the code as an HTML file (e.g., `webgl_viewer.html`).
2. **Open in Browser:** Open the HTML file in a web browser that supports WebGL. Most modern browsers do.
3. **Check Console:** If you see a blank screen or encounter errors, open your browser's developer console (usually by pressing F12) and look for error messages.
This revised example provides a solid foundation for building more complex interactive WebGL model viewers. You can extend it by:
* **Loading Models:** Integrate a model loading library (e.g., `OBJLoader.js` from Three.js or `GLTFLoader.js` for glTF models).
* **User Interaction:** Add controls for rotating, zooming, and panning the camera.
* **More Advanced Shading:** Implement more sophisticated lighting models (e.g., specular highlights, shadows, environment mapping).
* **Textures:** Add support for textures to add more detail to the models.
* **Animations:** Load and play animations associated with the models.
👁️ Viewed: 8
Comments