Im trying to implement a per-pixel displacement shader in GLSL. I read through several papers and "tutorials" I found and ended up with trying to implement the approach NVIDIA used in their Cascade Demo (http://www.slideshare.net/icastano/cascades-demo-secrets) starting at Slide 82.

At the moment I am completly stuck with following problem: When I am far away the displacement seems to work. But as more I move closer to my surface, the texture gets bent in x-axis and somehow it looks like there is a little bent in general in one direction.

EDIT: I added a video: click

I added some screen to illustrate the problem: Looks somehow okay, but it seems there is a slight bent to the right Looks just wrong, texture is totally bent to the right side The texture is starting to get bent, look at the right edge The texture is strongly bent, look at the right edge

Well I tried lots of things already and I am starting to get a bit frustrated as my ideas run out.

I added my full VS and FS code:


#version 400

layout(location = 0) in vec3 IN_VS_Position;
layout(location = 1) in vec3 IN_VS_Normal;
layout(location = 2) in vec2 IN_VS_Texcoord;
layout(location = 3) in vec3 IN_VS_Tangent;
layout(location = 4) in vec3 IN_VS_BiTangent;

uniform vec3 uLightPos;
uniform vec3 uCameraDirection;
uniform mat4 uViewProjection;
uniform mat4 uModel;
uniform mat4 uView;
uniform mat3 uNormalMatrix;

out vec2 IN_FS_Texcoord;
out vec3 IN_FS_CameraDir_Tangent;
out vec3 IN_FS_LightDir_Tangent;

void main( void )

   IN_FS_Texcoord = IN_VS_Texcoord;

   vec4 posObject = uModel * vec4(IN_VS_Position, 1.0);
   vec3 normalObject         = (uModel *  vec4(IN_VS_Normal, 0.0)).xyz;
   vec3 tangentObject        = (uModel *  vec4(IN_VS_Tangent, 0.0)).xyz;
   //vec3 binormalObject       = (uModel * vec4(IN_VS_BiTangent, 0.0)).xyz;
   vec3 binormalObject =  normalize(cross(tangentObject, normalObject));

   // uCameraDirection is the camera position, just bad named
   vec3 fvViewDirection  = normalize( uCameraDirection - posObject.xyz);
   vec3 fvLightDirection = normalize( uLightPos.xyz - posObject.xyz );

   IN_FS_CameraDir_Tangent.x  = dot( tangentObject, fvViewDirection );
   IN_FS_CameraDir_Tangent.y  = dot( binormalObject, fvViewDirection );
   IN_FS_CameraDir_Tangent.z  = dot( normalObject, fvViewDirection );

   IN_FS_LightDir_Tangent.x  = dot( tangentObject, fvLightDirection );
   IN_FS_LightDir_Tangent.y  = dot( binormalObject, fvLightDirection );
   IN_FS_LightDir_Tangent.z  = dot( normalObject, fvLightDirection );

   gl_Position = (uViewProjection*uModel) * vec4(IN_VS_Position, 1.0);

The VS just builds the TBN matrix, from incoming normal, tangent and binormal in world space. Calculates the light and eye direction in worldspace. And finally transforms the light and eye direction into tangent space.


#version 400

// uniforms
uniform Light 
    vec4 fvDiffuse;
    vec4 fvAmbient;
    vec4 fvSpecular;

uniform Material {
    vec4 diffuse;
    vec4 ambient;
    vec4 specular;
    vec4 emissive;
    float fSpecularPower;
    float shininessStrength;

uniform sampler2D colorSampler;
uniform sampler2D normalMapSampler;
uniform sampler2D heightMapSampler;

in vec2 IN_FS_Texcoord;
in vec3 IN_FS_CameraDir_Tangent;
in vec3 IN_FS_LightDir_Tangent;

out vec4 color;

vec2 TraceRay(in float height, in vec2 coords, in vec3 dir, in float mipmap){

    vec2 NewCoords = coords;
    vec2 dUV = - dir.xy * height * 0.08;
    float SearchHeight = 1.0;
    float prev_hits = 0.0;
    float hit_h = 0.0;

    for(int i=0;i<10;i++){
        SearchHeight -= 0.1;
        NewCoords += dUV;
        float CurrentHeight = textureLod(heightMapSampler,NewCoords.xy, mipmap).r;
        float first_hit = clamp((CurrentHeight - SearchHeight - prev_hits) * 499999.0,0.0,1.0);
        hit_h += first_hit * SearchHeight;
        prev_hits += first_hit;
    NewCoords = coords + dUV * (1.0-hit_h) * 10.0f - dUV;

    vec2 Temp = NewCoords;
    SearchHeight = hit_h+0.1;
    float Start = SearchHeight;
    dUV *= 0.2;
    prev_hits = 0.0;
    hit_h = 0.0;
    for(int i=0;i<5;i++){
        SearchHeight -= 0.02;
        NewCoords += dUV;
        float CurrentHeight = textureLod(heightMapSampler,NewCoords.xy, mipmap).r;
        float first_hit = clamp((CurrentHeight - SearchHeight - prev_hits) * 499999.0,0.0,1.0);
        hit_h += first_hit * SearchHeight;
        prev_hits += first_hit;    
    NewCoords = Temp + dUV * (Start - hit_h) * 50.0f;

    return NewCoords;

void main( void )
   vec3  fvLightDirection = normalize( IN_FS_LightDir_Tangent );
   vec3  fvViewDirection  = normalize( IN_FS_CameraDir_Tangent );

   float mipmap = 0;

   vec2 NewCoord = TraceRay(0.1,IN_FS_Texcoord,fvViewDirection,mipmap);

   //vec2 ddx = dFdx(NewCoord);
   //vec2 ddy = dFdy(NewCoord);

   vec3 BumpMapNormal = textureLod(normalMapSampler, NewCoord.xy, mipmap).xyz;
   BumpMapNormal = normalize(2.0 * BumpMapNormal - vec3(1.0, 1.0, 1.0));

   vec3  fvNormal         = BumpMapNormal;
   float fNDotL           = dot( fvNormal, fvLightDirection ); 

   vec3  fvReflection     = normalize( ( ( 2.0 * fvNormal ) * fNDotL ) - fvLightDirection ); 
   float fRDotV           = max( 0.0, dot( fvReflection, fvViewDirection ) );

   vec4  fvBaseColor      = textureLod( colorSampler, NewCoord.xy,mipmap);

   vec4  fvTotalAmbient   = fvAmbient * fvBaseColor; 
   vec4  fvTotalDiffuse   = fvDiffuse * fNDotL * fvBaseColor; 
   vec4  fvTotalSpecular  = fvSpecular * ( pow( fRDotV, fSpecularPower ) );

   color = ( fvTotalAmbient + (fvTotalDiffuse + fvTotalSpecular) );


The FS implements the displacement technique in TraceRay method, while always using mipmap level 0. Most of the code is from NVIDIA sample and another paper I found on the web, so I guess there cannot be much wrong in here. At the end it uses the modified UV coords for getting the displaced normal from the normal map and the color from the color map.

I looking forward for some ideas. Thanks in advance!

Edit: Here is the code loading the heightmap:




        //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
        //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);

Maybe something wrong in here?

  • 1
    \$\begingroup\$ From the visuals you've shown, it looks like a problem with how your viewing vector is being transformed into the tangent space of the quad. You're looking along what I'd call the x or u direction, but the texture is distorting as though you're looking along y or v. I haven't identified the exact error, but I think that's the part of your code that's causing this. Posting a video where you rotate around may help further. \$\endgroup\$ – DMGregory May 3 '14 at 15:09
  • \$\begingroup\$ That sounds like a very good suggestion. You mean a video where I am just rotating around with camera along x and y axis? Because in that case nothing special happens. \$\endgroup\$ – Chris May 3 '14 at 15:27
  • \$\begingroup\$ Rotating around the normal of the polygon while zoomed in close/at a low angle is what I had in mind. I'd like to see how the axis of distortion shifts as your view direction does. If it goes clockwise while your camera goes counter-clockwise for instance, that suggests you may be using the transpose of the matrix you need. If the distortion is always along y/v, then it may indicate that the view direction (or at least the "yaw" part) is not being included in the calculation at all. \$\endgroup\$ – DMGregory May 3 '14 at 16:04
  • \$\begingroup\$ I just uploaded another vid: paxi.at/random/sfg2_com.avi Just let me know if you need some more info / screens. \$\endgroup\$ – Chris May 3 '14 at 16:28
  • \$\begingroup\$ Something "interesting" that I found out yesterday is that, when im using the precaculated binormal, instead of calculating it in the shader via cross(t,n) behaves a bit different when rotating, its more like it goes with the direction. The problem itself anyway is still here :( \$\endgroup\$ – Chris May 4 '14 at 7:36

I dont know why to be honest. But it finally works now, when I am NOT normalizing the viewdirection in the VS before transforming it into the tangent space.

Here is a final video: http://www.paxi.at/random/final.avi

  • \$\begingroup\$ You have another issue in your vertex shader as well. You are using the model matrix to rotate your basis vectors... that matrix includes translation and scaling in addition to rotation. You have a proper normal matrix in your shader: uNormalMatrix - that is what you should be using to transform your basis. \$\endgroup\$ – Andon M. Coleman May 6 '14 at 16:58

Perhaps your heightmap texture is not sufficiently large when you are close to the object since the texels > pixel by a large factor? Also do you have any filters enabled on the heightmap texture?

  • \$\begingroup\$ I tested with a 512x512 and 1024x1024 heightmap. I tried disabling filters as well but get the same result ( I am using linear filter at the moment) :( I updated my code above how I am loading the texture and added a little video as well. \$\endgroup\$ – Chris May 3 '14 at 10:23

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.