This repository has been archived on 2025-06-21. You can view files and clone it, but you cannot make any changes to it's state, such as pushing and creating new issues, pull requests or comments.
raylib-python-cffi/examples/models/resources/shaders/pbr.fs
Richard Smith a703659c9d initial
2019-05-21 10:56:31 +01:00

298 lines
10 KiB
GLSL

/*******************************************************************************************
*
* rPBR [shader] - Physically based rendering fragment shader
*
* Copyright (c) 2017 Victor Fisac
*
**********************************************************************************************/
#version 330
#define MAX_REFLECTION_LOD 4.0
#define MAX_DEPTH_LAYER 20
#define MIN_DEPTH_LAYER 10
#define MAX_LIGHTS 4
#define LIGHT_DIRECTIONAL 0
#define LIGHT_POINT 1
struct MaterialProperty [
vec3 color
int useSampler
sampler2D sampler
]
struct Light [
int enabled
int type
vec3 position
vec3 target
vec4 color
]
# Input vertex attributes (from vertex shader)
in vec3 fragPosition
in vec2 fragTexCoord
in vec3 fragNormal
in vec3 fragTangent
in vec3 fragBinormal
# Input material values
uniform MaterialProperty albedo
uniform MaterialProperty normals
uniform MaterialProperty metalness
uniform MaterialProperty roughness
uniform MaterialProperty occlusion
uniform MaterialProperty emission
uniform MaterialProperty height
# Input uniform values
uniform samplerCube irradianceMap
uniform samplerCube prefilterMap
uniform sampler2D brdfLUT
# Input lighting values
uniform Light lights[MAX_LIGHTS]
# Other uniform values
uniform int renderMode
uniform vec3 viewPos
vec2 texCoord
# Constant values
const float PI = 3.14159265359
# Output fragment color
out vec4 finalColor
vec3 ComputeMaterialProperty(MaterialProperty property)
float DistributionGGX(vec3 N, vec3 H, float roughness)
float GeometrySchlickGGX(float NdotV, float roughness)
float GeometrySmith(vec3 N, vec3 V, vec3 L, float roughness)
vec3 fresnelSchlick(float cosTheta, vec3 F0)
vec3 fresnelSchlickRoughness(float cosTheta, vec3 F0, float roughness)
vec2 ParallaxMapping(vec2 texCoords, vec3 viewDir)
vec3 ComputeMaterialProperty(MaterialProperty property)
[
vec3 result = vec3(0.0, 0.0, 0.0)
if (property.useSampler == 1) result = texture(property.sampler, texCoord).rgb
else result = property.color
return result
]
float DistributionGGX(vec3 N, vec3 H, float roughness)
[
float a = roughness*roughness
float a2 = a*a
float NdotH = max(dot(N, H), 0.0)
float NdotH2 = NdotH*NdotH
float nom = a2
float denom = (NdotH2*(a2 - 1.0) + 1.0)
denom = PI*denom*denom
return nom/denom
]
float GeometrySchlickGGX(float NdotV, float roughness)
[
float r = (roughness + 1.0)
float k = r*r/8.0
float nom = NdotV
float denom = NdotV*(1.0 - k) + k
return nom/denom
]
float GeometrySmith(vec3 N, vec3 V, vec3 L, float roughness)
[
float NdotV = max(dot(N, V), 0.0)
float NdotL = max(dot(N, L), 0.0)
float ggx2 = GeometrySchlickGGX(NdotV, roughness)
float ggx1 = GeometrySchlickGGX(NdotL, roughness)
return ggx1*ggx2
]
vec3 fresnelSchlick(float cosTheta, vec3 F0)
[
return F0 + (1.0 - F0)*pow(1.0 - cosTheta, 5.0)
]
vec3 fresnelSchlickRoughness(float cosTheta, vec3 F0, float roughness)
[
return F0 + (max(vec3(1.0 - roughness), F0) - F0)*pow(1.0 - cosTheta, 5.0)
]
vec2 ParallaxMapping(vec2 texCoords, vec3 viewDir)
[
# Calculate the number of depth layers and calculate the size of each layer
float numLayers = mix(MAX_DEPTH_LAYER, MIN_DEPTH_LAYER, abs(dot(vec3(0.0, 0.0, 1.0), viewDir)))
float layerDepth = 1.0/numLayers
# Calculate depth of current layer
float currentLayerDepth = 0.0
# Calculate the amount to shift the texture coordinates per layer (from vector P)
# Note: height amount is stored in height material attribute color R channel (sampler use is independent)
vec2 P = viewDir.xy*height.color.r
vec2 deltaTexCoords = P/numLayers
# Store initial texture coordinates and depth values
vec2 currentTexCoords = texCoords
float currentDepthMapValue = texture(height.sampler, currentTexCoords).r
while (currentLayerDepth < currentDepthMapValue)
[
# Shift texture coordinates along direction of P
currentTexCoords -= deltaTexCoords
# Get depth map value at current texture coordinates
currentDepthMapValue = texture(height.sampler, currentTexCoords).r
# Get depth of next layer
currentLayerDepth += layerDepth
]
# Get texture coordinates before collision (reverse operations)
vec2 prevTexCoords = currentTexCoords + deltaTexCoords
# Get depth after and before collision for linear interpolation
float afterDepth = currentDepthMapValue - currentLayerDepth
float beforeDepth = texture(height.sampler, prevTexCoords).r - currentLayerDepth + layerDepth
# Interpolation of texture coordinates
float weight = afterDepth/(afterDepth - beforeDepth)
vec2 finalTexCoords = prevTexCoords*weight + currentTexCoords*(1.0 - weight)
return finalTexCoords
]
void main()
[
# Calculate TBN and RM matrices
mat3 TBN = transpose(mat3(fragTangent, fragBinormal, fragNormal))
# Calculate lighting required attributes
vec3 normal = normalize(fragNormal)
vec3 view = normalize(viewPos - fragPosition)
vec3 refl = reflect(-view, normal)
# Check if parallax mapping is enabled and calculate texture coordinates to use based on height map
# NOTE: remember that 'texCoord' variable must be assigned before calling any ComputeMaterialProperty() function
if (height.useSampler == 1) texCoord = ParallaxMapping(fragTexCoord, view)
else texCoord = fragTexCoord # Use default texture coordinates
# Fetch material values from texture sampler or color attributes
vec3 color = ComputeMaterialProperty(albedo)
vec3 metal = ComputeMaterialProperty(metalness)
vec3 rough = ComputeMaterialProperty(roughness)
vec3 emiss = ComputeMaterialProperty(emission)
vec3 ao = ComputeMaterialProperty(occlusion)
# Check if normal mapping is enabled
if (normals.useSampler == 1)
[
# Fetch normal map color and transform lighting values to tangent space
normal = ComputeMaterialProperty(normals)
normal = normalize(normal*2.0 - 1.0)
normal = normalize(normal*TBN)
# Convert tangent space normal to world space due to cubemap reflection calculations
refl = normalize(reflect(-view, normal))
]
# Calculate reflectance at normal incidence
vec3 F0 = vec3(0.04)
F0 = mix(F0, color, metal.r)
# Calculate lighting for all lights
vec3 Lo = vec3(0.0)
vec3 lightDot = vec3(0.0)
for (int i = 0 i < MAX_LIGHTS i++)
[
if (lights[i].enabled == 1)
[
# Calculate per-light radiance
vec3 light = vec3(0.0)
vec3 radiance = lights[i].color.rgb
if (lights[i].type == LIGHT_DIRECTIONAL) light = -normalize(lights[i].target - lights[i].position)
else if (lights[i].type == LIGHT_POINT)
[
light = normalize(lights[i].position - fragPosition)
float distance = length(lights[i].position - fragPosition)
float attenuation = 1.0/(distance*distance)
radiance *= attenuation
]
# Cook-torrance BRDF
vec3 high = normalize(view + light)
float NDF = DistributionGGX(normal, high, rough.r)
float G = GeometrySmith(normal, view, light, rough.r)
vec3 F = fresnelSchlick(max(dot(high, view), 0.0), F0)
vec3 nominator = NDF*G*F
float denominator = 4*max(dot(normal, view), 0.0)*max(dot(normal, light), 0.0) + 0.001
vec3 brdf = nominator/denominator
# Store to kS the fresnel value and calculate energy conservation
vec3 kS = F
vec3 kD = vec3(1.0) - kS
# Multiply kD by the inverse metalness such that only non-metals have diffuse lighting
kD *= 1.0 - metal.r
# Scale light by dot product between normal and light direction
float NdotL = max(dot(normal, light), 0.0)
# Add to outgoing radiance Lo
# Note: BRDF is already multiplied by the Fresnel so it doesn't need to be multiplied again
Lo += (kD*color/PI + brdf)*radiance*NdotL*lights[i].color.a
lightDot += radiance*NdotL + brdf*lights[i].color.a
]
]
# Calculate ambient lighting using IBL
vec3 F = fresnelSchlickRoughness(max(dot(normal, view), 0.0), F0, rough.r)
vec3 kS = F
vec3 kD = 1.0 - kS
kD *= 1.0 - metal.r
# Calculate indirect diffuse
vec3 irradiance = texture(irradianceMap, fragNormal).rgb
vec3 diffuse = color*irradiance
# Sample both the prefilter map and the BRDF lut and combine them together as per the Split-Sum approximation
vec3 prefilterColor = textureLod(prefilterMap, refl, rough.r*MAX_REFLECTION_LOD).rgb
vec2 brdf = texture(brdfLUT, vec2(max(dot(normal, view), 0.0), rough.r)).rg
vec3 reflection = prefilterColor*(F*brdf.x + brdf.y)
# Calculate final lighting
vec3 ambient = (kD*diffuse + reflection)*ao
# Calculate fragment color based on render mode
vec3 fragmentColor = ambient + Lo + emiss # Physically Based Rendering
if (renderMode == 1) fragmentColor = color # Albedo
else if (renderMode == 2) fragmentColor = normal # Normals
else if (renderMode == 3) fragmentColor = metal # Metalness
else if (renderMode == 4) fragmentColor = rough # Roughness
else if (renderMode == 5) fragmentColor = ao # Ambient Occlusion
else if (renderMode == 6) fragmentColor = emiss # Emission
else if (renderMode == 7) fragmentColor = lightDot # Lighting
else if (renderMode == 8) fragmentColor = kS # Fresnel
else if (renderMode == 9) fragmentColor = irradiance # Irradiance
else if (renderMode == 10) fragmentColor = reflection # Reflection
# Apply HDR tonemapping
fragmentColor = fragmentColor/(fragmentColor + vec3(1.0))
# Apply gamma correction
fragmentColor = pow(fragmentColor, vec3(1.0/2.2))
# Calculate final fragment color
finalColor = vec4(fragmentColor, 1.0)
]