Jump to content


Bubble Effect


2 replies to this topic

#1 Dia

    DevMaster Staff

  • Administrators
  • 1121 posts

Posted 20 September 2005 - 02:10 PM

Posted Image


Description
This demo shows a procedurally generated pulsating soap bubble through dynamic deformation of a sphere that I have developed recently. The algorithm has been taken from ATI's SDK, which provided the DirectX assembly shaders. I have written the HLSL/Cg shader code (below) myself after attempting to fully understand the theory behind the effect.

The vertex shader of the bubble effect uses sine waves to perturb the vertex position in the direction of the normal vector. The bubble uses two textures: a base texture and an environment texture. The base texture is a rainbow film map that simulates the rainbow effect on the bubble, which is then modulated with the environment map in the pixel shader. The glow map, which provides the white highlights on the bubble is stored in the alpha of the cube map, and is linearly interpolated into the result. The fresnel term and glow map are added to provide the final alpha value to blend with the frame buffer. The full explanation of the theory along with the full assembly shader code can be found at ATI' developer's page, which is an article that appeared in the ShaderX I book.

I'm providing the high-level code below, which should work in both HLSL and Cg.

Vertex shader:
[code=cpp]

struct VS_OUTPUT
{
float4 pos: POSITION;
float2 texCoord: TEXCOORD0;
float4 reflection: TEXCOORD1;
float4 NdotV: TEXCOORD2;
};

VS_OUTPUT main(float4 position: POSITION, float4 normal: NORMAL, float2 texCoord: TEXCOORD,
float4 tangent: TANGENT,
uniform float4x4 viewProjMatrix,
uniform float4x4 worldMatrix,
uniform float4 cameraPos,
uniform float time)
{
VS_OUTPUT Out;

const float TWO_PI = 6.2831853072;

const float4 wave_directions_in_X = {0, 2, 0, 4}; // relative to u
const float4 wave_directions_in_Y = {2, 0, 4, 0}; // relative to v

const float4 waveSpeed = {0.6, 0.7, 1.2, 1.4};
const float4 waveHeights = {0.5, 0.5, 0.25, 0.25};

// use texture coordinates as inputs to sinusoidal warp
float4 wave_vec = frac( wave_directions_in_X*texCoord.x +
wave_directions_in_Y*texCoord.y + waveSpeed*time );

// shift the texture coordinates to be in (pi, -pi) range
wave_vec = (wave_vec - 0.5) * TWO_PI;

float4 wave_vec_sin = sin(wave_vec);
float4 wave_vec_cos = (2 - cos(wave_vec))*0.04; // multiply by 0.04 as fix up factor

// dot with waveHeights and then apply deformation in the direction of the normal
wave_vec = dot(wave_vec_sin,waveHeights) * normal + position;
wave_vec.w = 1; // homogeneous component

// transform wave vector
Out.pos = mul(viewProjMatrix, wave_vec);

// compute the binomial
float4 binomial = float4(cross(tangent.xyz, normal.xyz), 1);

// warp normal based on tangent and binomial vectors
float4 warpedNormal = tangent * dot(-(wave_vec_cos * waveHeights), wave_directions_in_Y) +
binomial * dot(-(wave_vec_cos * waveHeights), wave_directions_in_X);
warpedNormal = warpedNormal + normal;

// transform and normalize the normal
warpedNormal = normalize(mul(worldMatrix, warpedNormal));

// compute a normalized view vector
float4 viewVector = normalize(cameraPos - mul(worldMatrix, wave_vec));

// compute the reflection vector: R = 2*N(N.V) - V
Out.reflection = 2*dot(warpedNormal.xyz, viewVector.xyz)*warpedNormal - viewVector;

// pass to the pixel shader N.V
Out.NdotV = dot(warpedNormal.xyz, viewVector.xyz);

// pass along texture coordinates
Out.texCoord = texCoord;

return Out;
}
[/code]

Pixel shader:
[code=cpp]
sampler baseMap: register(s0);
sampler cubeMap: register(s1);

float4 main(float2 texCoord: TEXCOORD0, float4 reflection: TEXCOORD1, float4 NdotV: TEXCOORD2 ) : COLOR
{
float4 modulatedCubeMap, result;

float4 base = tex2D(baseMap, texCoord); // sample base texture
float4 cube = texCUBE(cubeMap, reflection); // sample cubemap

modulatedCubeMap.rgb = saturate(2 * base * cube); // modulate cube map with base map
modulatedCubeMap.a = (1-abs(NdotV))*0.6 - 0.01; // compute fresnel term by scaling alpha,
// multipling by N.V, and adding some bias
// compute opacity from glow map
float opacity = saturate(4*(cube.a*cube.a - 0.75));

// linearly interpolate between (cubemap) and (base*cubemap) based on glow map
result.rgb = lerp(modulatedCubeMap, cube, opacity);
result.a = modulatedCubeMap.a + opacity; // add fresnel term and glow map for alpha

return result;
}
[/code]

#2 anubis

    Senior Member

  • Members
  • PipPipPipPip
  • 2225 posts

Posted 20 September 2005 - 02:34 PM

Very nice... that's how every image submission should be. Very well explained and in detail.
If Prolog is the answer, what is the question ?

#3 Mihail121

    Senior Member

  • Members
  • PipPipPipPip
  • 1059 posts

Posted 20 September 2005 - 03:18 PM

Awesome...





1 user(s) are reading this topic

0 members, 1 guests, 0 anonymous users