Unity SurfaceShader基础知识入门

##

表面着色器四个函数的入口

1:表面着色器包括4个函数:
(1): 顶点变换函数;
(2): 表面着色函数;
(3): 光照模型;
(4): 最终颜色修改函数;
2: 表面着色器最终会被编译为一个复杂的顶点着色程序;


定义入口函数

1:#pragma surface 入口函数名称 光照模型 [Options]
2: suface 后面跟 表面着色的入口函数 surf(Input IN, inout SurfaceOutput o);

input数据结构:

  Input:包含着色所需要的纹理坐标 uv纹理名字;使用第二张纹理是uv2纹理名字;
  附加数据:
  1:float3 viewDir 视图方向。
  2:float4 color 每个顶点的颜色插值
  3: float4 screenPos 屏幕空间中的位置。
  4: float3 worldPos 世界坐标空间;
  5: float3 worldRef1 世界空间中的反射向量;
  6: float3 worldNormal 世界空间中的法线向量;
  7: float3 worldRef1; INTERNAL_DATA 世界坐标反射向量, 但必须表面着色写入o.Normal参数;
  8: float3 worldNormal; INTERNAL_DATA 世界坐标法线向量, 但必须表面着色写入o.Normal参数;

SurfaceOutput 结构体:

  SurfaceOutput:

  1: half3 Albedo: 漫反射的颜色值;

  2: half3 Normal: 法线坐标;

  3: half3 Emission; 自发光颜色;

  4: half Specular;  镜面反射系数;

  5: half Gloss; 光泽系数;

  6: half Alpha; 透明度系数;

  SurfaceOutputStandard:

  7: half Smoothness;    // 0=粗糙, 1=光滑

  8: half Metallic;    // 0=非金属, 1=金属

  SurfaceOutputStandardSpecular:

      fixed3 Albedo;     

      fixed3 Specular;    

      fixed3 Normal;     

      half3 Emission;  

      half Smoothness;    // 0=粗糙, 1=光滑  

      half Occlusion;  // 遮挡(默认1)  

      fixed Alpha;  

3: 光照模型:
(1)系统内置 Lambert(漫反射光照) BlinnPhong (高光光照),要使用Lambert,就要搭配用SurfaceOutput,不能用SurfaceOutputStandard
(2)自定义光照: 名字为Name
half4 Lighting<Name>(SurfaceOutput s, half3 lightDir, half atten);//lightDir是当前顶点到光的光源的方位向量,atten是一个衰减值
half4 Lighting<Name>(SurfaceOutput s, half3 lightDir, half3 viewDir, half atten);
half4 Lighting<Name>(SurfaceOutput s, half4 light);
可选参数:
4: vertex: name vertex入口函数:
void <Name> (inout appdata_full v) 只需改顶点着色器中的输入顶点数据;
half4 <Name>(inout appdata_full v, out Input o) 修改输入顶点数据,以及为表面着色器传递数据;
5: finalcolor: name 最终颜色修改函数:
void <Name>(Input IN, SurfaceOutput o, inout fixed4 color);

其它可选参数

1:alpha: Alpha 混合模式,用户半透明着色器;
2: alphatest: varirableName Alpha测试模式,用户透明镂空着色器。
3: exclude_path:prepass 使用指定的渲染路径;
4: addshadow: 添加阴影投射器和集合通道;
5: dualforward: 将双重光照贴图用于正向渲染路径中;
6: fullforwardshadows 在正向渲染路径中支持的所有的阴影类型;
7: decal: add 附加印花着色器;
8: decal: blend 附加半透明印花着色器;
9: softvegetation 使用表面着色器,仅在Soft Vegetation 开启时被渲染;
10: noambient 不使用任何光照
11: novertexlights 在正向渲染中不适用球面调和光照或逐点光照;
12: nolightmap 在这个着色器上禁用光照贴图;
13: nodirlightmap 在这个着色器上禁用方向光照贴图;
14: noforwardadd 禁用正向渲染添加通道;
15: approxview: 对于有需要的着色器,逐顶点而不是逐像素计算规范化视线方向。
16: halfasview: 将半方向传递到光照函数中。

********************************************************************************************

表面着色器的标准输出结构(Surface Output)

    定义一个“表面函数(surface function)”,需要输入相关的UV信息或数据信息,并在输出结构中填充SurfaceOutput。SurfaceOutput基本上描述了表面的特性(光照的颜色反射率、法线、散射、镜面等)。其实还是需要用CG或者HLSL编写此部分的代码。

    我们其实是通过表面着色器(Surface Shader)来编译这段CG或者HLSL代码的,然后计算出需要填充输入什么,输出什么等相关信息,并产生真实的顶点(Vertex)&像素(pixel)着色器,以及把渲染路径传递到正向或延时渲染路径。

表面着色器(Surface Shader)的标准输出结构:

struct SurfaceOutput{

    half3 Albedo;        //反射率,也就是纹理颜色值(r,g,b)

    half3 Normal;        //法线,法向量(x,y,z)

    half3 Emission;        //自发光颜色值(r,g,b)

    half3 Specular;        //镜面反射度

    half Gloss;            //光泽度

    half Alpha;            //透明度

}

这个结构体的用法,其实就是对这些需要用到的成员变量在surf函数中赋一下值,比如说这样:

//表面着色函数的编写
void surf (Input IN, inout SurfaceOutput o)
{
	//反射率,也就是纹理颜色值赋为(0.6, 0.6, 0.6)
       o.Albedo= 0.6;
}

注意到Albedo是half3类型的。那么o.Albedo = 0.6和o.Albedo = float3(0.6,0.6,0.6)是等价的。

表面着色器的编译指令:

      表面着色器放在CGPROGRAM .. ENDCG块里面。表面着色器必须嵌在子着色器(SubShader)块里面,而不是Pass{}里面。因为表面着色器(Surface Shader)将在多重通道(multiple passes)内编译自己,而不是放在某个Pass中。

声明表面着色器:

#pragma surface surfaceFunction lightMode[optionalparams]

用到的参数详解:

  • surfaceFunction 

    • 表示指定名称的Cg函数中有表面着色器(surface shader)代码。这个函数的格式应该是这样:void surf(Input IN,inout SurfaceOutput o),其中Input是我们自己定义的结构。Input结构中应该包含所需的纹理坐标(texture coordinates)和表面函数(surfaceFunction)所需要的额外的必须变量。

  • LightModel 

    •  使用的光照模式。内置的是Lambert(diffuse)和BlingPhong(specular)两种,一般习惯使用Lambert。

  • 进阶可选参数

    • alpha - 透明(Alpha)混合模式。使用它可以写出半透明的着色器。

    • alphaTest:VariableName - 透明(Alpha)测试模式。使用它可以写出镂空效果的着色器。镂空大小的变量(VariableName)是一个float型的变量。

    • vertex:VertexFunction - 自定义的顶点函数(Vertex function)。

    • finalcolor:ColorFunction - 自定义的最终颜色函数(final color function).

#pragma surface surf Lambert finalcolor:mycolor

  • exclude_path:prepass 或者 exclude_path:forward - 使用指定的渲染路径,不需要生成通道。

  • addshadow - 添加阴影投射&收集通道(collector passes).通常用自定义顶点修改,使阴影也能投射在任何程序的顶点动画上。

  • dualforward - 在正向(forward)渲染路径中使用双重光照贴图(dual lightmaps).

  • fullforwardshadows - 在正向(forward)渲染路径中支持所有阴影类型。

  • decal:add - 添加贴图着色器(decal shader)(例如:terrain AddPass)。

  • softvegetation - 使表面着色器(surface shader)仅能在soft Vegetation打开时渲染

  • noambient - 不适用于任何环境光照(ambient lighting)或者球面调和光照(spherical harmonics lights)

  • novertexlights - 在正向渲染(Forward rendering)中不适用于球面调和光照(spherical harmonics lights)或者每个顶点光照(per-vertex lights)

  • nolightmap - 在这个着色器上禁用光照贴图(lightmap)

  • nodirlightmap - 在这个着色器上禁用方向光照贴图(directional lightmaps)

  • noforwardadd - 禁用正向渲染添加通道(Forward rendering additive pass)。这会使这个着色器支持一个完整的方向光和所有光照的per-vertex/SH计算。

  • approxview - 着色器需要计算标准视图的每个顶点(per-vertex)方向而不是每个像素(per-pixel)方向。这样更快,但是视图方向不完全是当前摄像机所接近的表面。

  • halfasview - 在光照函数(lighting function)中传递进来的half-direction向量,而不是视图方向(view-direction)向量。Half-direction会计算且会把每个顶点(per vertex)标准化。

此外,我们还可以在 CGPROGRA内编写 #pragma debug,然后表面编译器(surface compiler)会进行解释生成代码。

表面着色器输入结构(Input Structure)

    Input这个输入结构通常拥有着色器需要的所有纹理坐标(texture coordinates).纹理坐标(Texture coordinates)必须被命名为 “uv”后接纹理名称。

可以在输入结构中根据自己的需要,可选附加这样的一些候选值:

  • float3 viewDir

    • 视图方向(view direction)值。为了计算视差效果(Parallax effects),边缘光照(rim ligihting)等,需要包含视图方向(view direction)值。

  • float4 with COLOR semantic

    • 每个顶点(per-vertex)颜色的插值

  • float4 screenPos

    • 屏幕空间中的位置。为了反射效果,需要包含屏幕空间中的位置信息。比如在Dark Unity中所使用的WetStreet着色器

  • float3 worldPos

    • 世界空间中的位置

  • float3 worldRefl

    • 世界空间中的反射向量。如果表面着色器(surface shader)不写入法线(o.Normal)参数,将包含这个参数。

  • float3 worldNormal

    • 世界空间中的法线向量(normal vector)。如果表面着色器不写入法线(o.Normal)参数,将包含这个参数。

  • float3 worldRefl;INTERNAL_DATA

    • 世界空间中的反射向量。如果表面着色器(surface shader)不写入法线(o.Normal)参数,将包含这个参数。为了获得基于每个顶点法线贴图(per-pixel normal map)的反射向量(reflection vector)需要使用世界反射向量(WorldReflectionVector(IN,o.Normal))

  • float3 worldNormal;INTERNAL_DATA

    • 世界空间中的法线向量(normal vector)。如果表面着色器不写入法线(o.Normal)参数,将包含这个参数。为了获得获得基于每个顶点法线贴图(per-pixel normal map)的法线向量(normal vector)需要使用世界法线向量(WorldNormalVector(IN,o.Normal))

本次写Shader用到的CG函数讲解

    UnpackNormal()函数

        UnpackNormal接受一个fixed4的输入,并将其转换为所对应的法线值(fixed3),并将其赋值给输出的Normal,就可以参与到光线运算中完成接下来的的渲染工作。

调用示例

o.Normal = UnpackNormal(tex2D(_BumpMap,IN.uv_BumpMap));

    saturate()函数

        saturate函数的作用是将取值转化为[0,1]之内的一个值。其可选的原型如下:

float saturate(float x);

float1 saturate(float1 x);

float2 saturate(float2 x);

float3 saturate(float3 x);

float4 saturate(float4 x);

half saturate(half x);

half1 saturate(half1 x);

half2 saturate(half2 x);

half3 saturate(half3 x);

half4 saturate(half4 x);

fixed saturate(fixed x);

fixed1 saturate(fixed1 x);

fixed2 saturate(fixed2 x);

fixed3 saturate(fixed3 x);

fixed4 saturate(fixed4 x);

其唯一的一个参数x表示矢量或者标量的饱和值(Vector or scalar to saturate.),也就是将这个x转化为[0,1]之内的值。

        返回值:

  • 如果x取值小于0,则返回值为0

  • 如果x取值大于1,则返回值为1

  • 若x在0到1之间,则直接返回x的值

其代码实现大致如下:

float saturate(float x)
{
    return max(0,min(1, x));
}

一个调用示例:

half rim = 1.0 - saturate(dot(normalize(IN.viewDir), o.Normal));

dot()函数

    dot函数的作用用于返回两个向量的标量积,可选原型如下:

float dot(float a, float b);

float dot(float1 a, float1 b);

float dot(float2 a, float2 b);

float dot(float3 a, float3 b);

float dot(float4 a, float4 b);

half dot(half a, half b);

half dot(half1 a, half1 b);

half dot(half2 a, half2 b);

half dot(half3 a, half3 b);

half dot(half4 a, half4 b);

fixed dot(fixed a, fixed b);

fixed dot(fixed1 a, fixed1 b);

fixed dot(fixed2 a, fixed2 b);

fixed dot(fixed3 a, fixed3 b);

fixed dot(fixed4 a, fixed4 b);

其代码实现大致是这样的:

float dot(float4 a, float4 b)
{
    return a.x*b.x +a.y*b.y + a.z*b.z + a.w*b.w;
}

一个调用示例:

float answer= dot (normalize(IN.viewDir),o.Normal);

tex2D()函数

    该函数用于2D纹理采样,其可选原型有:

float4 tex2D(sampler2D samp, float2 s)

float4 tex2D(sampler2D samp, float2 s, inttexelOff)

float4 tex2D(sampler2D samp, float3 s)

float4 tex2D(sampler2D samp, float3 s, inttexelOff)

float4 tex2D(sampler2D samp, float2 s,float2 dx, float2 dy)

float4 tex2D(sampler2D samp, float2 s,float2 dx, float2 dy, int texelOff)

float4 tex2D(sampler2D samp, float3 s,float2 dx, float2 dy)

float4 tex2D(sampler2D samp, float3 s,float2 dx, float2 dy, int texelOff)

int4 tex2D(isampler2D samp, float2 s)

int4 tex2D(isampler2D samp, float2 s, inttexelOff)

int4 tex2D(isampler2D samp, float2 s,float2 dx, float2 dy)

int4 tex2D(isampler2D samp, float2 s,float2 dx, float2 dy, int texelOff)

unsigned int4 tex2D(usampler2D samp, float2s)

unsigned int4 tex2D(usampler2D samp, float2s, int texelOff)

unsigned int4 tex2D(usampler2D samp, float2s, float2 dx, float2 dy)

unsigned int4 tex2D(usampler2D samp, float2s, float2 dx, float2 dy,int texelOff)

    参数简介

  • samp -  需要查找采样的对象

  • s - 需进行查找的纹理坐标

  • dx -  预计算的沿x轴方向的导数

  • dy  -  预计算的沿y轴方向的导数

  • texeloff - 添加给最终纹理的偏移量

    其返回值为查找到的纹理。

综合了本次讲解的四个函数(UnpackNormal、saturate、tex2D、dot)的Surface Shader中surf函数的示例:

//【2】表面着色函数的编写
void surf (Input IN, inout SurfaceOutput o)
{
       //从主纹理获取rgb颜色值
       o.Albedo= tex2D (_MainTex, IN.uv_MainTex).rgb;
       //从凹凸纹理获取法线值
       o.Normal= UnpackNormal (tex2D (_BumpMap, IN.uv_BumpMap));
       //从_RimColor参数获取自发光颜色
       halfrim = 1.0 - saturate(dot (normalize(IN.viewDir), o.Normal));
       o.Emission= _RimColor.rgb * pow (rim, _RimPower);
}

Shader实例说明:

Shader "typedef/Volume4/9.凹凸纹理+颜色可调+边缘发光+细节纹理" {

        Properties {

               _MainTex("【主纹理】RGB",2D) = "white"{}

               _BumpMap("【凹凸纹理】RGB",2D) = "white"{}

               _DetailTex("【细节纹理】",2D) = "gray"{}

               _ColorTint("【色泽】",Color) = (0.5,0.6,0.5,1)

               _RimColor("【边缘颜色】",Color) = (0.3,1,0.7,0)

               _RimPower("【边缘颜色强度】",Range(0.6,9))=3.0

        }

        SubShader {

               //着色器标签

               Tags{"RenderType" = "Opaque"}

               //开始CG编程语言段

               CGPROGRAM

               //声明光照模式:兰伯特光照模式+自定义颜色

               #pragma surface surf Lambert finalcolor:setcolor

               //声明变量

               sampler2D _MainTex;

               sampler2D _BumpMap;

               sampler2D _DetailTex;

               fixed4 _ColorTint;

               fixed4 _RimColor;

               float _RimPower;

               //输入结构体

               struct Input{

                       //主纹理的uv坐标值

                       float2 uv_MainTex;

                       //凹凸纹理的uv坐标值

                       float2 uv_BumpMap;

                       //细节纹理的uv坐标值

                       float2 uv_DetailTex;

                       //当前坐标的视线方向

                       float3 viewDir;

               };

               //表面着色函数

               void surf(Input IN,inout SurfaceOutput o){

                       //设置主纹理的rgb值

                       o.Albedo = tex2D(_MainTex,IN.uv_MainTex).rgb;

                       //设置细节纹理

                       o.Albedo *= tex2D(_DetailTex,IN.uv_DetailTex).rgb*3;

                       //设置凹凸纹理

                       o.Normal = UnpackNormal(tex2D(_BumpMap,IN.uv_BumpMap));

                       //获得自发光强度系数

                       //获得射线方向与法线方向的点积,求点积就是求夹角的cos值

                       //将上面的值转化为[0,1]的一个值

                       half rim = 1.0 - saturate(dot(normalize(IN.viewDir),o.Normal));

                       //设置边缘发光颜色

                       o.Emission = _RimColor * pow(rim,_RimPower);

               }

               //自定义颜色函数

               void setcolor(Input IN,SurfaceOutput o, inout fixed4 color){

                       color *= _ColorTint;

               }

               //结束CG编程语言段

               ENDCG

        }

        FallBack "Diffuse"

}

参考原文:

关于Unity中表面着色器的使用 - 杭者 - 博客园

【Unity3D Shader编程】之六 暗黑城堡篇: 表面着色器(Surface Shader)的写法(一)_浅墨_毛星云的博客-CSDN博客_游戏黑暗着色器

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值