I’ve recently been thinking about accessibility and ways of adding various aids to my games to make them more enjoyable / playable to gamers with physical disabilities.

So, first up, I’ve written a shader to simulate various forms of colour blindness and a little sample application that allows you to switch between 3 different forms of colour deficient vision: Protanopia (absence of red retinal photoreceptors), Deutanopia (absence of green) and Tritanopia (absence of blue).

Normal color vision is trichromatic. It is initiated by the absorption of photons in three classes of cones, the peak sensitivities of which lie in the long-wavelength (L), middle-wavelength (M), and short-wavelength (S) regions of the spectrum. Therefore any color stimulus can be specified by three numbers, the cone responses; and all colors visible to the color-normal observer are included in a three-dimensional color space. Reduced forms of color vision arise from the effective absence of one of the retinal photopigments of the L type in protanopes, the M type in deuteranopes, and the S type in tritanopes.  For dichromatic observers any color stimulus initiates only two cone responses, and all colors that they can discriminate are included in a two-dimensional color space. Compared with trichromatic vision, dichromatic vision entails a loss of discrimination and results in a reduced color gamut.

If anybody is interested, the code can be found here:
http://www.pumpkin-games.net/downloads/src/XNAColourBlindnessSimulator.zip

Caveats:

The ‘magic’ numbers used in the shader were initially designed to work with older CRT monitors.  I’m not sure how this will affect the filters when viewed on newer LCDs. I’m continuing research around this area and will post findings as and when.

Typically you would use as a post process filter to simulate how your game would look to dichromats.

Sources:
http://vision.psychol.cam.ac.uk/jdmollon/papers/Dichromatsimulation.pdf
http://gimp.sourcearchive.com/lines/2.6.6-2/display-filter-color-blind_8c-source.html
http://www.iamcal.com/toys/colors/

Sometimes it is not possible, desirable or practical to alter artwork offline to cater for all gamers.  So the next task is to create a post process component that can (in real time) dynamically alter the colour range of the final output so that it is more friendly to colour deficient players. This component could be turned on or off depending on a players needs (e.g off for a ‘normal vision’ gamer and on for a dichromat). The approach I’m attempting to take is outlined in the following paper:

http://www.inf.ufrgs.br/~oliveira/pubs_files/CVD_PCA/Machado_Oliveira_EuroVis2010.pdf

  1. Hi Paul,

    excellent work. I think there should be more people like you who look into this kind of stuff to help accessibility in games.

    We (@glepas) use a similar shader in our XNA game BlindGiRL: http://www.youtube.com/watch?v=m1_asqbd7cg

    However, I persoanlly think it has more application as a game usability QA tool than a user feature.

    Anyway, in your code you have placed all the calculations in the shader. However most of them don’t depend on the pixel and can be precalculated. You can make it a lot more efficient by precalculating two deficiency matrices for the anchor points (because you know what kind of deficiency you are simulating and the calculations don’t depends on the pixel you are processing), do couple of dot products to select the matrix and then apply it. That way the shader is as efficent as it can be.

    Below is our shader taken directly fom our game – we apply it as a post processing pass on the final screen buffer. (note, our gamma correction is not that great either 😉

    Cheers,
    Fields

    uniform float3x3 RGBToLMS;
    uniform float3x3 LMSToRGB;
    uniform float3x3 Correction0;
    uniform float3x3 Correction1;
    uniform float3 Select0;
    uniform float3 Select1;

    float4 ThePixelShader(float2 texCoord: TEXCOORD0) : COLOR
    {
    float2 ConvergePoint = float2(-4.75, 1.31);
    float4 col = float4(tex2D(ScreenS, texCoord).rgb, 1);

    //gamma correct
    col = pow(abs(col), 2.2);

    /move to LMS space
    float3 lms = mul(col,RGBToLMS);

    /*select dficiency matrix*/

    float s0 = dot(lms, Select0);
    float s1 = dot(lms, Select1);

    float3x3 M = (s0-s1>0)?Correction0:Correction1;

    /* apply matrix */
    lms = mul(lms, M);

    /*back to RGB*/
    col.xyz = saturate(mul(lms, LMSToRGB));

    col = pow(col, 1.0/2.2);

    return col;
    }

Leave a Reply