I'm currently trying to implement Parallax Occlusion Mapping, based off a post on sunandblackcat.com
With my current implementation, I have the following:
Notice how the parallax effect is skewed away from the camera? I can't understand what's causing this to happen, so I'm posting here in the hope that someone can help me :)
So first up, I guess I should show what my vertex data looks like:
Vertex 0
position xyz: (1, 0, -1),
normal xyz: (0, 1, 0),
tangent xyzw: (1, 0, 0, 1)
Vertex 1
position xyz: (-1, 0, -1),
normal xyz: (0, 1, 0),
tangent xyzw: (1, 0, 0, 1)
Vertex 2
position xyz: (1, 0, 1),
normal xyz: (0, 1, 0),
tangent xyzw: (1, 0, 0, 1)
Vertex 3
position xyz: (-1, 0, 1),
normal xyz: (0, 1, 0),
tangent xyzw: (1, 0, 0, 1)
This vertex data is based off the following scene, as it appears in Blender:
Note that Blender is Z-up, but my vertex data has been converted from Z-up to Y-up, hence my normals point "up" on the Y axis
Given that vertex data, I have the following vertex shader:
#version 330
uniform mat4 projectionMatrix;
uniform mat4 viewMatrix;
uniform mat4 modelMatrix;
uniform vec3 eyePos;
in vec3 in_Position;
in vec2 in_TextureCoord;
in vec3 in_Normal;
in vec4 in_Tangent;
in float in_ModelOffset;
in float in_TexOffset;
out vec2 pass_TextureCoord;
out float pass_TexOffset;
out vec3 pass_toLightInTangentSpace;
out vec3 pass_toCameraInTangentSpace;
void main(void) {
mat3 normalMatrix = transpose(inverse(mat3(modelMatrix)));
pass_TexOffset = in_TexOffset;
pass_TextureCoord = in_TextureCoord;
// transform to world space
vec4 worldPosition = modelMatrix * vec4(in_Position, 1);
vec3 worldNormal = normalize(normalMatrix * in_Normal);
vec3 worldTangent = normalize(normalMatrix * in_Tangent.xyz);
// calculate vectors to the camera and to the light, hardcoded for now
vec3 worldDirectionToLight = normalize(vec3(0,10,0) - worldPosition.xyz);
vec3 worldDirectionToCamera = normalize(eyePos - worldPosition.xyz);
// calculate bitangent from normal and tangent
vec3 worldBitangnent = cross(worldNormal, worldTangent) * in_Tangent.w;
// transform direction to the light to tangent space
pass_toLightInTangentSpace = vec3(
dot(worldDirectionToLight, worldTangent),
dot(worldDirectionToLight, worldBitangnent),
dot(worldDirectionToLight, worldNormal)
);
// transform direction to the camera to tangent space
pass_toCameraInTangentSpace= vec3(
dot(worldDirectionToCamera, worldTangent),
dot(worldDirectionToCamera, worldBitangnent),
dot(worldDirectionToCamera, worldNormal)
);
// calculate screen space position of the vertex
gl_Position = projectionMatrix * viewMatrix * worldPosition;
}
And the following fragment shader:
#version 330
const int NUM_TEXTURES = 2;
uniform sampler2DArray diffuseTexture;
uniform vec3 ambientColor;
uniform float specularIntensity;
uniform float specularPower;
uniform float renderNormal;
uniform float height_scale;
in vec2 pass_TextureCoord;
in float pass_TexOffset;
in vec3 pass_toLightInTangentSpace;
in vec3 pass_toCameraInTangentSpace;
out vec4 out_Color;
const float parallaxScale = 0.1;
//////////////////////////////////////////////////////
// Implements Parallax Mapping technique
// Returns modified texture coordinates, and last used depth
vec2 parallaxMapping(in vec3 V, in vec2 T, out float parallaxHeight)
{
// determine optimal number of layers
const float minLayers = 10;
const float maxLayers = 15;
float numLayers = mix(maxLayers, minLayers, abs(dot(vec3(0, 0, 1), V)));
// height of each layer
float layerHeight = 1.0 / numLayers;
// current depth of the layer
float curLayerHeight = 0;
// shift of texture coordinates for each layer
vec2 dtex = parallaxScale * V.xy / V.z / numLayers;
// current texture coordinates
vec2 currentTextureCoords = T;
// depth from heightmap
float heightFromTexture = texture(diffuseTexture, vec3(currentTextureCoords, pass_TexOffset+1)).a;
// while point is above the surface
while(heightFromTexture > curLayerHeight)
{
// to the next layer
curLayerHeight += layerHeight;
// shift of texture coordinates
currentTextureCoords -= dtex;
// new depth from heightmap
heightFromTexture = texture(diffuseTexture, vec3(currentTextureCoords, pass_TexOffset+1)).a;
}
///////////////////////////////////////////////////////////
// previous texture coordinates
vec2 prevTCoords = currentTextureCoords + dtex;
// heights for linear interpolation
float nextH = heightFromTexture - curLayerHeight;
float prevH = texture(diffuseTexture, vec3(prevTCoords, pass_TexOffset+1)).a
- curLayerHeight + layerHeight;
// proportions for linear interpolation
float weight = nextH / (nextH - prevH);
// interpolation of texture coordinates
vec2 finalTexCoords = prevTCoords * weight + currentTextureCoords * (1.0-weight);
// interpolation of depth values
parallaxHeight = curLayerHeight + prevH * weight + nextH * (1.0 - weight);
// return result
return finalTexCoords;
}
//////////////////////////////////////////////////////
// Implements self-shadowing technique - hard or soft shadows
// Returns shadow factor
float parallaxSoftShadowMultiplier(in vec3 L, in vec2 initialTexCoord,
in float initialHeight)
{
float shadowMultiplier = 1;
const float minLayers = 15;
const float maxLayers = 30;
// calculate lighting only for surface oriented to the light source
if(dot(vec3(0, 0, 1), L) > 0)
{
// calculate initial parameters
float numSamplesUnderSurface = 0;
shadowMultiplier = 0;
float numLayers = mix(maxLayers, minLayers, abs(dot(vec3(0, 0, 1), L)));
float layerHeight = initialHeight / numLayers;
vec2 texStep = parallaxScale * L.xy / L.z / numLayers;
// current parameters
float currentLayerHeight = initialHeight - layerHeight;
vec2 currentTextureCoords = initialTexCoord + texStep;
float heightFromTexture = texture(diffuseTexture, vec3(currentTextureCoords, pass_TexOffset+1)).a;
int stepIndex = 1;
// while point is below depth 0.0 )
while(currentLayerHeight > 0)
{
// if point is under the surface
if(heightFromTexture < currentLayerHeight)
{
// calculate partial shadowing factor
numSamplesUnderSurface += 1;
float newShadowMultiplier = (currentLayerHeight - heightFromTexture) *
(1.0 - stepIndex / numLayers);
shadowMultiplier = max(shadowMultiplier, newShadowMultiplier);
}
// offset to the next layer
stepIndex += 1;
currentLayerHeight -= layerHeight;
currentTextureCoords += texStep;
heightFromTexture = texture(diffuseTexture, vec3(currentTextureCoords, pass_TexOffset+1)).a;
}
// Shadowing factor should be 1 if there were no points under the surface
if(numSamplesUnderSurface < 1)
{
shadowMultiplier = 1;
}
else
{
shadowMultiplier = 1.0 - shadowMultiplier;
}
}
return shadowMultiplier;
}
//////////////////////////////////////////////////////
// Calculates lighting by Blinn-Phong model and Normal Mapping
// Returns color of the fragment
vec4 normalMappingLighting(in vec2 T, in vec3 L, in vec3 V, float shadowMultiplier)
{
// restore normal from normal map
vec3 N = normalize(texture(diffuseTexture, vec3(T, pass_TexOffset+1)).xyz * 2 - 1);
vec3 D = texture(diffuseTexture, vec3(T, pass_TexOffset)).rgb;
// ambient lighting
float iamb = 0.2;
// diffuse lighting
float idiff = clamp(dot(N, L), 0, 1);
// specular lighting
float ispec = 0;
if(dot(N, L) > 0.2)
{
vec3 R = reflect(-L, N);
ispec = pow(dot(R, V), 32) / 1.5;
}
vec4 resColor;
resColor.rgb = D * (vec3(0.1, 0.1, 0.1) + (idiff + ispec) * pow(shadowMultiplier, 4));
resColor.a = 1;
return resColor;
}
/////////////////////////////////////////////
// Entry point for Parallax Mapping shader
void main(void)
{
// normalize vectors after vertex shader
vec3 V = normalize(pass_toCameraInTangentSpace);
vec3 L = normalize(pass_toLightInTangentSpace);
// get new texture coordinates from Parallax Mapping
float parallaxHeight;
vec2 T = parallaxMapping(V, pass_TextureCoord, parallaxHeight);
// get self-shadowing factor for elements of parallax
float shadowMultiplier = parallaxSoftShadowMultiplier(L, T, parallaxHeight - 0.05);
// calculate lighting
out_Color = normalMappingLighting(T, L, V, shadowMultiplier);
}
I think the majority of this lines up with the sunandblackcat implementation, although I load in my textures as a texture array - the first texture being an RGBA diffuse map, and the second texture being a normal map (RGB) and height map (A)
From what I've posted, can anyone tell me where I'm going wrong, and why my parallax effect is skewed away from the camera?
Thanks