-
Notifications
You must be signed in to change notification settings - Fork 11
Motivation
Not that long ago, the only way to write shader code was by getting down to the metal and program in assembly. The advent of high level shading languages such as Cg, HLSL and GLSL significantly lowered the barrier of entry for shader programming and opened up a world of opportunity for the MUL, ADD and MAD averse. While this was certainly a giant leap, these languages still very much resemble C, which by now is becoming ancient. Modern game engines all in some way make use of higher level languages to increase productivity, even if it means sacrificing some performance. Unity3D for instance supports Javascript and C#. The CryEngine 3 makes use of Lua and also has a .NET scripting plugin called CryMono.
So what could shader code look like if written in a modern language like F#?
type Shader(scene:SceneConstants,
obj:ObjectConstants,
mat:MaterialConstants) =
[<VertexShader>]
member m.vertex(input:VSInput) =
PSInput(input.Position * obj.WorldViewProjection,
input.Normal * float3x3(obj.World))
[<PixelShader>]
member m.pixel(input:PSInput) =
input.Normal
|> dot -scene.LightDirection
|> mul mat.Diffuse
|> saturate
|> withAlpha 1.0f
The equivalent in HLSL would be:
PSInput vertex(VSInput input)
{
PSInput o;
o.PositionHS = mul(input.Position,WorldViewProjection);
o.Normal = mul(input.Normal,(float3x3)(World));
return o;
};
float4 pixel(PSInput input) : SV_TARGET
{
float3 color = saturate(mul(Diffuse,dot(-(LightDirection),normalize(input.Normal))));
return float4(color,1);
};
The pipeline operator allows us to write the computation in the logical order of operation. If I did I code review, I would explain the pixel shader code as follows: You take the input normal, you apply the dot product with the negative light direction, multiply the result with the diffuse color, saturate it to keep the values within 0-1 and finally we set the alpha to 1. In HLSL we have to write the logic almost completely in reverse: I want to saturate the value I get when multiplying the diffuse color and the dot product of the negative light direction and the input normal.
Another convenient feature of F# is type inference. F# is not a dynamic language. In fact, it is more strict about types than C is. However, the compiler is smart enough to infer types based on how the variables are used.
let stripes x f =
let PI = 3.14159265f
let t = 0.5f + 0.5f * sin(f * 2.0f*PI * x)
t * t - 0.5f
We want to write our code like a mathematical equation. Semi-colons, types and curly braces are not essential to our understanding of the algorithm. In fact, I would argue that they get in the way.