User Guide Cancel

Lib Normal - Shader API | Substance 3D Painter

Lib Normal - Shader API

lib-normal.glsl

Public Functions: normalBlend normalBlendOriented normalFade normalUnpack normalFromBaseNormal normalFromNormal normalFromHeight getTSNormal computeWSBaseNormal computeWSNormal

Import from library

import lib-defines.glsl
import lib-sparse.glsl
import lib-defines.glsl import lib-sparse.glsl
import lib-defines.glsl 
import lib-sparse.glsl

All engine parameters useful for normal-centric operations.

//: param auto channel_height
uniform SamplerSparse height_texture;
//: param auto channel_normal
uniform SamplerSparse normal_texture;
//: param auto texture_normal
uniform SamplerSparse base_normal_texture;
//: param auto normal_blending_mode
uniform int normal_blending_mode;
//: param auto channel_height uniform SamplerSparse height_texture; //: param auto channel_normal uniform SamplerSparse normal_texture; //: param auto texture_normal uniform SamplerSparse base_normal_texture; //: param auto normal_blending_mode uniform int normal_blending_mode;
//: param auto channel_height 
uniform SamplerSparse height_texture; 
//: param auto channel_normal 
uniform SamplerSparse normal_texture; 
//: param auto texture_normal 
uniform SamplerSparse base_normal_texture; 
//: param auto normal_blending_mode 
uniform int normal_blending_mode;

Used to invert the Y axis of the normal map

//: param auto normal_y_coeff
uniform float base_normal_y_coeff;
//: param auto normal_y_coeff uniform float base_normal_y_coeff;
//: param auto normal_y_coeff 
uniform float base_normal_y_coeff;

Empirically determined by our artists...

const float HEIGHT_FACTOR = 400.0;
const float HEIGHT_FACTOR = 400.0;
const float HEIGHT_FACTOR = 400.0;

Perform the blending between 2 normal maps

This is based on Whiteout blending http://blog.selfshadow.com/publications/blending-in-detail/

vec3 normalBlend(vec3 baseNormal, vec3 overNormal)
{
return normalize(vec3(
baseNormal.xy + overNormal.xy,
baseNormal.z * overNormal.z));
}
vec3 normalBlend(vec3 baseNormal, vec3 overNormal) { return normalize(vec3( baseNormal.xy + overNormal.xy, baseNormal.z * overNormal.z)); }
vec3 normalBlend(vec3 baseNormal, vec3 overNormal) 
{ 
  return normalize(vec3( 
    baseNormal.xy + overNormal.xy, 
    baseNormal.z  * overNormal.z)); 
}

Perform a detail oriented blending between 2 normal maps

This is based on Detail Oriented blending http://blog.selfshadow.com/publications/blending-in-detail/

vec3 normalBlendOriented(vec3 baseNormal, vec3 overNormal)
{
baseNormal.z += 1.0;
overNormal.xy = -overNormal.xy;
return normalize(baseNormal * dot(baseNormal,overNormal) -
overNormal*baseNormal.z);
}
vec3 normalBlendOriented(vec3 baseNormal, vec3 overNormal) { baseNormal.z += 1.0; overNormal.xy = -overNormal.xy; return normalize(baseNormal * dot(baseNormal,overNormal) - overNormal*baseNormal.z); }
vec3 normalBlendOriented(vec3 baseNormal, vec3 overNormal) 
{ 
  baseNormal.z += 1.0; 
  overNormal.xy = -overNormal.xy; 
  return normalize(baseNormal * dot(baseNormal,overNormal) - 
    overNormal*baseNormal.z); 
}

Returns a normal flattened by an attenuation factor

vec3 normalFade(vec3 normal,float attenuation)
{
if (attenuation<1.0 && normal.z<1.0)
{
float phi = attenuation * acos(normal.z);
normal.xy *= 1.0/sqrt(1.0-normal.z*normal.z) * sin(phi);
normal.z = cos(phi);
}
return normal;
}
vec3 normalFade(vec3 normal,float attenuation) { if (attenuation<1.0 && normal.z<1.0) { float phi = attenuation * acos(normal.z); normal.xy *= 1.0/sqrt(1.0-normal.z*normal.z) * sin(phi); normal.z = cos(phi); } return normal; }
vec3 normalFade(vec3 normal,float attenuation) 
{ 
  if (attenuation<1.0 && normal.z<1.0) 
  { 
    float phi = attenuation * acos(normal.z); 
    normal.xy *= 1.0/sqrt(1.0-normal.z*normal.z) * sin(phi); 
    normal.z = cos(phi); 
  } 
 
  return normal; 
}

Unpack a normal w/ alpha channel

vec3 normalUnpack(vec4 normal_alpha, float y_coeff)
{
if (normal_alpha.a == 0.0 || normal_alpha.xyz == vec3(0.0)) {
return vec3(0.0, 0.0, 1.0);
}
// Attenuation in function of alpha
vec3 normal = normal_alpha.xyz/normal_alpha.a * 2.0 - vec3(1.0);
normal.y *= y_coeff;
normal.z = max(1e-3, normal.z);
normal = normalize(normal);
normal = normalFade(normal, normal_alpha.a);
return normal;
}
vec3 normalUnpack(vec4 normal_alpha, float y_coeff) { if (normal_alpha.a == 0.0 || normal_alpha.xyz == vec3(0.0)) { return vec3(0.0, 0.0, 1.0); } // Attenuation in function of alpha vec3 normal = normal_alpha.xyz/normal_alpha.a * 2.0 - vec3(1.0); normal.y *= y_coeff; normal.z = max(1e-3, normal.z); normal = normalize(normal); normal = normalFade(normal, normal_alpha.a); return normal; }
vec3 normalUnpack(vec4 normal_alpha, float y_coeff) 
{ 
  if (normal_alpha.a == 0.0 || normal_alpha.xyz == vec3(0.0)) { 
    return vec3(0.0, 0.0, 1.0); 
  } 
 
  // Attenuation in function of alpha 
  vec3 normal = normal_alpha.xyz/normal_alpha.a * 2.0 - vec3(1.0); 
  normal.y *= y_coeff; 
  normal.z = max(1e-3, normal.z); 
  normal = normalize(normal); 
  normal = normalFade(normal, normal_alpha.a); 
 
  return normal; 
}

Unpack a normal w/ alpha channel, no Y invertion

vec3 normalUnpack(vec4 normal_alpha)
{
return normalUnpack(normal_alpha, 1.0);
}
vec3 normalUnpack(vec4 normal_alpha) { return normalUnpack(normal_alpha, 1.0); }
vec3 normalUnpack(vec4 normal_alpha) 
{ 
  return normalUnpack(normal_alpha, 1.0); 
}

Compute the tangent space normal from document's height channel

vec3 normalFromHeight(SparseCoord coord, float height_force)
{
// Normal computation using height map
// Determine gradient offset in function of derivatives
vec2 dfd = max(coord.dfdx,coord.dfdy);
dfd = max(dfd,height_texture.size.zw);
vec2 dfdx,dfdy;
textureSparseQueryGrad(dfdx, dfdy, height_texture, coord);
float h_r = textureGrad(height_texture.tex, coord.tex_coord+vec2( dfd.x, 0 ), dfdx, dfdy).r;
float h_l = textureGrad(height_texture.tex, coord.tex_coord+vec2(-dfd.x, 0 ), dfdx, dfdy).r;
float h_t = textureGrad(height_texture.tex, coord.tex_coord+vec2( 0, dfd.y), dfdx, dfdy).r;
float h_b = textureGrad(height_texture.tex, coord.tex_coord+vec2( 0, -dfd.y), dfdx, dfdy).r;
float h_rt = textureGrad(height_texture.tex, coord.tex_coord+vec2( dfd.x, dfd.y), dfdx, dfdy).r;
float h_lt = textureGrad(height_texture.tex, coord.tex_coord+vec2(-dfd.x, dfd.y), dfdx, dfdy).r;
float h_rb = textureGrad(height_texture.tex, coord.tex_coord+vec2( dfd.x, -dfd.y), dfdx, dfdy).r;
float h_lb = textureGrad(height_texture.tex, coord.tex_coord+vec2(-dfd.x, -dfd.y), dfdx, dfdy).r;
vec2 dh_dudv = (0.5 * height_force) / dfd * vec2(
2.0*(h_l-h_r)+h_lt-h_rt+h_lb-h_rb,
2.0*(h_b-h_t)+h_rb-h_rt+h_lb-h_lt);
return normalize(vec3(dh_dudv, HEIGHT_FACTOR));
}
vec3 normalFromHeight(SparseCoord coord, float height_force) { // Normal computation using height map // Determine gradient offset in function of derivatives vec2 dfd = max(coord.dfdx,coord.dfdy); dfd = max(dfd,height_texture.size.zw); vec2 dfdx,dfdy; textureSparseQueryGrad(dfdx, dfdy, height_texture, coord); float h_r = textureGrad(height_texture.tex, coord.tex_coord+vec2( dfd.x, 0 ), dfdx, dfdy).r; float h_l = textureGrad(height_texture.tex, coord.tex_coord+vec2(-dfd.x, 0 ), dfdx, dfdy).r; float h_t = textureGrad(height_texture.tex, coord.tex_coord+vec2( 0, dfd.y), dfdx, dfdy).r; float h_b = textureGrad(height_texture.tex, coord.tex_coord+vec2( 0, -dfd.y), dfdx, dfdy).r; float h_rt = textureGrad(height_texture.tex, coord.tex_coord+vec2( dfd.x, dfd.y), dfdx, dfdy).r; float h_lt = textureGrad(height_texture.tex, coord.tex_coord+vec2(-dfd.x, dfd.y), dfdx, dfdy).r; float h_rb = textureGrad(height_texture.tex, coord.tex_coord+vec2( dfd.x, -dfd.y), dfdx, dfdy).r; float h_lb = textureGrad(height_texture.tex, coord.tex_coord+vec2(-dfd.x, -dfd.y), dfdx, dfdy).r; vec2 dh_dudv = (0.5 * height_force) / dfd * vec2( 2.0*(h_l-h_r)+h_lt-h_rt+h_lb-h_rb, 2.0*(h_b-h_t)+h_rb-h_rt+h_lb-h_lt); return normalize(vec3(dh_dudv, HEIGHT_FACTOR)); }
vec3 normalFromHeight(SparseCoord coord, float height_force) 
{ 
  // Normal computation using height map 
 
  // Determine gradient offset in function of derivatives 
  vec2 dfd = max(coord.dfdx,coord.dfdy); 
  dfd = max(dfd,height_texture.size.zw); 
 
  vec2 dfdx,dfdy; 
  textureSparseQueryGrad(dfdx, dfdy, height_texture, coord); 
  float h_r  = textureGrad(height_texture.tex, coord.tex_coord+vec2( dfd.x,  0    ), dfdx, dfdy).r; 
  float h_l  = textureGrad(height_texture.tex, coord.tex_coord+vec2(-dfd.x,  0    ), dfdx, dfdy).r; 
  float h_t  = textureGrad(height_texture.tex, coord.tex_coord+vec2(     0,  dfd.y), dfdx, dfdy).r; 
  float h_b  = textureGrad(height_texture.tex, coord.tex_coord+vec2(     0, -dfd.y), dfdx, dfdy).r; 
  float h_rt = textureGrad(height_texture.tex, coord.tex_coord+vec2( dfd.x,  dfd.y), dfdx, dfdy).r; 
  float h_lt = textureGrad(height_texture.tex, coord.tex_coord+vec2(-dfd.x,  dfd.y), dfdx, dfdy).r; 
  float h_rb = textureGrad(height_texture.tex, coord.tex_coord+vec2( dfd.x, -dfd.y), dfdx, dfdy).r; 
  float h_lb = textureGrad(height_texture.tex, coord.tex_coord+vec2(-dfd.x, -dfd.y), dfdx, dfdy).r; 
 
  vec2 dh_dudv = (0.5 * height_force) / dfd * vec2( 
    2.0*(h_l-h_r)+h_lt-h_rt+h_lb-h_rb, 
    2.0*(h_b-h_t)+h_rb-h_rt+h_lb-h_lt); 
 
  return normalize(vec3(dh_dudv, HEIGHT_FACTOR)); 
}

Helper to compute the tangent space normal from base normal and a height value, and an optional detail normal.

vec3 getTSNormal(SparseCoord coord, vec3 normalFromHeight)
{
vec3 normal = normalBlendOriented(
normalUnpack(textureSparse(base_normal_texture, coord), base_normal_y_coeff),
normalFromHeight);
if (normal_texture.is_set) {
vec3 channelNormal = normalUnpack(textureSparse(normal_texture, coord));
if (normal_blending_mode == BlendingMode_Replace) {
normal = normalBlendOriented(normalFromHeight, channelNormal);
} else if (normal_blending_mode == BlendingMode_NM_Combine) {
normal = normalBlendOriented(normal, channelNormal);
}
}
return normal;
}
vec3 getTSNormal(SparseCoord coord, vec3 normalFromHeight) { vec3 normal = normalBlendOriented( normalUnpack(textureSparse(base_normal_texture, coord), base_normal_y_coeff), normalFromHeight); if (normal_texture.is_set) { vec3 channelNormal = normalUnpack(textureSparse(normal_texture, coord)); if (normal_blending_mode == BlendingMode_Replace) { normal = normalBlendOriented(normalFromHeight, channelNormal); } else if (normal_blending_mode == BlendingMode_NM_Combine) { normal = normalBlendOriented(normal, channelNormal); } } return normal; }
vec3 getTSNormal(SparseCoord coord, vec3 normalFromHeight) 
{ 
  vec3 normal = normalBlendOriented( 
    normalUnpack(textureSparse(base_normal_texture, coord), base_normal_y_coeff), 
    normalFromHeight); 
 
  if (normal_texture.is_set) { 
    vec3 channelNormal = normalUnpack(textureSparse(normal_texture, coord)); 
    if (normal_blending_mode == BlendingMode_Replace) { 
      normal = normalBlendOriented(normalFromHeight, channelNormal); 
    } else if (normal_blending_mode == BlendingMode_NM_Combine) { 
      normal = normalBlendOriented(normal, channelNormal); 
    } 
  } 
 
  return normal; 
}

Helper to compute the tangent space normal from base normal and height, and an optional detail normal.

vec3 getTSNormal(SparseCoord coord)
{
float height_force = 1.0;
vec3 normalH = normalFromHeight(coord, height_force);
return getTSNormal(coord, normalH);
}
vec3 getTSNormal(SparseCoord coord) { float height_force = 1.0; vec3 normalH = normalFromHeight(coord, height_force); return getTSNormal(coord, normalH); }
vec3 getTSNormal(SparseCoord coord) 
{ 
  float height_force = 1.0; 
  vec3 normalH = normalFromHeight(coord, height_force); 
  return getTSNormal(coord, normalH); 
}

Helper to compute the world space normal from tangent space base normal.

vec3 computeWSBaseNormal(SparseCoord coord, vec3 tangent, vec3 bitangent, vec3 normal)
{
vec3 normal_vec = normalUnpack(textureSparse(normal_texture, coord), base_normal_y_coeff);
return normalize(
normal_vec.x * tangent +
normal_vec.y * bitangent +
normal_vec.z * normal
);
}
vec3 computeWSBaseNormal(SparseCoord coord, vec3 tangent, vec3 bitangent, vec3 normal) { vec3 normal_vec = normalUnpack(textureSparse(normal_texture, coord), base_normal_y_coeff); return normalize( normal_vec.x * tangent + normal_vec.y * bitangent + normal_vec.z * normal ); }
vec3 computeWSBaseNormal(SparseCoord coord, vec3 tangent, vec3 bitangent, vec3 normal) 
{ 
  vec3 normal_vec = normalUnpack(textureSparse(normal_texture, coord), base_normal_y_coeff); 
  return normalize( 
    normal_vec.x * tangent + 
    normal_vec.y * bitangent + 
    normal_vec.z * normal 
  ); 
}

Helper to compute the world space normal from tangent space normal given by getTSNormal helpers, and local frame of the mesh.

vec3 computeWSNormal(SparseCoord coord, vec3 tangent, vec3 bitangent, vec3 normal)
{
vec3 normal_vec = getTSNormal(coord);
return normalize(
normal_vec.x * tangent +
normal_vec.y * bitangent +
normal_vec.z * normal
);
}
vec3 computeWSNormal(SparseCoord coord, vec3 tangent, vec3 bitangent, vec3 normal) { vec3 normal_vec = getTSNormal(coord); return normalize( normal_vec.x * tangent + normal_vec.y * bitangent + normal_vec.z * normal ); }
vec3 computeWSNormal(SparseCoord coord, vec3 tangent, vec3 bitangent, vec3 normal) 
{ 
  vec3 normal_vec = getTSNormal(coord); 
  return normalize( 
    normal_vec.x * tangent + 
    normal_vec.y * bitangent + 
    normal_vec.z * normal 
  ); 
} 
 

Get help faster and easier

New user?