PBR全称(Physicallly-Based Rendering),基于物理的渲染。本文将提供一份GLSL实用型着色器。对于理论部分网络上已经有太多文章了。
仓库:https://bitbucket.org/mm_longcheng/mm-vulkan
扣扣交流群:554329899
一、着色器入参问题
对于着色器入参的不同会导致着色器实现的不同
1. 顶点着色器的顶点属性,是否有骨骼蒙皮。
2.片段着色器对于PBR需要的采样器的提供程度不同,而顶点着色器传过来的数据也有不同。
我的方案是将入参形式做一些哈希,得到一些序号组合,然后分别实现它们。
以下是顶点着色器,片段着色器,管线的取名规则
```// Primitive Attribute flag// // a0 | 0 NotNORMAL NotTANGENT | 1 HasNORMAL NotTANGENT | 2 HasNORMAL HasTANGENT |// a1 | 0 NotTEXCOORD0 NotTEXCOORD1 | 1 HasTEXCOORD0 NotTEXCOORD1 | 2 HasTEXCOORD0 HasTEXCOORD1 |// a2 | 0 NotCOLOR0VEC3 NotCOLOR0VEC4 | 1 HasCOLOR0VEC3 NotCOLOR0VEC4 | 2 HasCOLOR0VEC3 HasCOLOR0VEC4 |// a3 | 0 NotJOINTS0 NotJOINTS1 | 1 HasJOINTS0 NotJOINTS1 | 2 HasJOINTS0 HasJOINTS1 |//// Material Texture flag// // m0 has TextureBaseColor// m1 has TextureMetallicRoughness// m2 has TextureNormal// m3 has TextureOcclusion// m4 has TextureEmissive//// Attribute Type flag// // t0 type AttributeTEXCOORD0// t1 type AttributeTEXCOORD1// t2 type AttributeCOLOR0// t3 type AttributeJOINTS0// t4 type AttributeJOINTS1// t5 type AttributeWEIGHTS0// t6 type AttributeWEIGHTS1//// u0 = Primitive mode// d0 = Material double sided// // vertex shader hash: a3a2a1a0// fragment shader hash: a2a1a0-m4m3m2m1m0// pipeline hash: a3a2a1a0-m4m3m2m1m0-t6t5t4t3t2t1t0-u0d0以下是顶点着色器入参格式的哈希规则// Primitive Component Type// NONE 0// UNSIGNED_BYTE 1// UNSIGNED_SHORT 2// FLOAT 3//// POSITION "VEC3" 5126 (FLOAT)//// NORMAL "VEC3" 5126 (FLOAT)//// TANGENT "VEC4" 5126 (FLOAT)//// TEXCOORD_0 "VEC2" 5126 (FLOAT) // 5121 (UNSIGNED_BYTE)normalized // 5123 (UNSIGNED_SHORT)normalized //// COLOR_0 "VEC3" 5126 (FLOAT) // "VEC4" 5121 (UNSIGNED_BYTE)normalized // 5123 (UNSIGNED_SHORT)normalized //// JOINTS_0 "VEC4" 5121 (UNSIGNED_BYTE) // 5123 (UNSIGNED_SHORT) //// WEIGHTS_0 "VEC4" 5126 (FLOAT) // 5121 (UNSIGNED_BYTE)normalized // 5123 (UNSIGNED_SHORT)normalized 顶点着色器 常用81种 取名位无法线无切线 有法线无切线 有法线有切线 3 000F 0000 0001 0002无纹理 有纹理0 有纹理1 3 00F0 0000 0010 0020无颜色 有颜色0vec3 有颜色0vec4 3 0F00 0000 0100 0200无骨骼 有骨骼0 有骨骼1 3 F000 0000 1000 2000片段着色器 常用27种 取名位无法线无切线 有法线无切线 有法线有切线 3 000F 0000 0001 0002无纹理 有纹理0 有纹理1 3 00F0 0000 0010 0020无颜色 有颜色0vec3 有颜色0vec4 3 0F00 0000 0100 0200
```以下是材质参数的哈希规则
```
void
mmVKTFMaterial_MakeLayoutHashId(const struct mmVKTFMaterial* p,char l[2])
{int n = 0;n += (-1 != p->vIndex[mmVKTFTextureBaseColor]);n += (-1 != p->vIndex[mmVKTFTextureMetallicRoughness]);n += (-1 != p->vIndex[mmVKTFTextureNormal]);n += (-1 != p->vIndex[mmVKTFTextureOcclusion]);n += (-1 != p->vIndex[mmVKTFTextureEmissive]);// layout hash: nsprintf(l, "%d", n);
}
```
例子:
我们以加载Cesium_Man.glb为例,以下是gltf中提供的数据
以下是模型的材质和图元信息
```"materials": [{"pbrMetallicRoughness": {"baseColorTexture": {"index": 0,"texCoord": 0},"metallicFactor": 0,"baseColorFactor": [1, 1, 1, 1],"roughnessFactor": 1},"emissiveFactor": [0, 0, 0],"alphaMode": "OPAQUE","doubleSided": false,"name": "Cesium_Man-effect"}],"meshes": [{"primitives": [{"attributes": {"JOINTS_0": 1,"NORMAL": 2,"POSITION": 3,"TEXCOORD_0": 4,"WEIGHTS_0": 5},"indices": 0,"mode": 4,"material": 0}],"name": "Cesium_Man"}],
```以下是从模型加载出来的顶点属性的具体格式
```
VkVertexInputBindingDescription vibd =
{"POSITION"binding 0 unsigned intstride 12 unsigned intinputRate VK_VERTEX_INPUT_RATE_VERTEX (0) VkVertexInputRate"NORMAL"binding 1 unsigned intstride 12 unsigned intinputRate VK_VERTEX_INPUT_RATE_VERTEX (0) VkVertexInputRate"TEXCOORD_0"binding 2 unsigned intstride 8 unsigned intinputRate VK_VERTEX_INPUT_RATE_VERTEX (0) VkVertexInputRate"JOINTS_0"binding 3 unsigned intstride 8 unsigned intinputRate VK_VERTEX_INPUT_RATE_VERTEX (0) VkVertexInputRate"WEIGHTS_0"binding 4 unsigned intstride 16 unsigned intinputRate VK_VERTEX_INPUT_RATE_VERTEX (0) VkVertexInputRate
}VkVertexInputAttributeDescription viad =
{"POSITION"location 0 unsigned intbinding 0 unsigned intformat VK_FORMAT_R32G32B32_SFLOAT (106) VkFormatoffset 0 unsigned int"NORMAL"location 1 unsigned intbinding 1 unsigned intformat VK_FORMAT_R32G32B32_SFLOAT (106) VkFormatoffset 0 unsigned int"TEXCOORD_0"location 2 unsigned intbinding 2 unsigned intformat VK_FORMAT_R32G32_SFLOAT (103) VkFormatoffset 0 unsigned int"JOINTS_0"location 3 unsigned intbinding 3 unsigned intformat VK_FORMAT_R16G16B16A16_UINT (95) VkFormatoffset 0 unsigned int"WEIGHTS_0"location 4 unsigned intbinding 4 unsigned intformat VK_FORMAT_R32G32B32A32_SFLOAT (109) VkFormatoffset 0 unsigned int
}
```
我们得到了入参的哈希信息
它有骨骼动画 1
没有颜色入参 0
有纹理0 1
有法线无切线 1它有baseColor基础漫反射贴图 1
没有metallicRoughnessTexture物理属性贴图 0
没有normalTexture法线贴图 0
没有occlusionTexture遮蔽贴图 0
没有emissiveTexture高光贴图 0VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST 3
没有使用doubleSided双面渲染 0Pipeline lName: mmVKTFMaterial-1
Pipeline vName: spiv/gltf/3d-1011.vert.spv
Pipeline fName: spiv/gltf/3d-011-00001.frag.spv
Pipeline xName: 3d-1011-00001-0302003-30
二、PBR着色器函数
网上关于这个的其实很多,但是并没有把它们拼合成一个可用的版本,都在罗列理论,函数比较零散。
下面是顶点着色器和片段着色器的部分,注意我们是通过一些开关来处理不同入参形式。
顶点着色器 3d-1011.vert
```
#version 450#extension GL_GOOGLE_include_directive : enable#include "vs-UBOScene.glsl"
#include "vs-PushConstants.glsl"
#include "vs-UBOSkin.glsl"layout (location = 0) in vec3 a_position;
layout (location = 1) in vec3 a_normal;
layout (location = 2) in vec2 a_texcoord[1];
layout (location = 3) in uvec4 a_joints_0;
layout (location = 4) in vec4 a_weights_0;layout (location = 0) out vec3 v_position;
layout (location = 1) out vec3 v_normal;
layout (location = 2) out vec2 v_texcoord[1];out gl_PerVertex
{vec4 gl_Position;
};void main()
{// world space.mat4 skinMat = mat4( 0.0 );skinMat += a_weights_0.x * uboSkin.joints[a_joints_0.x];skinMat += a_weights_0.y * uboSkin.joints[a_joints_0.y];skinMat += a_weights_0.z * uboSkin.joints[a_joints_0.z];skinMat += a_weights_0.w * uboSkin.joints[a_joints_0.w];mat4 modelMat = constants.model * skinMat;mat3 normalMat = transpose(inverse(mat3(modelMat)));v_position = vec3(modelMat * vec4(a_position, 1.0));v_normal = normalize(normalMat * a_normal);v_texcoord[0] = a_texcoord[0];gl_Position = uboScene.projection * uboScene.view * vec4(v_position, 1.0);
}
```片段着色器 3d-011-00001.frag
```
#version 450#extension GL_GOOGLE_include_directive : enable// Interface protocol macro definition.
#define HAS_VaryingNormal
// #define HAS_VaryingTBN
// #define HAS_VaryingColor
#define HAS_VaryingTexcoord// Material texture macro definition.
#define HAS_TextureBaseColor
// #define HAS_TextureMetallicRoughness
// #define HAS_TextureNormal
// #define HAS_TextureOcclusion
// #define HAS_TextureEmissive// Vertex input Protocol.
layout (location = 0) in vec3 v_position;
layout (location = 1) in vec3 v_normal;
layout (location = 2) in vec2 v_texcoord[1];// Material sampler Protocol.
layout (set = 1, binding = 1) uniform sampler2D baseColorTexture;#include "fs-Main.glsl"
```
下面是include的实际部分
vs-UBOScene.glsl
```
layout (set = 0, binding = 0, std140) uniform UBOScene
{mat4 projection;mat4 view;
} uboScene;
```vs-PushConstants.glsl
```
layout (push_constant, std140) uniform PushConstants
{mat4 normal;mat4 model;
} constants;
```vs-UBOSkin.glsl
```
layout (set = 2, binding = 0, std430) readonly buffer UBOSkin
{mat4 joints[];
} uboSkin;
```fs-Main.glsl
```
#include "fs-Constant.glsl"
#include "fs-PerturbNormal.glsl"
#include "fs-SRGB.glsl"
#include "fs-Lights.glsl"
#include "fs-UBOScene.glsl"
#include "fs-UBOMaterial.glsl"
#include "fs-BRDF.glsl"
#include "fs-ToneMap.glsl"
#include "fs-AlphaMode.glsl"layout (location = 0) out vec4 outColor;void main()
{// view vector// world space.vec3 V = normalize(uboScene.camera - v_position);// normal vectorvec3 N = GetNormal();vec4 baseColor = GetBaseColor();baseColor = AlphaModeColor(baseColor);#ifndef HAS_TextureMetallicRoughnessfloat metallic = uboMaterial.metallicFactor;float roughness = uboMaterial.roughnessFactor;
#elsevec2 value = GetMetallicRoughness();float metallic = value.x;float roughness = value.y;
#endif//HAS_TextureMetallicRoughness vec3 color = vec3(0.0);vec3 ambient = uboScene.ambient;vec3 ambientColor = baseColor.rgb * ambient;const vec3 black = vec3(0.0);vec3 diffuseColor = mix(baseColor.rgb, black, metallic);const float f90 = 1.0;vec3 f0 = mix(vec3(0.04), baseColor.rgb, metallic);int i;for (i = 0;i < uboScene.lightCount;++i){UBOLight light = uboLights.lights[i];vec3 L = GetPointToLight(light, v_position);vec3 intensity = GetLighIntensity(light, L);vec3 material = BRDF_Material(L, V, N, diffuseColor, f0, f90, roughness);color += material * intensity;}color += ambientColor;/*vec3 lightColor = uboScene.lightColor;float distance = length(L);float attenuation = 1.0 / (distance * distance);vec3 radiance = lightColor * attenuation;vec3 material = BRDF_Material(L, V, N, diffuseColor, f0, f90, roughness);color = material * radiance + ambientColor;*/#ifdef HAS_TextureOcclusioncolor = GetOcclusionColor(color);
#endif//HAS_TextureOcclusion#ifdef HAS_TextureEmissivecolor += GetEmissiveColor();
#endif//HAS_TextureEmissive// Tone mappingoutColor = vec4(toneMap(color), baseColor.a);
}
```fs-Constant.glsl
```
#define MM_E 2.71828182845904523536028747135266250 /* e */
#define MM_LOG2E 1.44269504088896340735992468100189214 /* log2(e) */
#define MM_LOG10E 0.434294481903251827651128918916605082 /* log10(e) */
#define MM_LN2 0.693147180559945309417232121458176568 /* loge(2) */
#define MM_LN10 2.30258509299404568401799145468436421 /* loge(10) */
#define MM_PI 3.14159265358979323846264338327950288 /* pi */
#define MM_PI_DIV_2 1.57079632679489661923132169163975144 /* pi/2 */
#define MM_PI_DIV_4 0.785398163397448309615660845819875721 /* pi/4 */
#define MM_1_DIV_PI 0.318309886183790671537767526745028724 /* 1/pi */
#define MM_2_DIV_PI 0.636619772367581343075535053490057448 /* 2/pi */
#define MM_2_DIV_SQRTPI 1.12837916709551257389615890312154517 /* 2/sqrt(pi) */
#define MM_SQRT2 1.41421356237309504880168872420969808 /* sqrt(2) */
#define MM_1_DIV_SQRT2 0.707106781186547524400844362104849039 /* 1/sqrt(2) */#define MM_EPSILON 1e-6#define saturate( a ) clamp( a, 0.0, 1.0 )#define pow2( a ) (a * a)const float MM_GAMMA = 2.2;
const float MM_1_DIV_GAMMA = 1.0 / MM_GAMMA;// enum mmGLTFAlphaMode_t
const int mmGLTFAlphaModeOpaque = 0;// "OPAQUE"
const int mmGLTFAlphaModeMask = 1;// "MASK"
const int mmGLTFAlphaModeBlend = 2;// "BLEND"
```fs-PerturbNormal.glsl
```
// Bump Mapping.
// Bump Mapping Unparametrized Surfaces on the GPU by Morten S. Mikkelsen
// http://api.unrealengine.com/attachments/Engine/Rendering/LightingAndShadows/BumpMappingWithoutTangentSpace/mm_sfgrad_bump.pdf
vec3 PerturbNormalArb( vec3 surf_pos, vec3 surf_norm, vec2 dHdxy, float faceDirection)
{// Workaround for Adreno 3XX dFd*( vec3 ) bug. See #9988vec3 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; // normalizedvec3 R1 = cross( vSigmaY, vN );vec3 R2 = cross( vN, vSigmaX );float fDet = dot( vSigmaX, R1 ) * faceDirection;vec3 vGrad = sign( fDet ) * ( dHdxy.x * R1 + dHdxy.y * R2 );return normalize( abs( fDet ) * surf_norm - vGrad );
}// Normal Mapping Without Precomputed Tangents
// http://www.thetenthplanet.de/archives/1180
vec3 PerturbNormal2Arb(vec3 eye_pos, vec3 surf_norm, vec3 mapN, float faceDirection, vec2 vUv)
{// Workaround for Adreno 3XX dFd*( vec3 ) bug. See #9988vec3 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 );vec3 N = surf_norm; // normalizedvec3 q1perp = cross( q1, N );vec3 q0perp = cross( N, q0 );vec3 T = q1perp * st0.x + q0perp * st1.x;vec3 B = q1perp * st0.y + q0perp * st1.y;float det = max( dot( T, T ), dot( B, B ) );float scale = ( det == 0.0 ) ? 0.0 : faceDirection * inversesqrt( det );return normalize( T * ( mapN.x * scale ) + B * ( mapN.y * scale ) + N * mapN.z );
}
```fs-SRGB.glsl
```
// linear to sRGB approximation
// see http://chilliant.blogspot.com/2012/08/srgb-approximations-for-hlsl.html
vec3 linearTosRGB(vec3 color)
{return pow(color, vec3(MM_1_DIV_GAMMA));
}// sRGB to linear approximation
// see http://chilliant.blogspot.com/2012/08/srgb-approximations-for-hlsl.html
vec3 sRGBToLinear(vec3 srgbIn)
{return vec3(pow(srgbIn.xyz, vec3(MM_GAMMA)));
}vec4 sRGBToLinear(vec4 srgbIn)
{return vec4(sRGBToLinear(srgbIn.xyz), srgbIn.w);
}
```fs-Lights.glsl
```
// KHR_lights_punctual extension.
// see https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_lights_punctual
struct UBOLight
{vec3 direction;vec3 position;vec3 color;float range;float intensity;float innerConeCos;float outerConeCos;int type;
};const int mmGLTFLightTypeInvalid = 0; // Invalid
const int mmGLTFLightTypeDirectional = 1; // "directional"
const int mmGLTFLightTypePoint = 2; // "point"
const int mmGLTFLightTypeSpot = 3; // "spot"// https://github.com/KhronosGroup/glTF/blob/master/extensions/2.0/Khronos/KHR_lights_punctual/README.md#range-property
float GetRangeAttenuation(const in float range, const in float distance)
{if (range <= 0.0){// negative range means unlimitedreturn 1.0 / pow(distance, 2.0);}else{return max(min(1.0 - pow(distance / range, 4.0), 1.0), 0.0) / pow(distance, 2.0);}
}// https://github.com/KhronosGroup/glTF/blob/master/extensions/2.0/Khronos/KHR_lights_punctual/README.md#inner-and-outer-cone-angles
float GetSpotAttenuation(const in vec3 pointToLight, const in vec3 spotDirection, const in float outerConeCos, const in float innerConeCos)
{float actualCos = dot(normalize(spotDirection), normalize(-pointToLight));if (actualCos > outerConeCos){if (actualCos < innerConeCos){return smoothstep(outerConeCos, innerConeCos, actualCos);}else{return 1.0;}}else{return 0.0;}
}vec3 GetLighIntensity(const in UBOLight light, const in vec3 pointToLight)
{float rangeAttenuation = 1.0;float spotAttenuation = 1.0;if (light.type != mmGLTFLightTypeDirectional){rangeAttenuation = GetRangeAttenuation(light.range, length(pointToLight));}if (light.type == mmGLTFLightTypeSpot){spotAttenuation = GetSpotAttenuation(pointToLight, light.direction, light.outerConeCos, light.innerConeCos);}return rangeAttenuation * spotAttenuation * light.intensity * light.color;
}vec3 GetPointToLight(const in UBOLight light, const in vec3 position)
{if (light.type != mmGLTFLightTypeDirectional){return light.position - position;}else{return -light.direction;}
}
```fs-UBOScene.glsl
```
layout (set = 0, binding = 1, std140) uniform UBOScene
{vec3 camera;vec3 ambient;float exposure;int lightCount;
} uboScene;layout (set = 0, binding = 2, std430) readonly buffer UBOLights
{UBOLight lights[];
} uboLights;
```fs-UBOMaterial.glsl
```
struct UBOTextureInfo
{int index;int texCoord;
};layout (set = 1, binding = 0, std140) uniform UBOMaterial
{UBOTextureInfo baseColorTexture;UBOTextureInfo metallicRoughnessTexture;UBOTextureInfo normalTexture;UBOTextureInfo occlusionTexture;UBOTextureInfo emissiveTexture;vec4 baseColorFactor;vec3 emissiveFactor;float metallicFactor;float roughnessFactor;float scale;float strength;float alphaCutoff;int alphaMode;int doubleSided;
} uboMaterial;// BaseColor
#ifndef HAS_VaryingColor#ifndef HAS_TextureBaseColorvec4 GetBaseColor()
{return uboMaterial.baseColorFactor;
}#else#ifndef HAS_VaryingTexcoord
vec4 GetBaseColor()
{return uboMaterial.baseColorFactor;
}
#else
vec4 GetBaseColor()
{vec4 color;UBOTextureInfo t = uboMaterial.baseColorTexture;if (-1 == t.index){color = uboMaterial.baseColorFactor;}else{color = texture(baseColorTexture, v_texcoord[t.texCoord]);color = uboMaterial.baseColorFactor * sRGBToLinear(color);}return color;
}
#endif//HAS_VaryingTexcoord#endif//HAS_TextureBaseColor#else#ifndef HAS_TextureBaseColorvec4 GetBaseColor()
{return uboMaterial.baseColorFactor * v_color_0;
}#else#ifndef HAS_VaryingTexcoord
vec4 GetBaseColor()
{return uboMaterial.baseColorFactor * v_color_0;
}
#else
vec4 GetBaseColor()
{vec4 color;UBOTextureInfo t = uboMaterial.baseColorTexture;if (-1 == t.index){color = uboMaterial.baseColorFactor;}else{color = texture(baseColorTexture, v_texcoord[t.texCoord]);color = uboMaterial.baseColorFactor * sRGBToLinear(color);}return color * v_color_0;
}
#endif//HAS_VaryingTexcoord#endif//HAS_TextureBaseColor#endif//HAS_VaryingColor// Normal
#ifndef HAS_TextureNormal#ifndef HAS_VaryingNormal#ifndef HAS_VaryingTBN
// 0 NotNORMAL NotTANGENT NotTextureNormal
vec3 GetNormal()
{// Use flat normalreturn normalize(cross(dFdx(v_position), dFdy(v_position)));
}
#else// x NotNORMAL HasTANGENT NotTextureNormal
vec3 GetNormal()
{// Use TBN normalreturn normalize(v_tbn[2]);
}#endif//HAS_VaryingTBN#else#ifndef HAS_VaryingTBN
// 1 HasNORMAL NotTANGENT NotTextureNormal
vec3 GetNormal()
{// Use varying normalreturn normalize(v_normal);
}
#else
// 2 HasNORMAL HasTANGENT NotTextureNormal
vec3 GetNormal()
{// Use TBN normalreturn normalize(v_tbn[2]);
}
#endif//HAS_VaryingTBN#endif//HAS_VaryingNormal#else#ifndef HAS_VaryingNormal#ifndef HAS_VaryingTBN
// 0 NotNORMAL NotTANGENT HasTextureNormal
vec3 GetNormal()
{vec3 normal;UBOTextureInfo t = uboMaterial.normalTexture;if (-1 == t.index){// Use flat normalnormal = normalize(cross(dFdx(v_position), dFdy(v_position)));}else{vec2 vUv = v_texcoord[t.texCoord];normal = texture(normalTexture, vUv).rgb;normal = normalize(normal * 2.0f - 1.0f);}return normal;
}
#else
// x NotNORMAL HasTANGENT HasTextureNormal
vec3 GetNormal()
{vec3 normal;UBOTextureInfo t = uboMaterial.normalTexture;if (-1 == t.index){// Use TBN normalnormal = normalize(v_tbn[2]);}else{vec2 vUv = v_texcoord[t.texCoord];normal = texture(normalTexture, vUv).rgb;normal = normalize(normal * 2.0f - 1.0f);normal = normalize(v_tbn * normal);}return normal;
}#endif//HAS_VaryingTBN#else#ifndef HAS_VaryingTBN
// 1 HasNORMAL NotTANGENT HasTextureNormal
vec3 GetNormal()
{vec3 normal;UBOTextureInfo t = uboMaterial.normalTexture;if (-1 == t.index){// Use varying normalnormal = normalize(v_normal);}else{// face direction.float faceDirection = gl_FrontFacing ? 1.0 : - 1.0;vec2 vUv = v_texcoord[t.texCoord];normal = texture(normalTexture, vUv).rgb;normal = normalize(normal * 2.0f - 1.0f);normal = PerturbNormal2Arb(-uboScene.camera, v_normal, normal, faceDirection, vUv);}return normal;
}
#else
// 2 HasNORMAL HasTANGENT HasTextureNormal
vec3 GetNormal()
{vec3 normal;UBOTextureInfo t = uboMaterial.normalTexture;if (-1 == t.index){// Use varying normalnormal = normalize(v_normal);}else{normal = texture(normalTexture, v_texcoord[t.texCoord]).rgb;normal = normalize(normal * 2.0f - 1.0f);normal = normalize(v_tbn * normal);}return normal;
}
#endif//HAS_VaryingTBN#endif//HAS_VaryingNormal#endif//HAS_TextureNormal// Metallic Roughness
#ifndef HAS_TextureMetallicRoughness
vec2 GetMetallicRoughness()
{vec2 value;value.x = uboMaterial.metallicFactor;value.y = uboMaterial.roughnessFactor;return value;
}
#else
vec2 GetMetallicRoughness()
{vec2 value;UBOTextureInfo t = uboMaterial.metallicRoughnessTexture;if (-1 == t.index){value.x = uboMaterial.metallicFactor;value.y = uboMaterial.roughnessFactor;}else{value = texture(metallicRoughnessTexture, v_texcoord[t.texCoord]).bg;}return value;
}
#endif//HAS_TextureMetallicRoughness// Occlusion
#ifndef HAS_TextureOcclusion
vec3 GetOcclusionColor(vec3 color)
{return color;
}
#else
vec3 GetOcclusionColor(vec3 color)
{float occlusion;UBOTextureInfo t = uboMaterial.occlusionTexture;if (-1 == t.index){occlusion = 1.0;}else{occlusion = texture(occlusionTexture, v_texcoord[t.texCoord]).r;color = mix(color, color * occlusion, uboMaterial.strength);}return color;
}
#endif//HAS_TextureOcclusion// Emissive
#ifndef HAS_TextureEmissive
vec3 GetEmissiveColor()
{return vec3(0.0);
}
#else
vec3 GetEmissiveColor()
{vec3 color;UBOTextureInfo t = uboMaterial.emissiveTexture;if (-1 == t.index){color = vec3(0.0);}else{color = texture(emissiveTexture, v_texcoord[t.texCoord]).rgb;color = uboMaterial.emissiveFactor * sRGBToLinear(color);}return color;
}
#endif//HAS_TextureEmissive
```fs-BRDF.glsl
```
// Fresnel Schlick approximation translates
vec3 F_Schlick(const in vec3 f0, const in float f90, const in float dotVH)
{// Rf(l,h) = F0 + (1 - F0) * (1 - (l dot h))^5// F0 = Ff(h,h) = Cspecfloat fresnel = pow(1.0 - dotVH, 5.0);return f0 + (f90 - f0) * fresnel;
}// Fresnel Schlick approximation translates
vec3 F_SchlickEpic( const in vec3 f0, const in float f90, const in float dotVH)
{// Original approximation by Christophe Schlick '94// float fresnel = pow( 1.0 - dotVH, 5.0 );//// Optimized variant (presented by Epic at SIGGRAPH '13)// https://cdn2.unrealengine.com/Resources/files/2013SiggraphPresentationsNotes-26915738.pdffloat fresnel = exp2((-5.55473 * dotVH - 6.98316) * dotVH);return f0 + (f90 - f0) * fresnel;
}// Geometric Shadowing function
// Moving Frostbite to Physically Based Rendering 3.0 - page 12, listing 2
// https://seblagarde.files.wordpress.com/2015/07/course_notes_moving_frostbite_to_pbr_v32.pdf
float V_GGX_SmithCorrelated( const in float alpha, const in float dotNL, const in float dotNV)
{float a2 = pow2( alpha );float gv = dotNL * sqrt(a2 + (1.0 - a2) * pow2(dotNV));float gl = dotNV * sqrt(a2 + (1.0 - a2) * pow2(dotNL));return 0.5 / max(gv + gl, MM_EPSILON);
}
// Geometric Shadowing function
float G_GGX( const in float alpha, const in float dotNL, const in float dotNV)
{float a2 = pow2( alpha );float GGXV = 2 * dotNV / (dotNV + sqrt(a2 + (1.0 - a2) * (dotNV * dotNV)));float GGXL = 2 * dotNL / (dotNL + sqrt(a2 + (1.0 - a2) * (dotNL * dotNL)));return GGXV * GGXL;
}// Microfacet Models for Refraction through Rough Surfaces - equation (33)
// http://graphicrants.blogspot.com/2013/08/specular-brdf-reference.html
// alpha is "roughness squared" in Disney’s reparameterization
float D_GGX( const in float alpha, const in float dotNH )
{float a2 = pow2(alpha);// avoid alpha = 0 with dotNH = 1float denom = pow2(dotNH) * (a2 - 1.0) + 1.0;return MM_1_DIV_PI * a2 / pow2(denom);
}vec3 BRDF_Diffuse(const in vec3 diffuseColor)
{return MM_1_DIV_PI * diffuseColor;
}vec3 BRDF_Specular(const in float dotNH, const in float dotNL, const in float dotNV, const in float alpha)
{if (dotNL > 0.0 && dotNV > 0.0){// Geometric Shadowing functionfloat G = G_GGX(alpha, dotNL, dotNV);// Normal Distribution functionfloat D = D_GGX(alpha, dotNH);// Visibility functionfloat V = G / (4.0 * dotNL * dotNV);return vec3(V * D);}else{return vec3(0.0);}
}vec3 BRDF_Material(const in vec3 L, const in vec3 V, const in vec3 N,const in vec3 diffuseColor,const in vec3 f0,const in float f90,const in float roughness)
{vec3 H = normalize (V + L);float dotNV = clamp(dot(N, V), 0.0, 1.0);float dotNL = clamp(dot(N, L), 0.0, 1.0);float dotNH = clamp(dot(N, H), 0.0, 1.0);float dotVH = clamp(dot(V, H), 0.0, 1.0);float alpha = roughness * roughness;vec3 F = F_Schlick(f0, f90, dotVH);vec3 diffuse = BRDF_Diffuse(diffuseColor);vec3 specular = BRDF_Specular(dotNH, dotNL, dotNV, alpha);vec3 material = (1 - F) * diffuse + F * specular;return dotNL * material;
}
```fs-ToneMap.glsl
```
// ACES tone map (faster approximation)
// see: https://knarkowicz.wordpress.com/2016/01/06/aces-filmic-tone-mapping-curve/
vec3 toneMapACES_Narkowicz(vec3 color)
{const float A = 2.51;const float B = 0.03;const float C = 2.43;const float D = 0.59;const float E = 0.14;return clamp((color * (A * color + B)) / (color * (C * color + D) + E), 0.0, 1.0);
}vec3 toneMap(vec3 color)
{color *= uboScene.exposure;color = toneMapACES_Narkowicz(color);return linearTosRGB(color);
}
```fs-AlphaMode.glsl
```
vec4 AlphaModeColor(vec4 baseColor)
{switch(uboMaterial.alphaMode){case mmGLTFAlphaModeOpaque:{baseColor.a = 1.0;}break;case mmGLTFAlphaModeMask:{// Late discard to avoid samplig artifacts. // See https://github.com/KhronosGroup/glTF-Sample-Viewer/issues/267if (baseColor.a < uboMaterial.alphaCutoff){discard;}baseColor.a = 1.0;}break;case mmGLTFAlphaModeBlend:default:break;}return baseColor;
}
```
三、PBR原理
这里主要说一下BRDF的部分
根据gltf官方文档,实现pbr的混色流程
注意,这里得到的material并不是最终颜色,我们需要将结果乘以dotNL。
下面是pbr的实现理论 glTF-2.0-and-PBR-GTC_May17.pdf
仅截图BRDF的部分
下面是对部分函数实现不同版本的探讨
Fresnel Schlick 有两个版本,我使用的原始版本,这两个函数从渲染结果来看并没有太大不同
// Fresnel Schlick approximation translates
vec3 F_Schlick(const in vec3 f0, const in float f90, const in float dotVH)
{// Rf(l,h) = F0 + (1 - F0) * (1 - (l dot h))^5// F0 = Ff(h,h) = Cspecfloat fresnel = pow(1.0 - dotVH, 5.0);return f0 + (f90 - f0) * fresnel;
}// Fresnel Schlick approximation translates
vec3 F_SchlickEpic( const in vec3 f0, const in float f90, const in float dotVH)
{// Original approximation by Christophe Schlick '94// float fresnel = pow( 1.0 - dotVH, 5.0 );//// Optimized variant (presented by Epic at SIGGRAPH '13)// https://cdn2.unrealengine.com/Resources/files/2013SiggraphPresentationsNotes-26915738.pdffloat fresnel = exp2((-5.55473 * dotVH - 6.98316) * dotVH);return f0 + (f90 - f0) * fresnel;
}
Geometric Shadowing function 也有两个版本,第一个版本是从three.js获取的,第二个是根据官方给的pdf实现的。从渲染结果来看,我使用了原始版本。第一个版本在模型边缘处会产生一些高亮瑕疵,可能是我使用方式不太对。
// Geometric Shadowing function
// Moving Frostbite to Physically Based Rendering 3.0 - page 12, listing 2
// https://seblagarde.files.wordpress.com/2015/07/course_notes_moving_frostbite_to_pbr_v32.pdf
float V_GGX_SmithCorrelated( const in float alpha, const in float dotNL, const in float dotNV)
{float a2 = pow2( alpha );float gv = dotNL * sqrt(a2 + (1.0 - a2) * pow2(dotNV));float gl = dotNV * sqrt(a2 + (1.0 - a2) * pow2(dotNL));return 0.5 / max(gv + gl, MM_EPSILON);
}
// Geometric Shadowing function
float G_GGX( const in float alpha, const in float dotNL, const in float dotNV)
{float a2 = pow2( alpha );float GGXV = 2 * dotNV / (dotNV + sqrt(a2 + (1.0 - a2) * (dotNV * dotNV)));float GGXL = 2 * dotNL / (dotNL + sqrt(a2 + (1.0 - a2) * (dotNL * dotNL)));return GGXV * GGXL;
}