Shader - 编写Surface Shader

如果你还未了解过Shader lab 建议先了解一下Shader Lab 相关内容:跳转接口

编写与光照相互做的着色器是很复杂的,有不同的灯光类型,不同的阴影选项,不同的渲染路径,着色需要以某种方式处理所有的复杂性。
Unity中的Surface Shader是一种代码生成方法,它使编写着色比使用顶点/片段着色器要容易的多,Surface Shader只是生成所有需要手工编写的重复性的代码,仍然需要使用HLSL来编写着色器。

Surface Shader 运作原理:
定义一个“surface function”(表面处理函数),他会将我们需要的数据放入Input 结构中,然后在方法内填充输出结构"SurfaceOutput"结构。然后Surface Shader会计算出所需的输入、填充输出等操作,并生成实际的顶点/片段着色,以及创建处理Foward Rending和Deferred shadering渲染路径的渲染通道(Pass)。
例如:

//...
#pragma surface surf Lambert
struct Input
{
	//...
}
void surf(Input i,inout SurfaceOutput o)
{
	o.Albedo =1;
	//...
}
//...

上面就用#pragma surface 定义了一个surf表面函数,并且使用Lambert光照模型,定义了Input结构体,并在surf 方法中对输出结构体SurfaceOutput进行了填充

Surface Shader 编译指令:
Surface Shader 需要在CGPROGRAM和ENDCG块内编写,并且需要注意以下内容:

1.必须放在SubShder块内(注意不是Pass内,表面着色器本身将编译为多个通道)
2.使用#pragma surface ...指令来指示当前shader为Surface Shader。

#pragma surface指令格式:

#pragma surface surfaceFunction lightModel [optionalparams]
指令类型 指令 描述
必须参数 surfaceFunction 是一个CG函数,并包含表面着色器代码,格式:void surf(Input IN,inout SurfaceOutput o),input为自定义的结构,应包含表面函数所需要的任何纹理坐标和额外的自动变量
lightModel 要使用的照明模型,内置的有基于物理的Standard和StandardSpecular,以及简单的非基于物理的Lambert、BlinnPhong以及自定义光照模型
StandardSpecular:使用SurfaceOutputStandardSpecular输出结构,并匹配Unity中的Standard(Specular setup)着色器
Lamber和BlinnPhong照明模型不是基于物理的,但使用它们的着色器可以更快地在低端硬件上渲染
可选参数 透明或透明度测试 透明度,透明度通常可以有两种:alpha混合(用于淡出物体)和物理上更合理地"预乘混合"(premultiplied blending,运行半透明表面保留适当地镜面反射)。
alpha或alpha:auto 将为简单地照明方法选择淡入透明(与alpha:fade相同),为基于物理的照明方法选择欲乘透明度(与alpha:premul相同)。
alpha:blend 启用alpha混合。
alpha:fade 实现传统地淡入淡出
alpha:premul 启用预乘alpha透明度
alphatest 透明度测试,启用alpha剪切透明度。截止使用variableName的浮点数变量,还可以使用addshadow指令来生成正确的阴影。
keepalpha 默认情况下,不透明表面着色器将1.0写入alpha通道,无论无论输出结构的alpha值是什么,或者照明功能返回什么。使用此选项可以保持照明功能的alpha值,即使对于不透明的表面着色器也是如此。
decal:add 添加贴花着色器(例如地形addpass)。适用于位于其他表面顶部的物体,并使用添加进行混合。
decal:blend 半透明贴花着色器,适用于位于其他表面顶部的对象,并使用alpha混合。
自定义修改器函数 可用于更改或计算传入的顶点数据,或更改最终计算的片段颜色
vertex:VertexFunction 自定义顶点修改功能,在生成的顶点着色器的开始处调用此函数,并且可以修改或计算每顶点数据
finalcolor:ColorFunction 自定义最终颜色修改功能
finalgbuffer:ColorFunction 用于更改gbuffer内容的自定以延迟路径
finalprepass:ColorFunction 自定以预通道基本路径
阴影和曲面细分 可以提供其他指令来控制和曲面细分的处理方式
addshadow 生成阴影投射渲染通道(Pass),通常用于自定义顶点修改,以便阴影投射也可以获得任何程序顶点动画。通常着色器不需要任何特殊的阴影处理,因为它们可以使用指定fallback来投射阴影
fullforwardshadow 支持Forward Rendering路径中的所有光影类型。默认情况下,着色器仅支持Forward Rendering中一个平行光的阴影(节省内部着色器变量计数)。如果在Forward Rendering中需要点光源或聚光灯阴影,可以使用此指令
tessellate:TessFunction 使用DX11 GPU细分。该函数计算曲面细分因子。
代码生成选项 默认情况下,生成的表面着色器代码会尝试处理所有可能的光照/阴影/光照贴图,但是在某些情况下,是不需要其中的某些,并且可以调整生成的代码以跳过它们,以便生成更小的着色器提升加载速度。
exclude_path:deferred,exclude_path:forward,exclude_path:prepass 不产生对于给定的渲染路径渲染通道(Pass)(延迟着色,正向渲染和传统延迟照明)
noshadow 禁用此着色器中的所有阴影接收支持。
noambient 请勿使用任何环境照明或光探头。
novertexlights 请勿在向前渲染中应用任何光探测器或每顶点光源。
nolightmap 禁用此着色器中的所有光照贴图支持。
nodynlightmap 在此着色器中禁用运行时动态全局照明支持。
nodirlightmap 禁用此着色器中的方向光照贴图支持。
nofog 禁用所有内置的雾支持。
nometa 不生成"meta“通道(由光照贴图和动态全局照明用于提取表面信息)。
moforwardadd 禁用Forward add 渲染通道,这使得着色支持一个全方向光,所有其他的光源按照顶点/SH的方式计算,可以使着色器更小
其他
softvegetation 仅在启用”Soft Vegetation“时才渲染表面着色器
interpolateview 在顶点着色器中计算视图方向并进行插值,而不是在像素着色器中计算。这样可以使像素着色器更快,但需要消耗一个纹理插值器
halfasview 将半向方向矢量传递到光照函数而不是试图方向,每个顶点将计算并归一化半方向。这样会更快,但不完全正确
dualforward 在forward rendering中使用双光照贴图

InputStruct(输入结构)
输入结构通常具有着色器所需要的任何纹理坐标。纹理坐标必须命名为"uv",后跟纹理名称(或”uv2“开头,使用第二个纹理坐标)(例如:uv_MainTex 或 uv2_MainTex);
其他可以放入输入结构的值有:

指令 描述
float3 viewDir 包含视角方向,可用于计算边缘光照等效果
float4 变量名:Color 包含差值后逐顶点颜色
float4 screenPos 包含了屏幕空间的坐标,可用于反射或屏幕特效。注意,这不适合GrabPass;需要使用ComputeGrabScreenPos函数自己计算定义UV。
float3 worldPos 包含世界空间位置。
float3 worldRefl 如果没有修改o.Normal,则包含世界空间下的反射向量。
float3 worldNormal 如果修改o.Normal,则包含世界空间下的法线向量。
float3 worldRefl;INTERNAL_DATA 如果修改了o.Normal,需要使用该变量告诉Unity要基于修改后的法线计算世界空间下的反射向量。可以使用WorldReflectionVector(IN,o.Normal)来得到世界空间下的反射方向。
float3 worldNormal;INTERNAL_DATA 如果修改了o.normal,需要使用该变量告诉Unity要基于修改后的法线计算世界空间下的法线方向。可以使用WorldNormalVector(IN,o.Normal)来得到世界空间下的法线方向

SurfaceOutput结构
SurfaceOutput基本上描述了平面的属性(如albedo、normal、emission、specularity等)
标准输出结构如下:

struct SurfaceOutput
{
	fixed3 Albedo;					//漫反射
	fixed3 Normal; 					//正切空间法线
	fixed3 Emission;				//自发光
	half Specular;					//高光率 0-1
	fixed Gloss;						//	高光强度
	fixed Alpha;						// 透明度
};

在Unity中还可以使用基于物理的照明模型,内置Standard和StandardSpecular模型分别使用下面这些输出结构:

struct SurfaceOutputStandard
{
    fixed3 Albedo;      //漫反射颜色
    fixed3 Normal;      // 切线空间法线
    half3 Emission;	//自发光颜色
    half Metallic;      // 金属 0 - 1
    half Smoothness;    // 平滑度0-1
    half Occlusion;     // occlusion (default 1)
    fixed Alpha;        // 透明度
};
struct SurfaceOutputStandardSpecular
{
    fixed3 Albedo;      // 漫反射颜色
    fixed3 Specular;    // 高光颜色
    fixed3 Normal;      // 切线空间法线
    half3 Emission; //自发光颜色
    half Smoothness;    //平滑度0-1
    half Occlusion;     // occlusion (default 1)
    fixed Alpha;        // 透明度
};

实例:
1.简单的着色器:

Shader "Example/DiffuseSimple"
{
	SubShader
	{
		Tags{"RenderType" = "Opaque"}
		CGPROGRAM
		#pragma surface surf Lambert
		struct Input
		{
			fixed4 color : COLOR;
		};
		void surf(Input IN, inout SurfaceOutput o)
		{
			o.Albedo = 1;
		}
		ENDCG
	}
	FallBack "Diffuse"
}

//在上述着色器中,声明找色器为表面找色器(#pragma surface) ,定义了表面函数(surf)以及指定基本光照为Lambert。
//声明输入结构(Input) 包含顶点颜色。
//在表面方法(surf)中,设置输出函数的漫反射颜色为1(o.Albedo = 1;)
//如果此着色器在硬件上没法使用的话会使用Diffuse着色器进行替换

2.Texture(纹理):

Shader "Example/Texture"
{
	Properties
	{
		_MainTex("MainTex",2D) = ""{}
	}
	SubShader
	{
		Tags{"RenderType" = "Opaque"}
		CGPROGRAM
		#pragma surface surf Lambert
		struct Input //获取_MainTex的第一套纹理坐标
		{
			float2 uv_MainTex;
		};
		//注意 在Properties中声明的属性,需要在CGPROGRAM块内声明一个同样名称且类型匹配的变量来进行关联
		sampler2D _MainTex;
		void surf(Input IN, inout SurfaceOutput o)
		{
			//根据Input中纹理坐标匹配_MainTex的纹理设置漫反射颜色
			o.Albedo = tex2D(_MainTex, IN.uv_MainTex);
		}
		ENDCG
	}
	FallBack "Diffuse"
}

3.Normal(法线):

Shader "Example/DiffuseBump"
{
	Properties
	{
		_MainTex("MainTex",2D) = ""{}
		//	定义法线贴图
		_BumpTex("BumpTex",2D) = ""{}
	}

	SubShader
	{
		Tags{"RenderType"="Opaque"}
		CGPROGRAM
		#pragma surface surf Lambert
		struct Input
		{
			float2 uv_MainTex;
			//法线贴图纹理坐标
			float2 uv_BumpTex;
		};

		sampler2D _MainTex;
		sampler2D _BumpTex;

		void surf(Input IN, inout SurfaceOutput o)
		{
			o.Albedo = tex2D(_MainTex, IN.uv_MainTex);
			//设置法线
			o.Normal = UnpackNormal(tex2D(_BumpTex, IN.uv_BumpTex));
		}
		ENDCG
	}
	FallBack "Diffuse"
}

4.Rim Light(边缘照明):

Shader "Example/RimLight"
{
	Properties
	{
		_MainTex("MainTex",2D) = ""{}
		_BumpTex("BumpTex",2D) = ""{}
		_RimColor("Rim Color",Color) = (1,1,1,1)
		_RimPower("Rim Power",Range(0,8)) = 0
	}

	SubShader
	{
		Tags{"RenderType" = "Opaque"}
		CGPROGRAM
		#pragma surface surf Lambert
		sampler2D _MainTex;
		sampler2D _BumpTex;
		float4 _RimColor;
		float _RimPower;
		struct Input
		{
			float2 uv_MainTex;
			float2 uv_BumpTex;
			float3 viewDir;//视角方向
		};
		void surf(Input IN, inout SurfaceOutput o)
		{
			o.Albedo = tex2D(_MainTex, IN.uv_MainTex);
			o.Normal = UnpackNormal(tex2D(_BumpTex, IN.uv_BumpTex));
			half rim = 1.0 - saturate(dot(normalize(IN.viewDir), o.Normal));
			o.Emission = _RimColor.rgb*pow(rim, _RimPower);
		}
		ENDCG
	}
}
发布了32 篇原创文章 · 获赞 18 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/qq_18192161/article/details/90548647