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 }
Procedural

shader暴露给编辑器以下属性:分别是太阳模式,太阳尺寸,大气雾厚度,天空颜色,地面颜色,曝光度

以上是关于Unity2017程序化天空Shader实现的主要内容,如果未能解决你的问题,请参考以下文章

Unity 自定义天空盒,如 Unity 天空盒

Unity学习之Shader

unity shader2 --Unlit顶点片元shader

程序化天空盒过程记录01:日月 天空渐变 大气散射

Unity Shader入门精要学习笔记 - 第17章 Unity的表面着色器探秘

[Unity Shader] 逐顶点光照和逐片元光照