Как сделать туман в юнити
Перейти к содержимому

Как сделать туман в юнити

  • автор:

Синтаксис ShaderLab: туман

Затуманивание смешивает цвет сгенерированных пикселей вместе с постоянным цветом, основанным на расстоянии от камеры. Затуманивание не изменяет значение альфа-канала смешанного пикселя, изменяет только RGB компоненты.

Синтаксис

Туман

Команды тумана указываются в фигурных скобках.

Режим

Определяет режим тумана. Стандартный режим является глобальным, который переключается на Off или Exp2 в зависимости от того, включён ли туман в пункте меню Render Settings.

Назначает цвет тумана.

Плотность

Назначает плотность для экспоненциального тумана.

Пределы

Назначает ближний и дальний пределы для линейного тумана.

Детали

Default fog settings are based on settings in the Lighting Window: fog mode is either Exp2 or Off; density & color taken from settings as well.

Учтите, что если вы используете фрагментные программы, настройки тумана шейдера всё ещё будут применены. На платформах, где не поддерживается fixed function туман, Unity будет изменять шейдеры при запуске, чтобы поддерживать запрошенный режим тумана.

Fog (Unity3d)

Здесь присутствуют 6 настроек.
1) Fog —Включает/выключает туман.

2) Fog Color — Отвечает за цвет тумана. Днем можно выставлять белый цвет или светло-серый, для ночи темно серый.

Это можно сделать с помощью функции:

3) Fog mode — это тип изменения плотности тумана от расстояния.

Linear fog — это линейное изменения плотности тумана. Промежуток возрастания определяется двумя параметрами Lenear fog start (с какого расстояния от персонажа начнет появятся туман) и Lenear fog end (на каком расстоянии от персонажа туман достигнем своей максимальной плотности).

Exponentional — это экспоненциальная зависимость изменения плотности тумана от расстояния. Она более приближена к реальному туману.

Exp2 — это зависимость изменения плотности тумана по квадрату экспоненты от расстояния.

Rendering 14

This is part 14 of a tutorial series about rendering. The previous installment introduced deferred shading. This time we’ll add fog to our scene.

This tutorial was made with Unity 5.5.0f3.

Things tend to fade with distance.

Forward Fog

Up to this point, we’ve always treated light rays as if they traveled through a vacuum. 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 atmospheric interference would require an expensive volumetric approach, which is something we usually cannot afford. Instead, we’ll settle for an approximation which relies on only a few constant fog parameters. It’s known as fog, because the effect is typically used for foggy atmospheres. The visual distortions causes by clear atmospheres are usually so subtle that they can be ignored for shorter distances.

Standard Fog

Unity’s Lighting window contains a section with the scene’s fog settings. It’s disabled by default. When activated, you 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.

Default fog enabled.

We’ll deal with deferred mode later. For now, let’s focus on forward fog. To do so, we need to use forward rendering mode. You 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’s also disable HDR rendering for now.

Forward camera.

Create a small test scene, like a few spheres on top of a plane or cube. Use Unity’s default white material.

Unnoticeable fog.

With ambient lighting set to its default intensity of 1, you’ll get a few very bright objects and no noticeable fog at all.

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 isn’t realistic, but easy to configure. You 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.

game graph inspectorLinear fog.

The linear fog factor is computed with the function `f = (E — c) / (E — S)`, where `c` is the fog coordinate and `S` and `E` and the start and end. This factor is then clamped to the 0–1 range and used to interpolate between the fog and the object’s shaded color.

Why doesn’t fog affect the skybox?

The fog effect adjusts the fragment colors of forward-rendered objects. Thus, it only affects those objects, not the skybox.

Exponential Fog

The second fog mode that Unity supports is exponential, which is a more realistic approximation of fog. It uses the function `f = 1 / 2^(cd) = 2^(-cd)` 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 the camera.

game graph inspectorExponential fog.

Exponential Squared Fog

The last mode is exponential squared fog. This works like exponential fog, but uses the function `f = 1 / 2^((cd)^2) = 2^(-(cd)^2)` which results in less fog at close range, but it increases quicker.

game graph inspectorExponential squared fog.

Adding Fog

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

Our material on the left, standard material on the right.

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.

Next, let’s 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.

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.

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.

How does UNITY_CALC_FOG_FACTOR_RAW work?

The macro is defined in UnityCG. Which fog keyword is defined determines what gets computed.

There is also a UNITY_CALC_FOG_FACTOR macro, which uses this macro. It assumes that the fog coordinate is of a specific type which requires a conversion, which is why we use the raw version directly.

The unity_FogParams variable is defined in UnityShaderVariables and contains some useful pre-computed values.

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

Also, because fog doesn’t affect the alpha component, we can leave that out of the interpolation.

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

Linear fog, but different.

Our own shader now also includes fog. However, it doesn’t 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.

Curving vs. straight fog transition.

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. Specifically, it uses the clip-space depth value. As a result, the view angle doesn’t 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.

Flat depth vs. distance.

The advantage of using depth instead of distance is that you don’t have to calculate a square root, so it’s 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 shouldn’t.

Rotation changes depth.

Let’s 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.

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.

To make sure that our code remains correct, replace all usage of i.worldPos with i.worldPos . 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.

In ApplyFog , overwrite the computed view distance with the interpolated depth value. Keep the old computation, as we’ll still use it later.

Fog based on clip space depth.

Now you 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.

What does UNITY_Z_0_FAR_FROM_CLIPSPACE do?

Most importantly, it compensates for a possibly reversed clip-space Z dimension.

Note that the macro code mentions that a conversion is needed for OpenGL as well, but considers it not worth the effort.

The UNITY_CALC_FOG_FACTOR macro simply feeds the above to its raw equivalent.

Depth or Distance

So, which metric should we use for our fog? Clip-space depth, or world-space distance? Let’s support both! But it’s not worth making it a shader-feature. We’ll make it a shader configuration option instead, like BINORMAL_PER_FRAGMENT . Let’s say that depth-based fog it the default, and you can switch to distance-based fog by defining FOG_DISTANCE , in the CGINCLUDE section near the top of our shader.

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.

Disabling Fog

Of course we don’t always want to use fog. So only include the fog code when it’s actually turned on.

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’re using black fog, but try it with another color as well.

one twoGray fog with one and two directional lights.

The result is too bright. This happens because we’re adding the fog color once per light. This wasn’t 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.

Correct gray fog with two lights. unitypackage

Deferred Fog

Now that we have fog working for the forward rendering path, let’s switch to the deferred path. Duplicate the forward-mode camera. Change the duplicate into a deferred camera, then disable the forward camera. This way, you can quickly switch between rendering modes by changing which camera is enabled.

You’ll notice that there is no fog at all when using the deferred rendering path. That’s because the fog has to be applied after all lighting has been calculated. So we cannot add fog in the deferred pass of our shader.

To compare deferred and forward rendering in the same image, you can force some of the objects to be rendered in forward mode. For example, by using a transparent material while keeping it fully opaque.

Opaque and transparent materials.

Sure enough, the objects that use a transparent material are affected by the fog.

Why are two spheres missing?

The objects on the right side use a transparent material, even though they’re fully opaque. As a result, Unity orders then back-to-front when rendering them. The two spheres furthest away ended up being rendered before the cube below them. As transparent objects don’t write to the depth buffer, the cube got drawn on top of those spheres.

Image Effects

To add fog to deferred rendering, we have to wait until all lights are rendered, then do another pass to factor in the fog. As the fog applies to the entire scene, it’s like rendering a directional light.

A simply way to add such a pass is by adding a custom component to the camera. So create a DeferredFogEffect class than extends MonoBehaviour . Because it’s useful to be able to see the fog in edit mode, give it the ExecuteInEditMode attribute. Add this component to our deferred camera. That should eventually make the fog appear in the game view.

Deferred camera with fog effect.

To add an additional full-screen pass to the rendering process, give our component an OnRenderImage method. Unity will check whether the camera has components with this method and invoke them after rendering the scene. This allows you to alter or apply effects to the rendered image. If there are multiple such components, they will be invoked in the order that they’re attached to the camera.

The OnRenderImage method has two RenderTexture parameters. The first being the source texture, which contains the final colors of the scene, up to this point. The second parameter is the destination texture that we have to render to. It might be null , which means it goes directly to the frame buffer.

Once we have added this method, the game view will fail to render. We have to make sure that we’re drawing something. To do so, invoke the Graphics.Blit method with both textures as arguments. That method will draw a full-screen quad with a shader that simply reads the source texture and outputs the sampled colors, unmodified.

The scene once again gets rendered as usual. However, if you inspect the frame debugger, you’ll see that a pass has been added for our image effect.

Drawing an image effect.

Fog Shader

Simply copying the image data is useless. We have to create a new custom shader to apply the fog effect to the image. Start with a bare-bones shader. Because we’re only drawing a single full-screen quad that’s supposed to cover everything, we should ignore culling and the depth buffer. We shouldn’t write to the depth buffer either.

Our effect component requires this shader, so add a public field for it, then assign our new shader to it.

Using a fog shader.

We also need a material for rendering with our shader. We only need it when active, so no asset is required. Use a non-serialized field to hold a reference to it.

In OnRenderImage , we now begin by checking whether we have a material instance. If not, create a new one that uses the fog shader. Then invoke Graphics.Blit with this material.

This will result in a solid white image. We have to create our own shader pass to render something useful. Begin with simple vertex and fragment programs that copy the RGB colors from the source texture, using the vertex position and UV data from the full-screen quad. Also, let’s already include the multi-compile directive for the fog modes.

Depth-Based Fog

Because we’re using deferred rendering, we know that there is a depth buffer available. After all, the light passes need it to their work. So we can read from it as well, which means that we can use it to compute depth-based fog.

Unity makes the depth buffer available via the _CameraDepthTexture variable, so add it to our shader.

We can sample this texture, although the exact syntax depends on the target platform. The SAMPLE_DEPTH_TEXTURE macro, defined in HLSLSupport, takes care of this for us.

This gives us the raw data from the depth buffer, so after the conversion from homogeneous coordinates to a clip-space value in the 0–1 range. We have to convert this value so it becomes a linear depth value in world space. First, we can use the Linear01Depth function defined in UnityCG to convert it to a linear range.

What does Linear01Depth look like?

It performs a simple conversion using two convenient predefined values.

The buffer parameters are defined in UnityShaderVariables.

Next, we have to scale this value by the far clip plane’s distance, to get the actual depth-based view distance. The clip space settings are made available via the float4 _ProjectionParams variable, which is defined in UnityShaderVariables. Its Z component contains the far plane’s distance.

Once we have our distance, we can compute the fog factor and interpolate.

Incorrect fog.

Fixing the Fog

Unfortunately, our fog isn’t quite right yet. The most obvious error is that we’re drawing the fog on top of the transparent geometry. To prevent this from happening, we have to apply the fog effect before drawing the transparent objects. We can attach the ImageEffectOpaque attribute to our method to instruct Unity to do so.

frame debugger
gameFog after opaque, before transparent.

Another problem is that the fog colors are obviously wrong. This happens when not using an HDR camera, which screws up the colors. So simple enable HDR on our deferred camera.

With HDR camera.

Finally, we once again can get a difference in depth, because we’re not taking the near plane into consideration.

Different depths.

We can slightly compensate for this by subtracting the near plane distance from the view distance. It’s stored in the Y component of _ProjectionParams . Unfortunately, it won’t produce an exact match, because of the order in which we have to convert the depth value. Unity’s fog effects use it anyway to adjust the fog, so let’s do it as well.

Partially-compensated depth.

Distance-Based Fog

The shader for deferred lights reconstructs the world-space position from the depth buffer, in order to calculate lighting. We can do this as well.

The clip space of a perspective camera defines a trapezoid region of space. If we ignore the near plane, then we get a pyramid with its top at the camera’s world position. Its height is equal to the camera’s far plane distance. The linearized depth is 0 at its tip, and 1 and its base.

Side view of pyramid.

For every pixel of our image, we can shoot a ray from the top to a point on the base of the pyramid. If there’s nothing in the way, then the ray reaches the base, which is the far plane. Otherwise, it hits whatever object was rendered.

One ray per pixel.

If something was hit, then the corresponding pixel has a depth value less than 1. For example, if it hit something halfway, then the depth value will be ½. This means that the ray’s Z coordinate is half the size it would’ve been if it hadn’t been blocked. As the direction of the ray is still the same, this means that the X and Y coordinates are also halved. In general, we can find the actual ray by starting with a ray that goes all the way to the far plane, and then scaling it by the depth value.

Scaling rays.

Once we have this ray, we can add it to the camera’s position to find the world-space position of the rendered surface. But as we’re only interested in the distance, all we really need is the length of this ray.

For this to be useful, we have to know the rays from the camera to far the plane, for each pixel. Actually, we only need four rays, one per corner of the pyramid. Interpolation gives us the rays for all the pixels in between.

Calculating Rays

We can construct the rays based on the camera’s far plane and its field of view angle. The camera’s orientation and position don’t matter for distances, so we can ignore its transformation. The Camera.CalculateFrustumCorners method can do this for us. It has four parameters. The first is the rectangular area to use, which in our case is the entire image. The second is how far away to project the rays, which has to match the far plane. The third parameter involves stereoscopic rendering. We’ll just use the currently active eye. Finally, the method needs an array of 3D vectors to store the rays. So we have to cache both a reference to the camera and a vector array.

Next, we have to pass this data to the shader. We can do so with a vector array. However, we cannot directly use frustumCorners . The first reason is that we can only pass 4D vectors to the shader. So let’s include a Vector4[] field as well, and pass that to the shader as _FrustumCorners.

The second problem is that the order of the corners has to be changed. The CalculateFrustumCorners orders them bottom-left, top-left, top-right, bottom-right. However, the quad used to render the image effect has its corner vertices ordered bottom-left, bottom-right, top-left, top-right. So let’s reorder them to match the quad’s vertices.

Deriving Distances

To access the rays in our shader, add a float array variable. We don’t actually have to add a property for this to work, as we won’t be manually editing them anyway. Although we can only pass 4D vectors to the shader, internally we only need the first three components. So the float3 type suffices.

Next, define FOG_DISTANCE to indicate that we want to base our fog on actual distances, like in our other shader.

When we need distances, we have to interpolate the rays and send them to the fragment program.

In the vertex program, we can simply use the UV coordinates to access the corner array. The coordinates are (0, 0), (1, 0), (0, 1), and (1, 1). So the index is `u + 2v`.

Finally, we can replace the depth-based distance with the actual distance in the fragment program.

Fog based on distance.

Besides precision limitations of the depth buffer, both forward and deferred approaches produce the same distance-based fog.

Fogged Skybox

Actually, there is still a significant difference between forward and deferred fog. You might have noticed that deferred fog affects the skybox as well. It acts as is the far plane is a solid barrier, which is affected by the fog.

Fogged skybox.

We know that we’ve reached the far plane when the depth value approaches 1. If we don’t want to fog the skybox, we can prevent that by setting the fog factor to 1, when that’s the case.

Skybox without fog.

In case you do want to apply fog to the entire image, you can control it via a macro definition. When FOG_SKYBOX is defined, apply fog to the skybox, otherwise don’t.

No Fog

Finally, we have to consider the scenario in which the fog has been deactivated.

No fog, incorrect.

This can also be done by forcing the fog factor to 1, when none of the fog keywords are defined. This turns our shader into nothing but a texture copy operation, so it’s actually better to deactivate or remove the fog component if you don’t need it.

How to add Fog in Unity? Things you must know.

Looking to add fog to your scene? Fog is a great post processing effect that you can use in your game. But it has its limitations. In this tutorial, we will see how to add fog to your scene and also the Pros and Cons of Fog.

Adding Fog based on Camera depth

  1. Go to Window>Rendering>Lighting.
  2. Select the environment tab.
  3. Go to other settings.
  4. Check the Fog check box.

Now you should see Fog in your game scene.

It’s that easy but there are some issues with this

  1. Fog is a little buggy with URP. You need to play around with the settings to get the right fog.
  2. Fog is based on camera depth. If you need a particular place to be covered with fog then you have to use volumetric fog.

Here is how the basic fog in Unity looks like

Game scene comparison with fog and no fog

Adding volumetric fog in Unity

Volumetric fog is not supported in URP out of the box. You will need to create fog using particles which we will cover in a different post. If you are using HDRP you can add volumetric fog with the steps below. Read more on URP vs HDRP.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *