2.2 High-level Shading Languages
2.2.2 C for Graphics from NVIDIA
June 2002, a C-like programming language Cg (C for Graphics) was developed and first released for the programmable graphical processing units from NVIDIA. During the 2002 SIGGRAPH conference, NVIDIA presented its Cg Toolkit version 1.0.1 with great success.
The Cg language is based on both the syntax and the philosophy of C [Kernighan and Ritchie 1988]. In particular, Cg is hardware oriented language and intended to be general-purpose (as much as is possible on graphics hardware). As in C, most data types and operators have an obvious mapping to hardware operations, so that it is easy to write high-performance code. Cg includes a variety of new features designed to efficiently support the unique architectural characteristics of programmable GPUs. Cg also adopts a few features from C++ [Stroustrup 2000] and Java [Joy et al. 2000], with an intention to be a language for small programs. Cg is most commonly used for implementing shading
algorithms, but Cg is not an application-specific shading language in the sense that the RenderMan shading language [Hanrahan and Lawson 1990] or the Stanford real-time shading language (RTSL) [Proudfoot et al. 2001] are. For example, Cg omits high-level shading-specific facilities such as built-in support for separate surface and light shaders. It also omits specialized data types for colors and points, but supports general-purpose user-defined compound data types such as structs and arrays.
Each shader would be called upon several times during the rendering process, i.e., generally speaking a vertex shader will be called once for each of the vertices on the scene and the fragment shader (or pixel shader) will be called once for every pixel in the screen. In the same way that a shader for RenderMan has to be called by a structure of a scene in a RIB file, a Cg shader must be called by a structure of a scene in a main program that draws to the screen. What we can see is that all the input information is the properties of vertices, such as position and normal of each vertex and light source. Additionally, the fixed matrixes also are provided as input for transformation from object coordinate to world coordinate etc. Those matrixes come from the main program that will load the shader. The output of the vertex shader will be the color and location of each vertex in object coordinate or world space coordinate etc. Those values will be the input of the fragment process on the pipeline.
Figure 2 - 3 GPU – CPU Interface in modern Graphics Pipeline
Figure 2.3, which is borrowed from the Cg Tutorial book [Fernando, R., and Kilgard 2003], shows the GPU – CPU Interface in modern Graphics Pipeline. A 3D application program which contains a vertex shader and a fragment shader compiles and executes on both the CPU and GPU. The main program compiles and executes on the CPU and communicates with the graphics API (either OpenGL or Direct3D). The shaders are translated into assembly code. The main program calls the Cg run-time library, provided by NVIDIA and sit between the application and underlying graphics API, to execute the assembly code on the GPU. The calculation results from GPU are sent to the frame buffer.
OpenGL/DirectX API Application Cg source code OpenGL Driver Assembly Program Assembly source code
Assembler Executable code Graphics Hardware Cg Translator Provide by developer Provide by NVIDIA
Provide by graphics hardware vender Figure 2 - 4 Cg Shaders loading process
Let’s have a closer look at how a Cg shader is loaded into the executable environment. Figure 2.4 [Rost 2004] illustrates how does a Cg shader is loaded and handled by the execution environment. Compared to GLSL, Cg is designed as a source code to source code translator. The Cg compiler is really a translator that is outside of the OpenGL or Direct3D API compared to a GLSL driver that is in the OpenGL driver. The Cg program really compiles to assembly code vertex shader and fragment shaders in OpenGL or Direct3D. Advantage of Cg is that the translation can be done offline. But, the assembly code has to be parsed and assembled at execution time.
For a better understand of the Cg shading language, let’s look at a simple example of a vertex shader for brick shown as example 2.2.
Example 2.2: Vertex Shader of Brick in Cg language
Look at the parameter area in the main function, there is few parameters with the modifiers “in”, “out”. In the end of some parameter, there is a “:” with some capital letters which are called semantics in Cg. What are these decorations for? The modifier “in”, “out” indicates this parameter is a connector of main application with the vertex shader or from the vertex shader out to the fragment processor. The semantics tells which graphics register this parameter will flows into. The semanticed variable is a connector between shader programs to graphics API.
// Brick-vert.cg vertex shader of brick in Cg const float3 LightPosition = float3(0.0, 0.0, 4.0); const float specularContribution = 0.3;
const float diffuseContribution = 0.7; void main(
out float LightIntensity, out float2 MCposition, in float4 gl_Normal : NORMAL, float4 gl_Vertex : POSITION, out float4 gl_Position : POSITION, //out float4 Color0 : COLOR0,
uniform float4x4 ModelView,
uniform float4x4 gl_ModelViewProjectionMatrix, uniform float4x4 ModelViewIT
){
float4 ecPosition = mul(ModelView , gl_Vertex);
float3 tnorm = normalize(mul(ModelViewIT , gl_Normal).xyz); float3 lightVec = normalize(LightPosition - ecPosition.xyz); float3 reflectVec = reflect(-lightVec, tnorm);
float3 viewVec = normalize((float3)(-ecPosition)); float spec = max(dot(reflectVec, viewVec), 0.0); spec = pow(spec, 16.0);
LightIntensity = diffuseContribution * max(dot(lightVec, tnorm), 0.0) + specularContribution * spec;
MCposition = (float2)(gl_Vertex);
gl_Position = mul(gl_ModelViewProjectionMatrix , gl_Vertex); }