(Effects) Parallax mapping and World transformation

Hi all,
Question about parallax mapping again :slight_smile:
I wrote a basic parallax effect that works good from all angles, but when I rotate the surface itself (ie change its world transform) - the illusion breaks.

Please see following screenshot:

Whats the right way to apply world transformations on the parallax effect so that no matter how the surface is rotated or scaled it will look good? Moving the surface looks OK btw, the only problem is with rotation.

Here’s my vertex shader code:

// Vertex Shader 
VertexShaderOutput VertexShaderMain(VertexShaderInput input)
VertexShaderOutput output;

// Transform Position  
//float4 viewPosition = mul(worldPosition, View);
//output.Position = mul(viewPosition, Projection);
output.Position = mul(input.Position, WorldViewProjection);

// Pass Depth  
output.Depth.x = output.Position.z;
output.Depth.y = output.Position.w;

// Build TBN Matrix (Tangent, Bitangent, Normals)
output.TangentToWorld[0] = (mul(input.Tangent, World));
output.TangentToWorld[1] = (mul(input.BiTangent, World));
output.TangentToWorld[2] = (mul(input.Normal, World));

// calculate eye dir for displacement
if (EnableDisplacement == true)
	float4 worldPosition = mul(input.Position, World);
	float3x3 tbn = float3x3(input.Tangent, input.BiTangent, input.Normal);
	output.EyeVector = normalize(mul(normalize(CameraPosition.xyz - worldPosition), tbn));
	float3x3 rotation;
	rotation[0] = normalize(World[0]);
	rotation[1] = normalize(World[1]);
	rotation[2] = normalize(World[2]);
	output.EyeVector = mul(output.EyeVector, rotation);	
	output.EyeVector = 1;

// Pass UV and return 
output.UV = TextureOffset + input.UV * TexturesTiling;
return output;

and here’s the relevant part in the pixel shader:

	float height = tex2D(NormalSampler, UV).a * DisplacementScale - DisplacementBias;
	UV += (input.EyeVector.xy * height);

How do I fix it to work with rotation?
Thanks! :slight_smile:

Pretty sure you need to cast your World to a float3x3 and normalize your vectors.

// Build TBN Matrix (Tangent, Bitangent, Normals)
float3x3 worldRot = (float3x3)World;
output.TangentToWorld[0] = normalize(mul(input.Tangent, worldRot));
output.TangentToWorld[1] = normalize(mul(input.BiTangent, worldRot));
output.TangentToWorld[2] = normalize(mul(input.Normal, worldRot));

… though that may not be true because I have no idea what you’re doing there with your eye vector. I would think it’d make more sense to pass the camera-position in a uniform and just add the world-position as a vertex interpolator in the VS output than to interpolate the eye-vector.

I’ve never seen it done like you appear to be doing it, could you point to where you got that idea - I’m curious.

I really don’t think you should need to rotate that eye-vector though. If your world-position is good and your camera-position is good, then your eye-vector will be good without the rotation.

Yes that eye thing doesn’t look right.

When i was doing a couple normal mapping tutorials i had similar things in them about the eye vector.
Which were in fact wrong and i had to end up writing it out myself.

Seems to me that should be a dot operation to find intensity after a transform.
Then again im not entirely sure what is wrong in the posted image.
Or what the difference is in parallax mapping.

Dunno if this will help. Here is how i apply the world transform when i was working on normal mapping.

float3 NormalMap = tex2D(TextureNormalSampler, input.TexureCoordinateA).rgb;
NormalMap = normalize(NormalMap * 2.0 - 1.0);
float3 normal = input.Normal;
float3 tangent = input.Tangent;
float3x3 mat;
mat[0] = cross(normal, tangent); // right
mat[1] = tangent; // up
mat[2] = normal; // forward
NormalMap = mul(NormalMap, mat);
NormalMap = mul(NormalMap, World); // <<<<< world
NormalMap = normalize(NormalMap);

Here is one of my shaders were it was applied.

// technique 
// NormalMapLightShadowDrawing
// requires a tangent in the vertex data and 3 textures pixel , normalmap, depthmap
// ToDo fix
// I think the side opposite the light still can get some specular reflection when using normal maps. 

struct VsNormMapLightShadowInput
	float4 Position : POSITION0;
	float3 Normal : NORMAL0;
	float2 TexureCoordinateA : TEXCOORD0;
	float3 Tangent : NORMAL1;

struct VsNormMapLightShadowOutput
	float4 Position : SV_Position;//: SV_POSITION;
	float4 Position3D : TEXCOORD4;
	float2 TexureCoordinateA : TEXCOORD0;
	float3 Normal: TEXCOORD1;
	float3 Tangent : TEXCOORD2;

VsNormMapLightShadowOutput VsNormMapLightShadow(VsNormMapLightShadowInput input)

	VsNormMapLightShadowOutput output;
	output.Position3D = mul(input.Position, World);
	float4x4 vp = mul(View, Projection);
	output.Position = mul(output.Position3D, vp);
	output.Normal = input.Normal;
	output.Tangent = input.Tangent;
	output.TexureCoordinateA = input.TexureCoordinateA;
	return output;

float4 PsNormMapLightShadow(VsNormMapLightShadowOutput input) : COLOR0
	// Normal Map
	float3 NormalMap = tex2D(TextureNormalSampler, input.TexureCoordinateA).rgb;
	NormalMap.g = 1.0f - NormalMap.g; // flips the y. the program i used fliped the green.
	NormalMap = normalize(NormalMap * 2.0 - 1.0);
	float3 normal = input.Normal;
	float3 tangent = input.Tangent;
	float3x3 mat;
	mat[0] = cross(normal, tangent); // right
	mat[1] = tangent; // up
	mat[2] = normal; // forward
	NormalMap = mul(NormalMap, mat);
	NormalMap = mul(NormalMap, World);
	NormalMap = normalize(NormalMap); // we do this to ensure scaling wont break the normal.
    // prep
	float3 temp = WorldLightPosition - input.Position3D;
	float distancePixelToLight = length(temp);
	float3 surfaceToCamera = normalize(CameraPosition - input.Position3D);
	float3 surfaceToLight = temp / distancePixelToLight; // cheapen the normalize. normalize(pixelToLight);
	float3 lightToSurface = -surfaceToLight;
	float shadowDepth = texCUBE(TextureDepthSampler, float4(lightToSurface, 0)).x;
	float4 TexelColor = tex2D(TextureSamplerA, input.TexureCoordinateA) * (1.0f - LightVsTexelRatio) + (LightColor * LightVsTexelRatio); // LightVsTexelRatio == .5 is normal		
	// shadow 
	float lightFalloff = (1.0f - saturate(distancePixelToLight / (IlluminationRange + 0.001f)));
	float LightDistanceIntensity = saturate(sign((shadowDepth + .2f) - distancePixelToLight)) * lightFalloff; // if removal.
	// lighting
	float3 surfNom = NormalMap;
	float diffuse = saturate((dot(lightToSurface, -surfNom) + DiffuseCresting) * (1.0f / (1.0f + DiffuseCresting))); // I've added over or underdraw to the diffuse.
	diffuse *= diffuse;
	float3 reflectionTheta = dot(surfaceToCamera, -reflect(surfaceToLight, surfNom));
	float specular = saturate(reflectionTheta - SpecularSharpness) * (1.0f / (1.0f - SpecularSharpness)); // this is for sharpness i didn't want to use powers.
	// finalize it.
	float3 inverseAmbientControl = 1.0f - AmbientStrength;
	float3 additiveAmbient = AmbientStrength;
	float3 additiveDiffuse = diffuse * DiffuseStrength * LightDistanceIntensity * inverseAmbientControl;
	float3 additiveSpecular = specular * SpecularStrength  * LightDistanceIntensity  * inverseAmbientControl;
	float3 FinalColor = TexelColor * (additiveAmbient + additiveDiffuse + additiveSpecular);
	return float4(FinalColor, 1.0f);

technique NormalMapLightShadowDrawing
		VertexShader = compile VS_SHADERMODEL VsNormMapLightShadow();
		PixelShader = compile PS_SHADERMODEL PsNormMapLightShadow();