Shader - 编写Surface Shader

    xiaoxiao2023-10-29  151

    如果你还未了解过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 } }
    最新回复(0)