通常情况下有多个颜色需要输出到屏幕上,通常情况下我们可以利用参数对它们进行插值来融合这些颜色。
下面的脚本基于我们前面的教程 simple textured shader。
颜色插值
作为开始,我们仅仅使用两种纯色,因为这样我们不需要考虑uv坐标信息或者贴图信息。我们添加一个 _SecondaryColor 属性 代表第二种颜色,添加一个 _Blend 参数来 控制输出第一个颜色还是第二个颜色,我们声明 _Blend 的属性为Range,这样可以在Inspector上按滑动条的形式显示它。
//...
//show values to edit in inspector
Properties{
_Color ("Color", Color) = (0, 0, 0, 1) //the base color
_SecondaryColor ("Secondary Color", Color) = (1,1,1,1) //the color to blend to
_Blend ("Blend Value", Range(0,1)) = 0 //0 is the first color, 1 the second
}
//...
//the value that's used to blend between the colors
float _Blend;
//the colors to blend between
fixed4 _Color;
fixed4 _SecondaryColor;
除了删除和uv信息相关的代码之外,我们保留vertex shader 部分,然后修改fragment shader ,作为第一个版本我们只是通过 blend 的值添加第二个颜色到第一个颜色上。
//the fragment shader
fixed4 frag(v2f i) : SV_TARGET{
fixed4 col = _Color + _SecondaryColor * _Blend;
return col;
}
我们已经可以改变颜色了,但是还不能只显示第二种颜色。这是因为第二种颜第二种颜色是被加在第一种颜色之上的。(类似于将两个不同颜色的灯光照射到同一个区域)。
我们改进一下,可以随着 blend 的值增加将第一个颜色的值减弱。当blend 的值为0时,我们看不到任何第二种颜色,全部显示第一种颜色。当blend 的值为1时,全部显示第二种颜色,而不显示第一种颜色。我们通过第一种颜色乘上 1 - _Blend 来实现。
//the fragment shader
fixed4 frag(v2f i) : SV_TARGET{
fixed4 col = _Color * (1 - _Blend) + _SecondaryColor * _Blend;
return col;
}
这种方式也被称为 线性插值,unity也为我们提供了这样一个函数 lerp。
//the fragment shader
fixed4 frag(v2f i) : SV_TARGET{
fixed4 col = lerp(_Color, _SecondaryColor, _Blend);
return col;
}
完整的两种颜色插值融合 shader 代码如下:
Shader "Tutorial/009_Color_Blending/Plain"{
//show values to edit in inspector
Properties{
_Color ("Color", Color) = (0, 0, 0, 1) //the base color
_SecondaryColor ("Secondary Color", Color) = (1,1,1,1) //the color to blend to
_Blend ("Blend Value", Range(0,1)) = 0 //0 is the first color, 1 the second
}
SubShader{
//the material is completely non-transparent and is rendered at the same time as the other opaque geometry
Tags{ "RenderType"="Opaque" "Queue"="Geometry"}
Pass{
CGPROGRAM
//include useful shader functions
#include "UnityCG.cginc"
//define vertex and fragment shader
#pragma vertex vert
#pragma fragment frag
//the value that's used to blend between the colors
float _Blend;
//the colors to blend between
fixed4 _Color;
fixed4 _SecondaryColor;
//the object data that's put into the vertex shader
struct appdata{
float4 vertex : POSITION;
};
//the data that's used to generate fragments and can be read by the fragment shader
struct v2f{
float4 position : SV_POSITION;
};
//the vertex shader
v2f vert(appdata v){
v2f o;
//convert the vertex positions from object space to clip space so they can be rendered
o.position = UnityObjectToClipPos(v.vertex);
return o;
}
//the fragment shader
fixed4 frag(v2f i) : SV_TARGET{
fixed4 col = lerp(_Color, _SecondaryColor, _Blend);
return col;
}
ENDCG
}
}
}
贴图插值
接下来 我们 将 插值 从贴图读取出来的颜色。我们首先删除颜色相关的属性和变量,同时添加贴图相关的属性和变量。我们同时添加 uv 坐标信息,但是我们不在vertex shader 部分添加 tiling 和 offset。因为我们的多个贴图使用了同样的uv坐标信息。
//...
//show values to edit in inspector
Properties{
_MainTex ("Texture", 2D) = "white" {} //the base texture
_SecondaryTex ("Secondary Texture", 2D) = "black" {} //the texture to blend to
_Blend ("Blend Value", Range(0,1)) = 0 //0 is the first color, 1 the second
}
//...
//the object data that's put into the vertex shader
struct appdata{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
//the data that's used to generate fragments and can be read by the fragment shader
struct v2f{
float4 position : SV_POSITION;
float2 uv : TEXCOORD0;
};
//the vertex shader
v2f vert(appdata v){
v2f o;
//convert the vertex positions from object space to clip space so they can be rendered
o.position = UnityObjectToClipPos(v.vertex);
o.uv = v.uv;
return o;
}
//...
然后在 fragment shader 部分,我们可以分别对两张贴图添加 添加 tiling 和 offset。我们使用这些坐标信息从两个贴图读取颜色信息,并对他们进行插值操作输出最终颜色。
//the fragment shader
fixed4 frag(v2f i) : SV_TARGET{
//calculate UV coordinates including tiling and offset
float2 main_uv = TRANSFORM_TEX(i.uv, _MainTex);
float2 secondary_uv = TRANSFORM_TEX(i.uv, _SecondaryTex);
//read colors from textures
fixed4 main_color = tex2D(_MainTex, main_uv);
fixed4 secondary_color = tex2D(_SecondaryTex, secondary_uv);
//interpolate between the colors
fixed4 col = lerp(main_color, secondary_color, _Blend);
return col;
}
对两张贴图进行插值的完整 shader 代码如下:
Shader "Tutorial/009_Color_Blending/Texture"{
//show values to edit in inspector
Properties{
_MainTex ("Texture", 2D) = "white" {} //the base texture
_SecondaryTex ("Secondary Texture", 2D) = "black" {} //the texture to blend to
_Blend ("Blend Value", Range(0,1)) = 0 //0 is the first color, 1 the second
}
SubShader{
//the material is completely non-transparent and is rendered at the same time as the other opaque geometry
Tags{ "RenderType"="Opaque" "Queue"="Geometry"}
Pass{
CGPROGRAM
//include useful shader functions
#include "UnityCG.cginc"
//define vertex and fragment shader
#pragma vertex vert
#pragma fragment frag
//the value that's used to blend between the colors
float _Blend;
//the colors to blend between
sampler2D _MainTex;
float4 _MainTex_ST;
sampler2D _SecondaryTex;
float4 _SecondaryTex_ST;
//the object data that's put into the vertex shader
struct appdata{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
//the data that's used to generate fragments and can be read by the fragment shader
struct v2f{
float4 position : SV_POSITION;
float2 uv : TEXCOORD0;
};
//the vertex shader
v2f vert(appdata v){
v2f o;
//convert the vertex positions from object space to clip space so they can be rendered
o.position = UnityObjectToClipPos(v.vertex);
o.uv = v.uv;
return o;
}
//the fragment shader
fixed4 frag(v2f i) : SV_TARGET{
//calculate UV coordinates including tiling and offset
float2 main_uv = TRANSFORM_TEX(i.uv, _MainTex);
float2 secondary_uv = TRANSFORM_TEX(i.uv, _SecondaryTex);
//read colors from textures
fixed4 main_color = tex2D(_MainTex, main_uv);
fixed4 secondary_color = tex2D(_SecondaryTex, secondary_uv);
//interpolate between the colors
fixed4 col = lerp(main_color, secondary_color, _Blend);
return col;
}
ENDCG
}
}
}
基于贴图的插值
最后我们来看下如何通过一张贴图作为参数来插值,而不是像上面那样通过一个单一的数值 (_blend) 插值。
我们将 _blend 相关代码改为_BlendTex,它是一张贴图。
//...
//show values to edit in inspector
Properties{
_MainTex ("Texture", 2D) = "white" {} //the base texture
_SecondaryTex ("Secondary Texture", 2D) = "black" {} //the texture to blend to
_BlendTex ("Blend Texture", 2D) = "grey" //black is the first color, white the second
}
//...
//the texture that's used to blend between the colors
sampler2D _BlendTex;
float4 _BlendTex_ST;
//the colors to blend between
sampler2D _MainTex;
float4 _MainTex_ST;
sampler2D _SecondaryTex;
float4 _SecondaryTex_ST;
//...
我们同样对这张贴图进行 TRANSFORM_TEX 操作,并从贴图中读取颜色信息。然后我们使用其中的颜色信息中的 r 通道 ,也就是 红色 部分,作为插值参数。
//the fragment shader
fixed4 frag(v2f i) : SV_TARGET{
//calculate UV coordinates including tiling and offset
float2 main_uv = TRANSFORM_TEX(i.uv, _MainTex);
float2 secondary_uv = TRANSFORM_TEX(i.uv, _SecondaryTex);
float2 blend_uv = TRANSFORM_TEX(i.uv, _BlendTex);
//read colors from textures
fixed4 main_color = tex2D(_MainTex, main_uv);
fixed4 secondary_color = tex2D(_SecondaryTex, secondary_uv);
fixed4 blend_color = tex2D(_BlendTex, blend_uv);
//take the red value of the color from the blend texture
fixed blend_value = blend_color.r;
//interpolate between the colors
fixed4 col = lerp(main_color, secondary_color, blend_value);
return col;
}
完整的shader代码如下:
Shader "Tutorial/009_Color_Blending/TextureBasedBlending"{
//show values to edit in inspector
Properties{
_MainTex ("Texture", 2D) = "white" {} //the base texture
_SecondaryTex ("Secondary Texture", 2D) = "black" {} //the texture to blend to
_BlendTex ("Blend Texture", 2D) = "grey" //black is the first color, white the second
}
SubShader{
//the material is completely non-transparent and is rendered at the same time as the other opaque geometry
Tags{ "RenderType"="Opaque" "Queue"="Geometry"}
Pass{
CGPROGRAM
//include useful shader functions
#include "UnityCG.cginc"
//define vertex and fragment shader
#pragma vertex vert
#pragma fragment frag
//the texture that's used to blend between the colors
sampler2D _BlendTex;
float4 _BlendTex_ST;
//the colors to blend between
sampler2D _MainTex;
float4 _MainTex_ST;
sampler2D _SecondaryTex;
float4 _SecondaryTex_ST;
//the object data that's put into the vertex shader
struct appdata{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
//the data that's used to generate fragments and can be read by the fragment shader
struct v2f{
float4 position : SV_POSITION;
float2 uv : TEXCOORD0;
};
//the vertex shader
v2f vert(appdata v){
v2f o;
//convert the vertex positions from object space to clip space so they can be rendered
o.position = UnityObjectToClipPos(v.vertex);
o.uv = v.uv;
return o;
}
//the fragment shader
fixed4 frag(v2f i) : SV_TARGET{
//calculate UV coordinates including tiling and offset
float2 main_uv = TRANSFORM_TEX(i.uv, _MainTex);
float2 secondary_uv = TRANSFORM_TEX(i.uv, _SecondaryTex);
float2 blend_uv = TRANSFORM_TEX(i.uv, _BlendTex);
//read colors from textures
fixed4 main_color = tex2D(_MainTex, main_uv);
fixed4 secondary_color = tex2D(_SecondaryTex, secondary_uv);
fixed4 blend_color = tex2D(_BlendTex, blend_uv);
//take the red value of the color from the blend texture
fixed blend_value = blend_color.r;
//interpolate between the colors
fixed4 col = lerp(main_color, secondary_color, blend_value);
return col;
}
ENDCG
}
}
}