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: n
sprintf(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 int
stride 12 unsigned int
inputRate VK_VERTEX_INPUT_RATE_VERTEX (0) VkVertexInputRate
"NORMAL"
binding 1 unsigned int
stride 12 unsigned int
inputRate VK_VERTEX_INPUT_RATE_VERTEX (0) VkVertexInputRate
"TEXCOORD_0"
binding 2 unsigned int
stride 8 unsigned int
inputRate VK_VERTEX_INPUT_RATE_VERTEX (0) VkVertexInputRate
"JOINTS_0"
binding 3 unsigned int
stride 8 unsigned int
inputRate VK_VERTEX_INPUT_RATE_VERTEX (0) VkVertexInputRate
"WEIGHTS_0"
binding 4 unsigned int
stride 16 unsigned int
inputRate VK_VERTEX_INPUT_RATE_VERTEX (0) VkVertexInputRate
}
VkVertexInputAttributeDescription viad =
{
"POSITION"
location 0 unsigned int
binding 0 unsigned int
format VK_FORMAT_R32G32B32_SFLOAT (106) VkFormat
offset 0 unsigned int
"NORMAL"
location 1 unsigned int
binding 1 unsigned int
format VK_FORMAT_R32G32B32_SFLOAT (106) VkFormat
offset 0 unsigned int
"TEXCOORD_0"
location 2 unsigned int
binding 2 unsigned int
format VK_FORMAT_R32G32_SFLOAT (103) VkFormat
offset 0 unsigned int
"JOINTS_0"
location 3 unsigned int
binding 3 unsigned int
format VK_FORMAT_R16G16B16A16_UINT (95) VkFormat
offset 0 unsigned int
"WEIGHTS_0"
location 4 unsigned int
binding 4 unsigned int
format VK_FORMAT_R32G32B32A32_SFLOAT (109) VkFormat
offset 0 unsigned int
}
```
我们得到了入参的哈希信息
它有骨骼动画 1
没有颜色入参 0
有纹理0 1
有法线无切线 1
它有baseColor基础漫反射贴图 1
没有metallicRoughnessTexture物理属性贴图 0
没有normalTexture法线贴图 0
没有occlusionTexture遮蔽贴图 0
没有emissiveTexture高光贴图 0
VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST 3
没有使用doubleSided双面渲染 0
Pipeline 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 vector
vec3 N = GetNormal();
vec4 baseColor = GetBaseColor();
baseColor = AlphaModeColor(baseColor);
#ifndef HAS_TextureMetallicRoughness
float metallic = uboMaterial.metallicFactor;
float roughness = uboMaterial.roughnessFactor;
#else
vec2 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_TextureOcclusion
color = GetOcclusionColor(color);
#endif//HAS_TextureOcclusion
#ifdef HAS_TextureEmissive
color += GetEmissiveColor();
#endif//HAS_TextureEmissive
// Tone mapping
outColor = 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 #9988
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; // normalized
vec3 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 #9988
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 );
vec3 N = surf_norm; // normalized
vec3 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 unlimited
return 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_TextureBaseColor
vec4 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_TextureBaseColor
vec4 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 normal
return normalize(cross(dFdx(v_position), dFdy(v_position)));
}
#else
// x NotNORMAL HasTANGENT NotTextureNormal
vec3 GetNormal()
{
// Use TBN normal
return normalize(v_tbn[2]);
}
#endif//HAS_VaryingTBN
#else
#ifndef HAS_VaryingTBN
// 1 HasNORMAL NotTANGENT NotTextureNormal
vec3 GetNormal()
{
// Use varying normal
return normalize(v_normal);
}
#else
// 2 HasNORMAL HasTANGENT NotTextureNormal
vec3 GetNormal()
{
// Use TBN normal
return 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 normal
normal = 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 normal
normal = 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 normal
normal = 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 normal
normal = 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) = Cspec
float 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.pdf
float 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 = 1
float 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 function
float G = G_GGX(alpha, dotNL, dotNV);
// Normal Distribution function
float D = D_GGX(alpha, dotNH);
// Visibility function
float 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/267
if (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) = Cspec
float 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.pdf
float 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;
}