UnityShader学习之旅 -- 表面着色器

表面着色器

与顶点/片元区别CG代码是直接而且必须写在subshader块中,unity会在背后生成多个Pass, CG中定义表面着色器的具体代码
结构两个结构体、编译指令
工作原理1.surfaceFunc它将您需要的所有 UV 或数据作为输入,并填充输出结构 SurfaceOutput
2.表面着色器编译器随后计算出需要的输入、填充的输出等等,并生成实际的顶点和像素着色器以及渲染通道来处理前向和延迟渲染
特点生成的表面着色器代码会尝试处理所有可能的光照/阴影/光照贴图情况。
快速实现各种光照效果
缺点失去各种优化和各种特效实现的控制–对性能有影响
无法完成一些自定义的渲染效果
建议1.各种光源和全局光照==表面着色器,但注意性能
2.光源数目非常少 == 顶点/片元着色器
3.有很多自定义的渲染效果 == 顶点/片元着色器

编译指令

定义编写者与unity沟通的重要手段
作用指明该表面着色器使用的表面函数和光照函数,并设置一些可选参数
位置表面着色器的CG块中的第一句代码往往就是它的编译指令
格式#pragma surface surfaceFunction lightModel [optionalparams]
#pragma surface :用于定义表面着色器
surfaceFunction:指明使用的表面函数
lightModel:使用的光照模型
optionalparams:可选参数来控制表面着色器的一些行为
  1. 表面函数

一个对象的表面属性定义了它的反射率、光滑度、透明度等值 surfaceFunction就用于定义这些表面函数 surf

	/// <summary>
    /// 表面函数
    /// </summary>
    /// <param name="IN">输入结构体包含表面函数所需的任何纹理坐标和额外自动变量</param>
    /// <param name="SurfaceOutput">输出结构体 unity内置的结构体,需要配合不同的光照模型使用 </param>
	void surf (Input IN, inout SurfaceOutput o)
	void surf (Input IN, inout SurfaceOutputStandard o)
	void surf (Input IN, inout SurfaceOutputStandardSpecular o)
  1. 光照函数

使用表面函数中设置的各种表面属性,来应用某些光照模型,进而模拟物体表面的光照效果。

光照模型函数:

内置光照模型cginc
基于物理Standard使用SurfaceOutputStandard输出结构体,并与unity中的标准着色器(金属性工作流)匹配UnityPBSLighting.cginc
StandardSpecular使用SurfaceOutputStandardSpecular 输出结构体,并与unity中的标准着色器(镜面反射设置)匹配
非基于物理Lambert (漫反射光照)和 BlinnPhong(镜面反射光照)在低端硬件上可以提高渲染速度Lighting.cginc

自定义光照函数

表面着色器中的自定义光照模型

自定义光照函数注意
声明光照模型可包含多个名称以Lighting开头的常规函数无需声明所有函数。光照模型不一定会使用视图方向。同样,如果仅在前向渲染中使用光照模型,请勿声明 _Deferred 和 _Prepass 函数。这确保了使用视图方向的着色器仅编译到前向渲染
自定义GI自定义光照贴图数据和探针的解码要对 Unity 标准光照贴图和 SH 探针进行解码,您可以使用内置函数 DecodeLightmap 和 ShadeSHPerPixel
//自定义光照贴图数据和探针的解码
half4 Lighting<Name>_GI (SurfaceOutput s, UnityGIInput data, inout UnityGI gi)
常规函数适用
half4 Lighting < Name> (SurfaceOutput s, UnityGI gi)在_不依赖于_视图方向的光照模型的前向渲染路径中使用
half4 Lighting< Name> (SurfaceOutput s, half3 viewDir, UnityGI gi)在_依赖于_视图方向的光照模型的前向渲染路径中使用
half4 Lighting< Name>_Deferred (SurfaceOutput s, UnityGI gi, out half4 outDiffuseOcclusion, out half4 outSpecSmoothness, out half4 outNormal)在延迟光照路径中使用
half4 Lighting< Name>_PrePass (SurfaceOutput s, half4 light)在光照预通道(旧版延迟)光照路径中使用

表面着色器光照示例

  1. 其他可选参数
可选参数适用
自定义修改器函数更改或计算传入的顶点数据/更改最终计算的片元颜色vertex:vertexFunc自定义顶点修改函数
finalcolor:ColorFunc 自定义最终颜色修改函数
finalgbuffer:ColorFunc 更改G缓冲区内容的自定义延迟路径
finalprepass:ColorFunc 自定义预通道基本路径
阴影提供其他指令来控制阴影和曲面细分的处理方式addshadow:生成阴影投射物理通道。用于自定义的顶点修改,以便阴影投射可以获得程序化顶点动画。一般不需要修改
fullforwardshadows:支持前向渲染路径中的所有光源阴影类型。默认仅支持前向渲染中来自一个方向的阴影。如果前向渲染中需要点光源阴影或聚光灯阴影,就使用次指令。
tessellate:TessFunction :使用DX11 GPU曲面细分,计算曲面细分因子
noshadow:禁用此着色器中的所有阴影接受支持
noshadowmask:为此着色器禁用阴影遮罩支持(包括 Shadowmask 和 Distance Shadowmask)
透明度混合和透明度测试由alpha和alpha test指令控制透明度:1.传统Alpha混合 – 用于淡出对象
2.更符合物理规律的预乘混合–允许半透明表面保留适当的镜面反射。
启用半透明度:使生成的表面着色器代码包含混合命令
启用Alpha镂空:根据给定的变量在生成的像素着色器中执行片元废弃(clip)
光照控制光照对物体的影响
控制代码的生成可以调整生成的代码以跳过不需要处理的。这样可以减小着色器,从而提高加载速度exclude_path:deferred(延迟着色)、exclude_path:forward(前向) 和 exclude_path(旧版延迟):prepass - 不为给定的渲染路径生成通道。
透明相关解释
alpha/alpha:auto简单的光照函数,将选择淡化透明度==alpha:fade
基于物理的光照函数,将选择预乘透明度 == alpha : premul
alpha:blend启用Alpha 混合
alpha:fade启用传统的淡化透明度
alpha:premul启用预乘Alpha 透明度
alphatest:VariableName启用Alpha镂空透明度。VariableName:剪切值
keepalpha无论输出结构的 Alpha 输出是什么,或者光照函数返回什么,不透明表面着色器都将 1.0 写入 Alpha 通道
可以保持光照函数的 Alpha 值
decal:add附加贴花着色器
适用于位于其他表面之上并使用附加混合的对象
decal:blend半透明贴花着色器
适用于位于其他表面之上并使用 Alpha 混合的对象
光照指令解释
noambient不应用任何环境光照或光照探针
novertexlights在前向渲染中不应用任何光照探针或每顶点光源
noforwardadd禁用前向渲染附加通道。这会使着色器支持一个完整方向光,所有其他光源均进行每顶点/SH 计算。也能减小着色器。
nolightmap禁用此着色器中的所有光照贴图支持
nodynlightmap禁用此着色器中的运行时动态全局光照支持
nofog禁用所有内置雾效支持
nometa不生成“Meta”通道(由光照贴图和动态全局光照用于提取表面信息)
nolppv禁用此着色器中的光照探针代理体支持
其他指令解释
softvegetation仅在开启 Soft Vegetation 时才渲染表面着色器
interpolateview在顶点着色器中计算视图方向并进行插值;而不是在像素着色器中计算使像素着色器更快,但会额外消耗一个纹理插值器
halfasview将半方向矢量传入光照函数而不是视图方向计算半方向并按每个顶点对其进行标准化,稍微快点
dualforward在前向渲染路径中使用双光照贴图
dithercrossfade使表面着色器支持抖动效果此着色器应用于使用细节级别组 (LOD Group) 组件(配置为交叉淡入淡出过渡模式)的游戏对象

贴花

通常用于在运行时向材质添加细节(例如,子弹冲击力效果)。贴花在延迟渲染中特别有用,因为贴花在照亮之前会改变 G 缓冲区,因此可以节省开销。
在常规情况下,贴花应该在不透明对象之后渲染,并且不应该是阴影投射物"ForceNoShadowCasting"=“True”

两个结构体

表面着色器中不同函数之间信息传递的桥梁

  1. Input结构体

具有自由性

float3 viewDir包含视图方向,用于计算视差效果、边缘光照等等
使用COLOR 语义的 float4变量包含插值的每顶点颜色
float4 screenPos包含反射或屏幕空间效果的屏幕空间位置这不适合 GrabPass;需要使用 ComputeGrabScreenPos 函数自己计算自定义 UV
float3 worldPos包含世界空间位置
float3 worldRefl在不修改 o.Normal_ 的情况下,包含世界反射方向
float3 worldRefl;
INTERNAL_DATA
在修改o.Normal_ 的情况下,包含世界反射矢量要获得基于每像素法线贴图的反射矢量,请使用 WorldReflectionVector(IN, o.Normal)
float3 worldNormal在不修改 o.Normal_ 的情况下,包含世界法线方向
loat3 worldNormal
INTERNAL_DATA
在修改了o.Normal_ 的情况下,包含世界法线矢量要获得基于每像素法线贴图的法线矢量,请使用 WorldNormalVector (IN, o.Normal)
  1. 标准输出结构:SurfaceOutput

结构体里面的变量是提前声明好,不能增加也不能减少。没有赋值就直接使用默认值

	//表面着色器的标准输出结构
	struct SurfaceOutput
	{
    	fixed3 Albedo;  // 漫射颜色<== 纹理采样 * 颜色属性
    	fixed3 Normal;  // 切线空间法线(如果已写入)表面法线方向
    	fixed3 Emission;// 自发光 一般会在颜色输出前处理颜色叠加<== c.rgb += o.Emission
    	// float spec = pow(nh,s.Specular*128.0) *s.Gloss
    	half Specular;  // 高光反射中指数部分的系数 [0,1]范围内的镜面反射能力
    	fixed Gloss;    // 高光反射中强度系数。镜面反射强度
    	fixed Alpha;    // 透明度 Alpha
	};
	//内置标准光照模型
	struct SurfaceOutputStandard
	{
    	fixed3 Albedo;      // 基础(漫射或镜面反射)颜色
    	fixed3 Normal;      // 切线空间法线(如果已写入)
    	half3 Emission;		// 自发光
   	 	half Metallic;      // 0=非金属,1=金属
    	half Smoothness;    // 0=粗糙,1=平滑
    	half Occlusion;     // 遮挡(默认为 1)
    	fixed Alpha;        // 透明度 Alpha
	};
	//标准镜面反射光照模型
	struct SurfaceOutputStandardSpecular
	{
    	fixed3 Albedo;      // 漫射颜色
    	fixed3 Specular;    // 镜面反射颜色
    	fixed3 Normal;      // 切线空间法线(如果已写入)
    	half3 Emission;		// 自发光
    	half Smoothness;    // 0=粗糙,1=平滑
    	half Occlusion;     // 遮挡(默认为 1)
    	fixed Alpha;        // 透明度 Alpha
	};

表面着色器生成顶点/片元着色器

  1. 直接将表面着色器中CGPROGRAM和ENDCG之间的代码复制。

包括Input结构体、表面函数、光照函数等变量和函数的定义。

  1. Unity分析代码,并据此生成顶点着色器的输出—v2f_surf结构体(用于顶点着色器和片元着色器之间进行数据传递)

分析在自定义函数中所使用的变量。eg:纹理坐标、视角方向、反射方向等。
v2f_surf中包含一些其他需要的变量.eg:阴影纹理坐标、光照纹理坐标、逐顶点光照等。

  1. 生成顶点着色器

    1. 若定义顶点修改函数。1. 调用顶点修改函数修正顶点数据, 或填充自定义的Input结构体中的变量。2.分析顶点修改函数中修改的数据,需要时将修改结构存储到v2f_surf。
    2. 计算v2f_surf中其他生成的变量值。

    顶点位置、纹理坐标、法线方向、逐顶点光照、光照纹理的采样坐标等。

    1. 将v2f_surf传递给片元着色器。
  2. 生成片元着色器

    1. 调用v2f_surf中的对应变量填充到Input结构体

    纹理坐标、视角方向等。

    1. 调用自定义的表面函数填充SurfaceOutput结构体。
    2. 调用光照函数得到初始颜色值。

    使用内置Lamert / BlinnPhong光照函数Unity会自动计算动态全局光照,并添加到光照模型的计算中

    1. 进行其他颜色叠加

    没有使用光照烘培,还会添加逐顶点光照的影响

    1. 若自定义了最后的颜色修改函数。unity调用它进行最后的颜色修改。
/******************************************************
 *对模型进行膨胀
 1.沿顶点法线方向扩张顶点位置
 * NormalExtrusion.Shader
 *****************************************************/
Shader "NormlalExtrusion"{
	Properties{
		_Color("Color Tint", Color) = (1,1,1,1)	//颜色
		_MainTex("Main Tex", 2D)="white"{}		//纹理贴图
		_BumpMap("Normal Map", 2D) = "bump"{}	//法线纹理
		_Amount("Amout", Range(-0.5,0.5)) = 0.1	
	}
	SubShader{
		Tags{"RenderType"="Opaque"} //物体
		LOD 300 					//<300才渲染
		CGPROGRAM
		//自定义光照模型 修改顶点函数、最后颜色修改、添加阴影效果、不生成延迟渲染通道Pass、不生成meta通道
		#pragma surface surf CustomLambert vertex:myvert finalcolor:mycolor addshadow exclude_path:deferred exclude_path:prepass nometa
		#pragma target 3.0
		
		fixed4 _Color;
		sampler2D _MainTex;
		samper2D _BumpMap;
		half _Amount;
		struct Input{
			float2 uv_MainTex;
			float2 uv_BumpMap;
		};
		void myvert(inout appdata_full v){
			//对顶点进行伸缩
			v.vertex.xyz += v.normal * _Amount;
		}
		void mycolor(Input IN, SurfaceOutput o, iout fixed4 color){
			color *= _Color;
		}
		void surf (Input IN, iout SurfaceOutput o){
			fixed4 tex = tex2D(_MainTex, IN.uv_MainTex);	
			o.Albedo = tex.rgb;
			o.Alpha = tex.a;
			o.Normal = UnpackNormal(tex2D(_BumpMap, IN.uv_BumpMap));	//修正法线
		}
		//输出结构体、光照方向、衰减值
		half4 LightingCustomLambert(SurfaceOutput s, half3 lightDir, half atten){
			half NdotL = dot(s.Normal, lightDir);
			half4 c;
			//计算漫反射光照
			c.rgb = s.Albedo * _LightColor0.rgb * (atten * NdotL);
			c.a = s.Alpha;
			return c;
		}
		ENDCG
	}
	Fallback "Legacy Shaders/Diffuse"
}

正常
顶点扩大

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值