【图形学】《Shader入门精要》扩展【17-20】笔记
聪头 游戏开发萌新

扩展* 17-20

作者:聪头

笔记主题和书本主题并非一一对应

Ch17.Unity的表面着色器探秘

CSDN–关于Unity中表面着色器的使用:https://blog.csdn.net/weixin_34129145/article/details/85919551

17.1 表面着色器的一个例子

一个表面着色器中最重要的部分是两个结构体以及它的编译指令

  • 两个结构体:是表面着色器中不同函数之间信息传递的桥梁
  • 编译指令:我们和Unity沟通的重要手段
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
Shader "Unity Shaders Book/Chapter 17/Bumped Diffuse" {
Properties {
_Color ("Main Color", Color) = (1,1,1,1)
_MainTex ("Base (RGB)", 2D) = "white" {}
_BumpMap ("Normalmap", 2D) = "bump" {}
}
SubShader {
Tags { "RenderType"="Opaque" }
LOD 300

CGPROGRAM
#pragma surface surf Lambert
#pragma target 3.0

sampler2D _MainTex;
sampler2D _BumpMap;
fixed4 _Color;

struct Input {
float2 uv_MainTex;
float2 uv_BumpMap;
};

void surf (Input IN, inout SurfaceOutput o) {
fixed4 tex = tex2D(_MainTex, IN.uv_MainTex);
o.Albedo = tex.rgb * _Color.rgb;
o.Alpha = tex.a * _Color.a;
o.Normal = UnpackNormal(tex2D(_BumpMap, IN.uv_BumpMap));
}

ENDCG
}

FallBack "Legacy Shaders/Diffuse"
}

17.2 编译指令

编译指令最重要的作用是指明该表面着色器使用的表面函数光照函数,并设置一些可选参数

编译指令一般格式如下:

#pragma surface surfaceFunction lightModel [optionalparams]

#pragma surface:用于指明该编译指令是用于定义表面着色器的

表面函数 surfaceFunction

表面函数用于定义模型的表面属性(反射率、光滑度、透明度等值),通常就是名为surf的函数(可以任取哈)。它的函数格式是固定的:

void surf (Input IN, inout SurfaceOutput o)

void surf (Input IN, inout SurfaceOutputStandard o)

void surf (Input IN, inout SurfaceOutputStandardSpecular o)

解释:

  • 后两个是Unity 5中由于引入基于物理的渲染而新添加的两种结构体
  • 使用输入结构体Input IN来设置各种表面属性

光照模型 lightModel

光照模型使用表面函数中设置的各种表面属性,来模拟物体表面的光照效果

Unity内置光照模型函数

  • 基于物理的光照模型函数:Standard和StandardSpecular
    • Standard:默认的金属工作流程(Metallic Workflow)
    • StandardSpecular:高光工作流程(Specular Workflow)
  • 非基于物理的光照模型函数:Lambert和BlinnPhong

自定义光照函数(前向渲染)

1
2
3
4
//用于不依赖视角的光照模型,例如漫反射
half4 Lighting<Name> (SurfaceOutput s, half3 lightDir, half atten);
//用于依赖视角的光照模型,例如高光反射
half4 Lighting<Name> (SurfaceOutput s, half3 lightDir, half3 viewDir, half atten);

其他可选参数

1.自定义的修改函数。除了表面函数和光照模型外,表面着色器还可以支持其他两种自定义的函数

  • 顶点修改函数(vertex:VertexFunction):允许我们自定义一些顶点属性
    • 例如,将顶点颜色传递给表面函数;修改顶点位置,实现顶点动画等
  • 最后的颜色修改函数(finalcolor:ColorFunction):颜色绘制到屏幕前,最后一次修改颜色值
    • 例如,实现自定义的雾效等

2.阴影

  • addshadow:为表面着色器生成一个阴影投射的Pass(如果修改了顶点位置,照样投射正常的阴影)
  • fullforwardshadows:在前向渲染路径中支持所有光源类型的阴影
  • noshadow:禁用阴影

3.透明度混合和透明度测试

  • alpha:透明度混合
  • alphatest:透明度测试
    • 例如,alphatest:VariableName指令会使用名为VariableName的变量来剔除不满足条件的片元(此时我们还需要使用上面提到的addshadow生成正确的阴影投射Pass)

4.光照。一些指令可以控制光照对物体的影响

  • noambient:不应用任何环境光照或光照探针
  • novertexlights:不应用任何逐顶点光照
  • noforwardadd:去掉所有前向渲染中的额外Pass,即该Shader仅支持一个逐像素平行光,其他光源按照逐顶点或SH的方法计算光照影响(通常用于移动平台)
  • nolightmap:不使用光照纹理
  • nofog:不使用雾效

5.控制代码的生成。默认情况下一个表面着色器会生成前向渲染路径、延迟渲染路径使用的Pass,导致Shader文件较大。

  • exclude_path:deferred
  • exclude_path:forward
  • exclude_path:prepass

17.3 两个结构体

一个表面着色器需要使用两个结构体:表面函数的输入结构体Input,以及存储了表面属性的结构体SurfaceOutput(Unity新引入两种SurfaceOutputStandard和SurfaceOutputStandardSpecular)

数据来源:Input结构体

Input结构体包含了许多表面属性的数据来源,如果自定义了顶点修改函数,它还会是顶点修改函数的输出结构体。Input支持很多内置的变量名,通过这些变量名,我们告诉Unity需要使用的数据信息

Input结构体内置变量!!!

![image-20211104183857189](扩展 17-20.assets/image-20211104183857189.png)

采样坐标以”uv”为前缀(使用uv2表示使用第二套纹理坐标),后面紧跟纹理名称

表面属性:SurfaceOutput结构体

这个结构体里面的变量是提前就声明好的,不可以增加与减少

SurfaceOutput声明(在Lighting.cginc中):

![image-20211104184334518](扩展 17-20.assets/image-20211104184334518.png)

SurfaceOutputStandard和SurfaceOutputStandardSpecular(在UnityPBSLighting.cginc中):

image

在一个表面着色器中,只需要选择上述三者之一即可,具体取决于我们选择使用的**[光照模型](#光照模型 lightModel)**

SurfaceOutput 结构体表面属性介绍:

  • fixed3 Albedo:对光源的反射率。通常由纹理采样和颜色属性的乘积计算而得

  • fixed3 Normal:表面法线方向

  • fixed3 Emission:自发光。Unity通常会在片元着色器最后输出前,使用类似下面的语句进行简单的颜色叠加

    • c.rgb += o.Emission;

  • half Specular:高光反射中的指数部分系数。如果使用BlinnPhong光照模型,它会使用如下语句计算高光反射强度

    • float spec = pow(nh, s.Specular*128.0) * s.Gloss;

  • fixed Gloss:高光反射中的强度系数

  • fixed Alpha:透明通道。如果开启了透明度的话,会使用该值进行颜色混合

17.4 Unity背后做了什么

image image
 评论