/* Taken from the GLTF reference viewer: https://github.com/KhronosGroup/glTF-Sample-Viewer/tree/master/src/shaders */ // this is just here to test the `@module` prefix feature of the sokol-shdc // C code generator, other then that it's not needed @module cgltf @ctype mat4 mat44_t @ctype vec4 vec4_t @ctype vec3 vec3_t @vs vs layout(binding=0) uniform vs_params { mat4 model; mat4 view_proj; vec3 eye_pos; }; layout(location=0) in vec4 position; layout(location=1) in vec3 normal; layout(location=2) in vec2 texcoord; out vec3 v_pos; out vec3 v_nrm; out vec2 v_uv; out vec3 v_eye_pos; void main() { vec4 pos = model * position; v_pos = pos.xyz / pos.w; v_nrm = (model * vec4(normal, 0.0)).xyz; v_uv = texcoord; v_eye_pos = eye_pos; gl_Position = view_proj * pos; } @end @fs metallic_fs in vec3 v_pos; in vec3 v_nrm; in vec2 v_uv; in vec3 v_eye_pos; out vec4 frag_color; struct material_info_t { float perceptual_roughness; // roughness value, as authored by the model creator (input to shader) vec3 reflectance0; // full reflectance color (normal incidence angle) float alpha_roughness; // roughness mapped to a more linear change in the roughness (proposed by [2]) vec3 diffuse_color; // color contribution from diffuse lighting vec3 reflectance90; // reflectance color at grazing angle vec3 specular_color; // color contribution from specular lighting }; layout(binding=1) uniform metallic_params { vec4 base_color_factor; vec3 emissive_factor; float metallic_factor; float roughness_factor; }; layout(binding=2) uniform light_params { vec3 light_pos; float light_range; vec3 light_color; float light_intensity; }; layout(binding=0) uniform texture2D base_color_tex; layout(binding=1) uniform texture2D metallic_roughness_tex; layout(binding=2) uniform texture2D normal_tex; layout(binding=3) uniform texture2D occlusion_tex; layout(binding=4) uniform texture2D emissive_tex; layout(binding=0) uniform sampler base_color_smp; layout(binding=1) uniform sampler metallic_roughness_smp; layout(binding=2) uniform sampler normal_smp; layout(binding=3) uniform sampler occlusion_smp; layout(binding=4) uniform sampler emissive_smp; vec3 linear_to_srgb(vec3 linear) { return pow(linear, vec3(1.0/2.2)); } vec4 srgb_to_linear(vec4 srgb) { return vec4(pow(srgb.rgb, vec3(2.2)), srgb.a); } vec3 get_normal() { vec3 pos_dx = dFdx(v_pos); vec3 pos_dy = dFdy(v_pos); vec3 tex_dx = dFdx(vec3(v_uv,0.0)); vec3 tex_dy = dFdy(vec3(v_uv,0.0)); vec3 t = (tex_dy.t * pos_dx - tex_dx.t * pos_dy) / (tex_dx.s * tex_dy.t - tex_dy.s * tex_dx.t); vec3 ng = normalize(v_nrm); t = normalize(t - ng * dot(ng, t)); vec3 b = normalize(cross(ng, t)); mat3 tbn = mat3(t, b, ng); vec2 n_xy = texture(sampler2D(normal_tex, normal_smp), v_uv).xw * 2.0 - 1.0; vec3 n = vec3(n_xy.x, n_xy.y, sqrt(1.0 - n_xy.x*n_xy.x - n_xy.y*n_xy.y)); n = normalize(tbn * n); return n; } struct angular_info_t { float n_dot_l; // cos angle between normal and light direction float n_dot_v; // cos angle between normal and view direction float n_dot_h; // cos angle between normal and half vector float l_dot_h; // cos angle between light direction and half vector float v_dot_h; // cos angle between view direction and half vector vec3 padding; }; angular_info_t get_angular_info(vec3 point_to_light, vec3 normal, vec3 view) { // Standard one-letter names vec3 n = normalize(normal); // Outward direction of surface point vec3 v = normalize(view); // Direction from surface point to view vec3 l = normalize(point_to_light); // Direction from surface point to light vec3 h = normalize(l + v); // Direction of the vector between l and v float NdotL = clamp(dot(n, l), 0.0, 1.0); float NdotV = clamp(dot(n, v), 0.0, 1.0); float NdotH = clamp(dot(n, h), 0.0, 1.0); float LdotH = clamp(dot(l, h), 0.0, 1.0); float VdotH = clamp(dot(v, h), 0.0, 1.0); return angular_info_t( NdotL, NdotV, NdotH, LdotH, VdotH, vec3(0, 0, 0) ); } const float M_PI = 3.141592653589793; // The following equation models the Fresnel reflectance term of the spec equation (aka F()) // Implementation of fresnel from [4], Equation 15 vec3 specular_reflection(material_info_t material_info, angular_info_t angular_info) { return material_info.reflectance0 + (material_info.reflectance90 - material_info.reflectance0) * pow(clamp(1.0 - angular_info.v_dot_h, 0.0, 1.0), 5.0); } // Smith Joint GGX // Note: Vis = G / (4 * NdotL * NdotV) // see Eric Heitz. 2014. Understanding the Masking-Shadowing Function in Microfacet-Based BRDFs. Journal of Computer Graphics Techniques, 3 // see Real-Time Rendering. Page 331 to 336. // see https://google.github.io/filament/Filament.md.html#materialsystem/specularbrdf/geometricshadowing(specularg) float visibility_occlusion(material_info_t material_info, angular_info_t angular_info) { float n_dot_l = angular_info.n_dot_l; float n_dot_v = angular_info.n_dot_v; float alpha_roughness_sq = material_info.alpha_roughness * material_info.alpha_roughness; float GGXV = n_dot_l * sqrt(n_dot_v * n_dot_v * (1.0 - alpha_roughness_sq) + alpha_roughness_sq); float GGXL = n_dot_v * sqrt(n_dot_l * n_dot_v * (1.0 - alpha_roughness_sq) + alpha_roughness_sq); float GGX = GGXV + GGXL; if (GGX > 0.0) { return 0.5 / GGX; } return 0.0; } // The following equation(s) model the distribution of microfacet normals across the area being drawn (aka D()) // Implementation from "Average Irregularity Representation of a Roughened Surface for Ray Reflection" by T. S. Trowbridge, and K. P. Reitz // Follows the distribution function recommended in the SIGGRAPH 2013 course notes from EPIC Games [1], Equation 3. float microfacet_distribution(material_info_t material_info, angular_info_t angular_info) { float alpha_roughness_sq = material_info.alpha_roughness * material_info.alpha_roughness; float f = (angular_info.n_dot_h * alpha_roughness_sq - angular_info.n_dot_h) * angular_info.n_dot_h + 1.0; return alpha_roughness_sq / (M_PI * f * f); } // Lambert lighting // see https://seblagarde.wordpress.com/2012/01/08/pi-or-not-to-pi-in-game-lighting-equation/ vec3 diffuse(material_info_t material_info) { return material_info.diffuse_color / M_PI; } vec3 get_point_shade(vec3 point_to_light, material_info_t material_info, vec3 normal, vec3 view) { angular_info_t angular_info = get_angular_info(point_to_light, normal, view); if ((angular_info.n_dot_l > 0.0) || (angular_info.n_dot_v > 0.0)) { // Calculate the shading terms for the microfacet specular shading model vec3 F = specular_reflection(material_info, angular_info); float Vis = visibility_occlusion(material_info, angular_info); float D = microfacet_distribution(material_info, angular_info); // Calculation of analytical lighting contribution vec3 diffuse_contrib = (1.0 - F) * diffuse(material_info); vec3 spec_contrib = F * Vis * D; // Obtain final intensity as reflectance (BRDF) scaled by the energy of the light (cosine law) return angular_info.n_dot_l * (diffuse_contrib + spec_contrib); } return vec3(0.0, 0.0, 0.0); } float get_range_attenuation(float range, float distance) { if (range < 0.0) { return 1.0; } return max(min(1.0 - pow(distance / range, 4.0), 1.0), 0.0) / pow(distance, 2.0); } vec3 apply_point_light(material_info_t material_info, vec3 normal, vec3 view) { vec3 point_to_light = light_pos - v_pos; float distance = length(point_to_light); float attenuation = get_range_attenuation(light_range, distance); vec3 shade = get_point_shade(point_to_light, material_info, normal, view); return attenuation * light_intensity * light_color * shade; } // Uncharted 2 tone map // see: http://filmicworlds.com/blog/filmic-tonemapping-operators/ vec3 toneMapUncharted2Impl(vec3 color) { const float A = 0.15; const float B = 0.50; const float C = 0.10; const float D = 0.20; const float E = 0.02; const float F = 0.30; return ((color*(A*color+C*B)+D*E)/(color*(A*color+B)+D*F))-E/F; } vec3 toneMapUncharted(vec3 color) { const float W = 11.2; color = toneMapUncharted2Impl(color * 2.0); vec3 whiteScale = 1.0 / toneMapUncharted2Impl(vec3(W)); return linear_to_srgb(color * whiteScale); } vec3 tone_map(vec3 color) { // color *= exposure; return toneMapUncharted(color); } void main() { const vec3 f0 = vec3(0.04); // Roughness is stored in the 'a' channel, metallic is stored in the 'r' channel. // This layout intentionally reserves the 'r' channel for (optional) occlusion map data vec4 mr_sample = texture(sampler2D(metallic_roughness_tex, metallic_roughness_smp), v_uv); float perceptual_roughness = clamp(mr_sample.w * roughness_factor, 0.0, 1.0); float metallic = clamp(mr_sample.x * metallic_factor, 0.0, 1.0); vec4 base_color = srgb_to_linear(texture(sampler2D(base_color_tex, base_color_smp), v_uv)) * base_color_factor; vec3 diffuse_color = base_color.rgb * (vec3(1.0)-f0) * (1.0 - metallic); vec3 specular_color = mix(f0, base_color.rgb, metallic); // Roughness is authored as perceptual roughness; as is convention, // convert to material roughness by squaring the perceptual roughness [2]. float alpha_roughness = perceptual_roughness * perceptual_roughness; float reflectance = max(max(specular_color.r, specular_color.g), specular_color.b); vec3 specular_environment_r0 = specular_color; // Anything less than 2% is physically impossible and is instead considered to be shadowing. // Compare to "Real-Time-Rendering" 4th editon on page 325. vec3 specular_environment_r90 = vec3(clamp(reflectance * 50.0, 0.0, 1.0)); material_info_t material_info = material_info_t( perceptual_roughness, specular_environment_r0, alpha_roughness, diffuse_color, specular_environment_r90, specular_color ); // lighting vec3 normal = get_normal(); vec3 view = normalize(v_eye_pos - v_pos); vec3 color = apply_point_light(material_info, normal, view); color *= texture(sampler2D(occlusion_tex, occlusion_smp), v_uv).r; color += srgb_to_linear(texture(sampler2D(emissive_tex, emissive_smp), v_uv)).rgb * emissive_factor; frag_color = vec4(tone_map(color), 1.0); } @end @program metallic vs metallic_fs