Bubble Shader Bubble Shader John Isidoro ATI Research David Gosselin ATI Research Introduction One of the most visually interesting objects in the real world are soap bubbles. The reflective and partially transparent nature of them as well as the way they pulsate makes them a key candidate for vertex and pixel shaders. Strangely enough, on a vertex and pixel shader level, they are implemented in a very similar manner to the ocean water shader described in “Rendering Ocean Water.” There are a variety of different effects that make up the bubble shader. Setup As input this shader uses a model with a position, a normal, the tangent vector (in v direction of texture coordinates), and one set of texture coordinates for a rainbow like base map (shown in the following figure), Figure 1 – Base “rainbow” map This shader, like the “Crystal/Candy Shader” uses a cubic environment map shown below. Excerpted from ShaderX: Vertex and Pixel Shader Tips and Tricks 1 Bubble Shader Figure 2 – Cubic environment map color Figure 3 – Cubic environment map alpha Also, like the candy shader, we want this object to be semi-transparent which requires drawing the model in two passes, back faces first then front faces with alpha blending enabled. Vertex Shader The vertex shader uses four sine waves traveling in different directions over the surface of the bubble to perturb the vertex. The vertex is perturbed in the direction of the normal. In order to perturb the normal, cosine waves traveling in the same four directions are used. In order to provide a consistent looking warping across vertices, the tangent and bi-normal are used to guide the direction of the sinusoidal warp. The first portion of the vertex shader looks almost identical to the ocean shader and is shown below. // // // // // // // // v0 v3 v7 v8 c0 c1 c2 c3 - - Vertex Position Vertex Normal Vertex Texture Data u,v Vertex Tangent (v direction of tex coords) { 0.0, 0.5, 1.0, 2.0} { 4.0, .5pi, pi, 2pi} {1, -1/3!, 1/5!, -1/7!} for sin {1/2!, -1/4!, 1/6!, -1/8!} for cos Excerpted from ShaderX: Vertex and Pixel Shader Tips and Tricks 2 Bubble Shader // c4-7 // c8 // c9 // c10 // c11 // c12 // c13 // c14 // c15 // c16 // c17 vs.1.1 mul r0, mad r0, mov mad add frc frc mov - Composite World-View-Projection Matrix Model Space Camera Position Model Space Light Position {1.02, 0.04, 0, 0} fixup factor for Taylor series imprecision {0.5, 0.5, 0.25, 0.25} waveHeight0, waveHeight1, waveHeight2, waveHeight3 {0.0, 0.0, 0.0, 0.0} waveOffset0, waveOffset1, waveOffset2, waveOffset3 {0.6, 0.7, 1.2, 1.4} waveSpeed0, waveSpeed1, waveSpeed2, waveSpeed3 {0.0, 2.0, 0.0, 4.0} waveDirX0, waveDirX1, waveDirX2, waveDirX3 {2.0, 0.0, 4.0, 0.0} waveDirY0, waveDirY1, waveDirY2, waveDirY3 { time, sin(time)} {-0.00015, 1.0, 0.0, 0.0} base texcoord distortion x0, y0, x1, y1 c14, v7.x c15, v7.y, r0 r1, c16.x r0, r1, c13, r0 r0, r0, c12 r0.xy, r0 r1.xy, r0.zwzw r0.zw, r1.xyxy // use tex coords as inputs to sinusoidal warp // use tex coords as inputs to sinusoidal warp // time... // add scaled time to move bumps according to frequency // take frac of all 4 components mul r0, r0, c10.x sub r0, r0, c0.y mul r0, r0, c1.w // multiply by fixup factor (due to inaccuracy of taylor series) // subtract .5 // mult tex coords by 2pi coords range from(-pi to pi) mul mul mul mul mul mul mul // // // // // // // r5, r1, r6, r2, r7, r3, r8, r0, r5, r1, r6, r2, r7, r3, r0 r0 r0 r0 r0 r0 r0 (wave (wave (wave (wave (wave (wave (wave vec)^2 vec)^3 vec)^4 vec)^5 vec)^6 vec)^7 vec)^8 mad r4, r1, c2.y, r0 mad r4, r2, c2.z, r4 mad r4, r3, c2.w, r4 //(wave vec) - ((wave vec)^3)/3! // + ((wave vec)^5)/5! // - ((wave vec)^7)/7! mov mad mad mad mad //1 //-(wave //+(wave //-(wave //+(wave r0, r5, r5, r5, r5, c0.z r5, c3.x ,r0 r6, c3.y, r5 r7, c3.z, r5 r8, c3.w, r5 vec)^2/2! vec)^4/4! vec)^6/6! vec)^8/8! dp4 r0, r4, c11 //multiply wave heights by waves mul r0, r0, v3 //apply deformation in direction of normal add r10.xyz, r0, v0 mov r10.w, c0.z //add to position //homogenous component m4x4 mov // OutPos = WorldSpacePos * Composite View-Projection Matrix // Pass along texture coordinates oPos, r10, c4 oT0, v7 This is where the shader starts to diverge a bit from the Ocean shader. First the binormal is computed mov mul mad r3, v3 r4, v8.yzxw, r3.zxyw r4, v8.zxyw, -r3.yzxw, r4 //cross product to find binormal Then the normal is warped based on the tangent space basis vectors (tangent and binormal). mul dp4 dp4 mul r1, r5, c11 r9.x, -r1, c14 r9.y, -r1, c15 r1, r4, r9.x //cos * waveheight //amount of normal warping in direction of binormal //amount of normal warping in direction of tangent //normal warping in direction of binormal Excerpted from ShaderX: Vertex and Pixel Shader Tips and Tricks 3 Bubble Shader mad mad r1, v8, r9.y, r1 r5, r1, c10.y, v3 //normal warping in direction of tangent //warped normal move nx, ny: cos * wavedir * waveheight The normal is then renormalized. dp3 rsq mul r10.x, r5, r5 r10.y, r10.x r5, r5, r10.y //normalize warped normal Next the view vector is computed: sub dp3 rsq mul r2, c8, r0 r10.x, r2, r2 r10.y, r10.x r2, r2, r10.y //view vector //normalized view vector Then the dot product of the view vector and the warped normal is computed: dp3 mov r7, r5, r2 oT2, r7 //N.V //Pass along N.V This is used to compute the reflection vector. add mad mov r6, r7, r7 r6, r6, r5, -r2 oT1, r6 //2N.V //2N(N.V)-V //reflection vector And finally the distance to the camera is computed. rcp mad r10.y, r10.y //distance from camera oD0, r10.y, c17.x, c17.y //scale and bias distance The vertex shader then passes along the texture coordinates of the base map, the reflection vector, the value of N·V, and the distance to the camera along to the pixel shader for further processing. Pixel Shader The pixel shader uses the brightly colored rainbow film map as a base texture. This texture map is meant to simulate the soap film appearance of a bubble. The environment map is modulated with this texture. The white highlights are stored in the alpha of the environment map as a glow map, and are linearly interpolated into the result. The final alpha value used from blending with the frame buffer is the (1-abs(N·V)) + glow map. The full shader is shown below and further descriptions follow. // c0 – (0.0, 0.5, 1.0, -0.75) // c1 - (0.6, 0.1, 0.0, 0.0) Alpha Scale and bias ps.1.4 texld r0, t0 texld r1, t1 texcrd r2.rgb, t2 cmp r2.r, r2.r, r2.r, -r2.r // abs(V.N) +mad_x4_sat r1.a, r1.a, r1.a, c0.a Excerpted from ShaderX: Vertex and Pixel Shader Tips and Tricks 4 Bubble Shader mul_x2_sat r2.rgb, r0, r1 +mad r2.a, 1-r2.r, c1.x, c1.y // base * env (may change scale factor later) // alphascale * abs(V.N) + alphabias lrp r0.rgb, r1.a, r1, r2 +add r0.a, r2.a, r1.a // Lerp between Env and Base*Env based on glow map // Add glow map to Fresnel term for alpha The first thing that happens in this pixel shader is the sampling of the environment map. This happens in the following line of the pixel shader: texld r1, t1 It produces the following image: Figure 4 – Cubic environment map only Figure 5 – Base map This is multiplied by the base texture color (shown in figure 5) using the following code. mul_x2_sat r2.rgb, r0, r1 // base * env (may change scale factor later) With the results shown below. Excerpted from ShaderX: Vertex and Pixel Shader Tips and Tricks 5 Bubble Shader Figure 6 – Cubic environment map modulated with base map Figure 7 – Alpha for environment map Now that the basic colors are computed the opacity needs to be computed. The first step is to create strong specular looking term. The alpha of the environment map (show in figure 7) is squared, multiplied by four and then given a bit of a bias. The following pixel shader co-issued instruction accomplishes this. +mad_x4_sat r1.a, r1.a, r1.a, c0.a The resulting image is shown below. Figure 8 – Alpha of the environment map squared and multiplied by four. Excerpted from ShaderX: Vertex and Pixel Shader Tips and Tricks 6 Bubble Shader Next the Fresnel term is computed. This takes place in few stages the first is to take the absolute value of V·N and the second is to invert this value by computing 1abs(V·N). This value is then scaled and biased. The following code shows how this is computed. cmp r2.r, r2.r, r2.r, -r2.r // abs(V.N) +mad r2.a, 1-r2.r, c1.x, c1.y // alphascale * abs(V.N) + alphabias The following picture shows the result of this computation. Figure 9 – Scaled and biased Fresnel term. The next step is to add the Fresnel term with the glow map. Using the following code. +add r0.a, r2.a, r1.a // Add glowmap to Fresnel term for alpha This produces the final alpha result used for blending and is shown below. Figure 10 – Final alpha values Next the glow map is used to linearly interpolate between just the environment map and the base multiplied by the environment map based on the alpha value in the environment map. lrp r0.rgb, r1.a, r1, r2 // Lerp between Env and Base*Env based on glow map Excerpted from ShaderX: Vertex and Pixel Shader Tips and Tricks 7 Bubble Shader This produces the final color values shown below. Figure 11 – Final color values The next figure shows the blend produced by the alpha and color to produce the final image. Figure 12 – Final Image The next shot shows what this shader produces within a scene produced by the ATI Sushi engine. Excerpted from ShaderX: Vertex and Pixel Shader Tips and Tricks 8 Bubble Shader Figure 13 – Bubble in an environment Summary This section has shown a technique to produce an interesting bubble effect built on top of some techniques described in other sections. It used sine waves computed within a vertex shader to produce and undulating shape and cubic environment mapping to give interesting reflections. Excerpted from ShaderX: Vertex and Pixel Shader Tips and Tricks 9