您的位置:首页 > 其它

2.2.2 Transparency (about blending) 透明(关于混合)

2015-09-05 14:35 411 查看




«Le Printemps» by Pierre Auguste Cot, 1873. Note the transparent clothing.

This tutorial covers
blending of fragments (i.e. compositing them) using Cg shaders in Unity. It assumes that you are familiar with the concept of front and back faces as discussed in

Section “Cutaways”.

More specifically, this tutorial is about
rendering transparent objects, e.g. transparent glass, plastic, fabrics, etc. (More strictly speaking, these are actually semitransparent objects because they don't need to be perfectly transparent.) Transparent objects allow us to see through
them; thus, their color “blends” with the color of whatever is behind them.

1.Blending

As mentioned in
Section “Programmable Graphics Pipeline”, the fragment shader computes an RGBA color (i.e. red, green, blue, and alpha components in the fragment output parameter with semantic
COLOR
) for each fragment (unless the fragment is discarded). The fragments are then processed as discussed in

Section “Per-Fragment Operations”. One of the operations is the blending stage, which combines the color of the fragment (as specified in the fragment output parameter), which is called the “source color”, with the color of the corresponding pixel that
is already in the framebuffer, which is called the “destination color” (because the “destination” of the resulting blended color is the framebuffer).

Blending is a fixed-function stage, i.e. you can configure it but not program it. The way it is configured, is by specifying a
blend equation. You can think of the blend equation as this definition of the resulting RGBA color:

float4 result = SrcFactor * fragment_output + DstFactor * pixel_color;


where
fragment_output
is the RGBA color computed by the fragment shader and
pixel_color
is the RGBA color that is currently in the framebuffer and
result
is the blended result, i.e. the output of the blending stage.
SrcFactor
and
DstFactor
are configurable RGBA colors (of type
float4
) that are multiplied component-wise with the fragment output color and the pixel color. The values of
SrcFactor
and
DstFactor
are specified in Unity's ShaderLab syntax with this line:

Blend
{code for
SrcFactor
} {code for
DstFactor
}

The most common codes for the two factors are summarized in the following table (more codes are mentioned in

Unity's ShaderLab reference about blending):

CodeResulting Factor (
SrcFactor
or
DstFactor
)
One
float4(1.0, 1.0, 1.0, 1.0)
Zero
float4(0.0, 0.0, 0.0, 0.0)
SrcColor
fragment_output
SrcAlpha
fragment_output.aaaa
DstColor
pixel_color
DstAlpha
pixel_color.aaaa
OneMinusSrcColor
float4(1.0, 1.0, 1.0, 1.0) - fragment_output
OneMinusSrcAlpha
float4(1.0, 1.0, 1.0, 1.0) - fragment_output.aaaa
OneMinusDstColor
float4(1.0, 1.0, 1.0, 1.0) - pixel_color
OneMinusDstAlpha
float4(1.0, 1.0, 1.0, 1.0) - pixel_color.aaaa
As discussed in
Section “Vector and Matrix Operations”,
pixel_color.aaaa
is just a short way of writing
float4(pixel_color.a, pixel_color.a, pixel_color.a, pixel_color.a)
. Also note that all components of all colors and factors in the blend equation are clamped between 0 and 1.

2.Alpha Blending

One specific example for a blend equation is called “alpha blending”. In Unity, it is specified this way:

Blend SrcAlpha OneMinusSrcAlpha


which corresponds to:

float4 result = fragment_output.aaaa * fragment_output + (float4(1.0, 1.0, 1.0, 1.0) - fragment_output.aaaa) * pixel_color;


This uses the alpha component of
fragment_output
as an opacity. I.e. the more opaque the fragment output color is, the larger its opacity and therefore its alpha component, and thus the more of the fragment output color is mixed in the result and the less of the
pixel color in the framebuffer. A perfectly opaque fragment output color (i.e. with an alpha component of 1) will completely replace the pixel color.

This blend equation is sometimes referred to as an “over” operation, i.e. “
fragment_output
over
pixel_color
”, since it corresponds to putting a layer of the fragment output color with a specific opacity on top of the pixel color. (Think of a layer of colored glass or colored semitransparent plastic on top of something of another color.)

Due to the popularity of alpha blending, the alpha component of a color is often called opacity even if alpha blending is not employed. Moreover, note that in computer graphics a common formal definition
of transparency is 1 − opacity.

3.Premultiplied Alpha Blending

There is an important variant of alpha blending: sometimes the fragment output color has its alpha component already premultiplied to the color components. (You might think of it as a price that has
VAT already included.) In this case, alpha should not be multiplied again (VAT should not be added again) and the correct blending is:

Blend One OneMinusSrcAlpha


which corresponds to:

float4 result = float4(1.0, 1.0, 1.0, 1.0) * fragment_output + (float4(1.0, 1.0, 1.0, 1.0) - fragment_output.aaaa) * pixel_color;


4.Additive Blending

Another example for a blending equation is:

Blend One One


This corresponds to:

float4 result = float4(1.0, 1.0, 1.0, 1.0) * fragment_output + float4(1.0, 1.0, 1.0, 1.0) * pixel_color;


which just adds the fragment output color to the color in the framebuffer. Note that the alpha component is not used at all; nonetheless, this blending equation is very useful for many kinds of transparent
effects; for example, it is often used for particle systems when they represent fire or something else that is transparent and emits light. Additive blending is discussed in more detail in

Section “Order-Independent Transparency”.

More examples of blend equations are given in

Unity's ShaderLab reference about blending.

5.Shader Code

Here is a simple shader which uses alpha blending to render a green color with opacity 0.3:

Shader "Cg shader using blending" {
SubShader {
Tags { "Queue" = "Transparent" }
// draw after all opaque geometry has been drawn
Pass {
ZWrite Off // don't write to depth buffer
// in order not to occlude other objects

Blend SrcAlpha OneMinusSrcAlpha // use alpha blending

CGPROGRAM

#pragma vertex vert
#pragma fragment frag

float4 vert(float4 vertexPos : POSITION) : SV_POSITION
{
return mul(UNITY_MATRIX_MVP, vertexPos);
}

float4 frag(void) : COLOR
{
return float4(0.0, 1.0, 0.0, 0.3);
// the fourth component (alpha) is important:
// this is semitransparent green
}

ENDCG
}
}
}

Apart from the blend equation, which has been discussed above, there are only two lines that need more explanation:
Tags { "Queue" = "Transparent" }
and
ZWrite Off
.

ZWrite Off
deactivates writing to the depth buffer. As explained in

Section “Per-Fragment Operations”, the depth buffer keeps the depth of the nearest fragment and discards any fragments that have a larger depth. In the case of a transparent fragment, however, this is not what we want since we can (at least potentially)
see through a transparent fragment. Thus, transparent fragments should not occlude other fragments and therefore the writing to the depth buffer is deactivated. See also

Unity's ShaderLab reference about culling and depth testing.

The line
Tags { "Queue" = "Transparent" }
specifies that the meshes using this subshader are rendered after all the opaque meshes were rendered. The reason is partly because we deactivate
writing to the depth buffer: one consequence is that transparent fragments can be occluded by opaque fragments even though the opaque fragments are farther away. In order to fix this problem, we first draw all opaque meshes (in Unity´s “opaque queue”) before
drawing all transparent meshes (in Unity's “transparent queue”). Whether or not a mesh is considered opaque or transparent depends on the tags of its subshader as specified with the line
Tags { "Queue" = "Transparent" }
. More details about subshader tags are described in

Unity's ShaderLab reference about subshader tags.

It should be mentioned that this strategy of rendering transparent meshes with deactivated writing to the depth buffer does not always solve all problems. It works perfectly if the order in which
fragments are blended does not matter; for example, if the fragment color is just added to the pixel color in the framebuffer, the order in which fragments are blended is not important; see

Section “Order-Independent Transparency”. However, for other blending equations, e.g. alpha blending, the result will be different depending on the order in which fragments are blended. (If you look through almost opaque green glass at almost opaque red
glass you will mainly see green, while you will mainly see red if you look through almost opaque red glass at almost opaque green glass. Similarly, blending almost opaque green color over almost opaque red color will be different from blending almost opaque
red color over almost opaque green color.) In order to avoid artifacts, it is therefore advisable to use additive blending or (premultiplied) alpha blending with small opacities (in which case the destination factor
DstFactor
is close to 1 and therefore alpha blending is close to additive blending).

6.Including Back Faces

The previous shader works well with other objects but it actually doesn't render the “inside” of the object. However, since we can see through the outside of a transparent object, we should also render
the inside. As discussed in
Section “Cutaways”, the inside can be rendered by deactivating culling with
Cull Off
. However, if we just deactivate culling, we might get in trouble: as discussed above, it often matters in which order transparent fragments are rendered but without any culling, overlapping triangles from the inside and the outside might be
rendered in a random order which can lead to annoying rendering artifacts. Thus, we would like to make sure that the inside (which is usually farther away) is rendered first before the outside is rendered. In Unity's ShaderLab this is achieved by specifying
two passes, which are executed for the same mesh in the order in which they are defined:

Shader "Cg shader using blending" {
SubShader {
Tags { "Queue" = "Transparent" }
// draw after all opaque geometry has been drawn
Pass {
Cull Front // first pass renders only back faces
// (the "inside")
ZWrite Off // don't write to depth buffer
// in order not to occlude other objects
Blend SrcAlpha OneMinusSrcAlpha // use alpha blending

CGPROGRAM

#pragma vertex vert
#pragma fragment frag

float4 vert(float4 vertexPos : POSITION) : SV_POSITION
{
return mul(UNITY_MATRIX_MVP, vertexPos);
}

float4 frag(void) : COLOR
{
return float4(1.0, 0.0, 0.0, 0.3);
// the fourth component (alpha) is important:
// this is semitransparent red
}

ENDCG
}

Pass {
Cull Back // second pass renders only front faces
// (the "outside")
ZWrite Off // don't write to depth buffer
// in order not to occlude other objects
Blend SrcAlpha OneMinusSrcAlpha // use alpha blending

CGPROGRAM

#pragma vertex vert
#pragma fragment frag

float4 vert(float4 vertexPos : POSITION) : SV_POSITION
{
return mul(UNITY_MATRIX_MVP, vertexPos);
}

float4 frag(void) : COLOR
{
return float4(0.0, 1.0, 0.0, 0.3);
// the fourth component (alpha) is important:
// this is semitransparent green
}

ENDCG
}
}
}

In this shader, the first pass uses front-face culling (with
Cull Front
) to render the back faces (the inside) first. After that the second pass uses back-face culling (with
Cull Back
) to render the front faces (the outside). This works perfect for convex meshes (closed meshes without dents; e.g. spheres or cubes) and is often a good approximation for other meshes.

7.Summary

Congratulations, you made it through this tutorial! One interesting thing about rendering transparent objects is that it isn't just about blending but also requires knowledge about culling and the
depth buffer. Specifically, we have looked at:

What blending is and how it is specified in Unity.
How a scene with transparent and opaque objects is rendered and how objects are classified as transparent or opaque in Unity.
How to render the inside and outside of a transparent object, in particular how to specify two passes in Unity.

8.Further Reading

If you still want to know more

the programmable graphics pipeline, you should read

Section “Programmable Graphics Pipeline”.
about per-fragment operations in the pipeline (e.g. blending and the depth test), you should read

Section “Per-Fragment Operations”.
about front-face and back-face culling, you should read

Section “Cutaways”.
about how to specify culling and the depth buffer functionality in Unity, you should read

Unity's ShaderLab reference about culling and depth testing.
about how to specify blending in Unity, you should read

Unity's ShaderLab reference about blending.
about the render queues in Unity, you should read

Unity's ShaderLab reference about subshader tags.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: