rendering——fog

https://catlikecoding.com/unity/tutorials/rendering/part-14/

1 forward fog
up to this point, we have always treated light rays as if they traveled through a vaccum. this might be accurate when your scene is set in space, but otherwise light has to travel through an atmosphere or liquid. under those circumstances, light rays can get absorbed, scattered, and reflected anywhere in space, not only when hitting a solid surface.

an accurate rendering of atmosphere interference would require an expensive volumetric approach, which is sth. we usually can not afford. instead, we will settle for an approximate which relies on only a few constant fog parameters. it is known as fog, because the effect is typically used for foggy atmospheres. the visual distortion causes by clear atmospheres are usually so subtle that they can be ignored for shorter distances.

1.1 standard fog
unity’s lighting window contains a section with the scene’s fog settings. it is disabled by default. when activated, u get a default gray fog. however, this only works for objects that are rendered using the forward rendering path. when the deferred mode is active, this is mentioned in the fog section.

we will deal with deferred mode later. for now, let’s focus on forward fog. to do so, we need to use forward rendering mode. u can change the global rendering mode, or force the main camera to use the desired rendering mode. so set the camera’s rendering path to forward. let us also disable HDR rendering for now.

create a small test scene, like a few spheres on top of a plane or cube. use unity’s default white material.
with ambient lighting set to its default intensity of 1, u will get a few very bright objects and no noticeable fog at all.

1.2 linear fog

to make the fog more noticeable, set its color to solid black. that would represent an atmosphere that absorbs light without much scattering, like thick black smoke.

set the fog mode to linear. this is not realistic, but easy to configure. u can set the distance at which the fog’s influence begins and where it effectively becomes solid. it increases linearly in between. this is measured in view distance. before the fog starts, visibility is normal. beyond that distance, the fog will gradually obscure objects. beyond the end, nothing but the fog’s color is visible.

the linear fog factor is computed with function f = (e-c)/(e-s), where c is the fog coordinate and s and e is the start and end. this factor is then clamped to 0-1 range and used to interpolate between the fog and the object’s shaded color.

why does not fog affect the skybox???
the fog effect adjusts the fragment colors of forward-rendered objects. thus, it only affects those objects, not the skybox.

1.3 exponential fog

the second fog mode that unity supports is exponential, which is a more realistic approximation of fog. it uses the function
在这里插入图片描述

where d is the fog’s density factor. this equation will never reach zero, unlike the linear version. increase the density to 0.1 to make the fog appear closer to camera.

1.4 exponential squared fog
the last mode is exponential squared fog. this works like exponential fog, but uses the function
在这里插入图片描述
which results in less fog at close range, but it increases quicker.

1.5 adding fog
now that we know what fog looks like, let us add support for it to our own forward shader. to make comparison easier, set half of the objects to use own material, while the rest keeps using the default material.

the fog mode is controlled with shader keywords, so we have to add a multi-compile directive to support them. there is a pre-defined multi_compile_fog directive that we can use for this purpose. it results in extra shader variants for the FOG_LINEAR, FOG_EXP, and FOG_EXP2 keywords. add this directive to the two forward passes only.

#pragma multi_compile_fog

next, let us add a function to My Lighting to apply the fog to our fragment color. it takes the current color and the interpolators as parameters, and should return the final color with fog applied.

float4 ApplyFog (float4 color, Interpolators i) {
	return color;
}

the fog effect is based on the view distance, which is equal to the length of the vector between the camera position and the fragment’s world position. we have access to both positions, so we can compute this distance.

float4 ApplyFog (float4 color, Interpolators i) {
	float viewDistance = length(_WorldSpaceCameraPos - i.worldPos);
	return color;
}

then we use this as the fog coordinate for the fog density function, which is computed by the UNITY_CALC_FOG_FACTOR_RAW macro. this macro creates the unityFogFactor variable, which we can use to interpolate between the fog and fragment color. the fog color is stored in unity_FogColor, which is defined in ShaderVariables.

float4 ApplyFog (float4 color, Interpolators i) {
	float viewDistance = length(_WorldSpaceCameraPos - i.worldPos);
	UNITY_CALC_FOG_FACTOR_RAW(viewDistance);
	return lerp(unity_FogColor, color, unityFogFactor);
}

as the fog factor can end up outside the 0-1 range, we have to clamp it before interpolating.

return lerp(unity_FogColor, color, saturate(unityFogFactor));

also, becaues fog does not affect the alpha component, we can leave that out of the interpolation.

color.rgb = lerp(unity_FogColor.rgb, color.rgb, saturate(unityFogFactor));
return color;

now we can apply the fog to the final forward-pass color in MyFragmentProgram.

#if defined(DEFERRED_PASS)
		#if !defined(UNITY_HDR_ON)
			color.rgb = exp2(-color.rgb);
		#endif
		output.gBuffer0.rgb = albedo;
		output.gBuffer0.a = GetOcclusion(i);
		output.gBuffer1.rgb = specularTint;
		output.gBuffer1.a = GetSmoothness(i);
		output.gBuffer2 = float4(i.normal * 0.5 + 0.5, 1);
		output.gBuffer3 = color;
	#else
		output.color = ApplyFog(color, i);
	#endif

our own shader now also includes fog. however, it does not quite match the fog computed by the standard shader. to make the difference very clear, use linear fog with a start and end that have the same or nearly the same value. this results in a sudden transition from no to total fog.

1.6 depth-based fog
the difference between our and the standard shader is due to the way we compute the fog coordinate. while it makes sense to use the world-space view distance, the standard shader uses a different metric. specially, it uses the clip-space depth value. as a result, the view angle does not affect the fog coordinate. also, in some cases the distance is affected by the near clip plane distance of the camera, which pushes the fog away a bit.

the advantage of using depth instead of distance is that u do not have to calculate a square root, so it is faster. also, while less realistic, depth-based fog might be desirable in certain cases, like for side-scrolling games. the downside is that, because view-angles are ignored, the camera orientation influences the fog. as it rotates, the fog density changes, while it logically should not.

let us add support for depth-based fog to our shader, to match unity’s approach. this requires a few changes to our code. we now have to pass the clip-space depth value to the fragment program. so define a FOG_DEPTH keyword when one of the fog modes is active.

#include "UnityPBSLighting.cginc"
#include "AutoLight.cginc"

#if defined(FOG_LINEAR) || defined(FOG_EXP) || defined(FOG_EXP2)
	#define FOG_DEPTH 1
#endif

we have to include an interpolator for the depth value. but instead of giving it a separate interpolator, we can piggyback it on the world position, as its fourth component.

struct Interpolators {#if FOG_DEPTH
		float4 worldPos : TEXCOORD4;
	#else
		float3 worldPos : TEXCOORD4;
	#endif}

to make sure that our code remains correct, replace all usage of i.worldPos with i.worldPos.xyz. after that, assign the clip-space depth value to i.worldPos.w in the fragment program, when needed. it’s simply the z coordinate of the homogeneous clip-space position, so before it gets converted to a value in the 0-1 range.

Interpolators MyVertexProgram (VertexData v) {
	Interpolators i;
	i.pos = UnityObjectToClipPos(v.vertex);
	i.worldPos.xyz = mul(unity_ObjectToWorld, v.vertex);
	#if FOG_DEPTH
		i.worldPos.w = i.pos.z;
	#endif
	i.normal = UnityObjectToWorldNormal(v.normal);}

in ApplyFog, overwrite the computed view distance with the interpolated depth value. keep the old computation, as we will still use it later.

float4 ApplyFog (float4 color, Interpolators i) {
	float viewDistance = length(_WorldSpaceCameraPos - i.worldPos.xyz);
	#if FOG_DEPTH
		viewDistance = i.worldPos.w;
	#endif
	UNITY_CALC_FOG_FACTOR_RAW(viewDistance);
	return lerp(unity_FogColor, color, saturate(unityFogFactor));
}

now u most likely get the same result as the standard shader. however, in some cases the clip space is configured differently, producing incorrect fog. to compensate for that, use the UNITY_Z_0_FAR_FROM_CLIPSPACE macro to convert the depth value.

viewDistance = UNITY_Z_0_FAR_FROM_CLIPSPACE(i.worldPos.w);

1.7 depth or distance
so ,which metric should we use for our fog?? clip-space depth, or world-space distance??
let us support both!!
but it is not making it a shader-feature. we will make it a shader configuration option instead, like BINORMAL_PER_FRAGMENT. let us say that depth-based fog it the default, and u can switch to distance-based fog by defining FOG_DISTQANCE, in the CGINCLUDE section near the top of our shader.

CGINCLUDE

	#define BINORMAL_PER_FRAGMENT
	#define FOG_DISTANCE

	ENDCG

all we have to do in My Lighting to switch to distance-based fog, is to get rid of the FOG_DEPTH definition, if FOG_DISTANCE has already been defined.

#if defined(FOG_LINEAR) || defined(FOG_EXP) || defined(FOG_EXP2)
	#if !defined(FOG_DISTANCE)
		#define FOG_DEPTH 1
	#endif
#endif

1.8 disabling fog
of course we do not always want to use fog. so only include the fog code when it’s actually turned on.

#if defined(FOG_LINEAR) || defined(FOG_EXP) || defined(FOG_EXP2)
	#if !defined(FOG_DISTANCE)
		#define FOG_DEPTH 1
	#endif
	#define FOG_ON 1
#endif

…

float4 ApplyFog (float4 color, Interpolators i) {
	#if FOG_ON
		float viewDistance = length(_WorldSpaceCameraPos - i.worldPos.xyz);
		#if FOG_DEPTH
			viewDistance = UNITY_Z_0_FAR_FROM_CLIPSPACE(i.worldPos.w);
		#endif
		UNITY_CALC_FOG_FACTOR_RAW(viewDistance);
		color.rgb = lerp(unity_FogColor.rgb, color.rgb, saturate(unityFogFactor));
	#endif
	return color;
}

1.9 multiple lights
our fog works correctly with a single light, but how does it behave when there are multiple lights in the scene? it appears fine when we are using black fog, but try it with another color as well.

the result is too bright. this happens because we are adding the fog color once per light. this was not a problem when the fog color was black. so the solution is to always use a black color in the additive pass. that way, the fog fades the contribution of additional lights, without brightening the fog itself.

	float3 fogColor = 0; //默认是黑色,在add通道是黑色,黑色多次叠加还是黑色
		#if defined(FORWARD_BASE_PASS) //在base通道是雾的颜色
			fogColor = unity_FogColor.rgb;
		#endif
		color.rgb = lerp(fogColor, color.rgb, saturate(unityFogFactor));
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值