|
Using Shaders: 1 The Basics
In recent years, graphics cards have become programmable. The processing on the
graphics card to prepare for display on the screen is referred to as the rendering
pipeline. This pipeline can be changed at two key points in the process. The programs
inserted at these points are referred to as a vertex shader and a fragment shader.
This tutorial will give an overview of how shaders can be used within X3D. The code
inside the main() program will be treated as a black box. For a more detailed discussion
on shaders, the Orange OpenGL Shading Language book is good place to start. The SwirlX3D
viewer and other programs support only the OpenGL GLSL shader language.

The following is an example of a vertex program. It's purpose is to set the value of gl_Position
and to calculate the variables that will be used later by the fragment program. The three
variables NdotL, ReflectVec, ViewVec all have declarations starting with the keyword "varying".
This marks them as variables that will be used later by the fragment shader
uniform vec3 LightPosition; // 0 10 4
varying float NdotL;
varying vec3 ReflectVec;
varying vec3 ViewVec;
void main()
{
vec3 ecPos = vec3(gl_ModelViewMatrix * gl_Vertex);
vec3 tnorm = normalize(gl_NormalMatrix * gl_Normal);
vec3 lightVec = normalize(LightPosition - ecPos);
ReflectVec = normalize(reflect(-lightVec, tnorm));
ViewVec = normalize(-ecPos);
NdotL = (dot(lightVec, tnorm) + 1.0) * 0.5;
gl_Position = ftransform();
}
In the fragment shader below, the three varying variables from the vertex shader are
declared again and used within the program.
uniform vec3 SurfaceColor; // 0.75 0.75 0.75
uniform vec3 WarmColor; // 0.6 0.6 0.0
uniform vec3 CoolColor; // 0.0 0.0 0.6
uniform float DiffuseWarm; // 0.45
uniform float DiffuseCool; // 0.45
varying float NdotL;
varying vec3 ReflectVec;
varying vec3 ViewVec;
void main()
{
vec3 kcool = min(CoolColor + DiffuseCool * SurfaceColor, 1.0);
vec3 kwarm = min(WarmColor + DiffuseWarm * SurfaceColor, 1.0);
vec3 kfinal = mix(kcool, kwarm, NdotL);
vec3 nreflect = normalize(ReflectVec);
vec3 nview = normalize(ViewVec);
float spec = max(dot(nreflect, nview), 0.0);
spec = pow(spec, 32.0);
gl_FragColor = vec4(min(kfinal + spec, 1.0), 1.0);
}
In the vertex and fragment programs there are variables declared using the "uniform" keyword.
These variables are set within the program using the shaders. In this case that means that these
variables are set within the X3D file.
The following is part of an X3D file that uses these two shader programs. Notice that the
uniform variables are declared as fields of their respective shader programs. In this case
the file extension used for the shader programs is ".txt", but this is not important and can
be changed. The basic structure is that a ProgramShader node is placed within the shaders field
of the Appearance, and the ProgramShader node contains two ShaderProgram nodes of type "VERTEX"
and "FRAGMENT". "VERTEX" is the default value of the type field so it does need to appear in
the X3D file.
appearance Appearance {
shaders
ProgramShader {
language "GLSL"
programs [
ShaderProgram {
field SFVec3f LightPosition 0 10 4
url "vertshader.txt"
}
ShaderProgram {
type "FRAGMENT"
url "fragshader.txt"
field SFVec3f SurfaceColor 0.75 0.75 0.75
field SFVec3f WarmColor 0.6 0.6 0.0
field SFVec3f CoolColor 0.0 0.0 0.6
field SFFloat DiffuseWarm 0.45
field SFFloat DiffuseCool 0.45
}
]
}
}
The ComposedShader X3D node can also be used to specify the shader programs. The ComposedShader will have two
ShaderPart nodes in its parts field. In this case all the fields corresponding to uniform variables are moved
so that they are all fields of the ComposedShader nodes. Apart from this minor difference, the use of ProgramShader
or ComposedShader nodes is equivalent. There is no advantage of one over the other.
appearance Appearance {
shaders ComposedShader {
language "GLSL"
field SFVec3f LightPosition 0 10 4
field SFVec3f SurfaceColor 0.75 0.75 0.75
field SFVec3f WarmColor 0.6 0.6 0.0
field SFVec3f CoolColor 0.0 0.0 0.6
field SFFloat DiffuseWarm 0.45
field SFFloat DiffuseCool 0.45
parts [
ShaderPart {
url "vertshader.txt"
}
ShaderPart {
type "FRAGMENT"
url "fragshader.txt"
}
]
}
}
The previous excerpts are in classic Vrml format. The following shows how the same content looks in Xml style format.
The choice of format is usually a matter of personal preference, although the Xml style allows for use within Xml
aware programs.
<Appearance>
<ProgramShader language="GLSL">
<ShaderProgram url="vertshader.txt">
<field name="LightPosition" type="SFVec3f"
accesstype="initializeOnly" value="0 10 4" />
<ShaderProgram url="fragshader.txt" type="FRAGMENT">
<field name="SurfaceColor" type="SFVec3f"
value="0.75 0.75 0.75" accesstype="initializeOnly"/>
<field name="WarmColor type="SFVec3f"
value="0.6 0 0" accesstype="initializeOnly"/>
<field name="CoolColor type="SFVec3f"
value="0 0.6 0.6" accesstype="initializeOnly"/>
<field name="DiffuseWarm" type="SFFloat"
value="0.45" accesstype="initializeOnly"/>
<field name="DiffuseCool" type="SFFloat"
value="0.45" accesstype="initializeOnly"/>
</ShaderProgram>
</ProgramShader>>
</Appearance>
Here is a list of common X3D variable types and their corresponding types within the shader program.
X3D Type
| GLSL Type
| SFBool
| int
|
SFInt32
| int
| SFFloat
| float
|
SFDouble
| float
| SFVec2f
| vec2
|
SFVec2d
| vec2
| SFVec3f
| vec3
|
SFVec3d
| vec3
| SFVec4f
| vec4
|
SFVec4d
| vec4
| SFMat3f
| mat3
|
SFMat3d
| mat3
| SFMat4f
| mat4
|
SFMat4d
| mat4
| SFString
| sampler1D
|
SFString
| sampler2D
| SFString
| sampler3D
|
SFString
| samplerCube
| SFString
| sampler1DShadow
|
SFString
| sampler2DShadow
|
.
The sampler variable types are for various types of textures and will be discussed
in the next tutorial. It may be a considerable challenge to learn about shaders, but
they open up a great many possibilities.
Download tutorial files (same as previous tutorial)
|