This tutorial shows us a simple method to export 3d meshes from 3ds max to an own Javascript file format. We save a 3d-Mesh including textures and UV mapping from 3ds max using MaxScript and load them into a lightweight WebGL-Engine.
What you need
- 3ds max
- A text editor
- A WebGL compatible browser (Firefox, Chrome, …)
The MaxScript
Just open a new script file in 3ds max, paste the following code and press Strg-E or Evaluate. A small dialog appears. When you have a standard material with a diffuse texture press the “Get Diffuse Filename”-Button to get the name of your texture file. Don’t forget to copy this texture file into the same directory like the JS- and HTML-File later. To export your selected 3d model press “Export” and set your filepath and name.
rollout JSExport "js Export" ( global jsexport_name global js_export_captionname global js_export_pathname global js_export_filetype global diffuseName group "Export JS Mesh" ( button filenameBtn "Get Diffuse Filename" edittext imagePath "" button exportSelected "Export" ) on filenameBtn pressed do ( imagePath.text = filenameFromPath $.material.diffuseMap.filename ) on exportSelected pressed do ( jsexport_name = GetSaveFileName caption:"Save 3D as JS:" types:"JavaScript (*.js)|*.js" obj = selection if jsexport_name != undefined then( tmesh = snapshotAsMesh selection[1] out_file = createfile jsexport_name num_verts = tmesh.numverts num_tverts = tmesh.numtverts num_faces = tmesh.numfaces format "var triangles = [n" to:out_file for f = 1 to num_faces do( faceVertIndex = getFace tmesh f tVertIndex = getTVFace tmesh f for i = 1 to 3 do( vpos = getVert tmesh faceVertIndex[i] format "%,%,%," vpos.x vpos.y vpos.z to:out_file vuvpos = getTVert tmesh tVertIndex[i] format "%,%," vuvpos.x vuvpos.y to:out_file normals = getNormal tmesh faceVertIndex[i] nl = sqrt(normals.x^2 + normals.y^2 + normals.z^2) nx = (normals.x/nl) as string ny = (normals.y/nl) as string nz = (normals.z/nl) as string if nl == 0 then ( nx = 0 ny = 0 nz = 0 ) format "%,%,%," nx ny nz to:out_file ) format "n" to:out_file ) format "n];n" to:out_file format "var vertCount = %;n" num_verts to:out_file format "var faceCount = %;n" num_faces to:out_file format "var centre = %;n" selection[1].pos to:out_file format "var diffuseMap = "%";n" imagePath.text to:out_file close out_file ) ) )createDialog JSExport
What you get
After saving you get a JS-File similar this structure:
var triangles = [ 19.6391, -20.3159, 38.8219, 1.85714, 1.98571, -0.957021, 0.218757, -0.190408, 20.2092, -15.2357, 38.8219, 2.0, 1.98571, -0.981749, 0.0, -0.190181, 20.4854, -15.2357, 38.0573, 2.0, 2.0, -0.940499, -1.31962e-007, -0.339798, ... ]; var vertCount = 1598; var faceCount = 3180; var centre = [0,0,0]; var diffuseMap = "teapot.jpg";
The long row in the triangles variable shows only the position, the UVs and the normalized normals of all three vertices in the first triangle of the mesh. The center point is the pivot position in 3ds max.
The HTML
For this example you have to load three script files. The first one is your exported mesh. For our WebGL-Example we need the GLMatrix-Utility as secound javascript file. The third file will be our little WebGL example
<!DOCTYPE html> <html> <head> <title>WebGL</title> <script src="teapot.js" type="text/javascript"> <script src="http://glmatrix.googlecode.com/files/glMatrix-0.9.5.min.js" type="text/javascript"> <script src="webgl.js" type="text/javascript"> </head> <body bgcolor="#000" style="margin: auto 0" onload="webGLStart();"> <canvas id="GLWindow"></canvas> </body> </html>
The WebGL-Script
The webgl.js – File is a small 3d program viewer with an ambient, a directional light and a rotation matrix.
var program; var mvMatrix = mat4.create(); var pMatrix = mat4.create(); var textureMap; var VertexBuffer; var gl; var rotationMatrix = mat4.create(); var mouseTranslation = [0.0, 0.0, 0.0]; var currentAngle = 0; function checkGLError(func) { var error = gl.getError(); if(error != gl.NO_ERROR) alert("GL error: " + " " +func + " : " + error); } function webGLStart() { var canvas = document.getElementById("GLWindow"); initGL(canvas); initShaders(); initBuffers(); initTexture(); gl.clearColor(1.0, 1.0, 1.0, 1.0); gl.enable(gl.DEPTH_TEST); updateLoop(); } function initGL(canvas) { canvas.width = window.innerWidth; canvas.height = window.innerHeight; var names = ["webgl", "experimental-webgl", "webkit-3d", "moz-webgl"]; for (var i = 0; i < names.length; ++i) { try { gl = canvas.getContext(names[i]); } catch(e) {} if (gl) break; } if (!gl) alert("Kein WebGL verfuegbar!"); gl.viewportWidth = canvas.width; gl.viewportHeight = canvas.height; } function initShaders() { var vertexShaderSource = "attribute vec3 aVertexPosition;n" + "attribute vec3 aVertexNormal;n" + "attribute vec2 aTextureCoord;n" + "uniform mat4 uMVMatrix;n" + "uniform mat4 uPMatrix;n" + "uniform mat3 uNormalMatrix;n" + "uniform vec3 uAmbientColor;n" + "uniform vec3 uDirectionalColor;n" + "varying vec3 vLightWeighting;n" + "varying vec2 vTextureCoord;n" + "void main(void) {n" + "vec3 ulightDirection = vec3(1.0,1.0,0.0);n" + "vec3 transNormal = uNormalMatrix * aVertexNormal;n" + "float greyScaleVal = max(dot(transNormal, ulightDirection), 0.0);n" + "vec3 uAmbientColor = vec3(0.4,0.4,0.4);n" + "vLightWeighting = uAmbientColor + vec3(greyScaleVal,greyScaleVal,greyScaleVal);n" + "gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0);n" + "vTextureCoord = aTextureCoord;n" + "}"; var fragmentShaderSource = "#ifdef GL_ESn" + "precision highp float;n" + "#endifn" + "varying vec3 vLightWeighting;n" + "varying vec2 vTextureCoord;n" + "uniform sampler2D uSampler;n" + "void main(void) { n" + "vec4 textureColor = texture2D(uSampler, vec2(vTextureCoord.s, vTextureCoord.t));" + "gl_FragColor = vec4(textureColor.rgb * vLightWeighting, textureColor.a);" + "}"; var fragmentShader = loadShader(gl.FRAGMENT_SHADER, fragmentShaderSource); var vertexShader = loadShader(gl.VERTEX_SHADER, vertexShaderSource); program = gl.createProgram(); gl.attachShader(program, vertexShader); gl.attachShader(program, fragmentShader); gl.linkProgram(program); if (!gl.getProgramParameter(program, gl.LINK_STATUS)) { alert("Could not initialise program"); } gl.useProgram(program); program.vertexPositionAttribute = gl.getAttribLocation(program, "aVertexPosition"); gl.enableVertexAttribArray(program.vertexPositionAttribute); program.vertexNormalAttribute = gl.getAttribLocation(program, "aVertexNormal"); gl.enableVertexAttribArray(program.vertexNormalAttribute); program.textureCoordAttribute = gl.getAttribLocation(program, "aTextureCoord"); gl.enableVertexAttribArray(program.textureCoordAttribute); program.pMatrixUniform = gl.getUniformLocation(program, "uPMatrix"); program.mvMatrixUniform = gl.getUniformLocation(program, "uMVMatrix"); program.nMatrixUniform = gl.getUniformLocation(program, "uNormalMatrix"); program.samplerUniform = gl.getUniformLocation(program, "uSampler"); program.useLightingUniform = gl.getUniformLocation(program, "uUseLighting"); program.ambientColorUniform = gl.getUniformLocation(program, "uAmbientColor"); program.lightingDirectionUniform = gl.getUniformLocation(program, "uLightingDirection"); program.directionalColorUniform = gl.getUniformLocation(program, "uDirectionalColor"); } function loadShader(shaderType, source) { var shader = gl.createShader(shaderType); if (!shader) return null; gl.shaderSource(shader, source); gl.compileShader(shader); if(!gl.getShaderParameter(shader, gl.COMPILE_STATUS)){ alert(gl.getHeaderInfoLog(shader)); return null; } return shader; } function handleLoadedTexture(texture) { gl.bindTexture(gl.TEXTURE_2D, texture); gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, texture.image); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); gl.bindTexture(gl.TEXTURE_2D, null); } function initBuffers() { VertexBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, VertexBuffer); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(triangles), gl.STATIC_DRAW); VertexBuffer.numItems = faceCount * 3; } function initTexture() { textureMap = gl.createTexture(); textureMap.image = new Image(); textureMap.image.onload = function () { handleLoadedTexture(textureMap); } textureMap.image.src = diffuseMap; } function updateLoop() { requestAnimFrame(updateLoop); drawScene(); } window.requestAnimFrame = (function() { return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function( callback, element) { window.setTimeout(callback, 1000/60); }; })(); function drawScene() { gl.viewport(0, 0, gl.viewportWidth, gl.viewportHeight); gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); mat4.perspective(45, gl.viewportWidth / gl.viewportHeight, 0.1, 100.0, pMatrix); mat4.identity(mvMatrix); mat4.translate(mvMatrix, [-centre[0],-centre[1],-centre[2]]); mat4.translate(mvMatrix, [0.0,0.0,-3.0]); mat4.translate(mvMatrix, mouseTranslation); currentAngle += 0.2; if (currentAngle > 360) currentAngle = 0; mat4.rotate(mvMatrix, degToRad(-90), [1, 0, 0]); mat4.rotate(mvMatrix, degToRad(currentAngle), [0, 0, 1]); setMatrixUniforms(); gl.bindBuffer(gl.ARRAY_BUFFER, VertexBuffer); gl.vertexAttribPointer(program.vertexPositionAttribute, 3, gl.FLOAT, false, 8*4, 0); // position gl.vertexAttribPointer(program.textureCoordAttribute, 2, gl.FLOAT, false, 8*4, 3*4); // texcoord gl.vertexAttribPointer(program.vertexNormalAttribute, 3, gl.FLOAT, false, 8*4, 5*4); // normals gl.activeTexture(gl.TEXTURE0); gl.bindTexture(gl.TEXTURE_2D, textureMap); gl.uniform1i(program.samplerUniform, 0); gl.useProgram(program); gl.drawArrays(gl.TRIANGLES, 0, VertexBuffer.numItems); } function degToRad(degrees) { return degrees * Math.PI / 180; } function setMatrixUniforms() { gl.uniformMatrix4fv(program.pMatrixUniform, false, pMatrix); gl.uniformMatrix4fv(program.mvMatrixUniform, false, mvMatrix); var normalMatrix = mat3.create(); mat4.toInverseMat3(mvMatrix, normalMatrix); mat3.transpose(normalMatrix); gl.uniformMatrix3fv(program.nMatrixUniform, false, normalMatrix); }
View the 3d model
Now just open your HTML-File in your Browser. To view this 3d example online just click here. Use your browser to get the source online.
Thanks to Norman Pohl for some background information about WebGL.
Greetz
Vali