Можно ли с помощью шейдера сделать скайбокс? Закрыт

Можно ли вообще обойтись без текстур в скайбоксе и сделать каким-то образом небо и землю с помощью изменяемых по определённой логике шейдеров? Или это делается другим образом?

Воспрос задан "January 10, 2016" к уроку "Материалы и шейдеры "

Ответы (1):


Да, вполне можно. Например в Unity 5.3 есть такой шейдер, называемый Skybox\Procedural. На сколько я понял, он как раз-таки этим и занимается. В целом, по его примеру, можно написать какой-угодно скайбокс, хоть с эффектными озоновыми дырами. :)

Shader "Skybox/Procedural" {
Properties {
	[KeywordEnum(None, Simple, High Quality)] _SunDisk ("Sun", Int) = 2
	_SunSize ("Sun Size", Range(0,1)) = 0.04
	
	_AtmosphereThickness ("Atmoshpere Thickness", Range(0,5)) = 1.0
	_SkyTint ("Sky Tint", Color) = (.5, .5, .5, 1)
	_GroundColor ("Ground", Color) = (.369, .349, .341, 1)

	_Exposure("Exposure", Range(0, 8)) = 1.3
}

SubShader {
	Tags { "Queue"="Background" "RenderType"="Background" "PreviewType"="Skybox" }
	Cull Off ZWrite Off

	Pass {

		CGPROGRAM
		#pragma vertex vert
		#pragma fragment frag

		#include "UnityCG.cginc"
		#include "Lighting.cginc"

		#pragma multi_compile __ UNITY_COLORSPACE_GAMMA
		#pragma multi_compile _SUNDISK_NONE _SUNDISK_SIMPLE _SUNDISK_HIGH_QUALITY

		uniform half _Exposure;		// HDR exposure
		uniform half3 _GroundColor;
		uniform half _SunSize;
		uniform half3 _SkyTint;
		uniform half _AtmosphereThickness;

	#if defined(UNITY_COLORSPACE_GAMMA)
		#define GAMMA 2
		#define COLOR_2_GAMMA(color) color
		#define COLOR_2_LINEAR(color) color*color
		#define LINEAR_2_OUTPUT(color) sqrt(color)
	#else
		#define GAMMA 2.2
		// HACK: to get gfx-tests in Gamma mode to agree until UNITY_ACTIVE_COLORSPACE_IS_GAMMA is working properly
		#define COLOR_2_GAMMA(color) ((unity_ColorSpaceDouble.r>2.0) ? pow(color,1.0/GAMMA) : color)
		#define COLOR_2_LINEAR(color) color
		#define LINEAR_2_LINEAR(color) color
	#endif

		// RGB wavelengths
		// .35 (.62=158), .43 (.68=174), .525 (.75=190)
		static const float3 kDefaultScatteringWavelength = float3(.65, .57, .475);
		static const float3 kVariableRangeForScatteringWavelength = float3(.15, .15, .15);

		#define OUTER_RADIUS 1.025
		static const float kOuterRadius = OUTER_RADIUS;
		static const float kOuterRadius2 = OUTER_RADIUS*OUTER_RADIUS;
		static const float kInnerRadius = 1.0;
		static const float kInnerRadius2 = 1.0;

		static const float kCameraHeight = 0.0001;

		#define kRAYLEIGH (lerp(0, 0.0025, pow(_AtmosphereThickness,2.5)))		// Rayleigh constant
		#define kMIE 0.0010      		// Mie constant
		#define kSUN_BRIGHTNESS 20.0 	// Sun brightness

		#define kMAX_SCATTER 50.0 // Maximum scattering value, to prevent math overflows on Adrenos

		static const half kSunScale = 400.0 * kSUN_BRIGHTNESS;
		static const float kKmESun = kMIE * kSUN_BRIGHTNESS;
		static const float kKm4PI = kMIE * 4.0 * 3.14159265;
		static const float kScale = 1.0 / (OUTER_RADIUS - 1.0);
		static const float kScaleDepth = 0.25;
		static const float kScaleOverScaleDepth = (1.0 / (OUTER_RADIUS - 1.0)) / 0.25;
		static const float kSamples = 2.0; // THIS IS UNROLLED MANUALLY, DON'T TOUCH

		#define MIE_G (-0.990)
		#define MIE_G2 0.9801

		#define SKY_GROUND_THRESHOLD 0.02

		// fine tuning of performance. You can override defines here if you want some specific setup
		// or keep as is and allow later code to set it according to target api

		// if set vprog will output color in final color space (instead of linear always)
		// 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
		// #define SKYBOX_COLOR_IN_TARGET_COLOR_SPACE 0

		// sun disk rendering:
		// no sun disk - the fastest option
		#define SKYBOX_SUNDISK_NONE 0
		// simplistic sun disk - without mie phase function
		#define SKYBOX_SUNDISK_SIMPLE 1
		// full calculation - uses mie phase function
		#define SKYBOX_SUNDISK_HQ 2

		// uncomment this line and change SKYBOX_SUNDISK_SIMPLE to override material settings
		// #define SKYBOX_SUNDISK SKYBOX_SUNDISK_SIMPLE

	#ifndef SKYBOX_SUNDISK
		#if defined(_SUNDISK_NONE)
			#define SKYBOX_SUNDISK SKYBOX_SUNDISK_NONE
		#elif defined(_SUNDISK_SIMPLE)
			#define SKYBOX_SUNDISK SKYBOX_SUNDISK_SIMPLE
		#else
			#define SKYBOX_SUNDISK SKYBOX_SUNDISK_HQ
		#endif
	#endif

	#ifndef SKYBOX_COLOR_IN_TARGET_COLOR_SPACE
		#if defined(SHADER_API_MOBILE)
			#define SKYBOX_COLOR_IN_TARGET_COLOR_SPACE 1
		#else
			#define SKYBOX_COLOR_IN_TARGET_COLOR_SPACE 0
		#endif
	#endif

		// Calculates the Rayleigh phase function
		half getRayleighPhase(half eyeCos2)
		{
			return 0.75 + 0.75*eyeCos2;
		}
		half getRayleighPhase(half3 light, half3 ray)
		{
			half eyeCos	= dot(light, ray);
			return getRayleighPhase(eyeCos * eyeCos);
		}


		struct appdata_t
		{
			float4 vertex : POSITION;
		};

		struct v2f
		{
			float4	pos				: SV_POSITION;

		#if SKYBOX_SUNDISK == SKYBOX_SUNDISK_HQ
			// for HQ sun disk, we need vertex itself to calculate ray-dir per-pixel
			half3	vertex			: TEXCOORD0;
		#elif SKYBOX_SUNDISK == SKYBOX_SUNDISK_SIMPLE
			half3	rayDir			: TEXCOORD0;
		#else
			// as we dont need sun disk we need just rayDir.y (sky/ground threshold)
			half	skyGroundFactor	: TEXCOORD0;
		#endif

			// calculate sky colors in vprog
			half3	groundColor		: TEXCOORD1;
			half3	skyColor		: TEXCOORD2;

		#if SKYBOX_SUNDISK != SKYBOX_SUNDISK_NONE
			half3	sunColor		: TEXCOORD3;
		#endif
		};


		float scale(float inCos)
		{
			float x = 1.0 - inCos;
		#if defined(SHADER_API_N3DS)
			// The polynomial expansion here generates too many swizzle instructions for the 3DS vertex assembler
			// Approximate by removing x^1 and x^2
			return 0.25 * exp(-0.00287 + x*x*x*(-6.80 + x*5.25));
		#else
			return 0.25 * exp(-0.00287 + x*(0.459 + x*(3.83 + x*(-6.80 + x*5.25))));
		#endif
		}

		v2f vert (appdata_t v)
		{
			v2f OUT;
			OUT.pos = mul(UNITY_MATRIX_MVP, v.vertex);

			float3 kSkyTintInGammaSpace = COLOR_2_GAMMA(_SkyTint); // convert tint from Linear back to Gamma
			float3 kScatteringWavelength = lerp (
				kDefaultScatteringWavelength-kVariableRangeForScatteringWavelength,
				kDefaultScatteringWavelength+kVariableRangeForScatteringWavelength,
				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
			float3 kInvWavelength = 1.0 / pow(kScatteringWavelength, 4);

			float kKrESun = kRAYLEIGH * kSUN_BRIGHTNESS;
			float kKr4PI = kRAYLEIGH * 4.0 * 3.14159265;

			float3 cameraPos = float3(0,kInnerRadius + kCameraHeight,0); 	// The camera's current position

			// Get the ray from the camera to the vertex and its length (which is the far point of the ray passing through the atmosphere)
			float3 eyeRay = normalize(mul((float3x3)_Object2World, v.vertex.xyz));

			float far = 0.0;
			half3 cIn, cOut;

			if(eyeRay.y >= 0.0)
			{
				// Sky
				// Calculate the length of the "atmosphere"
				far = sqrt(kOuterRadius2 + kInnerRadius2 * eyeRay.y * eyeRay.y - kInnerRadius2) - kInnerRadius * eyeRay.y;

				float3 pos = cameraPos + far * eyeRay;

				// Calculate the ray's starting position, then calculate its scattering offset
				float height = kInnerRadius + kCameraHeight;
				float depth = exp(kScaleOverScaleDepth * (-kCameraHeight));
				float startAngle = dot(eyeRay, cameraPos) / height;
				float startOffset = depth*scale(startAngle);


				// Initialize the scattering loop variables
				float sampleLength = far / kSamples;
				float scaledLength = sampleLength * kScale;
				float3 sampleRay = eyeRay * sampleLength;
				float3 samplePoint = cameraPos + sampleRay * 0.5;

				// Now loop through the sample rays
				float3 frontColor = float3(0.0, 0.0, 0.0);
				// Weird workaround: WP8 and desktop FL_9_1 do not like the for loop here
				// (but an almost identical loop is perfectly fine in the ground calculations below)
				// Just unrolling this manually seems to make everything fine again.
//				for(int i=0; i<int(kSamples); i++)
				{
					float height = length(samplePoint);
					float depth = exp(kScaleOverScaleDepth * (kInnerRadius - height));
					float lightAngle = dot(_WorldSpaceLightPos0.xyz, samplePoint) / height;
					float cameraAngle = dot(eyeRay, samplePoint) / height;
					float scatter = (startOffset + depth*(scale(lightAngle) - scale(cameraAngle)));
					float3 attenuate = exp(-clamp(scatter, 0.0, kMAX_SCATTER) * (kInvWavelength * kKr4PI + kKm4PI));

					frontColor += attenuate * (depth * scaledLength);
					samplePoint += sampleRay;
				}
				{
					float height = length(samplePoint);
					float depth = exp(kScaleOverScaleDepth * (kInnerRadius - height));
					float lightAngle = dot(_WorldSpaceLightPos0.xyz, samplePoint) / height;
					float cameraAngle = dot(eyeRay, samplePoint) / height;
					float scatter = (startOffset + depth*(scale(lightAngle) - scale(cameraAngle)));
					float3 attenuate = exp(-clamp(scatter, 0.0, kMAX_SCATTER) * (kInvWavelength * kKr4PI + kKm4PI));

					frontColor += attenuate * (depth * scaledLength);
					samplePoint += sampleRay;
				}



				// Finally, scale the Mie and Rayleigh colors and set up the varying variables for the pixel shader
				cIn = frontColor * (kInvWavelength * kKrESun);
				cOut = frontColor * kKmESun;
			}
			else
			{
				// Ground
				far = (-kCameraHeight) / (min(-0.001, eyeRay.y));

				float3 pos = cameraPos + far * eyeRay;

				// Calculate the ray's starting position, then calculate its scattering offset
				float depth = exp((-kCameraHeight) * (1.0/kScaleDepth));
				float cameraAngle = dot(-eyeRay, pos);
				float lightAngle = dot(_WorldSpaceLightPos0.xyz, pos);
				float cameraScale = scale(cameraAngle);
				float lightScale = scale(lightAngle);
				float cameraOffset = depth*cameraScale;
				float temp = (lightScale + cameraScale);

				// Initialize the scattering loop variables
				float sampleLength = far / kSamples;
				float scaledLength = sampleLength * kScale;
				float3 sampleRay = eyeRay * sampleLength;
				float3 samplePoint = cameraPos + sampleRay * 0.5;

				// Now loop through the sample rays
				float3 frontColor = float3(0.0, 0.0, 0.0);
				float3 attenuate;
//				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.
				{
					float height = length(samplePoint);
					float depth = exp(kScaleOverScaleDepth * (kInnerRadius - height));
					float scatter = depth*temp - cameraOffset;
					attenuate = exp(-clamp(scatter, 0.0, kMAX_SCATTER) * (kInvWavelength * kKr4PI + kKm4PI));
					frontColor += attenuate * (depth * scaledLength);
					samplePoint += sampleRay;
				}

				cIn = frontColor * (kInvWavelength * kKrESun + kKmESun);
				cOut = clamp(attenuate, 0.0, 1.0);
			}

		#if SKYBOX_SUNDISK == SKYBOX_SUNDISK_HQ
			OUT.vertex 			= -v.vertex;
		#elif SKYBOX_SUNDISK == SKYBOX_SUNDISK_SIMPLE
			OUT.rayDir 			= half3(-eyeRay);
		#else
			OUT.skyGroundFactor	= -eyeRay.y / SKY_GROUND_THRESHOLD;
		#endif

			// if we want to calculate color in vprog:
			// 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)
			// 2. in case of gamma and SKYBOX_COLOR_IN_TARGET_COLOR_SPACE: do sqrt right away instead of doing that in fshader

			OUT.groundColor	= _Exposure * (cIn + COLOR_2_LINEAR(_GroundColor) * cOut);
			OUT.skyColor	= _Exposure * (cIn * getRayleighPhase(_WorldSpaceLightPos0.xyz, -eyeRay));

		#if SKYBOX_SUNDISK != SKYBOX_SUNDISK_NONE
			OUT.sunColor	= _Exposure * (cOut * _LightColor0.xyz);
		#endif

		#if defined(UNITY_COLORSPACE_GAMMA) && SKYBOX_COLOR_IN_TARGET_COLOR_SPACE
			OUT.groundColor	= sqrt(OUT.groundColor);
			OUT.skyColor	= sqrt(OUT.skyColor);
			#if SKYBOX_SUNDISK != SKYBOX_SUNDISK_NONE
				OUT.sunColor= sqrt(OUT.sunColor);
			#endif
		#endif

			return OUT;
		}


		// Calculates the Mie phase function
		half getMiePhase(half eyeCos, half eyeCos2)
		{
			half temp = 1.0 + MIE_G2 - 2.0 * MIE_G * eyeCos;
			temp = pow(temp, pow(_SunSize,0.65) * 10);
			temp = max(temp,1.0e-4); // prevent division by zero, esp. in half precision
			temp = 1.5 * ((1.0 - MIE_G2) / (2.0 + MIE_G2)) * (1.0 + eyeCos2) / temp;
			#if defined(UNITY_COLORSPACE_GAMMA) && SKYBOX_COLOR_IN_TARGET_COLOR_SPACE
				temp = pow(temp, .454545);
			#endif
			return temp;
		}

		half calcSunSpot(half3 vec1, half3 vec2)
		{
			half3 delta = vec1 - vec2;
			half dist = length(delta);
			half spot = 1.0 - smoothstep(0.0, _SunSize, dist);
			return kSunScale * spot * spot;
		}

		half4 frag (v2f IN) : SV_Target
		{
			half3 col = half3(0.0, 0.0, 0.0);

		// if y > 1 [eyeRay.y < -SKY_GROUND_THRESHOLD] - ground
		// if y >= 0 and < 1 [eyeRay.y <= 0 and > -SKY_GROUND_THRESHOLD] - horizon
		// if y < 0 [eyeRay.y > 0] - sky
		#if SKYBOX_SUNDISK == SKYBOX_SUNDISK_HQ
			half3 ray = normalize(mul((float3x3)_Object2World, IN.vertex));
			half y = ray.y / SKY_GROUND_THRESHOLD;
		#elif SKYBOX_SUNDISK == SKYBOX_SUNDISK_SIMPLE
			half3 ray = IN.rayDir.xyz;
			half y = ray.y / SKY_GROUND_THRESHOLD;
		#else
			half y = IN.skyGroundFactor;
		#endif

			// if we did precalculate color in vprog: just do lerp between them
			col = lerp(IN.skyColor, IN.groundColor, saturate(y));

		#if SKYBOX_SUNDISK != SKYBOX_SUNDISK_NONE
			if(y < 0.0)
			{
			#if SKYBOX_SUNDISK == SKYBOX_SUNDISK_SIMPLE
				half mie = calcSunSpot(_WorldSpaceLightPos0.xyz, -ray);
			#else // SKYBOX_SUNDISK_HQ
				half eyeCos = dot(_WorldSpaceLightPos0.xyz, ray);
				half eyeCos2 = eyeCos * eyeCos;
				half mie = getMiePhase(eyeCos, eyeCos2);
			#endif

				col += mie * IN.sunColor;
			}
		#endif

		#if defined(UNITY_COLORSPACE_GAMMA) && !SKYBOX_COLOR_IN_TARGET_COLOR_SPACE
			col = LINEAR_2_OUTPUT(col);
		#endif

			return half4(col,1.0);

		}
		ENDCG
	}
}


Fallback Off

}

 


Ответ получен "January 10, 2016"

чтобы дать ответ на вопрос