Skybox-Procedural.shader 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. // Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'
  2. Shader "Skybox/Procedural" {
  3. Properties {
  4. _SunTint ("Sun Tint", Color) = (1, 1, 1, 1)
  5. _SunStrength ("Sun Strength", Float) = 1.0
  6. _Color ("Atmosphere Tint", Color) = (.5, .5, .5, 1)
  7. _GroundColor ("Ground", Color) = (.369, .349, .341, 1)
  8. _HdrExposure("HDR Exposure", Float) = 1.3
  9. }
  10. SubShader {
  11. Tags { "Queue"="Background" "RenderType"="Background" "PreviewType"="Skybox" }
  12. Cull Off ZWrite Off
  13. Pass {
  14. CGPROGRAM
  15. #pragma vertex vert
  16. #pragma fragment frag
  17. #include "UnityCG.cginc"
  18. #include "Lighting.cginc"
  19. uniform half _HdrExposure; // HDR exposure
  20. uniform half3 _GroundColor;
  21. half3 _Color;
  22. half3 _SunTint;
  23. half _SunStrength;
  24. // RGB wavelengths
  25. #define GAMMA .454545
  26. static const float MN = 2;
  27. static const float MX = .7;
  28. #define WR (0.65*lerp(MN, MX, pow(_Color.r,GAMMA)))
  29. #define WG (0.57*lerp(MN, MX, pow(_Color.g,GAMMA)))
  30. #define WB (0.475*lerp(MN, MX, pow(_Color.b,GAMMA)))
  31. //#define WR pow(0.65,GAMMA)
  32. //#define WG pow(0.57,GAMMA)
  33. //#define WB pow(0.475,GAMMA)
  34. static const float3 kInvWavelength = float3(1.0 / (WR*WR*WR*WR), 1.0 / (WG*WG*WG*WG), 1.0 / (WB*WB*WB*WB));
  35. #define OUTER_RADIUS 1.025
  36. static const float kOuterRadius = OUTER_RADIUS;
  37. static const float kOuterRadius2 = OUTER_RADIUS*OUTER_RADIUS;
  38. static const float kInnerRadius = 1.0;
  39. static const float kInnerRadius2 = 1.0;
  40. static const float kCameraHeight = 0.0001;
  41. #define kRAYLEIGH 0.0025 // Rayleigh constant
  42. #define kMIE 0.0010 // Mie constant
  43. #define kSUN_BRIGHTNESS 20.0 // Sun brightness
  44. static const float kKrESun = kRAYLEIGH * kSUN_BRIGHTNESS;
  45. static const float kKmESun = kMIE * kSUN_BRIGHTNESS;
  46. static const float kKr4PI = kRAYLEIGH * 4.0 * 3.14159265;
  47. static const float kKm4PI = kMIE * 4.0 * 3.14159265;
  48. static const float kScale = 1.0 / (OUTER_RADIUS - 1.0);
  49. static const float kScaleDepth = 0.25;
  50. static const float kScaleOverScaleDepth = (1.0 / (OUTER_RADIUS - 1.0)) / 0.25;
  51. static const float kSamples = 2.0; // THIS IS UNROLLED MANUALLY, DON'T TOUCH
  52. #define MIE_G (-0.990)
  53. #define MIE_G2 0.9801
  54. struct appdata_t {
  55. float4 vertex : POSITION;
  56. };
  57. struct v2f {
  58. float4 pos : SV_POSITION;
  59. half3 rayDir : TEXCOORD0; // Vector for incoming ray, normalized ( == -eyeRay )
  60. half3 cIn : TEXCOORD1; // In-scatter coefficient
  61. half3 cOut : TEXCOORD2; // Out-scatter coefficient
  62. };
  63. float scale(float inCos)
  64. {
  65. float x = 1.0 - inCos;
  66. return 0.25 * exp(-0.00287 + x*(0.459 + x*(3.83 + x*(-6.80 + x*5.25))));
  67. }
  68. v2f vert (appdata_t v)
  69. {
  70. v2f OUT;
  71. OUT.pos = mul(UNITY_MATRIX_MVP, v.vertex);
  72. float3 cameraPos = float3(0,kInnerRadius + kCameraHeight,0); // The camera's current position
  73. // Get the ray from the camera to the vertex and its length (which is the far point of the ray passing through the atmosphere)
  74. float3 eyeRay = normalize(mul((float3x3)unity_ObjectToWorld, v.vertex.xyz));
  75. OUT.rayDir = half3(-eyeRay);
  76. float far = 0.0;
  77. if(eyeRay.y >= 0.0)
  78. {
  79. // Sky
  80. // Calculate the length of the "atmosphere"
  81. far = sqrt(kOuterRadius2 + kInnerRadius2 * eyeRay.y * eyeRay.y - kInnerRadius2) - kInnerRadius * eyeRay.y;
  82. float3 pos = cameraPos + far * eyeRay;
  83. // Calculate the ray's starting position, then calculate its scattering offset
  84. float height = kInnerRadius + kCameraHeight;
  85. float depth = exp(kScaleOverScaleDepth * (-kCameraHeight));
  86. float startAngle = dot(eyeRay, cameraPos) / height;
  87. float startOffset = depth*scale(startAngle);
  88. // Initialize the scattering loop variables
  89. float sampleLength = far / kSamples;
  90. float scaledLength = sampleLength * kScale;
  91. float3 sampleRay = eyeRay * sampleLength;
  92. float3 samplePoint = cameraPos + sampleRay * 0.5;
  93. // Now loop through the sample rays
  94. float3 frontColor = float3(0.0, 0.0, 0.0);
  95. // WTF BBQ: WP8 and desktop FL_9_1 do not like the for loop here
  96. // (but an almost identical loop is perfectly fine in the ground calculations below)
  97. // Just unrolling this manually seems to make everything fine again.
  98. // for(int i=0; i<int(kSamples); i++)
  99. {
  100. float height = length(samplePoint);
  101. float depth = exp(kScaleOverScaleDepth * (kInnerRadius - height));
  102. float lightAngle = dot(_WorldSpaceLightPos0.xyz, samplePoint) / height;
  103. float cameraAngle = dot(eyeRay, samplePoint) / height;
  104. float scatter = (startOffset + depth*(scale(lightAngle) - scale(cameraAngle)));
  105. float3 attenuate = exp(-scatter * (kInvWavelength * kKr4PI + kKm4PI));
  106. frontColor += attenuate * (depth * scaledLength);
  107. samplePoint += sampleRay;
  108. }
  109. {
  110. float height = length(samplePoint);
  111. float depth = exp(kScaleOverScaleDepth * (kInnerRadius - height));
  112. float lightAngle = dot(_WorldSpaceLightPos0.xyz, samplePoint) / height;
  113. float cameraAngle = dot(eyeRay, samplePoint) / height;
  114. float scatter = (startOffset + depth*(scale(lightAngle) - scale(cameraAngle)));
  115. float3 attenuate = exp(-scatter * (kInvWavelength * kKr4PI + kKm4PI));
  116. frontColor += attenuate * (depth * scaledLength);
  117. samplePoint += sampleRay;
  118. }
  119. // Finally, scale the Mie and Rayleigh colors and set up the varying variables for the pixel shader
  120. OUT.cIn.xyz = frontColor * (kInvWavelength * kKrESun);
  121. OUT.cOut = frontColor * kKmESun;
  122. }
  123. else
  124. {
  125. // Ground
  126. far = (-kCameraHeight) / (min(-0.00001, eyeRay.y));
  127. float3 pos = cameraPos + far * eyeRay;
  128. // Calculate the ray's starting position, then calculate its scattering offset
  129. float depth = exp((-kCameraHeight) * (1.0/kScaleDepth));
  130. float cameraAngle = dot(-eyeRay, pos);
  131. float lightAngle = dot(_WorldSpaceLightPos0.xyz, pos);
  132. float cameraScale = scale(cameraAngle);
  133. float lightScale = scale(lightAngle);
  134. float cameraOffset = depth*cameraScale;
  135. float temp = (lightScale + cameraScale);
  136. // Initialize the scattering loop variables
  137. float sampleLength = far / kSamples;
  138. float scaledLength = sampleLength * kScale;
  139. float3 sampleRay = eyeRay * sampleLength;
  140. float3 samplePoint = cameraPos + sampleRay * 0.5;
  141. // Now loop through the sample rays
  142. float3 frontColor = float3(0.0, 0.0, 0.0);
  143. float3 attenuate;
  144. for(int i=0; i<int(kSamples); i++)
  145. {
  146. float height = length(samplePoint);
  147. float depth = exp(kScaleOverScaleDepth * (kInnerRadius - height));
  148. float scatter = depth*temp - cameraOffset;
  149. attenuate = exp(-scatter * (kInvWavelength * kKr4PI + kKm4PI));
  150. frontColor += attenuate * (depth * scaledLength);
  151. samplePoint += sampleRay;
  152. }
  153. OUT.cIn.xyz = frontColor * (kInvWavelength * kKrESun + kKmESun);
  154. OUT.cOut.xyz = clamp(attenuate, 0.0, 1.0);
  155. }
  156. return OUT;
  157. }
  158. // Calculates the Mie phase function
  159. half getMiePhase(half eyeCos, half eyeCos2)
  160. {
  161. half temp = 1.0 + MIE_G2 - 2.0 * MIE_G * eyeCos;
  162. // A somewhat rough approx for :
  163. // temp = pow(temp, 1.5);
  164. temp = smoothstep(0.0, 0.01, temp) * temp;
  165. temp = max(temp,1.0e-4); // prevent division by zero, esp. in half precision
  166. return 1.5 * ((1.0 - MIE_G2) / (2.0 + MIE_G2)) * (1.0 + eyeCos2) / temp;
  167. }
  168. // Calculates the Rayleigh phase function
  169. half getRayleighPhase(half eyeCos2)
  170. {
  171. return 0.75 + 0.75*eyeCos2;
  172. }
  173. half4 frag (v2f IN) : SV_Target
  174. {
  175. half3 col;
  176. if(IN.rayDir.y < 0.0)
  177. {
  178. half eyeCos = dot(_WorldSpaceLightPos0.xyz, normalize(IN.rayDir.xyz));
  179. half eyeCos2 = eyeCos * eyeCos;
  180. col = getRayleighPhase(eyeCos2) * IN.cIn.xyz + getMiePhase(eyeCos, eyeCos2) * IN.cOut * _LightColor0.xyz * _SunTint * _SunStrength;
  181. }
  182. else
  183. {
  184. col = IN.cIn.xyz + _GroundColor * IN.cOut;
  185. }
  186. //Adjust color from HDR
  187. col *= _HdrExposure;
  188. return half4(col,1.0);
  189. }
  190. ENDCG
  191. }
  192. }
  193. Fallback Off
  194. }