most games that have graphics options like the sumi-e/pencil character costumes in sf4 or the b&w filter from the resident evil games are either model/texture swaps or simple filters applied post processing, the actual shader encompasses everything you see (and dont) and is a large collection of values, a lot of coding, and quite a bit of math on the back end

void GenerateFGLookupTexture(Graphics::Texture* texture, RenderingDevice& device)

{

Graphics::TextureDesc textureDesc;

device.GetDesc(texture, textureDesc);

assert(textureDesc.format == Graphics::Format::G16R16);

assert(textureDesc.levels == 1);

const float deltaNdotV = 1.f / static_cast<float>(textureDesc.width);

const float deltaRoughness = 1.f / static_cast<float>(textureDesc.height);

// Lock texture

const Graphics::MappedData mappedData = device.Map(texture, 0, Graphics::MapType::Write, 0);

uint8* const RESTRICT dataPtr = reinterpret_cast<uint8*>(mappedData.data);

const uint numSamples = 512;

float roughness = 0.f;

for (uint v = 0; v < textureDesc.height; v++)

{

float NdotV = 0.f;

uint32* dst = reinterpret_cast<uint32*>(dataPtr + v * mappedData.rowPitch);

for (uint u = 0; u < textureDesc.width; u++, ++dst)

{

float a = 0;

float b = 0;

CalculateFGCoeff(NdotV, roughness, numSamples, a, b);

assert(a <= 1.f);

assert(b <= 1.f);

*dst = (PACKINTOSHORT_0TO1(b) << 16) | PACKINTOSHORT_0TO1(a);

NdotV += deltaNdotV;

}

roughness += deltaRoughness;

}

device.Unmap(texture, 0);

}

void PlaneHammersley(float& x, float& y, int k, int n)

{

float u = 0;

float p = 0.5f;

// FIXME Optimize by removing conditional

for (int kk = k; kk; p *= 0.5f, kk /= 2)

{

if (kk & 1)

{

u += p;

}

}

x = u;

y = (k + 0.5f) / n;

}

vec3 ImportanceSampleGGX(float x, float y, float a4)

{

const float PI = 3.1415926535897932384626433832795028841971693993751f;

// Convert uniform random variables x, y to a sample direction

const float phi = 2 * PI * x;

const float cosTheta = std::sqrt( (1 - y) / ( 1 + (a4 - 1) * y) );

const float sinTheta = std::sqrt(1 - cosTheta * cosTheta);

// Convert direction to cartesian coordinates

const vec3 H(sinTheta * std::cos(phi), sinTheta * std::sin(phi), cosTheta);

//D = a2 / (PI * std::pow(cosTheta * (a2 - 1) + 1, 2));

return H;

}

// Reference: GPU Gems 3 - GPU Based Importance Sampling

//s2012_pbs_physics_math_slides.pdf

vec3 ImportanceSampleBlinn(float x, float y, float specularPower)

{

const float PI = 3.1415926535897932384626433832795028841971693993751f;

// Convert uniform random variables x, y to a sample direction

const float phi = 2 * PI * x;

const float cosTheta = std::pow(y, 1.f / (specularPower + 1));

const float sinTheta = std::sqrt(1 - cosTheta * cosTheta);

// Convert direction to cartesian coordinates

const vec3 H(sinTheta * std::cos(phi), sinTheta * std::sin(phi), cosTheta);

//D = (specularPower + 2) / (2 * PI) * std::pow(cosTheta, specularPower);

return H;

}

float G_Schlick(float k, float NdotV, float NdotL)

{

return (NdotV * NdotL) / ( (NdotV * (1 - k) + k) * (NdotL * (1 - k) + k) );

}

void CalculateFGCoeff(float NoV, float roughness, uint numSamples, float& a, float& b)

{

// Work in a coordinate system where normal = vec3(0,0,1)

#define GGX 0

#define BLINN 1

#define G_SCHLICK 0

// Build view vector

const vec3 V(std::sqrt(1.0f - NoV * NoV), 0, NoV);

#if BLINN

const float blinnSpecularPower = std::pow(2.f, 13.f * roughness);

#elif GGX

const float GXX_a4 = std::pow(roughness, 4);

#endif

#if G_SCHLICK

const float G_k = std::pow(roughness + 1, 2.f) / 8.f;

#endif

a = 0;

b = 0;

for (uint i = 0; i < numSamples; ++i)

{

float x, y;

PlaneHammersley(x, y, i, numSamples);

// Microfacet specular model:

// f = D*G*F / (4*NoL*NoV)

// V = G / (NoL*NoV)

// Importance-based sampling:

// f / pdf

// Calculate random half vector based on roughness

#if BLINN

const vec3 H = ImportanceSampleBlinn(x, y, blinnSpecularPower);

// D and pdfH cancel each other so just set to 1

const float D = 1;

const float pdfH = D;

#elif GXX

const vec3 H = ImportanceSampleGGX(x, y, GXX_a4);

// D and pdfH cancel each other so just set to 1

const float D = 1;

const float pdfH = D;

#endif

// Calculate light direction

const vec3 L = 2 * dot( V, H ) * H - V;

const float NoL = saturate( L.z );

const float VoH = saturate( dot( V, H ) );

const float NoH = saturate( H.z );

const float LoH = saturate( dot( L, H ) ); // = VoH

// Convert pdf(H) to pdf(L)

// Reference: Torrance and Sparrow

//

graphics.stanford.edu/~boulos/papers/brdftog.pdf const float pdfL = pdfH / (4 * LoH);

if (NoL > 0)

{

#if G_SCHLICK

// FIXME NoV cancel out

const float G = G_Schlick(G_k, NoV, NoL);

const float V = G / (NoL * NoV);

#elif

const float V = 1;

#endif

const float G_Vis = D * V / 4 / pdfL;

const float Fc = std::pow(1 - VoH, 5.f);

// FIXME : NoL ? Part of the lighting eq but gives dark reflections at grazing angles. Need a better BRDF probably

a += (1 - Fc) * G_Vis * NoL;

b += Fc * G_Vis * NoL;

}

}

a /= numSamples;

b /= numSamples;

}

}</uint32*></uint8*></float></float>

it would be cool if as a bonus they figured out how to texture miriam so you get the shader 1 clothlike look in shader 3 as a model swap option or bonus costume, but actually changing shaders ingame would necessitate nearly the whole game being made twice to accomodate for it