WebGL NormalMap Normal 切线空间

784 阅读5分钟

normalMap展示效果1

  • 根据下面这张图片做解析
    • 第一张是所有片元法线都是垂直于平面的,第二张并不是
    • 第一张是完全递减的,第二张距离光的远处仍有一些片元比旁边亮的 image.png

normalMap展示效果2

  • 法线贴图的优点是可以用一个低精度模型表现出非常高的细节,看起来像高精度模型那样。
  • 改变了物体表面的光照计算方式,不适合用在凹凸起伏较大的物体上,无法实现物体相互遮挡的效果。 image.png

NoramlMap贴图与颜色与向量

  • 偏蓝色,指法线更垂直于片元;偏青色,指法线相对于垂直于表面更往上些 image.png
  • 法线贴图的rgb分别储存切线空间下t(切线,X轴)、b(副切线,Y轴)、n(法线,Z轴)的值 image.png

NoramlMap贴图颜色计算

  • 法线贴图的颜色范围是[0,1],而作为表示位置方向的TBN坐标系则是[-1,1],坐标系NDC的XY也是[-1,1]
  • 法线贴图转TBN坐标系
// 将normal法线贴图转换为范围[-1,1]
normal = texture2D( normalMap, vUv ).xyz * 2.0 - 1.0; //贴图获得法线 [-1,1]

TBN矩阵(切线空间)

  • 实际情况计算出的TBN矩阵往往不是正交矩阵,需要对这个矩阵进行格拉姆-施密特正交化过程(Gram-Schmidt process)来进行正交化 根据shader想想 image.png
//顶点着色器部分
normalMatrix = transpose(inverse(normalMatrix));
vNormal = normalize(normalMatrix * v3Nor);
//片元着色器部分
vec3 randomVec = texture2D(noiseTexture, vUv * 10.0).xyz;
vec3 tangent = normalize(randomVec - vNormal * dot(randomVec, vNormal));
vec3 bitangent = cross(vNormal, tangent);
mat3 TBN = mat3(tangent, bitangent, vNormal);

NormalMap的颜色解析

  • 计算法向量的一种算法,导数(dFdx/dFdy)求出每个像素在插值化传值过来的点的变化率当成一个法线
  • 查看这个normal material
    • 可以用取色工具来知道一个点的rgb(我用的是QQ截图)
    • rgb里凡是小于255/2的,都会与屏幕空间xyz轴方向相反 image.png

Normal

  • Normal的测试语句
gl_FragColor=vec4(packNormalToRGB(normal),1.0);
  • Normal另一个方向(doubleside)
normal = normal * ( float( gl_FrontFacing ) * 2.0 - 1.0 );
#ifdef USE_TANGENT
    vec3 tangent = normalize( vTangent );
    vec3 bitangent = normalize( vBitangent );
    #ifdef DOUBLE_SIDED
        tangent = tangent * ( float( gl_FrontFacing ) * 2.0 - 1.0 );
        bitangent = bitangent * ( float( gl_FrontFacing ) * 2.0 - 1.0 );
    #endif
    #if defined( TANGENTSPACE_NORMALMAP ) || defined( USE_CLEARCOAT_NORMALMAP ) || defined (ANISOTROPY)
        mat3 vTBN = mat3( tangent, bitangent, normal );
    #endif
#endif
  • NormalMaterial的shader代码
#version 300 es
#define varying in
out highp vec4 pc_fragColor;
#define gl_FragColor pc_fragColor
#define gl_FragDepthEXT gl_FragDepth
#define texture2D texture
#define textureCube texture
#define texture2DProj textureProj
#define texture2DLodEXT textureLod
#define texture2DProjLodEXT textureProjLod
#define textureCubeLodEXT textureLod
#define texture2DGradEXT textureGrad
#define texture2DProjGradEXT textureProjGrad
#define textureCubeGradEXT textureGrad
precision highp float;
precision highp int;
#define HIGH_PRECISION
#define SHADER_NAME MeshNormalMaterial
#define GAMMA_FACTOR 2
#define DOUBLE_SIDED
uniform mat4 viewMatrix;
uniform vec3 cameraPosition;
uniform bool isOrthographic;
vec4 LinearToLinear( in vec4 value ) {
    return value;
}
vec4 GammaToLinear( in vec4 value, in float gammaFactor ) {
    return vec4( pow( value.rgb, vec3( gammaFactor ) ), value.a );
}
vec4 LinearToGamma( in vec4 value, in float gammaFactor ) {
    return vec4( pow( value.rgb, vec3( 1.0 / gammaFactor ) ), value.a );
}
vec4 sRGBToLinear( in vec4 value ) {
    return vec4( mix( pow( value.rgb * 0.9478672986 + vec3( 0.0521327014 ), vec3( 2.4 ) ), value.rgb * 0.0773993808, vec3( lessThanEqual( value.rgb, vec3( 0.04045 ) ) ) ), value.a );
}
vec4 LinearTosRGB( in vec4 value ) {
    return vec4( mix( pow( value.rgb, vec3( 0.41666 ) ) * 1.055 - vec3( 0.055 ), value.rgb * 12.92, vec3( lessThanEqual( value.rgb, vec3( 0.0031308 ) ) ) ), value.a );
}
vec4 RGBEToLinear( in vec4 value ) {
    return vec4( value.rgb * exp2( value.a * 255.0 - 128.0 ), 1.0 );
}
vec4 LinearToRGBE( in vec4 value ) {
    float maxComponent = max( max( value.r, value.g ), value.b );
    float fExp = clamp( ceil( log2( maxComponent ) ), -128.0, 127.0 );
    return vec4( value.rgb / exp2( fExp ), ( fExp + 128.0 ) / 255.0 );
}
vec4 RGBMToLinear( in vec4 value, in float maxRange ) {
    return vec4( value.rgb * value.a * maxRange, 1.0 );
}
vec4 LinearToRGBM( in vec4 value, in float maxRange ) {
    float maxRGB = max( value.r, max( value.g, value.b ) );
    float M = clamp( maxRGB / maxRange, 0.0, 1.0 );
    M = ceil( M * 255.0 ) / 255.0;
    return vec4( value.rgb / ( M * maxRange ), M );
}
vec4 RGBDToLinear( in vec4 value, in float maxRange ) {
    return vec4( value.rgb * ( ( maxRange / 255.0 ) / value.a ), 1.0 );
}
vec4 LinearToRGBD( in vec4 value, in float maxRange ) {
    float maxRGB = max( value.r, max( value.g, value.b ) );
    float D = max( maxRange / maxRGB, 1.0 );
    D = clamp( floor( D ) / 255.0, 0.0, 1.0 );
    return vec4( value.rgb * ( D * ( 255.0 / maxRange ) ), D );
}
const mat3 cLogLuvM = mat3( 0.2209, 0.3390, 0.4184, 0.1138, 0.6780, 0.7319, 0.0102, 0.1130, 0.2969 );
vec4 LinearToLogLuv( in vec4 value ) {
    vec3 Xp_Y_XYZp = cLogLuvM * value.rgb;
    Xp_Y_XYZp = max( Xp_Y_XYZp, vec3( 1e-6, 1e-6, 1e-6 ) );
    vec4 vResult;
    vResult.xy = Xp_Y_XYZp.xy / Xp_Y_XYZp.z;
    float Le = 2.0 * log2(Xp_Y_XYZp.y) + 127.0;
    vResult.w = fract( Le );
    vResult.z = ( Le - ( floor( vResult.w * 255.0 ) ) / 255.0 ) / 255.0;
    return vResult;
}
const mat3 cLogLuvInverseM = mat3( 6.0014, -2.7008, -1.7996, -1.3320, 3.1029, -5.7721, 0.3008, -1.0882, 5.6268 );
vec4 LogLuvToLinear( in vec4 value ) {
    float Le = value.z * 255.0 + value.w;
    vec3 Xp_Y_XYZp;
    Xp_Y_XYZp.y = exp2( ( Le - 127.0 ) / 2.0 );
    Xp_Y_XYZp.z = Xp_Y_XYZp.y / value.y;
    Xp_Y_XYZp.x = value.x * Xp_Y_XYZp.z;
    vec3 vRGB = cLogLuvInverseM * Xp_Y_XYZp.rgb;
    return vec4( max( vRGB, 0.0 ), 1.0 );
}
vec4 linearToOutputTexel( vec4 value ) {
    return LinearToLinear( value );
}
#define NORMAL
uniform float opacity;
#if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || defined( TANGENTSPACE_NORMALMAP )
    varying vec3 vViewPosition;
#endif
#ifndef FLAT_SHADED
    varying vec3 vNormal;
    #ifdef USE_TANGENT
        varying vec3 vTangent;
        varying vec3 vBitangent;
    #endif
#endif
vec3 packNormalToRGB( const in vec3 normal ) {
    return normalize( normal ) * 0.5 + 0.5;
}
vec3 unpackRGBToNormal( const in vec3 rgb ) {
    return 2.0 * rgb.xyz - 1.0;
}
const float PackUpscale = 256. / 255.;
const float UnpackDownscale = 255. / 256.;
const vec3 PackFactors = vec3( 256. * 256. * 256., 256. * 256., 256. );
const vec4 UnpackFactors = UnpackDownscale / vec4( PackFactors, 1. );
const float ShiftRight8 = 1. / 256.;
vec4 packDepthToRGBA( const in float v ) {
    vec4 r = vec4( fract( v * PackFactors ), v );
    r.yzw -= r.xyz * ShiftRight8;
    return r * PackUpscale;
}
float unpackRGBAToDepth( const in vec4 v ) {
    return dot( v, UnpackFactors );
}
vec4 pack2HalfToRGBA( vec2 v ) {
    vec4 r = vec4( v.x, fract( v.x * 255.0 ), v.y, fract( v.y * 255.0 ));
    return vec4( r.x - r.y / 255.0, r.y, r.z - r.w / 255.0, r.w);
}
vec2 unpackRGBATo2Half( vec4 v ) {
    return vec2( v.x + ( v.y / 255.0 ), v.z + ( v.w / 255.0 ) );
}
float viewZToOrthographicDepth( const in float viewZ, const in float near, const in float far ) {
    return ( viewZ + near ) / ( near - far );
}
float orthographicDepthToViewZ( const in float linearClipZ, const in float near, const in float far ) {
    return linearClipZ * ( near - far ) - near;
}
float viewZToPerspectiveDepth( const in float viewZ, const in float near, const in float far ) {
    return (( near + viewZ ) * far ) / (( far - near ) * viewZ );
}
float perspectiveDepthToViewZ( const in float invClipZ, const in float near, const in float far ) {
    return ( near * far ) / ( ( far - near ) * invClipZ - far );
}
#if ( defined( USE_UV ) && ! defined( UVS_VERTEX_ONLY ) )
    varying vec2 vUv;
#endif
////////////////////////////////
//有bumpMap的情况
#ifdef USE_BUMPMAP  
    uniform sampler2D bumpMap;
    uniform float bumpScale;
    vec2 dHdxy_fwd() {
        vec2 dSTdx = dFdx( vUv );
        vec2 dSTdy = dFdy( vUv );
        float Hll = bumpScale * texture2D( bumpMap, vUv ).x;
        float dBx = bumpScale * texture2D( bumpMap, vUv + dSTdx ).x - Hll;
        float dBy = bumpScale * texture2D( bumpMap, vUv + dSTdy ).x - Hll;
        return vec2( dBx, dBy );
    }
    vec3 perturbNormalArb( vec3 surf_pos, vec3 surf_norm, vec2 dHdxy ) {
        vec3 vSigmaX = vec3( dFdx( surf_pos.x ), dFdx( surf_pos.y ), dFdx( surf_pos.z ) );
        vec3 vSigmaY = vec3( dFdy( surf_pos.x ), dFdy( surf_pos.y ), dFdy( surf_pos.z ) );
        vec3 vN = surf_norm;
        vec3 R1 = cross( vSigmaY, vN );
        vec3 R2 = cross( vN, vSigmaX );
        float fDet = dot( vSigmaX, R1 );
        fDet *= ( float( gl_FrontFacing ) * 2.0 - 1.0 );
        vec3 vGrad = sign( fDet ) * ( dHdxy.x * R1 + dHdxy.y * R2 );
        return normalize( abs( fDet ) * surf_norm - vGrad );
    }
#endif

//Normal Map
#ifdef USE_NORMALMAP
    uniform sampler2D normalMap;
    uniform vec2 normalScale;
#endif
#ifdef OBJECTSPACE_NORMALMAP
    uniform mat3 normalMatrix;
#endif
#if ! defined ( USE_TANGENT ) && ( defined ( TANGENTSPACE_NORMALMAP ) || defined ( USE_CLEARCOAT_NORMALMAP ) )
    vec3 perturbNormal2Arb( vec3 eye_pos, vec3 surf_norm, vec3 mapN ) {
        vec3 q0 = vec3( dFdx( eye_pos.x ), dFdx( eye_pos.y ), dFdx( eye_pos.z ) );
        vec3 q1 = vec3( dFdy( eye_pos.x ), dFdy( eye_pos.y ), dFdy( eye_pos.z ) );
        vec2 st0 = dFdx( vUv.st );
        vec2 st1 = dFdy( vUv.st );
        float scale = sign( st1.t * st0.s - st0.t * st1.s );
        vec3 S = ( q0 * st1.t - q1 * st0.t ) * scale;
	vec3 T = ( - q0 * st1.s + q1 * st0.s ) * scale;
	//https://github.com/mrdoob/three.js/issues/17559
	if (S != vec3(0.0))
	  S = normalize(S);
	if (T != vec3(0.0))
	  T = normalize(T);
        vec3 N = normalize( surf_norm );
        mat3 tsn = mat3( S, T, N );
        mapN.xy *= ( float( gl_FrontFacing ) * 2.0 - 1.0 );
        return normalize( tsn * mapN );
    }
#endif
#if defined( USE_LOGDEPTHBUF ) && defined( USE_LOGDEPTHBUF_EXT )
    uniform float logDepthBufFC;
    varying float vFragDepth;
    varying float vIsPerspective;
#endif
#if 0 > 0
    varying vec3 vClipPosition;
    uniform vec4 clippingPlanes[ 0 ];
#endif
  • normalMaterial main函数
void main() {
    #if 0 > 0
        vec4 plane;
        #if 0 < 0
            bool clipped = true;
            if ( clipped ) discard;
        #endif
    #endif
    #if defined( USE_LOGDEPTHBUF ) && defined( USE_LOGDEPTHBUF_EXT )
        gl_FragDepthEXT = vIsPerspective == 0.0 ? gl_FragCoord.z : log2( vFragDepth ) * logDepthBufFC * 0.5;
    #endif
    #ifdef FLAT_SHADED
        vec3 fdx = vec3( dFdx( vViewPosition.x ), dFdx( vViewPosition.y ), dFdx( vViewPosition.z ) );
        vec3 fdy = vec3( dFdy( vViewPosition.x ), dFdy( vViewPosition.y ), dFdy( vViewPosition.z ) );
        vec3 normal = normalize( cross( fdx, fdy ) );
    #else
        vec3 normal = normalize( vNormal );
        #ifdef DOUBLE_SIDED
            normal = normal * ( float( gl_FrontFacing ) * 2.0 - 1.0 );
        #endif
        #ifdef USE_TANGENT
            vec3 tangent = normalize( vTangent );
            vec3 bitangent = normalize( vBitangent );
            #ifdef DOUBLE_SIDED
                tangent = tangent * ( float( gl_FrontFacing ) * 2.0 - 1.0 );
                bitangent = bitangent * ( float( gl_FrontFacing ) * 2.0 - 1.0 );
            #endif
            #if defined( TANGENTSPACE_NORMALMAP ) || defined( USE_CLEARCOAT_NORMALMAP )
                mat3 vTBN = mat3( tangent, bitangent, normal );
            #endif
        #endif
    #endif
    vec3 geometryNormal = normal;
    
    //三种计算方式
    #ifdef OBJECTSPACE_NORMALMAP //物体空间 计算
        normal = texture2D( normalMap, vUv ).xyz * 2.0 - 1.0;  //贴图获得法线
        #ifdef FLIP_SIDED //
            normal = - normal;
        #endif
        #ifdef DOUBLE_SIDED  //doubleside
            normal = normal * ( float( gl_FrontFacing ) * 2.0 - 1.0 );
        #endif
        normal = normalize( normalMatrix * normal );
    #elif defined( TANGENTSPACE_NORMALMAP ) //切线空间计算
        vec3 mapN = texture2D( normalMap, vUv ).xyz * 2.0 - 1.0;
        mapN.xy *= normalScale;
        #ifdef USE_TANGENT  //有的话
            normal = normalize( vTBN * mapN );
        #else  //没有的话
            normal = perturbNormal2Arb( -vViewPosition, normal, mapN );
        #endif
    #elif defined( USE_BUMPMAP )  //bump 计算
        normal = perturbNormalArb( -vViewPosition, normal, dHdxy_fwd() );
    #endif
    
}