Unity2017程序化天空Shader实现
Posted JaffHan
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Unity2017程序化天空Shader实现相关的知识,希望对你有一定的参考价值。
unity2017提供了一个程序化生成天空的Shader,程序化天空在实时昼夜变换和天气系统中都有必要性,所以简单来分析一下。
1 // Unity built-in shader source. Copyright (c) 2016 Unity Technologies. MIT license (see license.txt) 2 3 Shader "Skybox/Procedural" { 4 Properties { 5 [KeywordEnum(None, Simple, High Quality)] _SunDisk ("Sun", Int) = 2 6 _SunSize ("Sun Size", Range(0,1)) = 0.04 7 8 _AtmosphereThickness ("Atmosphere Thickness", Range(0,5)) = 1.0 9 _SkyTint ("Sky Tint", Color) = (.5, .5, .5, 1) 10 _GroundColor ("Ground", Color) = (.369, .349, .341, 1) 11 12 _Exposure("Exposure", Range(0, 8)) = 1.3 13 } 14 15 SubShader { 16 Tags { "Queue"="Background" "RenderType"="Background" "PreviewType"="Skybox" } 17 Cull Off ZWrite Off 18 19 Pass { 20 21 CGPROGRAM 22 #pragma vertex vert 23 #pragma fragment frag 24 25 #include "UnityCG.cginc" 26 #include "Lighting.cginc" 27 28 #pragma multi_compile _SUNDISK_NONE _SUNDISK_SIMPLE _SUNDISK_HIGH_QUALITY 29 30 uniform half _Exposure; // HDR exposure 31 uniform half3 _GroundColor; 32 uniform half _SunSize; 33 uniform half3 _SkyTint; 34 uniform half _AtmosphereThickness; 35 36 #if defined(UNITY_COLORSPACE_GAMMA) 37 #define GAMMA 2 38 #define COLOR_2_GAMMA(color) color 39 #define COLOR_2_LINEAR(color) color*color 40 #define LINEAR_2_OUTPUT(color) sqrt(color) 41 #else 42 #define GAMMA 2.2 43 // HACK: to get gfx-tests in Gamma mode to agree until UNITY_ACTIVE_COLORSPACE_IS_GAMMA is working properly 44 #define COLOR_2_GAMMA(color) ((unity_ColorSpaceDouble.r>2.0) ? pow(color,1.0/GAMMA) : color) 45 #define COLOR_2_LINEAR(color) color 46 #define LINEAR_2_LINEAR(color) color 47 #endif 48 49 // RGB wavelengths 50 // .35 (.62=158), .43 (.68=174), .525 (.75=190) 51 static const float3 kDefaultScatteringWavelength = float3(.65, .57, .475); 52 static const float3 kVariableRangeForScatteringWavelength = float3(.15, .15, .15); 53 54 #define OUTER_RADIUS 1.025 55 static const float kOuterRadius = OUTER_RADIUS; 56 static const float kOuterRadius2 = OUTER_RADIUS*OUTER_RADIUS; 57 static const float kInnerRadius = 1.0; 58 static const float kInnerRadius2 = 1.0; 59 60 static const float kCameraHeight = 0.0001; 61 62 #define kRAYLEIGH (lerp(0.0, 0.0025, pow(_AtmosphereThickness,2.5))) // Rayleigh constant 63 #define kMIE 0.0010 // Mie constant 64 #define kSUN_BRIGHTNESS 20.0 // Sun brightness 65 66 #define kMAX_SCATTER 50.0 // Maximum scattering value, to prevent math overflows on Adrenos 67 68 static const half kSunScale = 400.0 * kSUN_BRIGHTNESS; 69 static const float kKmESun = kMIE * kSUN_BRIGHTNESS; 70 static const float kKm4PI = kMIE * 4.0 * 3.14159265; 71 static const float kScale = 1.0 / (OUTER_RADIUS - 1.0); 72 static const float kScaleDepth = 0.25; 73 static const float kScaleOverScaleDepth = (1.0 / (OUTER_RADIUS - 1.0)) / 0.25; 74 static const float kSamples = 2.0; // THIS IS UNROLLED MANUALLY, DON‘T TOUCH 75 76 #define MIE_G (-0.990) 77 #define MIE_G2 0.9801 78 79 #define SKY_GROUND_THRESHOLD 0.02 80 81 // fine tuning of performance. You can override defines here if you want some specific setup 82 // or keep as is and allow later code to set it according to target api 83 84 // if set vprog will output color in final color space (instead of linear always) 85 // in case of rendering in gamma mode that means that we will do lerps in gamma mode too, so there will be tiny difference around horizon 86 // #define SKYBOX_COLOR_IN_TARGET_COLOR_SPACE 0 87 88 // sun disk rendering: 89 // no sun disk - the fastest option 90 #define SKYBOX_SUNDISK_NONE 0 91 // simplistic sun disk - without mie phase function 92 #define SKYBOX_SUNDISK_SIMPLE 1 93 // full calculation - uses mie phase function 94 #define SKYBOX_SUNDISK_HQ 2 95 96 // uncomment this line and change SKYBOX_SUNDISK_SIMPLE to override material settings 97 // #define SKYBOX_SUNDISK SKYBOX_SUNDISK_SIMPLE 98 99 #ifndef SKYBOX_SUNDISK 100 #if defined(_SUNDISK_NONE) 101 #define SKYBOX_SUNDISK SKYBOX_SUNDISK_NONE 102 #elif defined(_SUNDISK_SIMPLE) 103 #define SKYBOX_SUNDISK SKYBOX_SUNDISK_SIMPLE 104 #else 105 #define SKYBOX_SUNDISK SKYBOX_SUNDISK_HQ 106 #endif 107 #endif 108 109 #ifndef SKYBOX_COLOR_IN_TARGET_COLOR_SPACE 110 #if defined(SHADER_API_MOBILE) 111 #define SKYBOX_COLOR_IN_TARGET_COLOR_SPACE 1 112 #else 113 #define SKYBOX_COLOR_IN_TARGET_COLOR_SPACE 0 114 #endif 115 #endif 116 117 // Calculates the Rayleigh phase function 118 half getRayleighPhase(half eyeCos2) 119 { 120 return 0.75 + 0.75*eyeCos2; 121 } 122 half getRayleighPhase(half3 light, half3 ray) 123 { 124 half eyeCos = dot(light, ray); 125 return getRayleighPhase(eyeCos * eyeCos); 126 } 127 128 129 struct appdata_t 130 { 131 float4 vertex : POSITION; 132 UNITY_VERTEX_INPUT_INSTANCE_ID 133 }; 134 135 struct v2f 136 { 137 float4 pos : SV_POSITION; 138 139 #if SKYBOX_SUNDISK == SKYBOX_SUNDISK_HQ 140 // for HQ sun disk, we need vertex itself to calculate ray-dir per-pixel 141 half3 vertex : TEXCOORD0; 142 #elif SKYBOX_SUNDISK == SKYBOX_SUNDISK_SIMPLE 143 half3 rayDir : TEXCOORD0; 144 #else 145 // as we dont need sun disk we need just rayDir.y (sky/ground threshold) 146 half skyGroundFactor : TEXCOORD0; 147 #endif 148 149 // calculate sky colors in vprog 150 half3 groundColor : TEXCOORD1; 151 half3 skyColor : TEXCOORD2; 152 153 #if SKYBOX_SUNDISK != SKYBOX_SUNDISK_NONE 154 half3 sunColor : TEXCOORD3; 155 #endif 156 157 UNITY_VERTEX_OUTPUT_STEREO 158 }; 159 160 161 float scale(float inCos) 162 { 163 float x = 1.0 - inCos; 164 #if defined(SHADER_API_N3DS) 165 // The polynomial expansion here generates too many swizzle instructions for the 3DS vertex assembler 166 // Approximate by removing x^1 and x^2 167 return 0.25 * exp(-0.00287 + x*x*x*(-6.80 + x*5.25)); 168 #else 169 return 0.25 * exp(-0.00287 + x*(0.459 + x*(3.83 + x*(-6.80 + x*5.25)))); 170 #endif 171 } 172 173 v2f vert (appdata_t v) 174 { 175 v2f OUT; 176 UNITY_SETUP_INSTANCE_ID(v); 177 UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(OUT); 178 OUT.pos = UnityObjectToClipPos(v.vertex); 179 180 float3 kSkyTintInGammaSpace = COLOR_2_GAMMA(_SkyTint); // convert tint from Linear back to Gamma 181 float3 kScatteringWavelength = lerp ( 182 kDefaultScatteringWavelength-kVariableRangeForScatteringWavelength, 183 kDefaultScatteringWavelength+kVariableRangeForScatteringWavelength, 184 half3(1,1,1) - kSkyTintInGammaSpace); // using Tint in sRGB gamma allows for more visually linear interpolation and to keep (.5) at (128, gray in sRGB) point 185 float3 kInvWavelength = 1.0 / pow(kScatteringWavelength, 4); 186 187 float kKrESun = kRAYLEIGH * kSUN_BRIGHTNESS; 188 float kKr4PI = kRAYLEIGH * 4.0 * 3.14159265; 189 190 float3 cameraPos = float3(0,kInnerRadius + kCameraHeight,0); // The camera‘s current position 191 192 // Get the ray from the camera to the vertex and its length (which is the far point of the ray passing through the atmosphere) 193 float3 eyeRay = normalize(mul((float3x3)unity_ObjectToWorld, v.vertex.xyz)); 194 195 float far = 0.0; 196 half3 cIn, cOut; 197 198 if(eyeRay.y >= 0.0) 199 { 200 // Sky 201 // Calculate the length of the "atmosphere" 202 far = sqrt(kOuterRadius2 + kInnerRadius2 * eyeRay.y * eyeRay.y - kInnerRadius2) - kInnerRadius * eyeRay.y; 203 204 float3 pos = cameraPos + far * eyeRay; 205 206 // Calculate the ray‘s starting position, then calculate its scattering offset 207 float height = kInnerRadius + kCameraHeight; 208 float depth = exp(kScaleOverScaleDepth * (-kCameraHeight)); 209 float startAngle = dot(eyeRay, cameraPos) / height; 210 float startOffset = depth*scale(startAngle); 211 212 213 // Initialize the scattering loop variables 214 float sampleLength = far / kSamples; 215 float scaledLength = sampleLength * kScale; 216 float3 sampleRay = eyeRay * sampleLength; 217 float3 samplePoint = cameraPos + sampleRay * 0.5; 218 219 // Now loop through the sample rays 220 float3 frontColor = float3(0.0, 0.0, 0.0); 221 // Weird workaround: WP8 and desktop FL_9_3 do not like the for loop here 222 // (but an almost identical loop is perfectly fine in the ground calculations below) 223 // Just unrolling this manually seems to make everything fine again. 224 // for(int i=0; i<int(kSamples); i++) 225 { 226 float height = length(samplePoint); 227 float depth = exp(kScaleOverScaleDepth * (kInnerRadius - height)); 228 float lightAngle = dot(_WorldSpaceLightPos0.xyz, samplePoint) / height; 229 float cameraAngle = dot(eyeRay, samplePoint) / height; 230 float scatter = (startOffset + depth*(scale(lightAngle) - scale(cameraAngle))); 231 float3 attenuate = exp(-clamp(scatter, 0.0, kMAX_SCATTER) * (kInvWavelength * kKr4PI + kKm4PI)); 232 233 frontColor += attenuate * (depth * scaledLength); 234 samplePoint += sampleRay; 235 } 236 { 237 float height = length(samplePoint); 238 float depth = exp(kScaleOverScaleDepth * (kInnerRadius - height)); 239 float lightAngle = dot(_WorldSpaceLightPos0.xyz, samplePoint) / height; 240 float cameraAngle = dot(eyeRay, samplePoint) / height; 241 float scatter = (startOffset + depth*(scale(lightAngle) - scale(cameraAngle))); 242 float3 attenuate = exp(-clamp(scatter, 0.0, kMAX_SCATTER) * (kInvWavelength * kKr4PI + kKm4PI)); 243 244 frontColor += attenuate * (depth * scaledLength); 245 samplePoint += sampleRay; 246 } 247 248 249 250 // Finally, scale the Mie and Rayleigh colors and set up the varying variables for the pixel shader 251 cIn = frontColor * (kInvWavelength * kKrESun); 252 cOut = frontColor * kKmESun; 253 } 254 else 255 { 256 // Ground 257 far = (-kCameraHeight) / (min(-0.001, eyeRay.y)); 258 259 float3 pos = cameraPos + far * eyeRay; 260 261 // Calculate the ray‘s starting position, then calculate its scattering offset 262 float depth = exp((-kCameraHeight) * (1.0/kScaleDepth)); 263 float cameraAngle = dot(-eyeRay, pos); 264 float lightAngle = dot(_WorldSpaceLightPos0.xyz, pos); 265 float cameraScale = scale(cameraAngle); 266 float lightScale = scale(lightAngle); 267 float cameraOffset = depth*cameraScale; 268 float temp = (lightScale + cameraScale); 269 270 // Initialize the scattering loop variables 271 float sampleLength = far / kSamples; 272 float scaledLength = sampleLength * kScale; 273 float3 sampleRay = eyeRay * sampleLength; 274 float3 samplePoint = cameraPos + sampleRay * 0.5; 275 276 // Now loop through the sample rays 277 float3 frontColor = float3(0.0, 0.0, 0.0); 278 float3 attenuate; 279 // for(int i=0; i<int(kSamples); i++) // Loop removed because we kept hitting SM2.0 temp variable limits. Doesn‘t affect the image too much. 280 { 281 float height = length(samplePoint); 282 float depth = exp(kScaleOverScaleDepth * (kInnerRadius - height)); 283 float scatter = depth*temp - cameraOffset; 284 attenuate = exp(-clamp(scatter, 0.0, kMAX_SCATTER) * (kInvWavelength * kKr4PI + kKm4PI)); 285 frontColor += attenuate * (depth * scaledLength); 286 samplePoint += sampleRay; 287 } 288 289 cIn = frontColor * (kInvWavelength * kKrESun + kKmESun); 290 cOut = clamp(attenuate, 0.0, 1.0); 291 } 292 293 #if SKYBOX_SUNDISK == SKYBOX_SUNDISK_HQ 294 OUT.vertex = -v.vertex; 295 #elif SKYBOX_SUNDISK == SKYBOX_SUNDISK_SIMPLE 296 OUT.rayDir = half3(-eyeRay); 297 #else 298 OUT.skyGroundFactor = -eyeRay.y / SKY_GROUND_THRESHOLD; 299 #endif 300 301 // if we want to calculate color in vprog: 302 // 1. in case of linear: multiply by _Exposure in here (even in case of lerp it will be common multiplier, so we can skip mul in fshader) 303 // 2. in case of gamma and SKYBOX_COLOR_IN_TARGET_COLOR_SPACE: do sqrt right away instead of doing that in fshader 304 305 OUT.groundColor = _Exposure * (cIn + COLOR_2_LINEAR(_GroundColor) * cOut); 306 OUT.skyColor = _Exposure * (cIn * getRayleighPhase(_WorldSpaceLightPos0.xyz, -eyeRay)); 307 308 #if SKYBOX_SUNDISK != SKYBOX_SUNDISK_NONE 309 OUT.sunColor = _Exposure * (cOut * _LightColor0.xyz); 310 #endif 311 312 #if defined(UNITY_COLORSPACE_GAMMA) && SKYBOX_COLOR_IN_TARGET_COLOR_SPACE 313 OUT.groundColor = sqrt(OUT.groundColor); 314 OUT.skyColor = sqrt(OUT.skyColor); 315 #if SKYBOX_SUNDISK != SKYBOX_SUNDISK_NONE 316 OUT.sunColor= sqrt(OUT.sunColor); 317 #endif 318 #endif 319 320 return OUT; 321 } 322 323 324 // Calculates the Mie phase function 325 half getMiePhase(half eyeCos, half eyeCos2) 326 { 327 half temp = 1.0 + MIE_G2 - 2.0 * MIE_G * eyeCos; 328 temp = pow(temp, pow(_SunSize,0.65) * 10); 329 temp = max(temp,1.0e-4); // prevent division by zero, esp. in half precision 330 temp = 1.5 * ((1.0 - MIE_G2) / (2.0 + MIE_G2)) * (1.0 + eyeCos2) / temp; 331 #if defined(UNITY_COLORSPACE_GAMMA) && SKYBOX_COLOR_IN_TARGET_COLOR_SPACE 332 temp = pow(temp, .454545); 333 #endif 334 return temp; 335 } 336 337 half calcSunSpot(half3 vec1, half3 vec2) 338 { 339 half3 delta = vec1 - vec2; 340 half dist = length(delta); 341 half spot = 1.0 - smoothstep(0.0, _SunSize, dist); 342 return kSunScale * spot * spot; 343 } 344 345 half4 frag (v2f IN) : SV_Target 346 { 347 half3 col = half3(0.0, 0.0, 0.0); 348 349 // if y > 1 [eyeRay.y < -SKY_GROUND_THRESHOLD] - ground 350 // if y >= 0 and < 1 [eyeRay.y <= 0 and > -SKY_GROUND_THRESHOLD] - horizon 351 // if y < 0 [eyeRay.y > 0] - sky 352 #if SKYBOX_SUNDISK == SKYBOX_SUNDISK_HQ 353 half3 ray = normalize(mul((float3x3)unity_ObjectToWorld, IN.vertex)); 354 half y = ray.y / SKY_GROUND_THRESHOLD; 355 #elif SKYBOX_SUNDISK == SKYBOX_SUNDISK_SIMPLE 356 half3 ray = IN.rayDir.xyz; 357 half y = ray.y / SKY_GROUND_THRESHOLD; 358 #else 359 half y = IN.skyGroundFactor; 360 #endif 361 362 // if we did precalculate color in vprog: just do lerp between them 363 col = lerp(IN.skyColor, IN.groundColor, saturate(y)); 364 365 #if SKYBOX_SUNDISK != SKYBOX_SUNDISK_NONE 366 if(y < 0.0) 367 { 368 #if SKYBOX_SUNDISK == SKYBOX_SUNDISK_SIMPLE 369 half mie = calcSunSpot(_WorldSpaceLightPos0.xyz, -ray); 370 #else // SKYBOX_SUNDISK_HQ 371 half eyeCos = dot(_WorldSpaceLightPos0.xyz, ray); 372 half eyeCos2 = eyeCos * eyeCos; 373 half mie = getMiePhase(eyeCos, eyeCos2); 374 #endif 375 376 col += mie * IN.sunColor; 377 } 378 #endif 379 380 #if defined(UNITY_COLORSPACE_GAMMA) && !SKYBOX_COLOR_IN_TARGET_COLOR_SPACE 381 col = LINEAR_2_OUTPUT(col); 382 #endif 383 384 return half4(col,1.0); 385 386 } 387 ENDCG 388 } 389 } 390 391 392 Fallback Off 393 394 }
shader暴露给编辑器以下属性:分别是太阳模式,太阳尺寸,大气雾厚度,天空颜色,地面颜色,曝光度
以上是关于Unity2017程序化天空Shader实现的主要内容,如果未能解决你的问题,请参考以下文章
unity shader2 --Unlit顶点片元shader