Working with Shaders (GLSL)Timothy KimMy project involved using different shaders to produce different effects in a bunny mesh.I used the assignment 8 infrastructure as a base for my project.1. The Lava BunnyThis effect involves animating lava on the bunny mesh.I learned about animating textures in this tutorial by Jacobo Rodriguez Villar from Typhoon Labs:http://www.opengl.org/sdk/docs/tutorials/TyphoonLabs/Chapter_3.pdfFirst we apply a seamless lava texture to the bunny.For the lava texture, we use a seamless 512x512 image called "lava2.ppm" in the project folder.Credit for the image goes to Patrick Hoesly. His website is zooboing.com.We enable the repeating of texture coordinates in the texture.For the shaders, we use basic-gl3.vshader and bunny-gl3.fshader as bases.Now to apply the texture to the bunny, we need some way of defining the texture coordinates a givenpixel of the bunny will use. We could started off by defining an in-attribute variable called aTexCoord.However, since the texture is seamless and the repeating of texture coordinates is enabled, we dontneed to care about the texture coordinates as long as they are consistent. So we can use the vertexpositions as the texture coordinates.Looking at basic-gl3.vshader, if we use vPosition as the texture coordinate in the fragment shader, wewill run into trouble because vPosition is multiplied by the ModelView matrix and the Projectionmatrix. This results in the texture not being able to "stick on" to the bunny, and when we transform thebunny, the texture acts like a fixed projection. To fix this problem, we use the in-variable aPosition,which is vPosition without the matrices multiplied to it. We pass in the value of aPosition to thefragment shader using a new out-variable called vPosition2.To use the texture in the fragment shader, we need to define a new sampler2D uniform variable called"uTexture." We can get the appropriate texel from the texture by writing:vec4 texColor = texture(uTexture, vPosition2.xy * .25);The multiplication of .25 helps enlarge the texture on the bunny.Now to animate the lava on the bunny, we need to define a new uniform variable called "uTime" thatwill hold a value that increases with time. We use a timer callback function called "lavaAnimate()"(located in "final_project.h") that increases the value of uTime at a set interval. lavaAnimate can beturned on by pressing the l (lowercase "L") key.We now add uTime as an offset to the texture coordinates:vec4 texColor = texture(uTexture, vec2(vPosition2.x - uTime, vPosition2.y - uTime) * .25);This has the effect of moving the lava texture diagonally up on the bunny.
Here are some pictures of the lava bunny:The shaders for the lava bunny are called "lava-texture.vshader" and "lava-texture.fshader."2. The Toon BunnyThis effect draws the bunny in a cartoony style.Note that we will do not need to use a new vertex shader. It will remain as basic-gl3.vshader.We use bunny-gl3.fshader as the base for the new fragment shader.We can draw the bunny in a cartoony style by limiting the colors of the bunny to only 3 shades of itsoriginal color. We can measure the intensity with which the lights in the environment hit a point on thebunny. If the intensity is strong, we use the lightest of the 3 shades. If the intensity is moderate, we usethe 2nd lightest of the 3 shades. If the intensity is weak, we use the darkest of the 3 shades.Credits to lighthouse3d.com for this first toon shading method.We measure the intensity the lights hit a point by using the dot product of the normalized normal of thepoint and the normalized vector from the point to a light. This dot products computes the cosine of theangle between the normal of the point and the ray of light hitting the point. A cosine function is usedbecause it decreases as the angle increases from 0 to 90 degrees. A light ray parallel to the normalshould have a stronger effect than a light ray off from the normal. Luckily, this intensity is alreadycomputed for us in the "diffuse" variable in bunny-gl3.fshader.Here are the ways we separated the intensities:if (diffuse > .91) diffuse = 1.0; else if (diffuse > .6) diffuse = .6; else diffuse = .4;As you can see, only 3 shades of the bunny will be drawn.This shader is called "toon1.fshader" in the project.Now, we can stop here and we would have a decent toon shader, but one thing is missing: outlines.We could compute the outlines by making a 4th shade in the toon shader, where when the lightintensity of a point is really low, we just color it black.However, we run into a problem if we use a light intensity as the measure of when to color a pointblack. Since the light intensity of a point is measured with respect to the ray of light hitting the point, if
we change our view of the bunny, the light ray will not change. This means the outline on the bunnywill not move with respect to the viewer. So if we change our view to see an area of the bunny that isnot hit strongly by the light, we will see areas of black on the bunny and not an appropriate outline.What we really need to do is create the outline with respect to the viewer (or the camera).We can compute the "intensity" with which the camera views a point on the bunny using the dotproduct of the normal with the vector from the point to the camera (everything should be normalized ofcourse). If this intensity is low, we color the point black.Since in our shaders we are working in eyespace, the position of the camera will always be (0,0,0).This means that the vector from a point (with eyespace coordinate vector vPosition) to the camera willbe -vPosition.Heres what this looks like in code:vec3 toCamera = normalize(-vPosition);float cameraHit = max(0.0, dot(normal, toCamera));if (cameraHit < .4) intensity = vec3(0, 0, 0);"intensity" here does not refer to the camera view intensity. It is a variable from bunny-gl3.fshader thatrepresents the final color output of a point. "cameraHit" represents the camera view intensity.*This method for computing an outline was original and not taken from another source.For our other 3 shades, we would still want them to be colored with respect to the light intensity.Thus we only need to add a few lines of code to toon1.fshader.The new toon shader is called "toon2.fshader" in the project folder.Here are some pictures of the toon bunny:The 1st picture shows the effect from our 1st toon shader. The 2nd picture shows us the problem wecan get if we use light intensity to compute the outlines. The 3rd and 4th picture are examples using thecorrected, 2nd toon shader.3. The Eroding BunnyThis effect makes the bunny gradually crumble and disappear.Credits to 3Dlabs (3dshaders.com) for the idea for this effect. I looked at the source code for theshaders they used to make the erode effect. I only really used one of their coding ideas, but theimplementation of the effect into my project was original. Also, the texture used was original.
The vertex shader for this effect is "lava-texture.vshader."The fragment shader for this effect used "lava-texture.fshader" as its base.To make the bunny gradually disappear, we can use a texture. A texture is not only useful for applyingan image onto another object. It can be used as data as well.We create a texture that only consists of black, white, and shades of gray in between. We can use thesetexture values to represent the time at which a certain point on the bunny will disappear. To make thedisappearing smooth, we need to make sure that there are appropriate transitions of gray between blackand white points in the texture. So basically all we need to do is draw an image that has black dots on awhite background, and use a smudge tool to create the transitions between the black and white.The texture we use is called "erosion_map.ppm" in the project folder.With that texture in place, we can have a uniform variable "uTime" in the fragment shader thatincreases with time through a timer callback function (this callback function is called"erosionAnimate()" in "final_project.h").After getting the color of a point on the texture in the fragment shader, we measure the darkness of thatpoint and compare it to a value. If the measured darkness is less than the value, we discard theassociated fragment (lighter values are higher than darker values). This comparison value shouldincrease with time to let lighter fragments get discarded.Heres what this looks like in code:vec4 texColor = texture(uTexture, vPosition2.xy);if (texColor.r < 0 + uTime) discard;As you can see, the uTime variable increases the comparison value.We can turn on the eroding animation by pressing the e key.We also disable backface culling so that the eroding parts of the back of the bunny can be seen.Here are some pictures of the eroding bunny:You can see the bunny gradually disappearing. The last picture is a picture of the texture we used.Resources:1. The GLSL tutorials from lighthouse3d.com, Clockworkcoders(http://www.opengl.org/sdk/docs/tutorials/ClockworkCoders/ ), and Typhoon Labs(http://www.opengl.org/sdk/docs/tutorials/TyphoonLabs/Chapter_3.pdf)2. The shaders demo and source code from 3dshaders.com.