Thursday, January 21, 2010

A long delayed WebGLU update, some 360 degree video, and that's just for starters

Yes, I'm still here and actively developing my WebGLU library. For those of you who don't know what WebGLU is, it's a library for making WebGL application development easy and fun with minimal coding.  Since my last post WebGLU has gone through a major refactor adding several new features and cleaning up old ones. Plus, I've got a neat demo to show off. Both a live version and a video are included after the jump.

Oh, and by the way, there's a Google Wave embedded down by the comments for those who would like to use that.

Here's a some of the new features
  • Keyframe animation
  • Image textures
  • Video textures (only Firefox 3.7a has support for them at the moment)
  • Can load shaders from .vp, .vert, .fp, and .frag files
  • Partial .obj model file loading (only vertices for now)
Also, WebGLU now has a companion library, GameGLU, which handles interactivity and provides a nice neat place to tuck program state information. GameGLU is also available on WebGLU's SourceForge page. Speaking of SourceForge, I am taking the advice of several people and will be moving to GitHub over the weekend. Twelve days ago I saw a post on reddit.com entitled  Still think Google Street View is cool? Can your HTML5 do this? I can now say, with some sense of achievement, YES. The following demo uses the video provided on Quentin LengelĂ©'s blog, if there is any reason I should not be using this video, please contact me and I will find a different one.


360 degree interactive video has been around in some form or another for over a decade, but always through a plugin of some kind, from RealPlayer to Flash and many others. Here, it's just the browser, some HTML, and some JavaScript.


Here's the demo, you'll need Firefox 3.7 installed, updated, and have webgl.enable_for_all_sites set to true in the about:config page.
Click and drag to pan the camera


Here's a bigger version and a wireframe version, which shows the 1600 triangles used to form the sphere. You can change the width and height parameters in the url to see it at other sizes.

Here's a video of the same demo


Using Firebug's profiling capabilities I was able to determine that 90% of the running time is dedicated to just four lines of code. Namely, the four that get called every time the video's timer changes.
bindTexture(TEXTURE_2D, this.texture.texture);
texImage2D(TEXTURE_2D, 0, this.texture.video, false);
texParameteri(TEXTURE_2D, TEXTURE_MAG_FILTER, NEAREST);
texParameteri(TEXTURE_2D, TEXTURE_MIN_FILTER, NEAREST);


Thanks to WebGLU and GameGLU, this was pretty easy to put together. Now for how I actually did it.

Two simple shaders drive the rendering.
texture.vert
attribute vec3 vertex;
attribute vec3 color;
attribute vec2 texCoord;

uniform mat4 ProjectionMatrix;
uniform mat4 ModelViewMatrix;

varying vec2 vTexCoord;

void main(void) {
    vTexCoord = texCoord;

    gl_Position = ProjectionMatrix * ModelViewMatrix * vec4(vertex, 1.0);
}

texture.frag
uniform sampler2D sampler;
varying vec2 vTexCoord;

void main(void) {
    gl_FragColor = texture2D(sampler, vTexCoord);
}



Creating the shader program is just three lines.

$W.newProgram('textured');
$W.programs['textured'].attachShader('texVS', 'texture.vert');
$W.programs['textured'].attachShader('texFS', 'texture.frag');




Creating the sphere and applying the video texture to it are not all that much more.
// Create a new renderable object
var sphere = new $W.Object($W.GL.TRIANGLES);

// Have it use the shader program we created above
sphere.setShaderProgram('textured');

// Create a texture from the video tag in the page
new $W.texture.Video('video', 'videoElement');

// Apply the texture to the sphere
sphere.textures.push('video');

// Generate a unit sphere with 40 horizontal rings and 40
// vertical slices
var sphereModel = $W.util.genSphere(40,40);

// fill the data arrays, which correspond to the attributes
// of the same name in the shader.
sphere.fillArray("vertex", sphereModel.vertices);
sphere.fillArray("normal", sphereModel.normals);
sphere.fillArray("texCoord", sphereModel.texCoords);

// Use drawElements when rendering
sphere.setElements(sphereModel.indices);



The new GameGLU companion library makes it simple to respond to key and mouse events without actually dealing with the event listeners directly by providing $G.event.bind('keyname', action) where action is a function to perform when the event occurs. Appending + or - to keyname binds to the keydown or keyup respectively, keydown is assumed if you omit them.


var st = $G.state;
// Initialize the state variables we'll use
st.mouseDown    = false;
st.wasMouseDown = false;
st.lastM  = $V([0,0]);
st.posM   = $V([0,0]);
st.deltaM = $V([0,0]);
st.totalM = $V([Math.PI * 100,0]);

// mouseup event
$G.event.bind('-m1', function(x,y) {
    st.mouseDown = false;
});

// mousedown event
$G.event.bind('+m1', function(x,y) {
    st.mouseDown = true;
    st.lastM.elements = [x, y];
    st.posM.elements = [x,y];
});

// mousemove event
$G.event.bind('mousemove', function(x,y) {
    st.deltaM.elements = [0,0];

    // When the mouse is moved while clicking
    if (st.mouseDown) {
        // avoid jumping around when we click then release,
        // move the mouse, then click again.
        if (st.wasMouseDown) {
            st.lastM.elements = st.posM.elements;
        }

        st.posM.elements = [x, y];
        st.deltaM = st.posM.subtract(st.lastM);
        st.totalM = st.totalM.add(st.deltaM);
    }

    st.wasMouseDown = st.mouseDown;
});

// Rotate the camera based on the mouse movement
$W.camera.update = function() {
    var x = -1 * st.totalM.e(1) / 100;
    var y = st.totalM.e(2) / 10;

    this.target.elements = [Math.cos(x), y / 10, Math.sin(x)];
}

2 comments:

  1. Hey Benjamin -- this looks really promising. For some reason, though, I just get a static image rather than a video -- the panning works, but the viewpoint isn't moving down the street. I'm using Minefield, ATI Mobility Radeon 4670, Windows 7. Any thoughts?

    ReplyDelete
  2. The video will appear static if it buffers after it begins, just give it a moment.

    ReplyDelete