Cg Programming/Vector and Matrix Operations
The syntax of Cg is very similar to C (and therefore C++ and Java); however, there are built-in data types and functions for floating-point vectors and matrices, which are specific to Cg. These are discussed here. A full description of Cg can be found in Nvidia's Cg Tutorial and in Nvidia's Cg Language Specification.
Floating-Point Data Types with Full Precision
[edit | edit source]In Cg, the types float2
, float3
, and float4
represent 2D, 3D, and 4D floating-point vectors. Vector variables are defined as you would expect if C, C++ or Java had these types:
float2 a2DVector;
float3 three_dimensional_vector;
float4 vector4;
The data types for floating-point 2×2, 3×3, and 4×4 matrices are: float2x2
, float3x3
, and float4x4
:
float2x2 m2x2;
float3x3 linear_mapping;
float4x4 trafo;
There are also types for matrices with different numbers of rows and columns, e.g. 3 rows and 4 columns: float3x4
.
Data Types with Limited Precision
[edit | edit source]Apart from float
data types, there are additional half
types (half
, half2
, half3
, half4
, half2x2
, half3x3
, half4x4
, etc.), and fixed
types (fixed
, fixed2
, fixed3
, fixed4
, etc.), which represent numbers (i.e. scalars), vectors, and matrices of limited precision and range. Usually they provide better performance; thus, for best performance you should use one of the half
types instead of the corresponding float
types (in particular if the data represents geometric positions or texture coordinates) or even one of the fixed
types (usually if the data represents colors) if the limited precision and range does not result in rendering artifacts.
Here, the code will always use the float
types to avoid any problems related to limited precision and to keep it as simple as possible. There are also types for integer and boolean vectors, which will not be discussed here.
Constructors
[edit | edit source]Vectors can be initialized and converted by constructors of the same name as the data type:
float2 a = float2(1.0, 2.0);
float3 b = float3(-1.0, 0.0, 0.0);
float4 c = float4(0.0, 0.0, 0.0, 1.0);
Note that some Cg compilers might complain if integers are used to initialize floating-point vectors; thus, it is good practice to always include the decimal point.
Casting a lower-dimensional vector to a higher-dimensional vector is achieved by supplying these constructors with the correct number of components:
float2 a = float2(0.1, 0.2);
float3 b = float3(0.0, a); // = float3(0.0, 0.1, 0.2)
float4 c = float4(b, 1.0); // = float4(0.0, 0.1, 0.2, 1.0)
Similarly, matrices can be initialized and constructed. Note that the values specified in a matrix constructor are consumed to fill the first row, then the second row, etc.:
float3x3 m = float3x3(
1.1, 1.2, 1.3, // first row (not column as in GLSL!)
2.1, 2.2, 2.3, // second row
3.1, 3.2, 3.3 // third row
);
float3 row0 = float3(0.0, 1.0, 0.0);
float3 row1 = float3(1.0, 0.0, 0.0);
float3 row2 = float3(0.0, 0.0, 1.0);
float3x3 n = float3x3(row0, row1, row2); // sets rows of matrix n
Cg (but not all versions of HLSL) also accepts one floating-point number in the float4
constructor to set all components to the same value:
float4 a = float4(0.0); // = float4(0.0, 0.0, 0.0, 0.0)
Furthermore, Cg (but not all versions of HLSL) can cast a higher-dimensional vector to a lower-dimensional vector with the vector constructors:
float4 a = float4(-1.0, 2.5, 4.0, 1.0);
float3 b = float3(a); // = float3(-1.0, 2.5, 4.0)
float2 c = float2(b); // = float2(-1.0, 2.5)
If a smaller matrix is constructed from a larger matrix, the top, left submatrix of the larger matrix is chosen:
float3x3 m = float3x3(
1.1, 1.2, 1.3,
2.1, 2.2, 2.3,
3.1, 3.2, 3.3
);
float2x2 n = float2x2(m); // = float2x2(1.1, 1.2, 2.1, 2.2)
Components
[edit | edit source]Components of vectors are accessed by array indexing with the []
-operator (indexing starts with 0) or with the .
-operator and the element names x, y, z, w
or r, g, b, a
(or s, t, p, q
):
float4 v = float4(1.1, 2.2, 3.3, 4.4);
float a = v[3]; // = 4.4
float b = v.w; // = 4.4
float c = v.a; // = 4.4
It is also possible to construct new vectors by extending the .
-notation:
float4 v = float4(1.1, 2.2, 3.3, 4.4);
float3 a = v.xyz; // = float3(1.1, 2.2, 3.3)
float3 b = v.bgr; // = float3(3.3, 2.2, 1.1)
Matrices are considered to consist of row vectors, which are accessed by array indexing with the []
-operator. Elements of the resulting (row) vector can be accessed as discussed above:
float3x3 m = float3x3(
1.1, 1.2, 1.3, // first row
2.1, 2.2, 2.3, // second row
3.1, 3.2, 3.3 // third row
);
float3 row2 = m[2]; // = float3(3.1, 3.2, 3.3)
float m20 = m[2][0]; // = 3.1
float m21 = m[2].y; // = 3.2
Operators
[edit | edit source]If the binary operators *, /, +, -, =, *=, /=, +=, -=
are used between vectors of the same type, they just work component-wise:
float3 a = float3(1.0, 2.0, 3.0);
float3 b = float3(0.1, 0.2, 0.3);
float3 c = a + b; // = float3(1.1, 2.2, 3.3)
float3 d = a * b; // = float3(0.1, 0.4, 0.9)
Note in particular that a * b
represents a component-wise product of two vectors, which is not often seen in linear algebra. For matrices, these operators also work component-wise. Again, a component-wise product of two matrices is not often seen in linear algebra.
For the usual matrix-vector product or matrix-matrix product, see the built-in mul
function below. For the dot product and cross product between vectors, see the built-in functions dot
and cross
below.
The *
-operator can also be used to multiply a floating-point value (i.e. a scalar) to all components of a vector or matrix (from left or right):
float3 a = float3(1.0, 2.0, 3.0);
float2x2 m = float2x2(1.0, 0.0, 0.0, 1.0);
float s = 10.0;
float3 b = s * a; // float3(10.0, 20.0, 30.0)
float3 c = a * s; // float3(10.0, 20.0, 30.0)
float2x2 m2 = s * m; // = float2x2(10.0, 0.0, 0.0, 10.0)
float2x2 m3 = m * s; // = float2x2(10.0, 0.0, 0.0, 10.0)
Built-In Vector and Matrix Functions
[edit | edit source]Component-Wise Functions
[edit | edit source]The following functions work component-wise for variables of type float
, float2
, float3
, float4
, half
, half2
, half3
, half4
, fixed
, fixed2
, fixed3
, and fixed4
, which are denoted as TYPE
:
TYPE min(TYPE a, TYPE b) // returns a if a < b, b otherwise
TYPE max(TYPE a, TYPE b) // returns a if a > b, b otherwise
TYPE clamp(TYPE a, TYPE minVal, TYPE maxVal)
// = min(max(a, minVal), maxVal)
TYPE lerp(TYPE a, TYPE b, TYPE wb) // = a * (TYPE(1.0, 1.0, ...) - wb) + b * wb
TYPE lerp(TYPE a, TYPE b, float wb) // = a * TYPE(1.0 - wb, 1.0 - wb, ...) + b * TYPE(wb, wb, ...)
There are more built-in functions, which also work component-wise but are less useful for vectors, e.g., abs
, sign
, floor
, ceil
, round
, frac
, fmod
, step
, smoothstep
, sqrt
, pow
, exp
, exp2
, log
, log2
, radians
(converts degrees to radians), degrees
(converts radians to degrees), sin
, cos
, tan
, asin
, acos
, atan
, atan2
.
Matrix Functions
[edit | edit source]For the usual matrix-matrix product in linear algebra, the mul
function should be used. It computes components of the resulting matrix by multiplying a row (of the first matrix) with a column (of the second matrix), e.g.:
And in Cg:
float2x2 a = float2x2(1., 2., 3., 4.);
float2x2 b = float2x2(10., 20., 30., 40.);
float2x2 c = mul(a, b); // = float2x2(
// 1. * 10. + 2. * 30., 1. * 20. + 2. * 40.,
// 3. * 10. + 4. * 30., 3. * 20. + 4. * 40.)
Furthermore, the mul
function can be used for matrix-vector products of the corresponding dimension, e.g.:
And in Cg:
float2 v = float2(10., 20.);
float2x2 m = float2x2(1., 2., 3., 4.);
float2 w = mul(m, v); // = float2x2(1. * 10. + 2. * 20., 3. * 10. + 4. * 20.)
Note that the vector has to be the second argument to be multiplied to the matrix from the right.
If a vector is specified as first argument, it is multiplied to a matrix from the left:
float2 v = float2(10., 20.);
float2x2 m = float2x2(1., 2., 3., 4.);
float2 w = mul(v, m); // = float(10. * 1. + 20. * 3., 10. * 2. + 20. * 4.)
Multiplying a row vector from the left to a matrix corresponds to multiplying a column vector to the transposed matrix from the right:
In components:
Thus, multiplying a vector from the left to a matrix corresponds to multiplying it from the right to the transposed matrix without explicitly computing the transposed matrix.
There is also a function transpose
to compute the transposed matrix:
float2x2 m = float2x2(1., 2., 3., 4.);
float2x2 n = transpose(m); // = float2x2(1., 3., 2., 4)
Geometric Functions
[edit | edit source]The following functions are particularly useful for vector operations. TYPE
is any of: float, float2, float3
, float4
, half
, half2
, half3
, half4
, fixed
, fixed2
, fixed3
, and fixed4
(only one of them per line).
float3 cross(float3 a, float3 b)
// = float3(a[1] * b[2] - a[2] * b[1],
// a[2] * b[0] - a[0] * b[2],
// a[0] * b[1] - a[1] * b[0])
float dot(TYPE a, TYPE b) // = a[0] * b[0] + a[1] * b[1] + ...
float length(TYPE a) // = sqrt(dot(a, a))
float distance(TYPE a, TYPE b) // = length(a - b)
TYPE normalize(TYPE a) // = a / length(a)
TYPE faceforward(TYPE n, TYPE i, TYPE nRef)
// returns n if dot(nRef, i) < 0, -n otherwise
TYPE reflect(TYPE i, TYPE n) // = i - 2. * dot(n, i) * n
// this computes the reflection of vector 'i'
// at a plane of normalized(!) normal vector 'n'
Functions for Physics
[edit | edit source]The function
TYPE refract(TYPE i, TYPE n, float r)
computes the direction of a refracted ray if i
specifies the normalized(!) direction of the incoming ray and n
specifies the normalized(!) normal vector of the interface of two optical media (e.g. air and water). The vector n
should point to the side from where i
is coming, i.e. the dot product of n
and i
should be negative. The floating-point number r
is the ratio of the refractive index of the medium from where the ray comes to the refractive index of the medium on the other side of the surface. Thus, if a ray comes from air (refractive index about 1.0) and hits the surface of water (refractive index 1.33), then the ratio r
is 1.0 / 1.33 = 0.75. The computation of the function is:
float d = 1.0 - r * r * (1.0 - dot(n, i) * dot(n, i));
if (d < 0.0) return TYPE(0.0, 0.0, ...); // total internal reflection
return r * i - (r * dot(n, i) + sqrt(d)) * n;
As the code shows, the function returns a vector of length 0 in the case of total internal reflection (see the entry in Wikipedia), i.e. if the ray does not pass the interface between the two materials.
Further reading
[edit | edit source]A full description of Cg can be found in Nvidia's Cg Tutorial (including all standard library functions in Appendix E) and in Nvidia's Cg Language Specification.