表面着色器

作者:追风剑情 发布于:2014-6-12 22:14 分类:Shader

   不同于固定功能着色器,表面着色器和顶点片元着色器的实际着色器代码使用CG或HLSL编写的。

   表面着色器必须像其他着色器一样放在CGPROGRAM和ENDCG中间,但它必须放在子着色器块中,不能放在通道中,表面着色器将在多个通道内编译自己,同时使用#pragma surface指令,以声明它是一个表面着色器,该指令如下:

#pragma surface 表面函数  光照模式 [ 可选参数 ]

表面函数

   表明哪些CG函数中有表面着色器代码。这个函数的格式如下:

void surf(Input IN, inout SurfaceOutput o)

Input是自定义的输入结构体。Input结构体中包含所有纹理坐标和表面函数所需要的额外的自动变量。

光照模式

   在光照模式中使用,内置的光照模式有Lambert(漫反射)和BlinnPhong(高光)。

可选参数

常用的可选参数如表所示。

表面着色器编译指令可选参数

可选参数 说明
alpha Alpha混合模式,使用它可以写出用以渲染半透明效果的着色器
alphatest:VariableName Alpha测试模式,使用它可以写出镂空效果的着色器。镂空大小的变量(VariableName)是一个float型的变量
vertex:VertexFunction 自定义的顶点函数,VertexFunction为函数名
finalcolor:ColorFunction 自定义的最终颜色函数,ColorFunction为函数名
exclude_path:prepass或exclude_path:forward 使用指定的渲染路径,不需要生成通道
addshadow 添加阴影投射并且集合通道。通常用于自定义顶点的修改,使阴影也能投射在任何过程的顶点动画上
dualforward 在正向(forward)渲染路径中使用双重光照贴图(dual lightmaps)
fullforwardshadows 在正向(forward)渲染路径中支持所有阴影类型
decal:add 添加贴花着色器
decal:blend 混合半透明的贴花着色器
softvegetation 使表面着色器仅能在Soft Vegetation打开时进行渲染
noambient 不使用任何环境光照(ambient lighting)或者球面调和光照(spherical harmonics lights)
novertexlights 在正向渲染(Forward rendering)中不使用球面调和光照(spherical harmonics lights)或每顶点光照(per-vertex lights)
nolightmap 在这个着色器上禁用光照贴图(lightmap)
nodirlightmap 在这个着色器上禁用方向光照贴图(directional lightmaps)
noforwardadd 禁用正向渲染添加光照。这会使这个着色器支持一个完整的方向光和所有逐顶点/SH计算的光照
approxview 当着色器有需要的时候,计算每个顶点,而不是每个像素的方向。这样更快,但是当摄像机接近表面时,视图方向不完全正确
halfasview 在光照函数中传递进来的是half-direction向量,而不是视图方向向量。Half-direction计算且将每个顶点标准化。这样更快,但不完全正确

提示

   可以在CGPROGRAM块中使用#pragma debug指令,然后编译器会产生许多注释的代码,可以在属性查看器中打开这些已编译好的着色器并查看它们。


表面着色器的输出结构体是内置定义好的,只需要在表面函数中写入相应的值即可,标准的表面着色器输出结构体如下:

struct SurfaceOutput {

   half3 Albedo; //反射光颜色

   half3 Normal; //法线

   half3 Emission; //自发光,用于增强物体自身的亮度,使之看起来好像可以自己发光

   half Specular; //镜面高光

   half Gloss; //光泽度

   half Alpha; //透明度

};

   表面着色器的输入结构体Input包含有着色器所需要的纹理坐标和附加值。纹理坐标必须在纹理名称前面加上"uv",或者以"uv2"作为头命名来使用第二个纹理坐标集。附加值按需要加入Input中,常用的附加值如表所示。

表面着色器Input附加值

附加值 说明
float3 viewDir 视图方向,为了计算视差、边缘光照等效果,Input需要包含视图方向
float4 with COLOR semantic 每个顶点颜色的插值
float4 screenPos 屏幕空间中的位置,为了获得反射效果,需要包含屏幕坐标
float3 worldPos    世界坐标
float3 worldRefl 世界空间中的反射向量,如果表面着色器不写入法线(o.Normal)参数,将包含这个参数
float3 worldNormal 世界空间中的法线向量,如果表面着色器不写入法线(o.Normal)参数,将包含这个参数
float3 worldRefl;INTERNAL_DATA 内部数据。如果表面着色写入了o.Normal,将包含世界反射向量。为了得到基于每个像素的法线贴图的反射向量需要使用世界反射向量(WorldReflectionVector(IN, o.Normal))
float3 worldNormal;INTERNAL_DATA 内部数据。如果表面着色写入了o.Normal,将包含世界法线向量。为了得到基于每个像素的法线贴图的反射向量需要使用世界反射向量(WorldReflectionVector(IN, o.Normal))

着色器在属性块声明它的属性,如果想在CG代码中访问这些属性的话,需要在CG中声明一个与名字相同且类型对应的变量。ShaderLab的属性类型与CG变量类型的对应关系如下的列。


  • Color和Vector属性对应float4类型。
  • Range和Float属性对应float类型。
  • Texture属性对应普通2D纹理的sampler2D类型。
  • CUBE和RECT纹理对应samplerCUBE和samplerRECT类型。


下面的代码片段说明了CG中属性的声明:

//ShaderLab中声明的属性

_MyColor ("Some Color", Color) = (1,1,1,1)

_MyVector ("Some Vector", Vector) = (0,0,0,0)

_MyFloat ("My float", Float) = 0.5

_MyTexture ("Texture", 2D) = "white" { }

_MyCubemap ("Cubemap", CUBE) = "" { }

//CG中访问着色器属性时声明变量

float4 _MyColor;

float4 _MyVector;

float _MyFloat;

sampler2D _MyTexture;

samplerCUBE _MyCubemap;

提示

   Unity中CG也接受uniform关键字,但这是不必要的。

下面建立一个简单的着色器,这个着色器仅仅设置了表面颜色为白色:

Shader "Example/Diffuse Simple" {

   SubShader {

      Tags { "RenderType" = "Opaque" } //设置替换渲染时的类型

      CGPROGRAM

      #pragma surface surf Lambert

      struct Input { //设置输入结构体

         float4 color : COLOR;

      };

      void surf (Input IN, inout SurfaceOutput o) {

         o.Albedo = 1; //计算反射光颜色

      }

      ENDCG

   }

   Fallback "Diffuse" //降级时使用Diffuse着色器

}


下面的着色器使用了纹理,并且添加了法线贴图:


Shader "Example/Diffuse Bump" {
	Properties {
		_MainTex ("Texture", 2D) = "white" {}
		_BumpMap ("Bumpmap", 2D) = "bump" {}
	}
	SubShader {
		Tags {"RenderType" = "Opaque"}
		CGPROGRAM
		#pragma surface surf Lambert
		struct Input {
			float2 uv_MainTex;
			float2 uv_BumpMap;
		};
		sampler2D _MainTex;//声明sampler2D类型的样本对象
		sampler2D _BumpMap;
		void surf (Input IN, inout SurfaceOutput o){
			o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;//计算反射光颜色
			o.Normal = UnpackNormal (tex2D (_BumpMap, IN.uv_BumpMap));//计算法线
		}
		ENDCG
	}
	Fallback "Diffuse"
}


下面的着色器添加了细节纹理,并与基础纹理结合。细节纹理与基础纹理使用相同的UV。但是在材质球中平铺值通常是不同的。所以在输入结构中必须使用不同的UV坐标:


Shader "Example/ScreenPos" {
	Properties {
		_MainTex ("Texture", 2D) = "white" {}
		_Detail ("Detail", 2D) = "gray" {}
	}
	SubShader {
		Tags {"RenderType" = "Opaque"}
		CGPROGRAM
		#pragma surface surf Lambert
		struct Input {
			float2 uv_MainTex;
			float4 screenPos;
		};
		sampler2D _MainTex;//声明sampler2D类型的样本对象
		sampler2D _Detail;
		void surf (Input IN, input SurfaceOutput o) {
			o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;
			float2 screenUV = IN.screenPos.xy / IN.screenPos.w;
			screenUV *= float2(8, 6);
			o.Albedo *= tex2D (_Detail, screenUV).rgb *2;//计算反射光颜色
		}
		ENDCG
	}
	Fallback "Diffuse"
}


下面的着色器通过世界空间位置对对象进行切割,其中,被切割后所抛弃的像素形状是几乎与水平位置平行的环状。它是基于世界位置的像素通过使用CG语言的clip()函数实现:

Shader "Example/Slices" {
	Properties {
		_MainTex ("Texture", 2D) = "white" {}
		_BumpMap ("Bumpmap", 2D) = "bump" {}
	}
	SubShader {
		Tags {"RenderType" = "Opaque"}
		Cull Off
		CGPROGRAM
		#pragma surface surf Lambert
		struct Input {
			float2 uv_MainTex;
			float2 uv_BumpMap;
			float3 worldPos;
		};
		sampler2D _MainTex;
		sampler2D _BumpMap;
		void surf (Input IN, inout SurfaceOutput o) {
			clip (frac((IN.worldPos.y + IN.worldPos.z*0.1)*0.5) - 0.5);//切割抛弃像素
			o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;//计算反射光颜色
			o.Normal = UnpackNormal (tex2D(_BumpMap, IN.uv_BumpMap));//计算法线
		}
		ENDCG
	}
	Fallback "Diffuse" //降级时使用"Diffuse"着色器
}

Powered by emlog  蜀ICP备18021003号-1   sitemap

川公网安备 51019002001593号