Three PBR 环境光照 改版

681 阅读3分钟

间接光照,diffuse

  • 总公式:材质diffuse * 辐照度(ibl间接光) * 亮度
  • 与光源的间接光无关
void IndirectDiffuse_Physical( const in vec3 specularDFG, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {
    vec3 worldNormal = inverseTransformDirection( geometry.normal, viewMatrix );
    worldNormal = envMapRotationMat * worldNormal;
    vec3 irradiance = computeDiffuseSPH( worldNormal, envMapSH );
    reflectedLight.indirectDiffuse += material.diffuseColor * irradiance * envBrightness;
    //osg.js color += uBrightness * albedo * ao * evaluateDiffuseSphericalHarmonics(normal, view );
    // Filament:
    // reflectedLight.indirectDiffuse += BRDF_Diffuse_Lambert( material.diffuseColor ) * irradiance * envBrightness;
}
  • 用上了球谐函数
//三阶球谐 获取得到的是颜色
vec3 computeDiffuseSPH(vec3 normal, vec3 sphericalHarmonics[9]) {
    float x = normal.x;
    float y = normal.y;
    float z = normal.z;
    vec3 result = (
    sphericalHarmonics[0] +
    sphericalHarmonics[1] * y +
    sphericalHarmonics[2] * z +
    sphericalHarmonics[3] * x +
    sphericalHarmonics[4] * y * x +
    sphericalHarmonics[5] * y * z +
    sphericalHarmonics[6] * (3.0 * z * z - 1.0) +
    sphericalHarmonics[7] * (z * x) +
    sphericalHarmonics[8] * (x*x - y*y)
    );
    return max(result, vec3(0.0));
}
//旋转矩阵
vec3 inverseTransformDirection( in vec3 dir, in mat4 matrix ) {
    // dir can be either a direction vector or a normal vector
    // upper-left 3x3 of matrix is assumed to be orthogonal
    return normalize( ( vec4( dir, 0.0 ) * matrix ).xyz );
}
//filament 里面用的 
vec3 BRDF_Diffuse_Lambert( const in vec3 diffuseColor ) {
    return RECIPROCAL_PI * diffuseColor;
}

间接光照,specular

  • brdflut * prefiliterEnv
  • 这里进行了多重散射的能量守恒
void IndirectSpecular_Physical( const in vec3 specularDFG, const in vec3 radiance, const in vec3 irradiance, const in vec3 clearcoatRadiance, const in vec3 sheenRadiance, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight) {
    reflectedLight.indirectSpecular = specularDFG * radiance * envBrightness;
    // EnergyCompensation

    reflectedLight.indirectSpecular *= material.energyCompensation;
    reflectedLight.indirectDiffuse *= 1. - specularDFG;

    // Sheen
    #ifdef USE_SHEEN
        reflectedLight.indirectDiffuse *= material.sheenScaling;
        reflectedLight.indirectSpecular *= material.sheenScaling;
        vec3 reflectance = material.sheenDFG * material.sheenColor;
        reflectedLight.indirectSpecular += reflectance * sheenRadiance;
    #endif

    // ClearCoat
    #ifdef CLEARCOAT
        float clearCoatNoV = saturate( dot( geometry.clearcoatNormal, geometry.viewDir ) );
        // The clear coat layer assumes an IOR of 1.5 (4% reflectance)

        vec3 materialSpecular = vec3(0.04);
        float materialF90 = 1.0;
        float Fc = F_Schlick(clearCoatNoV, materialSpecular.x, materialF90) * material.clearcoat;
        float attenuation = 1.0 - Fc;
        reflectedLight.indirectDiffuse *= attenuation;
        reflectedLight.indirectSpecular *= attenuation;
        reflectedLight.indirectSpecular += clearcoatRadiance * Fc * envBrightness;
    #endif
}
  • energyCompensation变量
    • 能量守恒,有点搞不懂里面的dfg.xxx dfg.yyy 为啥这么用了
void getEnergyCompensationParams(inout PhysicalMaterial material) {
    // Energy compensation for multiple scattering in a microfacet model
    // See "Multiple-Scattering Microfacet BSDFs with the Smith Model"
    material.energyCompensation = 1.0 + material.specularColor * (1.0 / material.dfg.y - 1.0);
}
  • specularDFG变量 跟一般的brdflut不一样
//vec3 specularDFG = getSpecularDFG(material);
vec3 getSpecularDFG(const in PhysicalMaterial material) {
    return mix(material.dfg.xxx, material.dfg.yyy, material.specularColor);
}
//material.dfg = prefilteredDFG(material.specularRoughness, NoV);
vec3 prefilteredDFG(float roughness, float NoV) {
    return sRGBToLinear(texture2D(envMapLUTMap, vec2(NoV, roughness))).rgb;
}
  • radiance变量
    • 就是 prefilteredColor 预计算的环境图
vec3 getLightProbeIndirectRadiance( /*const in SpecularLightProbe specularLightProbe, */ const in vec3 viewDir, const in vec3 normal, const in float roughness, const in int maxMIPLevel ) {
    vec3 R = reflect(-viewDir, normal); //有其他计算方法
    // From Sebastien Lagarde Moving Frostbite to PBR page 69
    vec3 dominantR = getSpecularDominantDir(normal, R, roughness * roughness);
    // Sync viewMatrix
    vec3 dir = inverseTransformDirection( dominantR, viewMatrix );
    // EnvironmentTransform
    dir = envMapRotationMat * dir;
    vec3 prefilteredColor = prefilterEnvMap(roughness, dir);
    // marmoset tricks
    prefilteredColor *= occlusionHorizon( dominantR, normal );
    #if defined( ANISOTROPY ) && defined( TYPE_SILK )
        return prefilteredColor * envMapIntensity * 0.799; // 0.699
    #else
        return prefilteredColor * envMapIntensity;
    #endif
}
// From Sebastien Lagarde Moving Frostbite to PBR page 69
// We have a better approximation of the off specular peak
// but due to the other approximations we found this one performs better.
// N is the normal direction
// R is the mirror vector
// This approximation works fine for G smith correlated and uncorrelated
vec3 getSpecularDominantDir(vec3 N, vec3 R, float realRoughness) {
    // float smoothness = 1.0 - realRoughness;
    // float lerpFactor = smoothness * (sqrt(smoothness) + realRoughness);
    // return mix(N, R, lerpFactor);

    // Filament: getSpecularDominantDirection
    return mix(R, N, realRoughness * realRoughness);
}
vec3 prefilterEnvMap(float roughness, vec3 R) {
    vec3 dir = R;
    float rLinear = sqrt(roughness);
    float lod = rLinear * envMapLODRange[1];
    lod = min(envMapLODRange[0], lod);
    #ifdef CUBEMAP_LOD
        // http://seblagarde.wordpress.com/2012/06/10/amd-cubemapgen-for-physically-based-rendering/
        float scale = 1.0 - exp2(lod) / envMapSize[0];
        vec3 absDir = abs(dir);
        float M = max(max(absDir.x, absDir.y), absDir.z);
        // cubemapSeamlessFixDirection

        if (absDir.x ! = M) dir.x *= scale;
        if (absDir.y ! = M) dir.y *= scale;
        if (absDir.z ! = M) dir.z *= scale;
        // sync Filament's cmgen mirror

        // dir.x = -dir.x;
        return envMapTexelToLinear(textureCubeLodEXT(envMap, dir, lod)).rgb;
    #else
        return envMapTexelToLinear(texturePanoramaLod(envMap, envMapSize, dir, lod, envMapLODRange[0])).rgb;
    #endif
}
float occlusionHorizon( const in vec3 R, const in vec3 normal) {
    // http://marmosetco.tumblr.com/post/81245981087
    // marmoset uses 1.3, we force it to 1.0
    float factor = clamp( 1.0 + dot(R, normal), 0.0, 1.0 );
    return factor * factor;
}

// PanoramaLod Support
// For y up
vec2 normalToPanoramaUV(vec3 dir) {
    float n = length(dir.xz);
    // to avoid bleeding the max(-1.0, dir.x / n) is needed
    vec2 pos = vec2( (n>0.0000001) ? max(-1.0, dir.x / n) : 0.0, dir.y);
    // fix edge bleeding
    if ( pos.x > 0.0 ) pos.x = min( 0.999999, pos.x );
    pos = acos(pos)*0.31830988618; // RECIPROCAL_PI
    pos.x = (dir.z > 0.0) ? pos.x*0.5 : 1.0-(pos.x*0.5);
    // shift u to center the panorama to -z
    pos.x = mod(pos.x-0.25+1.0, 1.0 );
    pos.y = 1.0-pos.y;
    return pos;
}
vec2 computeUVForMipmap(float level, vec2 uv, float size, float maxLOD) {
    // width for level
    float widthForLevel = exp2( maxLOD-level);
    // the height locally for the level in pixel
    // to opimitize a bit we scale down the v by two in the inputs uv
    float heightForLevel = widthForLevel * 0.5;
    // compact version
    float texelSize = 1.0/size;
    vec2 uvSpaceLocal = vec2(1.0) + uv * vec2(widthForLevel - 2.0, heightForLevel - 2.0);
    uvSpaceLocal.y += size - widthForLevel;
    return uvSpaceLocal * texelSize;
}
vec4 texturePanoramaLod(sampler2D tex, vec2 size, vec3 direction, float lod, float maxLOD) {
    vec2 uvBase = normalToPanoramaUV(direction);
    // we scale down v here because it avoid to do twice in sub functions

    // uvBase.y *= 0.5;
    float lod0 = floor(lod);
    vec2 uv0 = computeUVForMipmap(lod0, uvBase, size.x, maxLOD);
    vec4 texel0 = texture2D(tex, uv0.xy);
    float lod1 = ceil(lod);
    vec2 uv1 = computeUVForMipmap(lod1, uvBase, size.x, maxLOD);
    vec4 texel1 = texture2D(tex, uv0.xy);
    return mix(texel0, texel1, fract(lod));
}