CSS Shaders & 3D

I’ve been giving several talks lately, talking about the different CSS standards Adobe is involved with, and the contributions we’re making to WebKit.  In order to make things easy, I had created a single web page with links to various demonstrations.  But I wanted something subtle on the link page itself that used CSS shaders in a way that would raise an eyebrow or two, to make people realize something different was going on here.

In a recent post, I was using an image of a tree:

courtesy of freefoto.com.  I thought it might be nice if I could use a CSS Shader to get that tree to wave in the wind.

Here’s a video of the final result for those of you that can’t wait:

Let’s step through how I built that…

WARNING: There is a bit of math involved here, but really it’s not that bad…

First we want to get the tree to wave from left to right.  We want the bottom of the tree to remain motionless for the most part, and want the tree to sway progressively more and more as you get higher on the tree.

A CSS Vertex Shader will allow me to overlay a mesh on part of my HTML (in this case, simply the <IMG> tag), and apply a 3D transformation to every individual vertex of the mesh.  I can pass a maximum angle as a parameter into the shader, and then the shader will calculate some actual angle to transform the point based on the y position of vertex being processed.

I need a power curve like one of the following that I can apply to the angle:

If you think of x in this graph as the current mesh vertex y position, you’ll see that the value of the function starts out small and then ramps up quickly.  If you stretch your imagination a bit, think of it as a waving tree lying on its side. Notice how the higher the exponent we use, the longer it takes for the values to ramp up, allowing us to control how much of the trunk we want to keep still.

We’ll start with least extreme curve.  What we want to do in the shader then is:

angle = max_angle * (current_vertex_y_position)2

Let’s create our simple HTML with an image, apply a custom filter to the image and animate the value of the angle passed in to the shader:

<html>
<head>
<style>
body {
    background-color:#eedddd;
    margin-left: auto;
    margin-right: auto;
    text-align: center;
}

@-webkit-keyframes wave{
    0%, 100% {
        -webkit-filter: custom(url(wave.vs), 20 20, angle -40.0, power 2.0);
    }
    50% {
       -webkit-filter: custom(url(wave.vs), 20 20, angle 40.0, power 2.0);
    }
}

img {
    width: 400px;
    -webkit-animation: wave ease-in-out 6s infinite;
}
</style>
</head>

<body>
   <h1>Fun With CSS Shaders</h1>
   <img src="tree.jpg" />
</body>
</html>

This should animate the value of the angle from -40 to +40 degrees on a 6 second loop.  We’re also passing in the power parameter so we can experiment with different options.  The “20 20″ parameter indicates that we want a 20 x 20 mesh.

Now let’s take a look at the actual shader:

precision mediump float;

attribute vec4 a_position;
attribute vec2 a_texCoord;

uniform mat4 u_projectionMatrix;

// These uniform values are passed in using CSS.
uniform float angle;
uniform float power;

varying vec2 v_texCoord;

const float PI = 3.1416;
const float degToRad = PI / 180.0;

mat4 zRot(float rads) {
    mat4 rotMat = mat4(
       cos(rads), -sin(rads), 0.0, 0.0,
       sin(rads), cos(rads), 0.0, 0.0,
       0.0, 0.0, 0.0, 0.0,
       0.0, 0.0, 0.0, 1.0);
    return rotMat;
}

void main()
{
    v_texCoord = a_texCoord;
    vec4 pos = a_position;

    mat4 txf = zRot(pow((pos.y), power) * angle * degToRad);

    gl_Position = u_projectionMatrix * txf * pos;
}

power & angle are passed in as css parameters by declaring them as uniforms.  The zRot() function returns a 4×4 transformation matrix that will rotate a point around the z-axis based on an angle, specified in radians.

Let’s see what this looks like:

Huh?  That’s interesting, but not right.  Of course, the x and y values are in a range of -0.5 to 0.5, not 0.0 to 1.0.  We need to add 0.5 to the y position before we raise it to the power, as in:

    mat4 txf = zRot(pow((pos.y+0.5), power) * angle * degToRad);

Still not quite. Our transform is upside down. We need to negate the y value before we do anything:

    mat4 txf = zRot(pow((-1.0*pos.y+0.5), power) * angle * degToRad);

That’s looking much better.

The edges are looking pretty wonky though.  We want to crop the image so we don’t see the edges being warped.  Let’s wrap the img with a div with some margins, and apply some cropping:

<style>
...
.crop { width: 280px; height: 400px; overflow: hidden; 
        margin-left: auto; margin-right: auto;}
.crop img { width: 400px; height: 400px; margin: -85px 0 0 -60px; }
</style>
<h1>Fun With CSS Shaders</h1>
<div class="crop">
    <img src="tree.jpg" />
</div>

and we now have it:

You can read more about shaders here.  If you want to try out the examples in this article, you’ll need to use this WebKit prototype.

Tagged , , , , , , . Bookmark the permalink.

One Response to CSS Shaders & 3D

  1. Pingback: Fun with 3D and CSS Shaders - Alan Greenblatt | F2E | Scoop.it

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>