WebGL Game Development Tutorials

Loading .PLY file format
Displaying complex 3D models in WebGL

Before we go any further, note that loading 3D models via a PLY format is not usually recommended. The PLY format loader in this tutorial is shown here only to demonstrate core principles of loading models in WebGL, and the common construction of vertex array buffer.

The source code for PLY file loader (a type of 3D model that you can create and save from Blender; the free 3D modeling software) provided here is shown for reference only. Or, perhaps for a quick way of loading models into your WebGL application for testing.

In all previous WebGL tutorials on this site we either created 3D geometry by hand, specifying each vertex, color and texture UV coordinate and packing them into our VAO/VBO or loaded it from pre-made cube primitive function.

But to render anything impressive in our WebGL engine, we need to be able to load complex geometry from PLY file format. It is similar to OBJ format, but it is even better because OBJ files do not store color vertex information, and PLY file format does.

I'll simply show you the source code for my PLY format model loader, that also contains several comments. But for the most part it is self-explanatory.

Feel free to copy and paste this function into your own project. You'll just probably have to figure out how to load PLY files asynchronously. Other than that, the function will return Float32Array objects once the data has been parsed and vertices have been accurately rewired to geometry's polygon face data.


First, let's prepare some data for crunching vertices, faces, textures, colors and normals stored by PLY file format.

    // PLY object
    function PLY() { this.object; }

    // Path to folder where models are stored
    var ModelFolderPath = "http://localhost/webgl/models/";

    // Number of vertices in PLY file
    var PLY_Vertices = 0;

    // Number of faces in PLY file
    var PLY_Faces = 0;

    // For skipping header
    var ReadingPLYData = false;

    // 11 entries per vertex (x,y,z,nx,ny,nz,r,g,b,u,v)
    var PLY_DataLenght = 11;

    var VAO_VertexIndex = 0;

    var FaceIndex = 0;

    // PLY file vertex entry format
    function PLY_Vertex(x, y, z, nx, ny, nz, u, v, r, g, b) {
        this.x = 0; // a_Position
        this.y = 0;
        this.z = 0;
        this.nx= 0; // a_Normal
        this.ny= 0;
        this.nz= 0;
        this.u = 0; // a_Texture
        this.v = 0;
        this.r = 0; // a_Color
        this.g = 0;
        this.b = 0;

    // PLY file face consisting of 3 vertex indices for each face
    function PLY_Face(a, b, c) {
        this.a = a;
        this.b = b;
        this.c = c;

Now let's define LoadPLY function that will load the PLY model filename from a URL location, parse the data and return it in separate arrays, ready for passing onto WebGL VBO attribute functions just as explained in other examples on the site.

    // Load PLY function;
    function LoadPLY(filename)
        var vertices = null;

        var xmlhttp = new XMLHttpRequest();

        xmlhttp.onreadystatechange = function() {

            if (xmlhttp.readyState == XMLHttpRequest.DONE) {

                if (xmlhttp.status == 200) {

                    var data = xmlhttp.responseText;

                    var lines = data.split("\n");

                    var PLY_index = 0;

                    var arrayVertex, arrayNormal, arrayTexture, arrayColor, arrayIndex;

                    var vertices = null;

                    var faces = null;

                    console.log("PLY number of lines = " + lines.length);

                    for (var i = 0; i < lines.length; i++)
                        if (ReadingPLYData)
                            var e = lines[i].split(" ");

                            // Read vertices
                            if (PLY_index < PLY_Vertices) {

                                vertices[PLY_index] = new PLY_Vertex();
                                vertices[PLY_index].x = e[0];
                                vertices[PLY_index].y = e[1];
                                vertices[PLY_index].z = e[2];
                                vertices[PLY_index].nx = e[3];
                                vertices[PLY_index].ny = e[4];
                                vertices[PLY_index].nz = e[5];
                                vertices[PLY_index].u = e[6];
                                vertices[PLY_index].v = e[7];
                                vertices[PLY_index].r = e[8];
                                vertices[PLY_index].g = e[9];
                                vertices[PLY_index].b = e[10];

                            // Read faces
                            } else {

                                // Reset index for building VAOs
                                if (PLY_index == PLY_Vertices) {

                                    console.log("Resetting Index...");

                                    FaceIndex = 0;

                                    VAO_VertexIndex = 0;

                                // Wire face data to appropriate vertices
                                var n = e[0]; // unused, always 3; assumes triangulated models only
                                var a = e[1]; // face vertex A
                                var b = e[2]; // face vertex B
                                var c = e[3]; // face vertex C

                                if (FaceIndex < PLY_Faces)
                                    // We don't really have to *store* face data
                                    // faces[FaceIndex] = new PLY_Face(a, b, c);

                                    // vertices

                                    // normals

                                    // colors

                                    // uv

                                    // index



                        } else { // Still reading header...

                            // Read number of vertices stored in the file
                            if (lines[i].substr(0, "element vertex".length) == "element vertex")
                                PLY_Vertices = lines[i].split(" ")[2];

                            // Read number of faces stored in the file
                            if (lines[i].substr(0, "element face".length) == "element face")
                                PLY_Faces = lines[i].split(" ")[2];

                        // Finished reading header data, prepare for reading vertex data
                        if (lines[i] == "end_header") {

                            // Allocate enough space for vertices
                            vertices = new Array(PLY_Vertices);

                            // Allocate enough space for faces
                            faces = new Array(PLY_Faces);

                            // Allocate memory for returned arrays (VAOs)
                            arrayVertex  = new Array(); // PLY_Vertices * 3
                            arrayNormal  = new Array(); // PLY_Vertices * 3
                            arrayTexture = new Array(); // PLY_Vertices * 2
                            arrayColor   = new Array(); // PLY_Vertices * 3
                            arrayIndex   = new Array(); // PLY_Vertices * 1

                            ReadingPLYData = true;

                    console.log("PLY_Vertices = " + PLY_Vertices + " loaded");
                    console.log("PLY_Faces = " + PLY_Faces + " loaded");
                    console.log("arrayVertex length = " + arrayVertex.length);
                    console.log("arrayNormal length = " + arrayNormal.length);
                    console.log("arrayTexture length = " + arrayTexture.length);
                    console.log("arrayColor length = " + arrayColor.length);
                    console.log("arrayIndex length = " + arrayIndex.length);

                    // We now have both complete vertex and face data loaded;
                    // return everything we loaded as Float32Array & Uint16Array for index

                    return [
                        new Float32Array(arrayVertex),
                        new Float32Array(arrayNormal),
                        new Float32Array(arrayTexture),
                        new Float32Array(arrayColor),
                        new Uint16Array(arrayIndex)

        console.log("Loading Model <" + filename + ">...");

        xmlhttp.open("GET", ModelFolderPath + filename, true);

When I started writing this function, I thought that it would take a lot quicker to write. In just under 2 hours, I had the basic version ready. But that's also because I've already written one in C++ for my OpenGL engine. Parsing PLY format isn't trivial, but it's not too complicated either.

Congrats, now your WebGL engine is capable of parsing out vertex data from PLY files. Using returned Float32Array and Uint16Array values (for index) you can now pass and bind these to VAO buffers and render the model on the GPU.

Where you take it from here is up to you.

WebGL Book: A WebGL Tutorial Reference Book
WebGL Book - A WebGL Tutorial Reference Book

If tutorials on this site are not enough, or you simply like reading from a physical book or a digital device (Kindle, iPad, tablets, etc.) check out WebGL Book. Written by the author of tutorials on this site.

This book is a WebGL tutorial and a reference that guides the reader through the process of setting up and initializing WebGL, drawing 3D primitives and creating 3D computer games.

Preorder Here
© 2017 Copyright WebGL Tutorials (webgltutorials.org)

All content and graphics on this website are the property of webgltutorials.org - please provide a back link when referencing on other sites.

Lyrics Haven: a song lyrics website, with clean printable lyrics react js tutorials react js elements react js components vue js tutorials angular js tutorials