/// <summary> /// Method for calculation the ambient lighting component. /// </summary> public static string AmbientComponent() { var methodBody = new List <string> { $"return vec3(color.xyz * ambientCoefficient);" }; return(GLSL.CreateMethod(GLSL.Type.Vec3, "ambientLighting", new[] { GLSL.CreateVar(GLSL.Type.Float, "ambientCoefficient"), GLSL.CreateVar(GLSL.Type.Vec4, "color") }, methodBody)); }
/// <summary> /// Calculates the attenuation of a point light. /// </summary> /// <returns></returns> public static string AttenuationPointComponent() { var methodBody = new List <string> { $"float distanceToLight = length(lightPos - fragPos);", "float distance = pow(distanceToLight / lightMaxDistance, 2.0);", "return (clamp(1.0 - pow(distance, 2.0), 0.0, 1.0)) / (pow(distance, 2.0) + 1.0);", }; return(GLSL.CreateMethod(GLSL.Type.Float, "attenuationPointComponent", new[] { GLSL.CreateVar(GLSL.Type.Vec3, "fragPos"), GLSL.CreateVar(GLSL.Type.Vec3, "lightPos"), GLSL.CreateVar(GLSL.Type.Float, "lightMaxDistance") }, methodBody)); }
//TODO: At the moment Blender's Principled BSDF material gets translated into a MaterialPBR and internally uses this lighting method for the specular component. //This is not the same lighting method as is used in Blender and will therefor only produce approximately visually correct results. /// <summary> /// Method for calculation the specular lighting component. /// Replaces the standard specular calculation with the Cook-Torrance-Shader /// </summary> public static string PbrSpecularComponent() { var methodBody = new List <string> { $"float roughnessValue = {UniformNameDeclarations.RoughnessValue}; // 0 : smooth, 1: rough", // roughness $"float F0 = {UniformNameDeclarations.FresnelReflectance}; // fresnel reflectance at normal incidence", // fresnel => Specular from Blender "float NdotL = max(dot(N, L), 0.0);", "float specular = 0.0;", "float BlinnSpecular = 0.0;", "", "if(dot(N, L) > 0.0)", "{", " // calculate intermediary values", " vec3 H = normalize(L + V);", " float NdotH = max(dot(N, H), 0.0); ", " float NdotV = max(dot(N, L), 0.0); // note: this is NdotL, which is the same value", " float VdotH = max(dot(V, H), 0.0);", " float mSquared = roughnessValue * roughnessValue;", "", "", "", "", " // -- geometric attenuation", " //[Schlick's approximation of Smith's shadow equation]", " float k= roughnessValue * sqrt(0.5 * 3.14159265);", " float one_minus_k= 1.0 - k;", " float geoAtt = ( NdotL / (NdotL * one_minus_k + k) ) * ( NdotV / (NdotV * one_minus_k + k) );", "", " // -- roughness (or: microfacet distribution function)", " // Trowbridge-Reitz or GGX, GTR2", " float a2 = mSquared * mSquared;", " float d = (NdotH * a2 - NdotH) * NdotH + 1.0;", " float roughness = a2 / (3.14 * d * d);", "", " // -- fresnel", " // [Schlick 1994, An Inexpensive BRDF Model for Physically-Based Rendering]", " float fresnel = pow(1.0 - VdotH, 5.0);", $" fresnel = clamp((50.0 * {UniformNameDeclarations.SpecularColor}.y), 0.0, 1.0) * fresnel + (1.0 - fresnel);", "", $" specular = (fresnel * geoAtt * roughness) / (NdotV * NdotL * 3.14);", " ", "}", "", $"return (k + specular * (1.0-k));" }; return(GLSL.CreateMethod(GLSL.Type.Float, "specularLighting", new[] { GLSL.CreateVar(GLSL.Type.Vec3, "N"), GLSL.CreateVar(GLSL.Type.Vec3, "L"), GLSL.CreateVar(GLSL.Type.Vec3, "V"), GLSL.CreateVar(GLSL.Type.Float, "k") }, methodBody)); }
/// <summary> /// Method for calculation the diffuse lighting component. /// </summary> public static string DiffuseComponent() { var methodBody = new List <string> { "return max(dot(N, L), 0.0);" }; return(GLSL.CreateMethod(GLSL.Type.Float, "diffuseLighting", new[] { GLSL.CreateVar(GLSL.Type.Vec3, "N"), GLSL.CreateVar(GLSL.Type.Vec3, "L") }, methodBody)); }
/// <summary> /// Creates the method for calculating whether a fragment is in shadow or not, by using a shadow map (sampler2DCube). /// The cube map is used when calculating the shadows for a point light. /// </summary> /// <returns></returns> public static string ShadowCalculationCubeMap() { var methodBody = new List <string>() { "float pcfKernelSize = pcfKernelHalfSize + pcfKernelHalfSize + 1.0;", "pcfKernelSize *= pcfKernelSize;", "vec3 sampleOffsetDirections[20] = vec3[]", "(", " vec3(pcfKernelHalfSize, pcfKernelHalfSize, pcfKernelHalfSize), vec3(pcfKernelHalfSize, -pcfKernelHalfSize, pcfKernelHalfSize), vec3(-pcfKernelHalfSize, -pcfKernelHalfSize, pcfKernelHalfSize), vec3(-pcfKernelHalfSize, pcfKernelHalfSize, pcfKernelHalfSize),", " vec3(pcfKernelHalfSize, pcfKernelHalfSize, -pcfKernelHalfSize), vec3(pcfKernelHalfSize, -pcfKernelHalfSize, -pcfKernelHalfSize), vec3(-pcfKernelHalfSize, -pcfKernelHalfSize, -pcfKernelHalfSize), vec3(-pcfKernelHalfSize, pcfKernelHalfSize, -pcfKernelHalfSize),", " vec3(pcfKernelHalfSize, pcfKernelHalfSize, 0), vec3(pcfKernelHalfSize, -pcfKernelHalfSize, 0), vec3(-pcfKernelHalfSize, -pcfKernelHalfSize, 0), vec3(-pcfKernelHalfSize, pcfKernelHalfSize, 0),", " vec3(pcfKernelHalfSize, 0, pcfKernelHalfSize), vec3(-pcfKernelHalfSize, 0, pcfKernelHalfSize), vec3(pcfKernelHalfSize, 0, -pcfKernelHalfSize), vec3(-pcfKernelHalfSize, 0, -pcfKernelHalfSize),", " vec3(0, pcfKernelHalfSize, pcfKernelHalfSize), vec3(0, -pcfKernelHalfSize, pcfKernelHalfSize), vec3(0, -pcfKernelHalfSize, -pcfKernelHalfSize), vec3(0, pcfKernelHalfSize, -pcfKernelHalfSize)", ");", "// get vector between fragment position and light position", "vec3 fragToLight = (fragPos - lightPos) * -1.0;", "// now get current linear depth as the length between the fragment and light position", "float currentDepth = length(fragToLight);", "float shadow = 0.0;", "float thisBias = max(bias * (1.0 - dot(normal, lightDir)), bias * 0.01);//0.15;", "int samples = 20;", $"vec3 camPos = {UniformNameDeclarations.IView}[3].xyz;", "float viewDistance = length(camPos - fragPos);", "float diskRadius = 0.5; //(1.0 + (viewDistance / farPlane)) / pcfKernelSize;", "for (int i = 0; i < samples; ++i)", "{", " float closestDepth = texture(shadowMap, fragToLight + sampleOffsetDirections[i] * diskRadius).r;", " closestDepth *= farPlane; // Undo mapping [0;1]", " if (currentDepth - thisBias > closestDepth)", " shadow += 1.0;", "}", "shadow /= float(samples);", "return shadow;" }; return(GLSL.CreateMethod(GLSL.Type.Float, "ShadowCalculationCubeMap", new[] { GLSL.CreateVar(GLSL.Type.SamplerCube, "shadowMap"), GLSL.CreateVar(GLSL.Type.Vec3, "fragPos"), GLSL.CreateVar(GLSL.Type.Vec3, "lightPos"), GLSL.CreateVar(GLSL.Type.Float, "farPlane"), GLSL.CreateVar(GLSL.Type.Vec3, "normal"), GLSL.CreateVar(GLSL.Type.Vec3, "lightDir"), GLSL.CreateVar(GLSL.Type.Float, "bias"), GLSL.CreateVar(GLSL.Type.Float, "pcfKernelHalfSize") }, methodBody)); }
/// <summary> /// Calculates the cone component of the attenuation of a spot light. /// </summary> /// <returns></returns> public static string AttenuationConeComponent() { var methodBody = new List <string> { "vec3 coneDir = lightDir;", "float lightToSurfaceAngleCos = dot(coneDir, -fragToLightDir);", "float epsilon = cos(innerConeAngle) - cos(outerConeAngle);", "float t = (lightToSurfaceAngleCos - cos(outerConeAngle)) / epsilon;", "return clamp(t, 0.0, 1.0);" }; return(GLSL.CreateMethod(GLSL.Type.Float, "attenuationConeComponent", new[] { GLSL.CreateVar(GLSL.Type.Vec3, "lightDir"), GLSL.CreateVar(GLSL.Type.Vec3, "fragToLightDir"), GLSL.CreateVar(GLSL.Type.Float, "innerConeAngle"), GLSL.CreateVar(GLSL.Type.Float, "outerConeAngle") }, methodBody)); }
/// <summary> /// Creates the method for calculating whether a fragment is in shadow or not, by using a shadow map (sampler2D). /// </summary> /// <returns></returns> public static string ShadowCalculation() { var methodBody = new List <string>() { "float shadow = 0.0;", "int pcfLoop = int(pcfKernelHalfSize);", "float pcfKernelSize = pcfKernelHalfSize + pcfKernelHalfSize + 1.0;", "pcfKernelSize *= pcfKernelSize;", "// perform perspective divide", "vec4 projCoords = fragPosLightSpace / fragPosLightSpace.w;", "projCoords = projCoords * 0.5 + 0.5;", "//float closestDepth = texture(shadowMap, projCoords.xy).r;", "float currentDepth = projCoords.z;", "float thisBias = max(bias * (1.0 - dot(normal, lightDir)), bias / 100.0);", "vec2 texelSize = 1.0 / vec2(textureSize(shadowMap, 0));", "//use this for using sampler2DShadow (automatic PCF) instead of sampler2D", "//float depth = texture(shadowMap, projCoords.xyz).r;", "//shadow += (currentDepth - thisBias) > depth ? 1.0 : 0.0;", "for (int x = -pcfLoop; x <= pcfLoop; ++x)", "{", "for (int y = -pcfLoop; y <= pcfLoop; ++y)", "{", "float pcfDepth = texture(shadowMap, projCoords.xy + vec2(x, y) * texelSize).r;", "shadow += (currentDepth - thisBias) > pcfDepth ? 1.0 : 0.0;", "}", "}", "shadow /= pcfKernelSize;", "return shadow;" }; return(GLSL.CreateMethod(GLSL.Type.Float, "ShadowCalculation", new[] { GLSL.CreateVar(GLSL.Type.Sampler2D, "shadowMap"), GLSL.CreateVar(GLSL.Type.Vec4, "fragPosLightSpace"), GLSL.CreateVar(GLSL.Type.Vec3, "normal"), GLSL.CreateVar(GLSL.Type.Vec3, "lightDir"), GLSL.CreateVar(GLSL.Type.Float, "bias"), GLSL.CreateVar(GLSL.Type.Float, "pcfKernelHalfSize") }, methodBody)); }
/// <summary> /// Method for calculation the specular lighting component. /// </summary> public static string SpecularComponent() { var methodBody = new List <string> { "float specularTerm = 0.0;", "if(dot(N, L) > 0.0)", "{", " // half vector", "vec3 reflectDir = reflect(-L, N);", $" specularTerm = pow(max(0.0, dot(V, reflectDir)), shininess);", "}", "return specularTerm;" }; return(GLSL.CreateMethod(GLSL.Type.Float, "specularLighting", new[] { GLSL.CreateVar(GLSL.Type.Vec3, "N"), GLSL.CreateVar(GLSL.Type.Vec3, "L"), GLSL.CreateVar(GLSL.Type.Vec3, "V"), GLSL.CreateVar(GLSL.Type.Float, "shininess") }, methodBody)); }
/// <summary> /// Wraps all the lighting methods into a single one. /// </summary> public static string ApplyLightForward(ShaderEffectProps effectProps) { var bumpNormals = new List <string> { "///////////////// Normal mapping, tangent space ///////////////////", $"vec3 N = ((texture({UniformNameDeclarations.NormalMap}, {VaryingNameDeclarations.TextureCoordinates}).rgb * 2.0) - 1.0f) * vec3({UniformNameDeclarations.NormalMapIntensity}, {UniformNameDeclarations.NormalMapIntensity}, 1.0);", $"N = (N.x * vec3({VaryingNameDeclarations.Tangent})) + (N.y * {VaryingNameDeclarations.Bitangent}) + (N.z * {VaryingNameDeclarations.Normal});", "N = normalize(N);" }; var normals = new List <string> { $"vec3 N = normalize({VaryingNameDeclarations.Normal});" }; var fragToLightDirAndLightInit = new List <string> { "vec3 L = vec3(0.0, 0.0, 0.0);", "if(lightType == 1){L = -normalize(direction);}", "else", "{", $" L = normalize(position - {VaryingNameDeclarations.Position}.xyz);", "}", $"vec3 V = normalize(-{VaryingNameDeclarations.Position}.xyz);", "if(lightType == 3) {", " L = normalize(vec3(0.0,0.0,-1.0));", "}", $"vec2 o_texcoords = {VaryingNameDeclarations.TextureCoordinates};", "", "vec3 Idif = vec3(0);", "vec3 Ispe = vec3(0);", "" }; var applyLightParams = new List <string>(); applyLightParams.AddRange(effectProps.MatProbs.HasNormalMap ? bumpNormals : normals); applyLightParams.AddRange(fragToLightDirAndLightInit); if (effectProps.MatProbs.HasAlbedo) { //TODO: Test alpha blending between diffuse and texture if (effectProps.MatProbs.HasAlbedoTexture) { applyLightParams.Add( $"vec3 blendedCol = mix({UniformNameDeclarations.AlbedoColor}.rgb, texture({UniformNameDeclarations.AlbedoTexture}, {VaryingNameDeclarations.TextureCoordinates}).rgb, {UniformNameDeclarations.AlbedoMix});" + $"Idif = blendedCol * diffuseLighting(N, L) * intensities.rgb;"); } else { applyLightParams.Add($"Idif = vec3({UniformNameDeclarations.AlbedoColor}.rgb * intensities.rgb * diffuseLighting(N, L));"); } } if (effectProps.MatProbs.HasSpecular) { if (effectProps.MatType == MaterialType.Standard) { applyLightParams.Add($"float specularTerm = specularLighting(N, L, V, {UniformNameDeclarations.SpecularShininess});"); applyLightParams.Add($"Ispe = vec3(({ UniformNameDeclarations.SpecularColor}.rgb * { UniformNameDeclarations.SpecularIntensity} *intensities.rgb) *specularTerm);"); } else if (effectProps.MatType == MaterialType.MaterialPbr) { applyLightParams.Add($"float k = 1.0 - {UniformNameDeclarations.DiffuseFraction};"); applyLightParams.Add("float specular = specularLighting(N, L, V, k);"); applyLightParams.Add($"Ispe = intensities.rgb * {UniformNameDeclarations.SpecularColor}.rgb * (k + specular * (1.0 - k));"); } } var pointLight = new List <string> { $"float att = attenuationPointComponent({VaryingNameDeclarations.Position}.xyz, position, maxDistance);", "lighting = (Idif * att) + (Ispe * att);", "lighting *= strength;" }; //No attenuation! var parallelLight = new List <string> { "lighting = Idif + Ispe;", "lighting *= strength;" }; var spotLight = new List <string> { $"float att = attenuationPointComponent({VaryingNameDeclarations.Position}.xyz, position, maxDistance) * attenuationConeComponent(direction, L, innerConeAngle, outerConeAngle);", "lighting = (Idif * att) + (Ispe * att);", "lighting *= strength;" }; // - Disable GammaCorrection for better colors /*var gammaCorrection = new List<string>() * { * "vec3 gamma = vec3(1.0/2.2);", * "result = pow(result, gamma);" * };*/ var methodBody = new List <string>(); methodBody.AddRange(applyLightParams); methodBody.Add("vec3 lighting = vec3(0);"); methodBody.Add(""); //methodBody.AddRange(attenuation); methodBody.Add("if(lightType == 0) // PointLight"); methodBody.Add("{"); methodBody.AddRange(pointLight); methodBody.Add("}"); methodBody.Add("else if(lightType == 1 || lightType == 3) // ParallelLight or LegacyLight"); methodBody.Add("{"); methodBody.AddRange(parallelLight); methodBody.Add("}"); methodBody.Add("else if(lightType == 2) // SpotLight"); methodBody.Add("{"); methodBody.AddRange(spotLight); methodBody.Add("}"); methodBody.Add(""); //methodBody.AddRange(gammaCorrection); // - Disable GammaCorrection for better colors methodBody.Add(""); methodBody.Add("return lighting;"); return(GLSL.CreateMethod(GLSL.Type.Vec3, "ApplyLight", new[] { GLSL.CreateVar(GLSL.Type.Vec3, "position"), GLSL.CreateVar(GLSL.Type.Vec4, "intensities"), GLSL.CreateVar(GLSL.Type.Vec3, "direction"), GLSL.CreateVar(GLSL.Type.Float, "maxDistance"), GLSL.CreateVar(GLSL.Type.Float, "strength"), GLSL.CreateVar(GLSL.Type.Float, "outerConeAngle"), GLSL.CreateVar(GLSL.Type.Float, "innerConeAngle"), GLSL.CreateVar(GLSL.Type.Int, "lightType"), }, methodBody)); }