Blog

Archive
| All posts |
  • Conversion to GLSL status update

    published on 5/1/2015 8:12 PM

    The scenes in Plane9 are meant to be flexible so I can try and experiment with new things easily. Being a programmer and creating a program I want to use made me avoid going down the route that most full game engines have gone. That is using some form of node/dag based editor to create new shaders, if they even allow custom shaders. Instead in Plane9 you write the shaders directly into the scene files. Very powerful, especially since the editor refreshes the scene in realtime as you type. However with this there is the issue with the shading language used. When the project was started you could choose between GLSL (OpenGL), HLSL (DirectX) and CgFX (Both OpenGL and DirectX). Seeing as I wanted to support both apis the choice was quite easy since only one shader language did that.

    This worked quite well for some time but as the years passed by Nvidia stopped supporting CgFX and have since killed it off completely. Looking towards the future this means I have to change to something else sooner rather than later since each scene needs to be manually updated to the new format. The usage of CgFX also stopped some optimization like background compilation of shaders and usage of various performance tools. Since I still want to be multiplatform compatible so was the decision quite easy to go with GLSL because I also want something that most are familiar with and can use for other tasks like WebGL development. It’s also good to know that the future vulkan api/compiler will fully support it.

    However GLSL is quite difficult to work with since each and every graphics card manufacturer creates their own compiler for it, all of course with subtle differences. CgFx protected me from quite a lot of this because it converted the CgFX shader code into GLSL but now this needs to be handled manually.

    The major issues I have run into so far are that all types needs to match. So before I quite commonly did “pi.tex * 2.0 -1.0”. CgFX handled this fine but not all supports this in GLSL, the specification states it’s allowed, the reality is apparently different. So better to always match the types correctly and change the above code to “pi.tex * vec2(2.0) - vec2(1.0)”. This same type matching also goes with float to int since I have lazily used things like “float value = time * 3”. This is once again illegal and should be “time * 3.0” since time is a float value.

    Fixing these issues is difficult to do automatically so I need to go over all shaders in about 1000 scenes and do it manually. This will take some time but I’ll update them with some new features while I’m at it so something else good will come out of this work. I will also remove some scenes I don't feel add anything or where I don't feel the quality is good enough any more.

    As an example when the shader before looked like this

    struct PSInput
    {
        float4 hpos : POSITION;
        float4 diffuse : COLOR0;
        float2 tex : TEXCOORD0;
        float3 wnormal : TEXCOORD1;
        float3 viewdir : TEXCOORD2;
        float3 worldpos : TEXCOORD3;
        float3 pos : TEXCOORD4;
        float3 viewpos : TEXCOORD5;
    };
    
    PSInput VS_Program(VSInput vi)
    {
        PSInput pi;
        float4 v    = float4(vi.pos.xyz, 1.0);
        pi.pos        = v.xyz;
        pi.worldpos = mul(gW, v).xyz;
        pi.viewpos   = mul(gWV, v).xyz;
        pi.hpos       = mul(gWVP, v);
        pi.tex          = vi.tex;
        pi.diffuse    = vi.col*color;
        pi.wnormal    = normalize(mul(gWIT, vi.normal).xyz);
        pi.viewdir     = normalize(float3(gVI[0).w, gVI[1).w, gVI[2).w) - pi.worldpos.xyz);    
        
        // Hemisphere light
        float a = dot(pi.wnormal, float3(0, 1, 0))*0.5+0.5;
        pi.diffuse *= lerp(vec4(0.957,0.655,0.055,1.0), vec4(0.165,0.675,0.988,1.0), a);
        return pi;
    }
    
    float4 PS_Program(PSInput pi) : COLOR
    {
        float2 p = pi.tex*2.0 - 1.0;
        // return tex2D(tex1, pi.tex)*pi.diffuse;
        vec3 v = (_fbm(vec3(pi.tex.xy*10, time*0.1),3)*0.5+0.5)*pi.diffuse;
        v = pow(v, 1.0/2.2);
        return vec4(v, 1.0);
    }
    

    and after the conversion it will look like the following.

    VERTEXOUTPUT
    {
        vec4 diffuse;
        vec2 tex;
        vec3 wnormal;
        vec3 viewdir;
        vec3 worldpos;
        vec3 pos;
        vec3 viewpos;
    }
    
    #ifdef VERTEX
    void main()
    {
        so.pos        = iPosition.xyz;
        so.worldpos= (gM * iPosition).xyz;
        so.viewpos    = (gMV * iPosition).xyz;
        gl_Position    = gMVP * iPosition;
        so.tex        = iTexCoord;
        so.diffuse    = iColor*color;
        so.wnormal    = normalize((mat3(gM) * iNormal);
        so.viewdir = normalize(gViewPosition - so.worldpos);    
        
        // Hemisphere light
        float a = dot(so.wnormal, vec3(0.0, 1.0, 0.0))*0.5+0.5;
        so.diffuse *= mix(vec4(0.957, 0.655, 0.055, 1.0), vec4(0.165, 0.675, 0.988, 1.0), a);
    }
    #endif
    
    #ifdef FRAGMENT
    void main()
    {
        vec2 p = si.tex*vec2(2.0) - vec2(1.0);
        vec3 v = vec3(_fbm(vec3(si.tex.xy*vec2(10.0), time*0.1),3)*0.5+0.5)*si.diffuse.rgb;
        //vec3 v = texture(tex1, si.tex).rgb*si.diffuse.rgb;
        oColor = vec4(_tosrgb(v), 1.0);
    }
    #endif
    

    I prefer the new GLSL style since I find it clearer in what it does. Even though some magic types are used like “so” and “si”. The parser will make sure those always contain the “shader output” and “shader input” as needed for each shader stage. This will hide most of the complexity with working with GLSL shaders and bring some standard for the shaders.

    While I have been doing this I also managed to finalize my PBR shader so it will be easy to work with in any scenes.

    PBR Test