GeeXLab
Current version: 0.17.x
»GeeXLab homepage

FurMark
Current version: 1.19.1
»FurMark homepage
»FurMark 1.9.x Submissions

GPU Caps Viewer
Current version: 1.37.0.0
»GPU Caps Viewer homepage
»GPU DB Submissions

GPU Shark
Current version: 0.10.0.4
»GPU Shark homepage


Blogs
»JeGX's HackLab

Geeks3D's Articles
»GPU Memory Speed Demystified

»Multi-Threading Programming Resources

»GeForce and Radeon OpenCL Overview

»How to Get your Multi-core CPU Busy at 100%

»How To Make a VGA Dummy Plug

»Night Vision Post Processing Filter


PhysX FluidMark
Current version: 1.5.2
»FluidMark homepage
»FluidMark 1.4.x Submissions

TessMark
Current version: 0.3.0
»TessMark homepage

ShaderToyMark
Current version: 0.3.0
»ShaderToyMark homepage
»ShaderToyMark Scores

Demoniak3D
Current Version: 1.23.0
»Demoniak3D
»Download
»Libraries and Plugins
»Demos
»Online Help - Reference Guide
»Codes Samples

3D Graphics Search Engine:

The Geeks Of 3D

 
Mesh Deformers with the GLSL

By Jerome Guinot aka 'JeGX' - jegx_AT_ozone3d(dot)net

Initial draft: June 1, 2006


[ Index ]

Introduction | Page 2 | Page 3 | Conclusion

»Next Page



2 - Surface Deformer

Let's start with the chapter 42 of GPU Gems #1. This chapter, quite mathematical, gives an overview of the polygonal surface deformation theory. But as always with real time shaders, the problem lies in the calculation of the per vertex normal vector. Indeed, the change of the vertex position is a simple operation while its normal calculation maybe quite a complex or even an impossible task in some cases. The author (Eugene d'Eon) shows us a solution based on Jacobian matrices. In a word, if the first-order partial derivatives of the surface deformer function are continuous, we can then compute for each vertex a Jacobian 3x3 matrix that will help us calculating the new normal vector.

If the surface deformer is given by f(x, y, z)=(fx, fy, fz) where fx, fy and fz are three functions of the x, y, and z variables, the per vertex Jacobian matrix will look like:

∂fx/∂x∂fx/∂y∂fx/∂z
∂fy/∂x∂fy/∂y∂fy/∂z
∂fz/∂x∂fz/∂y∂fz/∂z

where ∂fx/∂x is the derivative of the fx function regarding x.

This is the global idea. For more explanations, please refer to the GPU Gems #1. With this Jacobian matrix, the calculation of the normal is given by the following relation:

n' = normalize( (J(v)*t) cross (J(v)*b))

where J(v) is the Jacobian matrix at the vertex v, t is the vertex tangent vector, b the binormal vertex vector (the same used in bump mapping) and cross the cross product operator.

The goal of the following demo is to deform a mesh plane (or a plane polygonal surface) as shown in the next image:

The function we are going to use to deform this mesh is:

f(x, y, z) = (x, y+sin(f*sqrt(x2+z2) + p), z)

where f is the wave frequency and p its phase. For this function, the Jacobian matrix at each vertex is:

100
jac_coef * v.x1jac_coef * v.z
001

where v.x and v.z are the vertex x and z coordinates. jac_coef is equal to:

jac_coef = cos(f*dist + p) / (dist+0.00001);
with
dist = sqrt(v.x2+v.z2)

Software such as Maple can help you to find the literal expressions of the derivatives.

Okay, now the pseudo-theory is assumed, let's go the the shader GLSL implementation. Only the vertex shader code will be shown, the pixel shader one showing less interest in our case. You can however refer to the Phong Lighting tutorial for more details about the pixel shader.

uniform float time;
uniform float frequency;

varying vec3 vNormal;
varying vec4 vVertex;

vec3 computeNormal( vec3 pos, 
                    vec3 tangent, 
                    vec3 binormal, 
                    float phase, 
                    float freq )
{
	mat3 J;
	
	float dist = sqrt(pos.x*pos.x + pos.z*pos.z);
	float jac_coef = cos(freq*dist + phase) / (dist+0.00001);
	
	// A matrix is an array of column vectors so J[2] is 
	// the third column of J.
	
	J[0][0] = 1.0;
	J[0][1] = jac_coef * pos.x;
	J[0][2] = 0.0;
	
	J[1][0] = 0.0;
	J[1][1] = 1.0;
	J[1][2] = 0.0;

	J[2][0] = 0.0;
	J[2][1] = jac_coef * pos.z;
	J[2][2] = 1.0;
	
	vec3 u = J * tangent;
	vec3 v = J * binormal;
	
	vec3 n = cross(v, u);
	return normalize(n);
}

vec4 displaceVertexFunc( vec4 pos, float phase, float frequency ) 
{
	vec4 new_pos;
	
	new_pos.x = pos.x;
	new_pos.z = pos.z;
	new_pos.w = pos.w;
	
	float dist = sqrt(pos.x*pos.x + pos.z*pos.z);
	new_pos.y = pos.y + 20.0 * sin( frequency * dist + phase );
	
	return new_pos;
}

void main(void)
{
	vec4 displacedVertex;
	vec3 displacedNormal;
	
	// 1 - Compute the diplaced position.
	//	
	displacedPosition = displaceVertexFunc(gl_Vertex, time*2.0, frequency );
   	
	gl_Position = gl_ModelViewProjectionMatrix * displacedPosition;	   	
	vVertex = gl_ModelViewMatrix * displacedPosition;
	
	
	// 2 - Compute the displaced normal
	//
	
	// if the engine does not provide the tangent vector you 
	// can compute it with the following piece of of code:
	//
	vec3 tangent; 
	vec3 binormal; 
	
	vec3 c1 = cross(gl_Normal, vec3(0.0, 0.0, 1.0)); 
	vec3 c2 = cross(gl_Normal, vec3(0.0, 1.0, 0.0)); 
	
	if(length(c1)>length(c2))
	{
		tangent = c1;	
	}
	else
	{
		tangent = c2;	
	}
	
	tangent = normalize(tangent);
	
	binormal = cross(gl_Normal, tangent); 
	binormal = normalize(binormal);

	displacedNormal = computeNormal( displacedPosition.xyz, 
	                                 tangent.xyz, 
	                                 binormal, 
	                                 time*2.0, 
	                                 frequency );
   	
   	vNormal = normalize(gl_NormalMatrix * displacedNormal);
}

Tangent Space Computing:

This vertex shader is a direct implemtation of the previous equations. The little trick, because there is always a tick, concerns the tangent vector calculation. Usually, we use the texture coordinates variations to determine accuratly the tangent vector. But in our situation, the following routine provides quite good results:

vec3 tangent; 
	vec3 binormal; 
	
	vec3 c1 = cross(gl_Normal, vec3(0.0, 0.0, 1.0)); 
	vec3 c2 = cross(gl_Normal, vec3(0.0, 1.0, 0.0)); 
	
	if(length(c1)>length(c2))
	{
		tangent = c1;	
	}
	else
	{
		tangent = c2;	
	}
	
	tangent = normalize(tangent);
	
	binormal = cross(gl_Normal, tangent); 
	binormal = normalize(binormal);
}

The mesh density has to be taken into account to get a good visual quality. The previous image showed a 245,000 triangles mesh plane while in the next image, the mesh plane has only 5,000 triangles:

The main interest to perform the mesh deformation through the GPU capabilities is that we can apply real time deformations on very dense meshes with an acceptable FPS. On With my 7800GT, the results are the following:

  • 5,000 faces: 580 fps
  • 245,000 faces: 170 fps
  • 500,000 faces: 90 fps
  • 720,000 faces: 64 fps
  • 1,280,000 faces: 37 fps

And with a Radeon X700:

  • 5,000 faces: 142 fps
  • 245,000 faces: 57 fps
  • 500,000 faces: 37 fps
  • 720,000 faces: 28 fps
  • 1,280,000 faces: 17 fps

More than one million faces deformed in real time, only a GPU process can achieve such a job!

At the Demoniak3D demo level, the number of mesh-plane's faces can be set by modifying the following piece of code in the Surface_Deformer.xml file:

<mesh name="mesh_plane" shape_type="PLANE" render="TRUE" 
	lighting="TRUE" use_vbo="TRUE" >
	<position x="0.0" y="25.0" z="0.0" />
	<plane x_size="400.0" z_size="400.0" num_segs_x="350" num_segs_z="350" />
	<attach_material name="mesh_mat" />
</mesh>

The num_segs_x and num_segs_z attributes allow specifying the number of mesh subdivisions along the X and Z axis.

At the RaptorGL/Win32 demo level, go in the Surface_Deformer project. In the cDemo.cpp file, find the following line:

m_pMeshPlane->buildPlane( 400.0, 400.0, 800, 800 ); // 1 280 000 faces

The two last parameters of the buildPlane() method specify the number of mesh subdivisions along the X and Z axis.

More generally, the number of triangles is given by the following relation:

num_triangles = x_subdiv * z_subdiv * 2;

If x_subdiv=350 and z_subdiv=350 then the mesh plane will have 245,000 faces.





[ Index ]

Introduction | Page 2 | Page 3 | Conclusion

»Next Page





GeeXLab demos


GLSL - Mesh exploder


PhysX 3 cloth demo


Normal visualizer with GS


Compute Shaders test on Radeon


Raymarching in GLSL



Geeks3D latest news


Geeks3D forum news


Misc
»Texture DataPack #1
»Asus Silent Knight CPU Cooler
Page generated in 0.0069100856781006 seconds.