Unity SurfaceShader案例入门

##

Unity SurfaceShader的来源:

在2009年的时候(当时Unity的版本是2.x),Unity的渲染工程师Aras(就是经常活跃在论坛和各种会议上的,大名鼎鼎的Aras Pranckevicius)连续发表了3篇名为<Shaders must die>的博客。在这些博客里,Aras认为,把渲染流程分为顶点和像素的抽象层面是错误的,是一种不易理解的抽象。目前,这种在顶点/几何/片元着色器上的操作是对硬件友好的一种方式,但不符合我们人类的思考方式。相反,他认为,应该划分为表面着色器、光照模型和光照着色器这样的层面。

其中,表面着色器定义了模型表面的反射率、法线和高光等,光照模型则选择是使用兰伯特还是Blinn-Phong模型。而光照着色器负责计算光照衰减、阴影等。这样,绝大部分时间我们只需要和表面着色器打交道,例如,混合纹理和颜色等。光照模型可以是提前定义好的,我们只需要选择哪种预定义的光照模型即可。而光照着色器一旦由系统实现后,更不会被轻易改动,从而大大减轻了Shader编写者的工作量。有了这样的想法后,Aras在随后的文章中开始尝试把表面着色器整合到Unity中。最终,在2010年的Unity3中,Surface Shader被加入到Unity的大家族中了。

Surface Shader是Unity为了方便shader编写提供的特殊功能,它对底层的vertex/fragment shader做了封装,省去了一些重复代码编写的工作量。我的理解是它同时具有vertex/fragment shader的功能,只是写法上更加简洁,更容易上手。

Unity的官方manual上就提供了几个最好的学习例子,我在学习的过程中加上了注释。

简单的漫反射(SIMPLE)

  Shader "Example/Diffuse Simple" {
    SubShader {
      Tags { "RenderType" = "Opaque" } // 标签,决定什么时候渲染(对不透明物体渲染)
      CGPROGRAM // //CG语言标记开始
      #pragma surface surf Lambert // 编译指令 surface shader 自定义函数 光照模型[1] 
      struct Input {               // 输入的结构体
          float4 color : COLOR;    // 颜色值
      };
      void surf (Input IN, inout SurfaceOutput o) { // surface shader处理函数
          o.Albedo = 1;  // 将基础颜色设为白色[2]
      }
      ENDCG
    }
    Fallback "Diffuse" // 发生异常时回滚成Unity内置的Diffuse shader
  }

带纹理的漫反射(TEXTURE)

  Shader "Example/Diffuse Texture" {
    Properties {
      _MainTex ("Texture", 2D) = "white" {} // 纹理,若没有赋值则默认为全白
    }
    SubShader {
      Tags { "RenderType" = "Opaque" }
      CGPROGRAM
      #pragma surface surf Lambert
      struct Input {
          float2 uv_MainTex; // 纹理uv坐标
      };
      sampler2D _MainTex;
      void surf (Input IN, inout SurfaceOutput o) {
          o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb; // 按uv坐标查找纹理上的像素,并获取rgb值
      }
      ENDCG
    } 
    Fallback "Diffuse"
  }

带法向贴图的漫反射(NORMAL MAPPING)

Shader "Example/Diffuse Bump" {
    Properties {
      _MainTex ("Texture", 2D) = "white" {}
      _BumpMap ("Bumpmap", 2D) = "bump" {} // 法向贴图[3]
    }
    SubShader {
      Tags { "RenderType" = "Opaque" } 
      CGPROGRAM
      #pragma surface surf Lambert
      struct Input {
        float2 uv_MainTex;
        float2 uv_BumpMap;
      };
      sampler2D _MainTex;
      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)); // 将从法向贴图中取得的法向量赋给output
      }
      ENDCG
    } 
    Fallback "Diffuse"
  }

边缘光(RIM LIGHTING)

 Shader "Example/Rim" {
    Properties {
      _MainTex ("Texture", 2D) = "white" {}
      _BumpMap ("Bumpmap", 2D) = "bump" {}
      _RimColor ("Rim Color", Color) = (0.26,0.19,0.16,0.0) // 边缘光的颜色
      _RimPower ("Rim Power", Range(0.5,8.0)) = 3.0 // 边缘光的强度
    }
    SubShader {
      Tags { "RenderType" = "Opaque" }
      CGPROGRAM
      #pragma surface surf Lambert
      struct Input {
          float2 uv_MainTex;
          float2 uv_BumpMap;
          float3 viewDir; // 观察向量
      };
      sampler2D _MainTex;
      sampler2D _BumpMap;
      float4 _RimColor;
      float _RimPower;
      void surf (Input IN, inout SurfaceOutput o) {
          o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;
          o.Normal = UnpackNormal (tex2D (_BumpMap, IN.uv_BumpMap));
          // 对观察向量和法向量求点积,这个值越小,代表这两个方向夹角越接近90度,即为轮廓边缘
          half rim = 1.0 - saturate(dot (normalize(IN.viewDir), o.Normal)); 
          // 越接近边缘,发出的光越亮
          o.Emission = _RimColor.rgb * pow (rim, _RimPower);
      }
      ENDCG
    } 
    Fallback "Diffuse"
  }

细节纹理(DETAIL TEXTURE)

  Shader "Example/Detail" {
    Properties {
      _MainTex ("Texture", 2D) = "white" {}
      _BumpMap ("Bumpmap", 2D) = "bump" {}
      _Detail ("Detail", 2D) = "gray" {} // 细节纹理[4]
    }
    SubShader {
      Tags { "RenderType" = "Opaque" }
      CGPROGRAM
      #pragma surface surf Lambert
      struct Input {
          float2 uv_MainTex;
          float2 uv_BumpMap;
          float2 uv_Detail;
      };
      sampler2D _MainTex;
      sampler2D _BumpMap;
      sampler2D _Detail;
      void surf (Input IN, inout SurfaceOutput o) {
          o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;
          // 将纹理和细节纹理叠加
          o.Albedo *= tex2D (_Detail, IN.uv_Detail).rgb * 2;
          o.Normal = UnpackNormal (tex2D (_BumpMap, IN.uv_BumpMap));
      }
      ENDCG
    } 
    Fallback "Diffuse"
  }

屏幕空间下的细节纹理(DETAIL TEXTURE IN SCREEN SPACE)

 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 _Detail;
      void surf (Input IN, inout SurfaceOutput o) {
          o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;
          float2 screenUV = IN.screenPos.xy / IN.screenPos.w;
          screenUV *= float2(8,6);
          // 根据屏幕的uv坐标来叠加细节纹理[5]
          o.Albedo *= tex2D (_Detail, screenUV).rgb * 2;
      }
      ENDCG
    } 
    Fallback "Diffuse"
  }

立方体贴图反射(CUBEMAP REFLECTION)

  Shader "Example/WorldRefl" {
    Properties {
      _MainTex ("Texture", 2D) = "white" {}
      _Cube ("Cubemap", CUBE) = "" {} // 立方体贴图[6]
    }
    SubShader {
      Tags { "RenderType" = "Opaque" }
      CGPROGRAM
      #pragma surface surf Lambert
      struct Input {
          float2 uv_MainTex; 
          float3 worldRefl; // 世界反射向量
      };
      sampler2D _MainTex;
      samplerCUBE _Cube;
      void surf (Input IN, inout SurfaceOutput o) {
          o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb * 0.5;
          // 根据世界反射向量和立方体贴图,反射相应的rgb
          o.Emission = texCUBE (_Cube, IN.worldRefl).rgb;
      }
      ENDCG
    } 
    Fallback "Diffuse"
  }

世界坐标下的切片(SLICES VIA WORLD SPACE POSITION)

  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) {
      // 自定义的切片函数(将y和z作为参数,意味沿x轴做切片)
          clip (frac((IN.worldPos.y+IN.worldPos.z*0.1) * 5) - 0.5);
          o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;
          o.Normal = UnpackNormal (tex2D (_BumpMap, IN.uv_BumpMap));
      }
      ENDCG
    } 
    Fallback "Diffuse"
  }

使用顶点修改器的法线挤压(NORMAL EXTRUSION WITH VERTEX MODIFIER)

  Shader "Example/Normal Extrusion" {
    Properties {
      _MainTex ("Texture", 2D) = "white" {}
      _Amount ("Extrusion Amount", Range(-1,1)) = 0.5 // 挤压参数
    }
    SubShader {
      Tags { "RenderType" = "Opaque" }
      CGPROGRAM
      #pragma surface surf Lambert vertex:vert // 声明会使用顶点修改器
      struct Input {
          float2 uv_MainTex;
      };
      float _Amount;
      void vert (inout appdata_full v) { // 顶点修改器实现
          v.vertex.xyz += v.normal * _Amount; // 沿法线移动顶点的坐标
      }
      sampler2D _MainTex;
      void surf (Input IN, inout SurfaceOutput o) {
          o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;
      }
      ENDCG
    } 
    Fallback "Diffuse"
  }

逐顶点计算自定义数据(CUSTOM DATA COMPUTED PER-VERTEX)

  Shader "Example/Custom Vertex Data" {
    Properties {
      _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader {
      Tags { "RenderType" = "Opaque" }
      CGPROGRAM
      #pragma surface surf Lambert vertex:vert
      struct Input {
          float2 uv_MainTex;
          float3 customColor; // 自定义数据
      };
      void vert (inout appdata_full v, out Input o) {
          UNITY_INITIALIZE_OUTPUT(Input,o);
          // 在顶点修改器中,将法向量的绝对值赋值给参数customColor
          o.customColor = abs(v.normal); 
      }
      sampler2D _MainTex;
      void surf (Input IN, inout SurfaceOutput o) {
          o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;
          // 使用传递过来的参数,对颜色做叠加
          o.Albedo *= IN.customColor;
      }
      ENDCG
    } 
    Fallback "Diffuse"
  }

注释:
[1]:Unity中最常用的是两种内置的光照模型:Lambert 和 BlinnPhong
[2]:Albedo是物体的基础颜色,与之不同的Emission是自发光颜色,详细的input和output变量表参见官网
[3]:法向贴图是为了实现凹凸不平的效果,可参见文章
[4]:细节纹理是为了让纹理产生更细腻的感觉,可参见文章
[5]:注意rgb相乘和相加的应用场合不同,这里给出了解释。
[6]:立方体贴图可用来做环境反射和天空盒,可参见文章

版权声明:本文为needmorecode原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

参考链接:Shader学习笔记(一):Surface Shader - 灰信网(软件开发博客聚合)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值