pbr.fs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298
  1. /*******************************************************************************************
  2. *
  3. * rPBR [shader] - Physically based rendering fragment shader
  4. *
  5. * Copyright (c) 2017 Victor Fisac
  6. *
  7. **********************************************************************************************/
  8. #version 330
  9. #define MAX_REFLECTION_LOD 4.0
  10. #define MAX_DEPTH_LAYER 20
  11. #define MIN_DEPTH_LAYER 10
  12. #define MAX_LIGHTS 4
  13. #define LIGHT_DIRECTIONAL 0
  14. #define LIGHT_POINT 1
  15. struct MaterialProperty {
  16. vec3 color;
  17. int useSampler;
  18. sampler2D sampler;
  19. };
  20. struct Light {
  21. int enabled;
  22. int type;
  23. vec3 position;
  24. vec3 target;
  25. vec4 color;
  26. };
  27. // Input vertex attributes (from vertex shader)
  28. in vec3 fragPosition;
  29. in vec2 fragTexCoord;
  30. in vec3 fragNormal;
  31. in vec3 fragTangent;
  32. in vec3 fragBinormal;
  33. // Input material values
  34. uniform MaterialProperty albedo;
  35. uniform MaterialProperty normals;
  36. uniform MaterialProperty metalness;
  37. uniform MaterialProperty roughness;
  38. uniform MaterialProperty occlusion;
  39. uniform MaterialProperty emission;
  40. uniform MaterialProperty height;
  41. // Input uniform values
  42. uniform samplerCube irradianceMap;
  43. uniform samplerCube prefilterMap;
  44. uniform sampler2D brdfLUT;
  45. // Input lighting values
  46. uniform Light lights[MAX_LIGHTS];
  47. // Other uniform values
  48. uniform int renderMode;
  49. uniform vec3 viewPos;
  50. vec2 texCoord;
  51. // Constant values
  52. const float PI = 3.14159265359;
  53. // Output fragment color
  54. out vec4 finalColor;
  55. vec3 ComputeMaterialProperty(MaterialProperty property);
  56. float DistributionGGX(vec3 N, vec3 H, float roughness);
  57. float GeometrySchlickGGX(float NdotV, float roughness);
  58. float GeometrySmith(vec3 N, vec3 V, vec3 L, float roughness);
  59. vec3 fresnelSchlick(float cosTheta, vec3 F0);
  60. vec3 fresnelSchlickRoughness(float cosTheta, vec3 F0, float roughness);
  61. vec2 ParallaxMapping(vec2 texCoords, vec3 viewDir);
  62. vec3 ComputeMaterialProperty(MaterialProperty property)
  63. {
  64. vec3 result = vec3(0.0, 0.0, 0.0);
  65. if (property.useSampler == 1) result = texture(property.sampler, texCoord).rgb;
  66. else result = property.color;
  67. return result;
  68. }
  69. float DistributionGGX(vec3 N, vec3 H, float roughness)
  70. {
  71. float a = roughness*roughness;
  72. float a2 = a*a;
  73. float NdotH = max(dot(N, H), 0.0);
  74. float NdotH2 = NdotH*NdotH;
  75. float nom = a2;
  76. float denom = (NdotH2*(a2 - 1.0) + 1.0);
  77. denom = PI*denom*denom;
  78. return nom/denom;
  79. }
  80. float GeometrySchlickGGX(float NdotV, float roughness)
  81. {
  82. float r = (roughness + 1.0);
  83. float k = r*r/8.0;
  84. float nom = NdotV;
  85. float denom = NdotV*(1.0 - k) + k;
  86. return nom/denom;
  87. }
  88. float GeometrySmith(vec3 N, vec3 V, vec3 L, float roughness)
  89. {
  90. float NdotV = max(dot(N, V), 0.0);
  91. float NdotL = max(dot(N, L), 0.0);
  92. float ggx2 = GeometrySchlickGGX(NdotV, roughness);
  93. float ggx1 = GeometrySchlickGGX(NdotL, roughness);
  94. return ggx1*ggx2;
  95. }
  96. vec3 fresnelSchlick(float cosTheta, vec3 F0)
  97. {
  98. return F0 + (1.0 - F0)*pow(1.0 - cosTheta, 5.0);
  99. }
  100. vec3 fresnelSchlickRoughness(float cosTheta, vec3 F0, float roughness)
  101. {
  102. return F0 + (max(vec3(1.0 - roughness), F0) - F0)*pow(1.0 - cosTheta, 5.0);
  103. }
  104. vec2 ParallaxMapping(vec2 texCoords, vec3 viewDir)
  105. {
  106. // Calculate the number of depth layers and calculate the size of each layer
  107. float numLayers = mix(MAX_DEPTH_LAYER, MIN_DEPTH_LAYER, abs(dot(vec3(0.0, 0.0, 1.0), viewDir)));
  108. float layerDepth = 1.0/numLayers;
  109. // Calculate depth of current layer
  110. float currentLayerDepth = 0.0;
  111. // Calculate the amount to shift the texture coordinates per layer (from vector P)
  112. // Note: height amount is stored in height material attribute color R channel (sampler use is independent)
  113. vec2 P = viewDir.xy*height.color.r;
  114. vec2 deltaTexCoords = P/numLayers;
  115. // Store initial texture coordinates and depth values
  116. vec2 currentTexCoords = texCoords;
  117. float currentDepthMapValue = texture(height.sampler, currentTexCoords).r;
  118. while (currentLayerDepth < currentDepthMapValue)
  119. {
  120. // Shift texture coordinates along direction of P
  121. currentTexCoords -= deltaTexCoords;
  122. // Get depth map value at current texture coordinates
  123. currentDepthMapValue = texture(height.sampler, currentTexCoords).r;
  124. // Get depth of next layer
  125. currentLayerDepth += layerDepth;
  126. }
  127. // Get texture coordinates before collision (reverse operations)
  128. vec2 prevTexCoords = currentTexCoords + deltaTexCoords;
  129. // Get depth after and before collision for linear interpolation
  130. float afterDepth = currentDepthMapValue - currentLayerDepth;
  131. float beforeDepth = texture(height.sampler, prevTexCoords).r - currentLayerDepth + layerDepth;
  132. // Interpolation of texture coordinates
  133. float weight = afterDepth/(afterDepth - beforeDepth);
  134. vec2 finalTexCoords = prevTexCoords*weight + currentTexCoords*(1.0 - weight);
  135. return finalTexCoords;
  136. }
  137. void main()
  138. {
  139. // Calculate TBN and RM matrices
  140. mat3 TBN = transpose(mat3(fragTangent, fragBinormal, fragNormal));
  141. // Calculate lighting required attributes
  142. vec3 normal = normalize(fragNormal);
  143. vec3 view = normalize(viewPos - fragPosition);
  144. vec3 refl = reflect(-view, normal);
  145. // Check if parallax mapping is enabled and calculate texture coordinates to use based on height map
  146. // NOTE: remember that 'texCoord' variable must be assigned before calling any ComputeMaterialProperty() function
  147. if (height.useSampler == 1) texCoord = ParallaxMapping(fragTexCoord, view);
  148. else texCoord = fragTexCoord; // Use default texture coordinates
  149. // Fetch material values from texture sampler or color attributes
  150. vec3 color = ComputeMaterialProperty(albedo);
  151. vec3 metal = ComputeMaterialProperty(metalness);
  152. vec3 rough = ComputeMaterialProperty(roughness);
  153. vec3 emiss = ComputeMaterialProperty(emission);
  154. vec3 ao = ComputeMaterialProperty(occlusion);
  155. // Check if normal mapping is enabled
  156. if (normals.useSampler == 1)
  157. {
  158. // Fetch normal map color and transform lighting values to tangent space
  159. normal = ComputeMaterialProperty(normals);
  160. normal = normalize(normal*2.0 - 1.0);
  161. normal = normalize(normal*TBN);
  162. // Convert tangent space normal to world space due to cubemap reflection calculations
  163. refl = normalize(reflect(-view, normal));
  164. }
  165. // Calculate reflectance at normal incidence
  166. vec3 F0 = vec3(0.04);
  167. F0 = mix(F0, color, metal.r);
  168. // Calculate lighting for all lights
  169. vec3 Lo = vec3(0.0);
  170. vec3 lightDot = vec3(0.0);
  171. for (int i = 0; i < MAX_LIGHTS; i++)
  172. {
  173. if (lights[i].enabled == 1)
  174. {
  175. // Calculate per-light radiance
  176. vec3 light = vec3(0.0);
  177. vec3 radiance = lights[i].color.rgb;
  178. if (lights[i].type == LIGHT_DIRECTIONAL) light = -normalize(lights[i].target - lights[i].position);
  179. else if (lights[i].type == LIGHT_POINT)
  180. {
  181. light = normalize(lights[i].position - fragPosition);
  182. float distance = length(lights[i].position - fragPosition);
  183. float attenuation = 1.0/(distance*distance);
  184. radiance *= attenuation;
  185. }
  186. // Cook-torrance BRDF
  187. vec3 high = normalize(view + light);
  188. float NDF = DistributionGGX(normal, high, rough.r);
  189. float G = GeometrySmith(normal, view, light, rough.r);
  190. vec3 F = fresnelSchlick(max(dot(high, view), 0.0), F0);
  191. vec3 nominator = NDF*G*F;
  192. float denominator = 4*max(dot(normal, view), 0.0)*max(dot(normal, light), 0.0) + 0.001;
  193. vec3 brdf = nominator/denominator;
  194. // Store to kS the fresnel value and calculate energy conservation
  195. vec3 kS = F;
  196. vec3 kD = vec3(1.0) - kS;
  197. // Multiply kD by the inverse metalness such that only non-metals have diffuse lighting
  198. kD *= 1.0 - metal.r;
  199. // Scale light by dot product between normal and light direction
  200. float NdotL = max(dot(normal, light), 0.0);
  201. // Add to outgoing radiance Lo
  202. // Note: BRDF is already multiplied by the Fresnel so it doesn't need to be multiplied again
  203. Lo += (kD*color/PI + brdf)*radiance*NdotL*lights[i].color.a;
  204. lightDot += radiance*NdotL + brdf*lights[i].color.a;
  205. }
  206. }
  207. // Calculate ambient lighting using IBL
  208. vec3 F = fresnelSchlickRoughness(max(dot(normal, view), 0.0), F0, rough.r);
  209. vec3 kS = F;
  210. vec3 kD = 1.0 - kS;
  211. kD *= 1.0 - metal.r;
  212. // Calculate indirect diffuse
  213. vec3 irradiance = texture(irradianceMap, fragNormal).rgb;
  214. vec3 diffuse = color*irradiance;
  215. // Sample both the prefilter map and the BRDF lut and combine them together as per the Split-Sum approximation
  216. vec3 prefilterColor = textureLod(prefilterMap, refl, rough.r*MAX_REFLECTION_LOD).rgb;
  217. vec2 brdf = texture(brdfLUT, vec2(max(dot(normal, view), 0.0), rough.r)).rg;
  218. vec3 reflection = prefilterColor*(F*brdf.x + brdf.y);
  219. // Calculate final lighting
  220. vec3 ambient = (kD*diffuse + reflection)*ao;
  221. // Calculate fragment color based on render mode
  222. vec3 fragmentColor = ambient + Lo + emiss; // Physically Based Rendering
  223. if (renderMode == 1) fragmentColor = color; // Albedo
  224. else if (renderMode == 2) fragmentColor = normal; // Normals
  225. else if (renderMode == 3) fragmentColor = metal; // Metalness
  226. else if (renderMode == 4) fragmentColor = rough; // Roughness
  227. else if (renderMode == 5) fragmentColor = ao; // Ambient Occlusion
  228. else if (renderMode == 6) fragmentColor = emiss; // Emission
  229. else if (renderMode == 7) fragmentColor = lightDot; // Lighting
  230. else if (renderMode == 8) fragmentColor = kS; // Fresnel
  231. else if (renderMode == 9) fragmentColor = irradiance; // Irradiance
  232. else if (renderMode == 10) fragmentColor = reflection; // Reflection
  233. // Apply HDR tonemapping
  234. fragmentColor = fragmentColor/(fragmentColor + vec3(1.0));
  235. // Apply gamma correction
  236. fragmentColor = pow(fragmentColor, vec3(1.0/2.2));
  237. // Calculate final fragment color
  238. finalColor = vec4(fragmentColor, 1.0);
  239. }