Skip to content

Instantly share code, notes, and snippets.

@zeux
Last active August 29, 2015 14:11
Show Gist options
  • Save zeux/01d4555fb000fa25bc3c to your computer and use it in GitHub Desktop.
Save zeux/01d4555fb000fa25bc3c to your computer and use it in GitHub Desktop.
Shader source for blog post; this shader computes lighting for 8 point lights and tries hard to fit into 64 instructions.
#define LIGHT_SPACE "Object"
#include "bindings.h"
struct VS_IN
{
float4 position: POSITION;
float2 texcoord: TEXCOORD;
float3 tangent: TANGENT;
float3 bitangent: BINORMAL;
float3 normal: NORMAL;
};
struct VS_OUT
{
float2 texcoord: TEXCOORD0;
// view vector, tangent space
float3 view: TEXCOORD1;
// light vectors, unnormalized, tangent space
// l0.x l1.x l2.x l3.x
// l0.y l1.y l2.y l3.y
// l0.z l1.z l2.z l3.z
// twice.
float4 lights[6]: TEXCOORD2;
};
uniform float4x4 world_view_inverse: WorldViewInverse;
uniform float4x4 world_view_projection: WorldViewProjection;
// light position, object space
static float3 light_positions[8] =
{
Lamp1Pos, Lamp2Pos, Lamp3Pos, Lamp4Pos,
Lamp5Pos, Lamp6Pos, Lamp7Pos, Lamp8Pos
};
VS_OUT vs_main(VS_IN I, out float4 clippos: POSITION)
{
clippos = mul(I.position, world_view_projection);
VS_OUT O;
O.texcoord = I.texcoord;
// view vector
float3 eye_pos = mul(float4(0, 0, 0, 1), world_view_inverse);
float3 view = normalize(I.position - eye_pos);
// transform to tangent space
O.view.x = dot(view, I.tangent);
O.view.y = dot(view, I.bitangent);
O.view.z = dot(view, I.normal);
// note: this loop can be optimized if application feeds light data in SoA
// order, we can do SoA-style calculation here also. It's not quite easy to
// do in FX Composer, I guess, and anyway this is left as is for clarity.
for (int i = 0; i < 2; ++i)
{
// compute light vectors in tangent space
float3 light_vectors[4];
for (int j = 0; j < 4; ++j)
{
float3 l = light_positions[i*4 + j] - I.position;
light_vectors[j].x = dot(l, I.tangent);
light_vectors[j].y = dot(l, I.bitangent);
light_vectors[j].z = dot(l, I.normal);
}
// splat in SoA order
O.lights[i*3 + 0] = float4(light_vectors[0].x, light_vectors[1].x, light_vectors[2].x, light_vectors[3].x);
O.lights[i*3 + 1] = float4(light_vectors[0].y, light_vectors[1].y, light_vectors[2].y, light_vectors[3].y);
O.lights[i*3 + 2] = float4(light_vectors[0].z, light_vectors[1].z, light_vectors[2].z, light_vectors[3].z);
}
return O;
}
// light colors
static float3 light_colors[8] =
{
Lamp1Col, Lamp2Col, Lamp3Col, Lamp4Col,
Lamp5Col, Lamp6Col, Lamp7Col, Lamp8Col
};
// 1 / light radius^2
uniform float4 light_radius_inv[2] =
{
(1 / 0.25).xxxx,
(1 / 0.25).xxxx
};
// specular power A, B constant
// http://www.gamasutra.com/features/20020801/beaudoin_01.htm
// m = 2, n = 18 => A = 6.645, B = -5.645
uniform const float2 specular_ab = float2(6.645, -5.645);
texture diffuse_texture;
uniform sampler2D diffuse_map = TRILINEAR_SAMPLER(diffuse_texture);
texture normal_texture;
uniform sampler2D normal_map = TRILINEAR_SAMPLER(normal_texture);
texture specular_texture;
uniform sampler2D specular_map = TRILINEAR_SAMPLER(specular_texture);
float4 compute_specular_power(float4 v)
{
// originally: pow(v, N)
// x^N is roughly equal to (max(Ax+B, 0))^2
// A,B depend on N
float4 t = saturate(specular_ab.x * v + specular_ab.y);
return t * t;
}
struct LightingData
{
float4 diffuse;
float4 specular;
};
LightingData computeLighting4(VS_OUT I, int light_bucket_index, float3 normal)
{
// compute squared lengths in parallel
float4 squared_lengths = 0;
for (int i = 0; i < 3; ++i)
{
squared_lengths += pow(I.lights[light_bucket_index * 3 + i], 2);
}
// compute NdotL in parallel
float4 NdotL = 0;
for (int i = 0; i < 3; ++i)
{
NdotL += I.lights[light_bucket_index * 3 + i] * normal[i];
}
// compute RdotL in parallel
float3 reflected = reflect(I.view, normal);
float4 RdotL = 0;
for (int i = 0; i < 3; ++i)
{
RdotL += I.lights[light_bucket_index * 3 + i] * reflected[i];
}
// correct NdotL and RdotL
float4 correction = 1 / sqrt(squared_lengths);
NdotL = saturate(NdotL * correction);
RdotL = saturate(RdotL * correction);
// attenuation
// 1 - d^2 / r^2 for diffuse
float4 atten = squared_lengths * light_radius_inv[light_bucket_index];
// modulate diffuse by attenuation
NdotL = saturate(NdotL - NdotL * atten);
// specular
float4 spec = compute_specular_power(RdotL);
LightingData data;
data.diffuse = NdotL;
data.specular = spec;
return data;
}
float4 ps_main(VS_OUT I): COLOR
{
float3 normal = tex2D(normal_map, I.texcoord).xyz * 2 - 1;
LightingData lights[] =
{
computeLighting4(I, 0, normal),
computeLighting4(I, 1, normal)
};
// final diffuse color
float3 diffuse = 0;
for (int i = 0; i < 8; ++i)
{
diffuse += lights[i/4].diffuse[i%4] * light_colors[i];
}
// final specular color
float4 specular_color = tex2D(specular_map, I.texcoord);
float3 specular = specular_color.rgb * saturate(dot(lights[0].specular + lights[1].specular, 1));
// final color
float4 albedo = tex2D(diffuse_map, I.texcoord);
return float4(albedo.rgb * diffuse + specular, albedo.a);
}
technique t0
{
pass p0
{
VertexShader = compile vs_1_1 vs_main();
PixelShader = compile ps_2_0 ps_main();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment