Skip to main content
Bumped by Community user
Bumped by Community user
added 876 characters in body
Source Link
totbl
  • 21
  • 4

Parallax Occlusion Mapping skewed away from the camera

https://i.sstatic.net/H0c2s.png

Blender scene containing a textured plane

https://i.sstatic.net/Y3hE9.png
  • Update *

So I decided to cut out the middle man (my exporter), and manually tweaked the tangents to see how far it got me. So after some brute forcing, for a simple plane, it seems that a tangent xyzw of (-1, 0, 0, -1) gave me something that looked like parallax occlusion mapping:

Acceptable

But when I move in quite close to the mesh, the effect skews again:

Not so nice

Is this an expected side effect of POM, or does this indicate an issue with how the effect is being calculated?

As for the updated tangents - I'm not sure if I need to update my exporter to negate the xyzw components, or if only x and w need to be negated - but I don't really understand why I need to modify the tangents at all? Given that even with the updated tangents, there are still some strange effects happening up close to the surface, I can't help but feel that there's something going wrong in my shaders...

Parallax Occlusion Mapping skewed away from the camera

Blender scene containing a textured plane

https://i.sstatic.net/H0c2s.png
https://i.sstatic.net/Y3hE9.png
  • Update *

So I decided to cut out the middle man (my exporter), and manually tweaked the tangents to see how far it got me. So after some brute forcing, for a simple plane, it seems that a tangent xyzw of (-1, 0, 0, -1) gave me something that looked like parallax occlusion mapping:

Acceptable

But when I move in quite close to the mesh, the effect skews again:

Not so nice

Is this an expected side effect of POM, or does this indicate an issue with how the effect is being calculated?

As for the updated tangents - I'm not sure if I need to update my exporter to negate the xyzw components, or if only x and w need to be negated - but I don't really understand why I need to modify the tangents at all? Given that even with the updated tangents, there are still some strange effects happening up close to the surface, I can't help but feel that there's something going wrong in my shaders...

Source Link
totbl
  • 21
  • 4

GLSL - skewed parallax occlusion mapping

I'm currently trying to implement Parallax Occlusion Mapping, based off a post on sunandblackcat.com

With my current implementation, I have the following:

Parallax Occlusion Mapping skewed away from the camera

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:

Blender scene containing a textured plane

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