Derivative normal maps

Very interseting indeed:
http://www.rorydriscoll.com/2012/01/11/derivative-maps/

I started typing in FX Composer from the default Phong_Reflect material and extended it with some tweaks and this cool normal mapping technique.

The current implementation has height maps commented out and actual derivative normal maps commented out, so normal maps work. Of course it’s no use doing this with regular normal maps hence you’d have to convert to derivative maps and change the comments before this is of any use. That’s a bit I’m still figuring out however.

The height maps don’t seem as successful as the normal / derivative maps implementation (and their Y is reversed) o it’s best left untouched. Scroll down to the pixel shader or paste this code in FX composer (the reason I posted the full code is that you may review it in a more proper environment without having to tiresomely figure out how to implement some snippet first).

#define FLIP_TEXTURE_Y

float Script : STANDARDSGLOBAL <
    string UIWidget = "none";
    string ScriptClass = "object";
    string ScriptOrder = "standard";
    string ScriptOutput = "color";
    string Script = "Technique=Phong?Main:Main10;";
> = 0.8;

//// UN-TWEAKABLES - AUTOMATICALLY-TRACKED TRANSFORMS ////////////////

float4x4 WorldITXf : WorldInverseTranspose < string UIWidget="None"; >;
float4x4 WvpXf : WorldViewProjection < string UIWidget="None"; >;
float4x4 WorldXf : World < string UIWidget="None"; >;
float4x4 ViewIXf : ViewInverse < string UIWidget="None"; >;

//// TWEAKABLE PARAMETERS ////////////////////

/// Point Light 0 ////////////
float3 Light0Pos : Position <
    string Object = "PointLight0";
    string UIName =  "Light 0 Position";
    string Space = "World";
> = {-0.5f,2.0f,1.25f};
float3 Light0Color : Specular <
    string UIName =  "Light 0";
    string Object = "Pointlight0";
    string UIWidget = "Color";
> = {1.0f,1.0f,1.0f};

/// Point Light 1 ////////////
float3 Light1Pos : Position <
    string Object = "PointLight1";
    string UIName =  "Light 1 Position";
    string Space = "World";
> = {-0.5f,2.0f,1.25f};
float3 Light1Color : Specular <
    string UIName =  "Light 1";
    string Object = "Pointlight1";
    string UIWidget = "Color";
> = {1.0f,1.0f,1.0f};

// Ambient Light
float3 AmbiColor : Ambient <
    string UIName =  "Ambient Light";
    string UIWidget = "Color";
> = {0.07f,0.07f,0.07f};

float Ks <
    string UIWidget = "slider";
    float UIMin = 0.0;
    float UIMax = 1.0;
    float UIStep = 0.05;
    string UIName =  "Specular";
> = 0.4;

float SpecExpon : SpecularPower <
    string UIWidget = "slider";
    float UIMin = 1.0;
    float UIMax = 128.0;
    float UIStep = 1.0;
    string UIName =  "Specular Power";
> = 55.0;


float Bump <
    string UIWidget = "slider";
    float UIMin = 0.00;
    float UIMax = 2;
    float UIStep = 0.001;
    string UIName =  "Bumpiness";
> = 1.0; 

float Kr <
    string UIWidget = "slider";
    float UIMin = 0.0;
    float UIMax = 1.0;
    float UIStep = 0.01;
    string UIName =  "Reflection Strength";
> = 0.5;

float ReflBias <
    string UIWidget = "slider";
    float UIMin = 0.0;
    float UIMax = 10.0;
    float UIStep = 0.01;
    string UIName =  "Reflection supression";
> = 1.0;

//////// COLOR & TEXTURE /////////////////////

texture ColorTexture : DIFFUSE <
    string ResourceName = "";
    string UIName =  "Diffuse Texture";
    string ResourceType = "2D";
>;

sampler2D ColorSampler = sampler_state {
    Texture = ;
    FILTER = MIN_MAG_MIP_LINEAR;
    AddressU = Wrap;
    AddressV = Wrap;
}; 

texture SpecularTexture  <
    string ResourceName = "";
    string UIName =  "Specular-Map Texture";
    string ResourceType = "2D";
>;

sampler2D SpecularSampler = sampler_state {
    Texture = ;
    FILTER = MIN_MAG_MIP_LINEAR;
    AddressU = Wrap;
    AddressV = Wrap;
}; 

texture HeightTexture  <
    string ResourceName = "";
    string UIName =  "Normal-Map Texture";
    string ResourceType = "2D";
>;

sampler2D HeightSampler = sampler_state {
    Texture = ;
    FILTER = MIN_MAG_MIP_LINEAR;
    AddressU = Wrap;
    AddressV = Wrap;
}; 

texture EnvTexture : ENVIRONMENT <
    string ResourceName = "";
    string UIName =  "Environment";
    string ResourceType = "Cube";
>;

samplerCUBE EnvSampler = sampler_state {
    Texture = ;
    FILTER = MIN_MAG_MIP_LINEAR;
    AddressU = CLamp;
    AddressV = CLamp;
    AddressW = CLamp;
};

// shared shadow mapping supported in Cg version

//////// CONNECTOR DATA STRUCTURES ///////////

/* data from application vertex buffer */
struct appdata {
    float3 Position	: POSITION;
    float4 UV		: TEXCOORD0;
    float4 Normal	: NORMAL;
};

/* data passed from vertex shader to pixel shader */
struct vertexOutput {
    float4 HPosition	: POSITION;
    float2 UV		: TEXCOORD0;
    // The following values are passed in "World" coordinates since
    //   it tends to be the most flexible and easy for handling
    //   reflections, sky lighting, and other "global" effects.
    float3 WorldNormal	: TEXCOORD1;
	float3 WorldPosition : TEXCOORD2;
    float3 WorldView	: TEXCOORD3;
};
 
///////// VERTEX SHADING /////////////////////

/*********** Generic Vertex Shader ******/

vertexOutput std_VS(appdata IN) {
    vertexOutput OUT = (vertexOutput)0;
    OUT.WorldNormal = mul(IN.Normal,WorldITXf).xyz;
	
    float4 Po = float4(IN.Position.xyz,1);
    float3 Pw = mul(Po,WorldXf).xyz;
#ifdef FLIP_TEXTURE_Y
    OUT.UV = float2(IN.UV.x,(1.0-IN.UV.y));
#else /* !FLIP_TEXTURE_Y */
    OUT.UV = IN.UV.xy;
#endif /* !FLIP_TEXTURE_Y */
    OUT.WorldView = normalize(ViewIXf[3].xyz - Pw);
    OUT.HPosition = mul(Po,WvpXf);
	OUT.WorldPosition = Pw;
    return OUT;
}

///////// DERIVATIVE NORMAL MAPPING //////////////////////
float3 surface_gradient(float3 Nn, float3 dpdx, float3 dpdy, float dhdx, float dhdy )
{
	float3 r1 = cross( dpdy, Nn );
	float3 r2 = cross( Nn, dpdx );
	return (r1*dhdx + r2*dhdy)/dot(dpdx, r1);
}

float3 modify_normal( float3 Nn, float3 dpdx, float3 dpdy, float dhdx, float dhdy )
{
	return normalize(Nn - surface_gradient(Nn, dpdx, dpdy, dhdx, dhdy));
}

/*
//from a height map
float3 surface_normal(float3 position, float3 normal, float height)
{
	float3 dpdx = ddx(position);
	float3 dpdy = ddy(position);
	float dhdx = ddx(height);
	float dhdy = ddy(height);
	
	return modify_normal( normal, dpdx, dpdy, dhdx, dhdy );
}
*/

//from a derivative map
float ApplyChainRule(float dhdu, float dhdv, float dud_, float dvd_)
{
    return dhdu * dud_ + dhdv * dvd_;
}

float3 surface_normal(float3 position, float3 normal, float2 gradient, float2 uv)
{
    float3 dpdx = ddx(position);
    float3 dpdy = ddy(position);
 
    float dhdx = ApplyChainRule(gradient.x, gradient.y, ddx(uv.x), ddx(uv.y));
    float dhdy = ApplyChainRule(gradient.x, gradient.y, ddy(uv.x), ddy(uv.y));
 
    return modify_normal(normal, dpdx, dpdy, dhdx, dhdy);
}

///////// PIXEL SHADING //////////////////////
struct data
{
	float3 nWorldNormal;
	float3 vColor;
	float3 vSpecularColor;
	float3 vReflectedColor;
};

data parse_inputs( vertexOutput IN )
{
	data P;
	
	// Sample textures
	P.vColor = tex2D(ColorSampler, IN.UV).rgb;
	P.vSpecularColor = tex2D(SpecularSampler, IN.UV).rgb;
	
	
	// Height map
	// note that the height map's Bump value is more sensitive and should
	// be scaled by about 0.05 compared to when using a normal map
	//P.nWorldNormal = surface_normal(IN.WorldPosition, IN.WorldNormal, height);
	//float height = tex2D(HeightSampler, IN.UV).r*Bump;
	
	// Normal map
	// note that the tangent normal to gradient conversion can be done in the
	// source texture beforehand with ease, use derivative map after doing so
	float3 tangentNormal = normalize( tex2D(HeightSampler, IN.UV).rgb*2-1 );
	float2 gradient = float2(-tangentNormal.x, tangentNormal.y) / tangentNormal.z * Bump;
	P.nWorldNormal = surface_normal(IN.WorldPosition, IN.WorldNormal, gradient, IN.UV);
	
	//Derivative map
	//float3 gradient = tex2D(HeightSampler, IN.UV).rg*2-1;
	//P.nWorldNormal = surface_normal(IN.WorldPosition, IN.WorldNormal, gradient, IN.UV);
	
	
	// Reflection vector
	float3 R = -reflect(IN.WorldView, P.nWorldNormal);
	
	// Sample reflection map
	P.vReflectedColor = texCUBE(EnvSampler, R).rgb;
	
	return P;
}

float3 apply_light_diffuse( float3 nWorldNormal, float3 nWorldView, float3 vColor,
	float3 nLightVec, float3 vLightColor )
{
    float3 diffuse = max( 0.0, dot(nLightVec, nWorldNormal) ) * vLightColor;
    float3 result = vColor*(diffuse + AmbiColor);
	return result;
}

float3 apply_point_light_diffuse( data P, float3 nWorldPosition, float3 nWorldView,
	float3 vLightPos, float3 vLightColor )
{
    float3 nLightVec = normalize(vLightPos - nWorldPosition);
    return apply_light_diffuse(P.nWorldNormal, nWorldView, P.vColor, nLightVec, vLightColor);
}

float3 apply_light( data P, float3 nWorldView,
	float3 nLightVec, float3 vLightColor )
{
    float3 Hn = normalize(nWorldView + nLightVec);
    float4 litV = lit(dot(nLightVec, P.nWorldNormal),dot(Hn, P.nWorldNormal),SpecExpon);
    float3 diffuse = litV.y * vLightColor;
    float3 specularReflection = litV.y * litV.z * Ks * vLightColor;
	
	//// APPLY ////
	// Diffuse
    float3 result = P.vColor*(diffuse + AmbiColor);
	
    // Specular reflection
	result += specularReflection * P.vSpecularColor;
	
	return result;
}

float3 apply_point_light( data P, float3 nWorldPosition, float3 nWorldView,
	float3 vLightPos, float3 vLightColor )
{
    float3 nLightVec = normalize(vLightPos - nWorldPosition);
	return apply_light( P, nWorldView, nLightVec, vLightColor );
}

float4 std_PS(vertexOutput IN) : COLOR
{
	data P = parse_inputs( IN );
	
	/// Hard coded light types & count ///
	float3 outPixel = apply_point_light( P, IN.WorldPosition, IN.WorldView, Light0Pos, Light0Color );
	outPixel += apply_point_light_diffuse( P, IN.WorldPosition, IN.WorldView, Light1Pos, Light1Color );
	
	// Environment cubemap reflection
	float fInvBrightness = max( 0.0, 1-(outPixel.x+outPixel.y+outPixel.z)*0.333 );
	if( ReflBias < 1 )
	{
		fInvBrightness = fInvBrightness * ReflBias + (1-ReflBias);
	}
	else
	{
		fInvBrightness = pow( fInvBrightness, ReflBias );
	}
    float3 reflColor = P.vSpecularColor * Kr * P.vReflectedColor;
    outPixel += reflColor * fInvBrightness;
	
	return float4(outPixel, 1);
}
///// TECHNIQUES /////////////////////////////
RasterizerState DisableCulling
{
    CullMode = NONE;
};

DepthStencilState DepthEnabling
{
	DepthEnable = TRUE;
};

BlendState DisableBlend
{
	BlendEnable[0] = FALSE;
};

technique10 Main10
{
    pass p0
	{
        SetVertexShader( CompileShader( vs_4_0, std_VS() ) );
        SetGeometryShader( NULL );
        SetPixelShader( CompileShader( ps_4_0, std_PS() ) );
                
        SetRasterizerState(DisableCulling);       
		SetDepthStencilState(DepthEnabling, 0);
		SetBlendState(DisableBlend, float4( 0.0f, 0.0f, 0.0f, 0.0f ), 0xFFFFFFFF);
    }
}

technique Main
{
    pass p0
	{
        VertexShader = compile vs_3_0 std_VS();
		ZEnable = true;
		ZWriteEnable = true;
		ZFunc = LessEqual;
		AlphaBlendEnable = false;
		CullMode = CW;
        PixelShader = compile ps_2_a std_PS();
    }
}

/////////////////////////////////////// eof //

Leave a Reply

Your email address will not be published. Required fields are marked *