[Unity Shaders] 半透明材质的混合效果


{今天在做正片叠底等混合效果的试验}

发现如果一张材质所有像素的半透明度都一致,很正常就可以达到混合效果;但是如果一张材质的像素们存在一种以上的半透明度,使用常规的做法就不能轻易实现。研究了一些网上大神们的技术博客,并参考了Unity内置的Transparent Shader的写法,最终鼓捣出了这篇技术博客出来。


即将被Texture混合效果的底图(其实是通过Main Camera可以看到的内容,可以是任何场景,本文用一张2D图片来做演示,实际上完全可以是3D场景):

[Unity Shaders] 半透明材质的混合效果_第1张图片


用来达到混合效果的Texture(这张图片是矩形的,最外围是alpha = 0,每接近中心一层alpha值增加0.19,具体数字不重要,主要是用这张图片说明当一张材质的像素们不透明度不一致时如何进行混合效果处理):

[Unity Shaders] 半透明材质的混合效果_第2张图片


使用叠加效果(Overlay)为例混合之后的效果(混合效果可以看出不同半透明值的区域混合出来的效果也有差别):

[Unity Shaders] 半透明材质的混合效果_第3张图片


shader代码如下,核心逻辑部分可以支持任何0-1之间的半透明值。

// Test_Effect.shader
// This shader is used for overlay effects with transparent blending textures.
// Last edit: 2017-08-29

Shader "Custom/Test_Effect" {  
	Properties {  
		_MainTex ("RGBA Texture Image", 2D) = "white" {}  // note "RGBA Texture Image" instead of "Base (RGB)" or "Base Texture"
		_BlendTex ("Blend Texture", 2D) = "white" {}
	}  
	SubShader {  
    	Pass {  
    		CGPROGRAM  
        	#pragma vertex vert  
        	#pragma fragment frag  
                  
        	#include "UnityCG.cginc"  
                  
			uniform sampler2D _MainTex;  
			uniform sampler2D _BlendTex;

			struct vertexInput {
				float4 vertex : POSITION;
				float4 texcoord : TEXCOORD0;
			};

			struct vertexOutput {
				float4 pos : SV_POSITION;
				float4 tex : TEXCOORD0;
			};

			vertexOutput vert(vertexInput input) {
				vertexOutput output;

				output.tex = input.texcoord;
				output.pos = UnityObjectToClipPos(input.vertex);
				return output;
			}
                        
                        // the blending function for overlay effects
			fixed OverlayBlendMode(fixed basePixel, fixed blendPixel) {
				if(basePixel < 0.5) {
					return (2.0 * basePixel * blendPixel);
				} else {
					return (1.0 - 2.0 * (1.0 - basePixel) * (1.0 - blendPixel));
				}
			}

			// core function HERE
			float4 frag(vertexOutput input) : COLOR {
				float4 renderTex = tex2D(_MainTex, input.tex.xy); // get the current pixel of the base picture
				float4 blendTex = tex2D(_BlendTex, input.tex.xy); // get the current pixel of the blending texture

				float4 blendedImage = renderTex;
				float4 img = blendTex;

                                // rgba blending 
				if(img.a > 0.1) {
                                        // blend when the blending texture's alpha is greater than 0.1
                                        blendedImage.r = OverlayBlendMode(renderTex.r, blendTex.r);
					blendedImage.g = OverlayBlendMode(renderTex.g, blendTex.g);
					blendedImage.b = OverlayBlendMode(renderTex.b, blendTex.b);

					blendedImage.a = blendTex.a;
				} else {
                                        // do not blend if the blending texture's alpha is close to 0                
                                        blendedImage = renderTex;
				}

				// blend by the alpha of the current blending pixel
                                renderTex = lerp(renderTex, blendedImage, img.a);
                                return renderTex;
			}
                  
        	ENDCG  
    	        }  
	}  
	FallBack "Diffuse"  // use "Diffuse" if the upper subshader fails
}  
基本的逻辑思路是~

如果Texture上当前像素的alpha = 0,则显示底图原本的样子;

如果Texture上当前像素的alpha = 1,则根据混合效果计算公式混合Texture与画面的颜色;

如果Texture上当前像素的0 < alpha < 1,则根据不透明度与画面混合。


C#代码如下,引用自candycat1992。


using UnityEngine;  
using System.Collections;  

[ExecuteInEditMode]  
public class Overlay_ImageEffect : MonoBehaviour {  

	#region Variables  
	public Shader curShader;  
	public Texture2D blendTexture;

	private Material curMaterial;  
	#endregion  

	#region Properties  
	public Material material {  
		get {  
			if (curMaterial == null) {  
				curMaterial = new Material(curShader);  
				curMaterial.hideFlags = HideFlags.HideAndDontSave;  
			}  
			return curMaterial;  
		}  
	}  
	#endregion  

	// Use this for initialization  
	void Start () {  
		if (SystemInfo.supportsImageEffects == false) {  
			enabled = false;  
			return;  
		}  

		if (curShader != null && curShader.isSupported == false) {  
			enabled = false;  
		}  
	}  

	void OnRenderImage (RenderTexture sourceTexture, RenderTexture destTexture){  
		if (curShader != null) {  
			material.SetTexture("_BlendTex", blendTexture);

			Graphics.Blit(sourceTexture, destTexture, material);  
		} else {  
			Graphics.Blit(sourceTexture, destTexture);  
		}  
	}  

	// Update is called once per frame  
	void Update () {  
	}  

	void OnDisable () {  
		if (curMaterial != null) {  
			DestroyImmediate(curMaterial);  
		}  
	}  
}  




本文是以叠加(Overlay)效果为例,同样可以把核心计算公式换成正片叠底(Multiply)或者滤色(Screen)或者其他种类的混合效果。

使用的Unity版本为2017.1.0f3





可以看出我的代码是在网上几个大神的代码基础上改的,现在加上这些代码就可以支持一张拥有不同半透明度的材质了。如果有错误的地方请多多指正,更深入的shader知识本文不再解释。


使用方法:

把Texture拖进Unity,勾选Alpha Is Transparency,Alpha Source项选择Input Texture Alpha;

[Unity Shaders] 半透明材质的混合效果_第4张图片

混合效果材质的去向有两种,一种是跟随摄像机移动,另一种是附加在game object上。

1. 跟随摄像机移动

把本文里的C#文件拖进Main Camera下,把本文里的shader文件拖进Cur Shader空隙处,把Texture拖进Blend Texture处;


这种方法做出来的混合效果就像一个滤镜罩在摄像机上,如果混合效果本身有一些特定的图案,那么它的图案不会随着摄像机的缩放和移动改变。

2. 附加在game object上

新建一个material,把本文的shader代码文件拖拽进该material,再分别把底图和材质拖拽进该material的两个空隙处。

[Unity Shaders] 半透明材质的混合效果_第5张图片

这样我们就做出了一个按照shader混合了底图和材质的material。这个做法应该只适用于混合2D的美术素材。然后新建一个game object,类型可以选择sprite也可以选择Plane,用sprite可以比Plane让底图更清晰。再把刚才做好的material拖拽进这个game object上。就做出了专门只混合底图的混合效果,这个效果是跟随着game object的。


Unity内置Transparent Shader源代码与解释:

http://en.wikibooks.org/wiki/Cg_Programming/Unity/Transparent_Textures

开发Unity Shader混合效果的详细方法:

http://blog.csdn.net/candycat1992/article/details/39343309


你可能感兴趣的:(Unity,Shaders)