Shader编程学习笔记(六)—— Fixed Function Shader 2

24 篇文章 1 订阅
18 篇文章 1 订阅


1、Fixed Function Shader的知识点

  上一篇中我们提到固定管线着色器中要涉及到的知识点,其中我们已经了解了Properties(属性)、Material(材质)、Lighting(光照)、settexture(设置纹理),由于篇幅原因还没说完。这里我们继续完善Fixed Function Shader的一个重要命令settexture(设置纹理)。

2、示例演示

  这里我们还是做一个场景来演示具体功能!

2.1、场景搭建

  场景跟上篇一样,一个小球,一个材质球M2、创建一个新的shader,命名为FixedFunctionShader2。方便起见我们直接在上一节的场景中演示(unity2017.4.27f1)。
在这里插入图片描述

2.2、settexture命令

  这里我们使用上一篇中的shader内容,并添加settexture命令,详细代码如下:

Shader "lxt610/FixedFunctionShader2" {
	properties{
          _Color("Main Color",color) = (1,1,1,1)
		  _Ambient("Ambient",color)=(0.3,0.3,0.3,0)
		  _Specular("Specular",COLOR) = (1,1,1,1)
		  _Shininess("Shininess",Range(0,8)) = 4
		  _Emission("Emission",COLOR) = (1,1,1,1)
		  _MainTex("Main Texture",2D) = ""
    }
  
    SubShader {
        pass{
            // color(1,0,0,1) // 分别代表了 r,g,b,a 
            // color[_Color] // 小括号内容表示固定值,中括号内容表示可变参数值
            material{
                diffuse[_Color]			// 漫反射
				ambient[_Ambient]		// 环境光
				specular[_Specular]		// 高光
				shininess[_Shininess]	// 述specular强度
				emission[_Emission]		//自发光
            }

			lighting on // 光照开关
			separatespecular  on //镜面高光开关

			SetTexture[_MainTex]
			{
				Combine texture
			}

        }
     }
}

  要使用贴图采样,我们需要命令settexture,在固定管线着色器中settexture是最重要的也是最复杂的一个命令。settexture需要一个纹理属性参数“_MainTex(“MainTex”,2d)="",其中的值就是纹理的名称,默认可以是空字符串,接着在settexture结构体中添加命令“combine texture”。回到Unity工程中,创建一个新的球体,将新建的材质拖放在球体上,在该球体材质的检视面板中拖放一张贴图,效果如下:
在这里插入图片描述

2.3、primary关键字

  这时就可以看到在球体上使用这个贴图以后,球体上原有的灯光照明和高光效果已经没有了。这时因为:

在着色器中的combine命令指的是合并,它的参数目前只是用了texture纹理,这个texture指的就是属性中的_MainTex设置的贴图,而这里只有贴图而没有应用先前已经计算好的光照数据。这里如果需要先前的光照数据,我们需要为当前贴图texture乘上primary,primary是Fixed Function Shader的关键字,代表了前面所有计算材质和光照后的颜色值,将贴图和这个值相乘,就会得到一个混合的新的颜色值。

Shader "lxt610/FixedFunctionShader2" {
	properties{
          _Color("Main Color",color) = (1,1,1,1)
		  _Ambient("Ambient",color)=(0.3,0.3,0.3,0)
		  _Specular("Specular",COLOR) = (1,1,1,1)
		  _Shininess("Shininess",Range(0,8)) = 4
		  _Emission("Emission",COLOR) = (1,1,1,1)
		  _MainTex("Main Texture",2D) = ""
    }
  
    SubShader {
        pass{
            // color(1,0,0,1) // 分别代表了 r,g,b,a 
            // color[_Color] // 小括号内容表示固定值,中括号内容表示可变参数值
            material{
                diffuse[_Color]			// 漫反射
				ambient[_Ambient]		// 环境光
				specular[_Specular]		// 高光
				shininess[_Shininess]	// 述specular强度
				emission[_Emission]		//自发光
            }

			lighting on // 光照开关
			separatespecular  on //镜面高光开关

			SetTexture[_MainTex]
			{
				Combine texture * primary
			}
        }
     }
}

  值得注意的是texture纹理的颜色值rgba每一个分量都是0到1,primary值的每一个分量也是0到1,当一个小于1的浮点数与另一个小于1的浮点数相乘,就会得到一个更小的浮点数,因此得到的颜色会变深,效果如下:
在这里插入图片描述

2.4、double关键字

  考虑到这个问题,Fixed Function Shader当中有一个关键字“double”,表示对某个结果乘以2的运算,用法为“combine texture * primary double”。修改完shader,回到工程中,可以观察到球体变亮了。

Shader "lxt610/FixedFunctionShader2" {
	properties{
          _Color("Main Color",color) = (1,1,1,1)
		  _Ambient("Ambient",color)=(0.3,0.3,0.3,0)
		  _Specular("Specular",COLOR) = (1,1,1,1)
		  _Shininess("Shininess",Range(0,8)) = 4
		  _Emission("Emission",COLOR) = (1,1,1,1)
		  _MainTex("Main Texture",2D) = ""
    }
  
    SubShader {
        pass{
            // color(1,0,0,1) // 分别代表了 r,g,b,a 
            // color[_Color] // 小括号内容表示固定值,中括号内容表示可变参数值
            material{
                diffuse[_Color]			// 漫反射
				ambient[_Ambient]		// 环境光
				specular[_Specular]		// 高光
				shininess[_Shininess]	// 述specular强度
				emission[_Emission]		//自发光
            }

			lighting on // 光照开关
			separatespecular  on //镜面高光开关

			SetTexture[_MainTex]
			{
				Combine texture * primary  double
			}
        }
     }
}

效果如下:
在这里插入图片描述

2.5、previous关键字

  以上就是一个最基本的settexture的运用,当然在某些情况下需要为这个物体不只混合一张图,如果要混合两张或者两张以上的纹理该怎么处理呢?

这里我需要注意的是,一个settexture只能带上一个参数,不能再添加第二个参数,因此需要再编写一个settexture和一个纹理属性参数_SecTex。

  这里我们修添加一个settexture命令,代码修改为下所示:

Shader "lxt610/FixedFunctionShader2" {
	properties{
          _Color("Main Color",color) = (1,1,1,1)
		  _Ambient("Ambient",color)=(0.3,0.3,0.3,0)
		  _Specular("Specular",COLOR) = (1,1,1,1)
		  _Shininess("Shininess",Range(0,8)) = 4
		  _Emission("Emission",COLOR) = (1,1,1,1)
		  _MainTex("Main Texture",2D) = ""
		  _SecTex("Main Texture",2D) = ""
    }
  
    SubShader {
        pass{
            // color(1,0,0,1) // 分别代表了 r,g,b,a 
            // color[_Color] // 小括号内容表示固定值,中括号内容表示可变参数值
            material{
                diffuse[_Color]			// 漫反射
				ambient[_Ambient]		// 环境光
				specular[_Specular]		// 高光
				shininess[_Shininess]	// 述specular强度
				emission[_Emission]		//自发光
            }

			lighting on // 光照开关
			separatespecular  on //镜面高光开关

			SetTexture[_MainTex]
			{
				Combine texture * primary  double
			}

			SetTexture[_SecTex]
			{
				Combine texture * primary  double
			}
        }
     }
}

  这时等待编译通过,然后给给_SecTex拖上一张纹理,效果如下:
在这里插入图片描述
  我们可以看到仅仅显示了第二张贴图,第一张贴图没有和第二张贴图进行混合。这时什么原因呢?这时因为:

这里虽然进行了两次settexture计算,但是如果想让第一次settexture和第二次settexture的结果进行混合就不能使用“combine texture * primary”语句,因为primary总是代表前面顶点光照计算的颜色值,这里还需要加上第一次settexture的结果。

  在Fixed Function Shader当中除了primary外,还有一个previous关键字,previous指的是先前的数据。

将代码改成“combine texture * previous”,这里的意思就是用当前纹理的值去乘上当前settexture操作之前所有计算和采样过后的结果。

可以看到效果如下:

在这里插入图片描述
  可以观察到已经有了需要的结果,球体已经被混合了两张纹理贴图。

按照这个思路可以继续去编写多个settexture,但是纹理混合的次数不是无限的,Fixed Function Shader是基本对照于显卡硬件的固定渲染部分,所以显卡有一个混合纹理的最大个数,一般来讲越好的硬件可以混合的纹理越多,越差的硬件可以混合的纹理越少,基本上两张纹理的混合是目前所有显卡都能够支持的。

2.6、透明处理

  有两个物体一前一后,透明的物体放在前面我们是可以看到后面的物体的。就医上述场景中的球体做演示。我们要实现物体的透明化,需要借助alpha值,alpha值介于0到1之间,0为完全透明,1位不透明。
  这里先分析下前面的计算,所有的计算都和颜色有关系。在当前的shader程序中,最后一个settexture使用了第二张纹理与先前所有计算的颜色值相乘,如果我们去改变先前计算过程中的alpha值,那么应该可以去影响这个球体的半透明度。在当前material材质计算当中,使用了四种颜色值,分别是diffuse、ambient、specular和emission,这四种颜色的alpha值基本都是1,我们可以在检视面板中把它们的alpha值都相对降低,按理来说这些颜色的alpha值都很低了,这四个颜色去影响贴图的时候,即使贴图的alpha值为1,相乘以那些颜色的alpha后,也会得到一个比较低的alpha值,然后第二个settexture又去相乘比较低的alpha值,最后应该会得到一个很低的alpha值,但是为什么不能观察到球体的半透明化呢?这个时候就需要去了解一下Unity Shader当中一个比较重要的命令,这个命令是ShaderLab当中的Blending。如图:
在这里插入图片描述

2.6.1、透明处理——Blending

  在着色器渲染过后,会进入到alpha测试,alpha测试以后就会进入到Blending阶段,Blending指的是混合,它可以用SrcFactor(源元素)和DstFactor(目标元素)去进行混合运算。其中,当我们正在渲染当前这个球体时,渲染这个球体得到的颜色值就是SrcFactor,而球体以外其他的物体包括天空盒等已经在这个球体渲染之前被渲染完成的值就是DstFactor。
  查看Unity Manual中的ShaderLab: Blending,在Blend factors下可以看到很多关键词,其中我们可以先关注"SrcAlpha"和“OneMinusSrcAlpha”,"SrcAlpha"指的就是当前已经渲染得到的alpha值,而“OneMinusSrcAlpha”是指用1减去"SrcAlpha"的值,我们可以将命令“Blend SrcAlpha OneMinusSrcAlpha”放在shader程序当前的渲染通道当中。

Shader "lxt610/FixedFunctionShader2" {
	properties{
          _Color("Main Color",color) = (1,1,1,1)
		  _Ambient("Ambient",color)=(0.3,0.3,0.3,0)
		  _Specular("Specular",COLOR) = (1,1,1,1)
		  _Shininess("Shininess",Range(0,8)) = 4
		  _Emission("Emission",COLOR) = (1,1,1,1)
		  _MainTex("Main Texture",2D) = ""
		  _SecTex("Main Texture",2D) = ""
    }
  
    SubShader {
        pass{
			Blend SrcAlpha OneMinusSrcAlpha
            // color(1,0,0,1) // 分别代表了 r,g,b,a 
            // color[_Color] // 小括号内容表示固定值,中括号内容表示可变参数值
            material{
                diffuse[_Color]			// 漫反射
				ambient[_Ambient]		// 环境光
				specular[_Specular]		// 高光
				shininess[_Shininess]	// 述specular强度
				emission[_Emission]		//自发光
            }

			lighting on // 光照开关
			separatespecular  on //镜面高光开关

			SetTexture[_MainTex]
			{
				Combine texture * primary  double
			}

			SetTexture[_SecTex]
			{
				Combine texture * previous
			}
        }
     }
}

  这句指令的意义是用当前渲染的alpha值与1减去当前渲染的alpha值的比例去混合之前已经被渲染好的场景颜色值。回到工程中,可以查看到球体发生了变化。如图:
在这里插入图片描述

2.6.2、透明处理——Tags标签

  虽然看起来不明显,但是我们可以发现球体整体变亮了一些,不过它没有真正达到透明,原因是在Unity引擎当中,存在一种渲染顺序的问题。图形显卡渲染某一帧图像的时候,当场景中有很多物体,到先渲染哪一个,后渲染哪一个,就取决于渲染顺序。在当前场景中,如果先渲染后面的球体,再渲染前面的球体,那么在渲染后面球体的时候是看不到前面这个球体的,因此在第一次渲染后面的球体时是可以看到整个球体的,如下图:
在这里插入图片描述
  当第二次渲染前面这个球体时,该球体由于深度(z次序)被放在了靠近摄像机的地方,它会被稍后渲染,因此它就被叠加到先前生成的图像上,并且遮住了后面的物体,在这种情况下,我们就需要稍微去改变一下前面这个球体的渲染顺序,要改变渲染顺序,我们就需要了解使用ShaderLab: SubShader Tags
  在SubShader当中允许添加Tags标签,并以类似键值对的形式存在,语法格式为:

Tags { “TagName1” = “Value1” “TagName2” = “Value2” }

  查看“Rendering Order - Queue tag”,这是渲染队列的标签,它包括以下这些值,分别是Background(背景)、
Geometry(几何图形)、AlphaTest、Transparent、和Overlay。一个实实在在的物体,它不透明的时候是默认使用Geometry进行渲染的,如果要去渲染半透明的物体,要使用在默认值后的渲染队列,其中我们可以使用Transparent,将“Tags { “Queue” = “Transparent” }”加到SubShader中。

Shader "lxt610/FixedFunctionShader2" {
	properties{
          _Color("Main Color",color) = (1,1,1,1)
		  _Ambient("Ambient",color)=(0.3,0.3,0.3,0)
		  _Specular("Specular",COLOR) = (1,1,1,1)
		  _Shininess("Shininess",Range(0,8)) = 4
		  _Emission("Emission",COLOR) = (1,1,1,1)
		  _MainTex("Main Texture",2D) = ""
		  _SecTex("Main Texture",2D) = ""
    }
  
    SubShader {
		Tags { "Queue" = "Transparent" }
        pass{
			Blend SrcAlpha OneMinusSrcAlpha
            // color(1,0,0,1) // 分别代表了 r,g,b,a 
            // color[_Color] // 小括号内容表示固定值,中括号内容表示可变参数值
            material{
                diffuse[_Color]			// 漫反射
				ambient[_Ambient]		// 环境光
				specular[_Specular]		// 高光
				shininess[_Shininess]	// 述specular强度
				emission[_Emission]		//自发光
            }

			lighting on // 光照开关
			separatespecular  on //镜面高光开关

			SetTexture[_MainTex]
			{
				Combine texture * primary  double
			}
			SetTexture[_SecTex]
			{
				Combine texture * previous
			}
        }
     }
}

  这句指令的意义是用当前渲染的alpha值与1减去当前渲染的alpha值的比例去混合之前已经被渲染好的场景颜色值。回到工程中,可以查看到球体发生了变化。如图:
在这里插入图片描述

2.6.3、透明处理——texture命令

  假设一张贴图里已经含有alpha通道,我们希望使用这张图本身的alpha通道去进行透明化处理,如果贴图的一部分alpha为0,一部分alpha为1,那么就会形成一种镂空的效果,怎么去设置贴图的alpha通道处理呢?我们可以在最后的settexture的combine命令中加入第二个参数texture,代码如下:

combine texture * previous double,texture

  其中,逗号后面的参数只是取了alpha通道值,我们期望用当前纹理的alpha通道去做当前alpha通道的设置。
  回到场景中,发现球体已经不透明了,这是因为如果在settexture的combine命令的第二个位置填写了alpha的参数,那么它只能针对这个部分去取alpha值运算,而之前所有的颜色alpha值都失效了,要想看到透明效果,我们可以把这张贴图的灰度值编程alpha值,如图:
在这里插入图片描述
回到场景中,可以发现球体已经半透明了,如图:
在这里插入图片描述

2.6.4、透明处理——constantColor命令

  还有一个constantColor命令,它可带一个参数,在properties(属性中)中声明一个参数:

“_ConstantColor(“ConstantColor”,color)=(1,1,1,0.3)”

  将其放在第二个settexture中,然后在combine命令中参数texture乘上constant,用constant的alpha值0.3与纹理颜色中的alpha值再相乘,那么运算结果的alpha值会变得更小。回到工程场景中后会发现一个编译错误,但是没发现代码中有什么问题,这里需要注意的是先前在编写“_MainTex”和“_SecTex”纹理属性时直接用了一个空字符串,而在官方Shader教程中,纹理的值往往是有值的,比如“_MainTex(“MainTex”,2d)=“white”()”,如果不愿意去添加这些值,可以将语句“_ConstantColor(“ConstantColor”,color)=(1,1,1,0.3)”添加在纹理属性之前,如:

Shader "lxt610/FixedFunctionShader2" {
	properties{
          _Color("Main Color",color) = (1,1,1,1)
		  _Ambient("Ambient",color)=(0.3,0.3,0.3,0)
		  _Specular("Specular",COLOR) = (1,1,1,1)
		  _Shininess("Shininess",Range(0,8)) = 4
		  _Emission("Emission",COLOR) = (1,1,1,1)
		  _ConstantColor("ConstantColor",color)=(1,1,1,1)
		  _MainTex("Main Texture",2D) = ""
		  _SecTex("Main Texture",2D) = ""
    }
  
    SubShader {
		Tags { "Queue" = "Transparent" }
        pass{
			Blend SrcAlpha OneMinusSrcAlpha
            // color(1,0,0,1) // 分别代表了 r,g,b,a 
            // color[_Color] // 小括号内容表示固定值,中括号内容表示可变参数值
            material{
                diffuse[_Color]			// 漫反射
				ambient[_Ambient]		// 环境光
				specular[_Specular]		// 高光
				shininess[_Shininess]	// 述specular强度
				emission[_Emission]		//自发光
            }

			lighting on // 光照开关
			separatespecular  on //镜面高光开关

			SetTexture[_MainTex]
			{
				Combine texture * primary  double
			}

			SetTexture[_SecTex]
			{
				constantColor[_ConstantColor]
				Combine texture * previous,texture * constant
			}
        }
     }
}

  切回到场景中,着色器已经可以正常工作了,这个时候可以托送检视面板中的“ContantColor”值来改变球体的透明程度,但是可以发现当“ContantColor”的值为1时,球体也是半透明的,原因就是这里使用纹理的灰度值来当透明度。
在这里插入图片描述

2.7、总结

  以上就是ShaderLab中的Fixed Function Shader的简单应用,比较重要的命令就是“settexture”,并且有一点复杂,而“settexture”中比较重要的命令是“combine”,“combine”命令可以配合“constantColor”使用来控制透明度,使用“blend”指令来控制最后的混合,并且使用tags标签来控制渲染顺序。

3、结束语


The End
  好了,今天的分享就到这里,如有不足之处,还望大家及时指正,随时欢迎探讨交流!!!


喜欢的朋友们,请帮顶、点赞、评论!您的肯定是我写作的不竭动力!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

对酒当歌﹏✍

您的鼓励是我写作的最大动力!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值