Shader 笔记一 表面着色器 Surface Shader

您所在的位置:网站首页 猫都能学会的shader Shader 笔记一 表面着色器 Surface Shader

Shader 笔记一 表面着色器 Surface Shader

2024-07-14 07:05| 来源: 网络整理| 查看: 265

参考

猫都能学会的Unity3D Shader入门指南(一)

猫都能学会的Unity3D Shader入门指南(二)

https://docs.unity3d.com/Manual/SL-SurfaceShaders.html

 

概念 Shader和Material:

Shader(着色器)实际上就是一小段程序,它负责将输入的Mesh(网格)以指定的方式和输入的贴图或者颜色等组合作用,然后输出。绘图单元可以依据这个输出来将图像绘制到屏幕上。输入的贴图或者颜色等,加上对应的Shader,以及对Shader的特定的参数设置,将这些内容(Shader及输入参数)打包存储在一起,得到的就是一个Material(材质)。之后,我们便可以将材质赋予合适的renderer(渲染器)来进行渲染(输出)了。

所以说Shader只是一段规定好输入(颜色,贴图等)和输出(渲染器能够读懂的点和颜色的对应关系)的程序。开发者只需根据输入,进行计算变换,产生输出而已。

shader的结构:

属性定义:用来指定这段代码将有哪些输入

子着色器:代码的主体,可以有多个,每一个子着色器中包含一个或者多个的Pass。在计算着色时,平台先选择最优先可以使用的着色器,然后依次运行其中的Pass,然后得到输出的结果。

回滚:用来处理所有Subshader都不能运行的情况(比如目标设备实在太老,所有Subshader中都有其不支持的特性)

需要提前说明的是,在实际进行表面着色器的开发时,我们将直接在Subshader这个层次上写代码,系统将把我们的代码编译成若干个合适的Pass。

Shader大体上可以分为如下三类:

固定功能管线着色器(Fixed Function Shaders)

固定功能管线着色器的关键代码一般都在Pass的材质设置Material{}和纹理设置SetTexture{}部分。

表面着色器(Surface Shader)

在Unity中,表面着色器的关键代码用Cg/HLSL语言编写,然后嵌在ShaderLab的结构代码中使用。使用表面着色器,用户仅需要编写最关键的表面函数,其余周边代码将由Unity自动生成,包括适配各种光源类型、渲染实时阴影以及集成到前向/延迟渲染管线中等。

顶点片段着色器(Vertex And Fragment Shader)

顶点片段着色器运行于具有可编程渲染管线的硬件上,它包括顶点程序Vertex Programs和片段程序Fragment Programs。当在使用顶点程序或片段程序进行渲染的时候,图形硬件的固定功能管线会关闭,具体来说就是编写的顶点程序会替换掉固定管线中标准的3D变换,光照,纹理坐标生成等功能,而片段程序会替换掉SetTexture命令中的纹理混合模式。因此编写顶点片段着色器需要对3D变化,光照计算等有非常透彻的了解,需要写代码来替代D3D或者OpenGL原先在固定功能管线中要做的工作。

  Surface Shader

本文先主要讲讲简单的表面着色器,我们先来自己试试手,在Project面板中,右击Create->Shader->Standard Surface Shader,命名为SurfaceShaderDemo.shader,如下:

//shader 名称 Shader "Custom/SurfaceShaderDemo" { //属性定义 Properties { _Color ("Color", Color) = (1,1,1,1) _MainTex ("Albedo (RGB)", 2D) = "white" {} _Glossiness ("Smoothness", Range(0,1)) = 0.5 _Metallic ("Metallic", Range(0,1)) = 0.0 } //子着色器 SubShader { Tags { "RenderType"="Opaque" } LOD 200 CGPROGRAM #pragma surface surf Standard fullforwardshadows #pragma target 3.0 sampler2D _MainTex; struct Input { float2 uv_MainTex; }; half _Glossiness; half _Metallic; fixed4 _Color; UNITY_INSTANCING_CBUFFER_START(Props) UNITY_INSTANCING_CBUFFER_END void surf (Input IN, inout SurfaceOutputStandard o) { fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color; o.Albedo = c.rgb; o.Metallic = _Metallic; o.Smoothness = _Glossiness; o.Alpha = c.a; } ENDCG } //回滚 FallBack "Diffuse" }

然后我们新建一个Material,右击Create->Material,命名为MaterialDemo。在Inspector面板中,即可选择上面我们新建的Shader(Custom->SurfaceShaderDemo,对应shader的名称)

下面红框的部分就是对应Shader中的属性定义模块。接下里我们讲讲属性定义中的一些常用字段的含义

Properties

在Properties{}中定义着色器属性,在这里定义的属性将被作为输入提供给所有的子着色器。每一条属性的定义的语法是这样的:

_Name("Display Name", type) = defaultValue[{options}]

_Name - 属性的名字,简单说就是变量名,在之后整个Shader代码中将使用这个名字来获取该属性的内容Display Name - 这个字符串将显示在Unity的材质编辑器中作为Shader的使用者可读的内容type - 这个属性的类型,可能的type所表示的内容有以下几种:

Color一种颜色,由RGBA(红绿蓝和透明度)四个量来定义2D一张2的阶数大小(256,512之类)的贴图。这张贴图将在采样后被转为对应基于模型UV的每个像素的颜色,最终被显示出来Rect一个非2阶数大小的贴图Range(min, max)一个介于最小值和最大值之间的浮点数,一般用来当作调整Shader某些特性的参数(比如透明度渲染的截止值可以是从0至1的值等)Cube即Cube map texture(立方体纹理),简单说就是6张有联系的2D贴图的组合,主要用来做反射效果(比如天空盒和动态反射),也会被转换为对应点的采样Float任意一个浮点数Vector一个四维数

defaultValue - 定义了这个属性的默认值,通过输入一个符合格式的默认值来指定对应属性的初始值

Color以0~1定义的rgba颜色,比如(1,1,1,1)2D/Rect/Cube对于贴图来说,默认值可以为一个代表默认tint颜色的字符串,可以是空字符串或者”white”,”black”,”gray”,”bump”中的一个Float,Range某个指定的浮点数Vector一个4维数,写为 (x,y,z,w)

{option} - 它只对2D,Rect或者Cube贴图有关,在写输入时我们最少要在贴图之后写一对什么都不含的空白的{},当我们需要打开特定选项时可以把其写在这对花括号内。如果需要同时打开多个选项,可以使用空白分隔。可能的选择有ObjectLinear, EyeLinear, SphereMap, CubeReflect, CubeNormal中的一个,这些都是OpenGL中TexGen的模式。

Subshader

关于Tag和LOD等属性,由于知识点也不少,所以单独写在了一起,详情请见https://blog.csdn.net/wangjiangrong/article/details/89335208

Shader本体

CGPROGRAM,这是一个开始标记,表明从这里开始是一段CG程序(我们在写Unity的Shader时用的是Cg/HLSL语言)。最后一行的ENDCG与它是对应的,表明CG程序到此结束。

 

#pragma 编译指令:

// #pargma 关键词 函数名 光照模型 [其它选项] #pragma surface surfaceFunction lightModel [optionalparams]

例如上面代码中的:

//表示当前是一个 surface 着色器, 函数名是 surf(在下面能找到该函数), 使用 Standard 基于物理系统光照模式, 有一个完整的向前的阴影 (Standard 必须是Unity 5.x后才有) #pragma surface surf Standard fullforwardshadows

或者

//表示当前是一个 surfac 着色器, 函数名是 surf, 使用 Lambert 兰伯特光照模型, addshadow 表示给物体添加一个阴影 #pragma surface surf Lambert addshadow

内置的光照模型:

1. Standard 光照模型使用 SurfaceOutputStandard 作为输出结构并且匹配了Unity3D内置的Standard Shader(金属流)。 

2. StandardSpecular 光照模型使用 SurfaceOutputStandardSpecular 作为输出结构并且匹配了Unity3D内置的Standard Shader(镜面反射)。

3. Lambert 和 BlinnPhong 光照模型则不以物理为基础,使用 SurfaceOutput 作为输出结构,使用它们时可以在低端设备上运行得更快。

 

可选参数(部分):

详见:《Unity3D高级编程之进阶主程》第七章,Shader(八) - Surface

alpha

透明( Alpha)混合模式。使用它可以写出半透明的着色器(重要!!!)

alpha:blend,开启alpha混合。

alpha:fade,开启传统的渐进透明函数。

alpha:premul,开启左自乘alpha透明度。

alphatest:VariableName透明( Alpha)测试模式。使用它可以写出 镂空效果的着色器。镂空大小的变量(VariableName)是一个float型的变量vertex:VertexFunction自定义的顶点函数(vertex function)nolightmap  在这个着色器上禁用光照贴图(lightmap) (适合写一些小着色器)fullforwardshadows 在正向(forward)渲染路径中支持所有阴影类型dualforward 在正向(forward)渲染路径中使用 双重光照贴图(dual lightmaps)addshadow 添加阴影投射 & 收集通道(collector passes)。通常用自定义顶点修改,使阴影也能投射在任何程序的顶点动画上finalcolor:ColorFunction 自定义的最终颜色函数(final color function)。 请参考范例:表面着色器例子(Surface Shader Examples

#pragma target 3.0  使用model 3.0 可以得到一个更好的光照效果, 默认是2.0

 

变量:

在Properties中我们定义了不少的着色器属性,但是要在CG代码块中访问那些属性必须使用和之前变量相同的名字进行声明。如果在Properties使用2D,CG里要用sampler2D,代表使用的是2维纹理(相应的,还有sampler1D,sampler3D,samplerCube等等格式),如果在Properties使用color, CG里要用fixed4,如果在Properties使用Range, CG里要用half,实际上描述的是一个float。

例如:

//Properties _MainTex ("Albedo (RGB)", 2D) = "white" {} //SubShader sampler2D _MainTex;

 

Input,SurfaceOutputStandard:

前面提到着色器就是给定了输入,然后给出输出进行着色的代码。CG规定了声明为表面着色器的方法(就是后面的surf方法)的参数类型和名字,因此我们没有权利决定surf的输入输出参数的类型,只能按照规定写。这个规定就是第一个参数是一个Input结构,第二个参数是一个inout的SurfaceOutputStandard结构。

其中Input其实是需要我们去定义的结构,这给我们提供了一个机会,可以把所需要参与计算的数据都放到这个Input结构中,传入surf函数使用:

// 输入结构体用于模述UV的坐标。必须命名为Input struct Input { // 变量必须是uv_开头,_号后面的_MainTex自动对应Properties中的_MainTex和sampler2D // _MainTex 也是不能变的。 float2 uv_MainTex; };

这个结构体中定义了一个float2的变量,通过访问uv_MainTex即可取得这张贴图当前需要计算的点的坐标值了。

除此以外我们还可以添加如下数据:

float3 viewDir视图方向( view direction)值。为了计算视差效果(Parallax effects),边缘光照(rim lighting)等,需要包含视图方向( view direction)值float4 COLOR每个顶点(per-vertex)颜色的插值float4 screenPos 屏幕空间中的位置。 为了反射效果,需要包含屏幕空间中的位置信息float3 worldPos世界空间中的位置float3 worldRefl世界空间中的反射向量。如果表面着色器(surface shader)不写入法线(o.Normal)参数,将包含这个参数float3 worldNormal世界空间中的法线向量(normal vector)。如果表面着色器(surface shader)不写入法线(o.Normal)参数,将包含这个参数INTERNAL_DATA当输入结构包含worldRefl或worldNormal且表面函数会写入输出结构的Normal字段时需包含此声明

知识点1:float2,float和vec都可以在之后加入一个2到4的数字,来表示被打包在一起的2到4个同类型数。例如float4 num,表示变量num中包含四个float的数据。可以使用.xyzw,.rgba或它们的部分比如.x等,来获得某个值。num.xyzw,num.x。

知识点2:UV mapping的作用是将一个2D贴图上的点按照一定规则映射到3D模型上,是3D渲染中最常见的一种顶点处理手段。在CG程序中,我们有这样的约定,在一个贴图变量(在我们例子中是_MainTex)之前加上uv两个字母,就代表提取它的uv值(其实就是两个代表贴图上点的二维坐标 )。

SurfaceOutputStandard是已经定义好了里面类型的输出结构,但是一开始的时候内容暂时是空白的,我们需要向里面填写输出,这样就可以完成着色了。

//SurfaceOutput原型如下: struct SurfaceOutput{ fixed3 Albedo; // 漫反射颜色 fixed3 Normal; // 切线空间法线 fixed3 Emission; // 自发光 half Specular; // 镜面 in 0..1 range fixed Gloss; // 光泽度 fixed Alpha; // 透明度 }; //SurfaceOutputStandard原型如下: struct SurfaceOutputStandard { fixed3 Albedo; fixed3 Normal; half3 Emission; half Metallic; // 金属度;取0为非金属, 取1为金属 half Smoothness; // 光泽度;取0为非常粗糙, 取1为非常光滑 half Occlusion; // 遮挡(默认值为1) fixed Alpha; };

 

函数:

最后就是我们在#pragma中声明的函数surf了。例子中,我们根据输入的值,对金属度和光泽度进行了新的赋值,并且对_MainTex输入点的颜色乘以了Properties中输入的颜色。并将rgb赋值给了输出的像素颜色,将a值赋予透明度。

知识点:tex2d函数,这是CG程序中用来在一张贴图中对一个点进行采样的方法,返回一个float4。

最后我们在场景中创建一个Cube,然后拖上自己新建的material,选择刚刚创建的shader后,通过设置相关属性,即可看见效果了。



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3