Last active
January 10, 2026 01:25
-
-
Save kvu787/d0a8c90d0c9328c42f0e70864d327bb0 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* | |
| MIT License | |
| Copyright (c) 2026 K | |
| Permission is hereby granted, free of charge, to any person obtaining a copy | |
| of this software and associated documentation files (the "Software"), to deal | |
| in the Software without restriction, including without limitation the rights | |
| to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
| copies of the Software, and to permit persons to whom the Software is | |
| furnished to do so, subject to the following conditions: | |
| The above copyright notice and this permission notice shall be included in all | |
| copies or substantial portions of the Software. | |
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
| SOFTWARE. | |
| */ | |
| shader_type spatial; | |
| render_mode unshaded; | |
| /* | |
| This brightness value means that the base color appears at a facing ratio of cosine(37 degrees), | |
| which means the surface normal forms an angle of 37 degrees with the light direction. | |
| cosine(37 degrees) = 0.79863551 | |
| 1 - cosine(37 degrees) = 0.20136449 | |
| */ | |
| #define BrightnessCosine37Degrees 0.20136449 | |
| #define Blue vec4(0.039215688, 0.24705882, 0.78431374, 1) | |
| const float ZEpsilon = 0.0001; | |
| uniform vec4 BaseColor : source_color = Blue; | |
| /* | |
| Brightness is defined as the complement of the facing ratio at which the base color appears. | |
| The facing ratio is a number from 0.0 to 1.0 that indicates how much of the surface aligns with | |
| the light source. 0.0 means the surface is facing perpendicular or away from the light, so it | |
| should receive no light. 1.0 means the surface is facing the light directly, so it should | |
| receive the most light. | |
| For example, if you want high brightness, you would want the base color to appear at a very | |
| low facing ratio such as 0.2. This limits darkening to a small range from 0.0 to 0.2 and | |
| lightening to a much larger range from 0.2 to 1.0. The larger the lightening range is, the | |
| brighter the object is. | |
| */ | |
| uniform float Brightness : hint_range(0.0, 1.0) = BrightnessCosine37Degrees; | |
| uniform float Shift : hint_range(0.0, 1.0) = 0.0; | |
| uniform float Rotation : hint_range(0.0, 360.0) = 0.0; | |
| uniform float ShadowIntensity : hint_range(0.0, 1.0) = 1.0; | |
| uniform float HighlightIntensity : hint_range(0.0, 1.0) = 1.0; | |
| /* | |
| This is a modified form of Christophe Schlick's bias function. | |
| References: | |
| 1. Graphics Gems IV, 1994, Christophe Schlick | |
| 2. A Convenient Generalization of Schlick's Bias and Gain Function, 2020, Jonathan T. Barron | |
| */ | |
| float SchlickAB_Raw(float t, float a, float b) { | |
| float k1 = b * (1.0 - a); | |
| float k2 = b - a; | |
| float k3 = a * (1.0 - b); | |
| return (k1 * t) / (k2 * t + k3); | |
| } | |
| float SchlickAB(float t, float a, float b) { | |
| if (isnan(t)) { return t; } | |
| if (isnan(a)) { return a; } | |
| if (isnan(b)) { return b; } | |
| t = clamp(t, 0.0, 1.0); | |
| a = clamp(a, 0.0, 1.0); | |
| b = clamp(b, 0.0, 1.0); | |
| if ((a == 0.0) && (b == 0.0)) { | |
| return t; | |
| } else if ((a == 0.0) && (0.0 < b && b < 1.0)) { | |
| return 1.0; | |
| } else if ((a == 0.0) && (b == 1.0)) { | |
| return 1.0; | |
| } else if ((0.0 < a && a < 1.0) && (b == 0.0)) { | |
| return 0.0; | |
| } else if ((0.0 < a && a < 1.0) && (0.0 < b && b < 1.0)) { | |
| return SchlickAB_Raw(t, a, b); | |
| } else if ((0.0 < a && a < 1.0) && (b == 1.0)) { | |
| return 1.0; | |
| } else if ((a == 1.0) && (b == 0.0)) { | |
| return 0.0; | |
| } else if ((a == 1.0) && (0.0 < b && b < 1.0)) { | |
| return 0.0; | |
| } else if ((a == 1.0) && (b == 1.0)) { | |
| return t; | |
| } | |
| // This doesn't handle a defined case. | |
| return 0.0; | |
| } | |
| float SchlickA(float t, float a) { | |
| return SchlickAB(t, a, 0.5); | |
| } | |
| /* | |
| This modifies SchlickAB_Raw to be able to "squish" the output into the [c, d] range. | |
| Even though the output is bounded in [c, d] the curve still intersects point (a, b). | |
| */ | |
| float SchlickABCD_Raw(float t, float a, float b, float c, float d) { | |
| float k1 = d * (b - c) - a * b * (d - c); | |
| float k2 = a * c * (d - b); | |
| float k3 = (b - c) - a * (d - c); | |
| float k4 = a * (d - b); | |
| return (k1 * t + k2) / (k3 * t + k4); | |
| } | |
| float SchlickABCD(float t, float a, float b, float c, float d) { | |
| if (isnan(t)) { return t; } | |
| if (isnan(a)) { return a; } | |
| if (isnan(b)) { return b; } | |
| if (isnan(c)) { return c; } | |
| if (isnan(d)) { return d; } | |
| t = clamp(t, 0.0, 1.0); | |
| a = clamp(a, 0.0, 1.0); | |
| c = clamp(c, 0.0, 1.0); | |
| d = clamp(d, 0.0, 1.0); | |
| if (c > d) { | |
| return 0.0; | |
| } | |
| b = clamp(b, c, d); | |
| if ((a == 0.0) && (b == c)) { | |
| return (d - c) * t + c; | |
| } else if ((a == 0.0) && (c < b && b < d)) { | |
| return d; | |
| } else if ((a == 0.0) && (b == d)) { | |
| return d; | |
| } else if ((0.0 < a && a < 1.0) && (b == c)) { | |
| return c; | |
| } else if ((0.0 < a && a < 1.0) && (c < b && b < d)) { | |
| return SchlickABCD_Raw(t, a, b, c, d); | |
| } else if ((0.0 < a && a < 1.0) && (b == d)) { | |
| return d; | |
| } else if ((a == 1.0) && (b == c)) { | |
| return c; | |
| } else if ((a == 1.0) && (c < b && b < d)) { | |
| return c; | |
| } else if ((a == 1.0) && (b == d)) { | |
| return (d - c) * t + c; | |
| } | |
| // This doesn't handle a defined case. | |
| return 0.0; | |
| } | |
| float MapRange(float t, float aStart, float aEnd, float bStart, float bEnd) { | |
| return ((bEnd - bStart) / (aEnd - aStart)) * (t - aStart) + bStart; | |
| } | |
| vec2 RotateXY(vec2 xy, float angle) { | |
| float s = sin(angle); | |
| float c = cos(angle); | |
| // Standard CCW Matrix: [ c -s ] | |
| // [ s c ] | |
| // GLSL Column-Major: vec2(c, s), vec2(-s, c) | |
| mat2 rotation_matrix = mat2(vec2(c, s), vec2(-s, c)); | |
| return rotation_matrix * xy; | |
| } | |
| float ZComponent(float x, float y) { | |
| return sqrt(max(0.0, 1.0 - x*x - y*y)); | |
| } | |
| float XComponent(float y, float z) { | |
| return sqrt(max(0.0, 1.0 - y*y - z*z)); | |
| } | |
| /* | |
| Typically, the FacingRatio is simply the z component of the surface normal. | |
| This forms a hemisphere surface with respect to x and y. | |
| RemapFacingRatio remaps the facing ratio such that "peak" is shifted by xShift along the positive x-axis. | |
| The output of RemapFacingRatio forms a surface that is smooth (infinitely differentiable) with respect to x and y. | |
| The remapped surface resembles a warped hemisphere. | |
| */ | |
| float RemapFacingRatio(float x, float y, float z, float xShift) { | |
| if (abs(z) < ZEpsilon) { | |
| return 0.0; | |
| } | |
| float xStart = -XComponent(y, 0.0); | |
| float xEnd = XComponent(y, 0.0); | |
| float schlickT = MapRange(x, xStart, xEnd, 0.0, 1.0); | |
| float schlickA = MapRange(xShift, -1.0, 1.0, 0.0, 1.0); | |
| float schlickOut = SchlickA(schlickT, schlickA); | |
| float remappedX = MapRange(schlickOut, 0.0, 1.0, xStart, xEnd); | |
| float remappedFacingRatio = ZComponent(remappedX, y); | |
| return remappedFacingRatio; | |
| } | |
| void fragment() { | |
| vec3 normal = normalize(NORMAL); | |
| vec2 rotatedNormalXY = RotateXY(normal.xy, -1.0 * radians(Rotation)); | |
| float remappedFacingRatio = RemapFacingRatio(rotatedNormalXY.x, rotatedNormalXY.y, normal.z, Shift); | |
| float facingRatioPositionOfBaseColor = 1.0 - Brightness; | |
| vec3 darkPoint = mix(BaseColor.rgb, vec3(0.0, 0.0, 0.0), ShadowIntensity); | |
| vec3 lightPoint = mix(BaseColor.rgb, vec3(1.0, 1.0, 1.0), HighlightIntensity); | |
| ALBEDO = vec3( | |
| SchlickABCD(remappedFacingRatio, facingRatioPositionOfBaseColor, BaseColor.r, darkPoint.r, lightPoint.r), | |
| SchlickABCD(remappedFacingRatio, facingRatioPositionOfBaseColor, BaseColor.g, darkPoint.g, lightPoint.g), | |
| SchlickABCD(remappedFacingRatio, facingRatioPositionOfBaseColor, BaseColor.b, darkPoint.b, lightPoint.b)); | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment