Unity Shader 概述

Posted YF云飞

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Unity Shader 概述相关的知识,希望对你有一定的参考价值。

目录

前言

正文

什么是Shader?

Shader分为两类:

Shader编程语言

在Unity中有3种Shader(其实就是三种不同的写法):

一个Unity Shader的基本结构如下:

材质和Unity Shader的桥梁

答疑解惑

Unity Shader和CG/HLSL之间的关系

GLSL

参考文档


前言

Shader,中文名为着色器,对很多开发者来说它是⼀个神秘的存在,想学但是不知道如何去学,或者学了一段时间发现Get不到它的点,始终感觉游离在外,无法开窍。那么本文将讲下如何从零基础入门Shader,目的在于让初学Shader的程序员或者美术师可以快速进入这个丰富多彩的世界。

正文

什么是Shader?

虽然上面简单说了Shader就是着色器,但还是有必要详细说明⼀下具体什么是Shader。

Shader其实就是专门用来渲染图形的⼀种技术,通过shader,我们可以自定义显卡渲染画面的算法,使画面达到我们想要的效果。小到每⼀个像素点,大到整个屏幕,比如下面这两个游戏内比较常见的效果:

Shader分为两类:

1.顶点Shader(3D图形都是由⼀个个三角面片组成的,顶点Shader就是计算每个三角面片上的顶点,并为最终像素渲染做准备)

2.像素Shader,顾名思义,就是以像素为单位,计算光照、颜色的⼀系列算法。

几个不同的图形API都有各自的Shader语言,在DirectX中,顶点shader叫做Vertex Shader,像素Shader叫做Pixel Shader;在OpenGL中,顶点Shader也叫做Vertex Shader,但像素Shader叫做Fragment Shader,也就是我们常说的片断Shader或者片元Shader。说白了,Shader其实就是一段代码,这段代码的作用是告诉GPU具体怎样去绘制模型的每一个顶点的颜色以及最终每一个像素点的颜色。

Shader编程语言

既然Shader是一段代码,那必然要用一种语言来书写它,目前主流的有三种语言:

1.基于OpenGL的OpenGL Shading Language,简称 GLSL。

2.基于DirectX的High Level Shading Language,简称 HLSL。

3. 还有NVIDIA公司的C for Graphic,简称 Cg语言。

GLSL与HLSL分别是基于OpenGL和Direct3D的接口,两者不能混用。而 Cg语言是用于图形的C语言,这其实说明了当时设计人员的一个初衷:

让基于图形硬件的编程变得和C语言编程一样方便、自由,让基于图形硬件的编程变得和C语言编程一样方便、自由。(正如 C++ 和 Java 的语法是基于C的,Cg语言本身也是基于C语言的。如果您使用过C、C++、Java其中任意一个,那么Cg的语法也是比较容易掌握的)

Cg语言极力保留了C语言的大部分语义,力图让开发人员从硬件细节中解脱出来,Cg同时拥有高级语言的好处,如代码的易重用性,可读性高等。Cg语言是Microsoft和NVIDIA相互协作在标准硬件光照语言的语法和语义上达成了一致,所以,HLSL和Cg其实是同一种语言。

(有关基础语法可以先移步至unity shader 之基础五 基本语法_TO_ZRG的博客-CSDN博客

Unity Shader严格来说并不是传统上的Shader,而是Unity自身封装后的一种便于书写的Shader,又称为Shader Lab。

在Unity中有3种Shader(其实就是三种不同的写法):

Surface Shaders 表面着色器

Vertex Fragment Shaders 顶点/片断着色器

Fixed Function Shaders 固定管线着色器

其中Fixed Function Shaders已经被淘汰,完全没有学习的必要了。

Surface Shader其实就是Unity对Vertex Fragment Shader的又一层包装,以使Shader的制作方式更符合人类的思维模式,同时可以以极少的代码来完成不同的光照模型与不同平台下需要考虑的事情。

一个Unity Shader的基本结构如下:

Shader "ShaderName"

    Properties //属性

    SubShader //显卡A使用的子着色器 

    SubShader //显卡B使用的子着色器

而Unity会在背后根据使用的平台来把这些结构编译成真正的代码和Shader文件。 

材质和Unity Shader的桥梁

 Properties语义块中包含了一系列属性(Property),这些属性将会出现在材质面板中。

        Properties Name "Display name", PropertyType  = DefaultValue

        其中Name是Shader中调用需要的属性,display name是出现在材质面板山的名字,PropertyType则是属性的类型。

答疑解惑

Unity Shader和CG/HLSL之间的关系

        Unity Shader是用Shader Lab语言编写的,但对于表面着色器和顶点/片元着色器,我们可以在ShaderLab内部嵌套CG/HLSL语言来编写着色器代码。

        通常CG代码块是位于Pass语义块内部的,如下所示:

GLSL

Unity Shader同样支持用GLSL进行编写,但是编写的产品则只能上架支持OpenGL的平台。

参考文档

NVIDIA提供的CG文档

NVIDIA提供的系列教程

Unity官方手册提供的Shader教学文档

Unity Shader入门精要学习笔记 - 第3章 Unity Shader 基础

来源作者:candycat   http://blog.csdn.net/candycat1992/article/

概述

总体来说,在Unity中我们需要配合使用材质和Unity Shader才能达到需要的效果。一个最常见的流程是。

1)创建一个材质

2)创建一个Unity Shader,并把它赋给上一步创建的材质

3)把材质赋给要渲染的对象

4)在材质面板中调整Unity Shader的属性,以得到满意的效果

下图显示了Unity Shader和材质是如何一起工作来控制物体的渲染的。

技术分享

Unity中的材质需要结合一个GameObject的Mesh 或者Particle Systems 组件来工作。它决定了我们的游戏对象看起来是什么样子的(这当然也需要Unity Shader的配合)。

 

Unity Shader 的基础:ShaderLab

技术分享

Unity Shader 为控制渲染过程提供了一层抽象。如果没有使用Unity Shader(上左),开发者需要很多文件设置打交道,才能让画面呈现想要的效果,而在Unity Shader的帮助下(上右),开发者只需要使用ShaderLab来编写Unity Shader文件就可以完成所有的工作。

在Unity中,所有的Unity Shader 都是使用ShaderLab来编写的。ShaderLab是Unity提供的编写Unity Shader 的一种说明性语言。它使用了一些嵌套在花括号内部的语义来描述一个Unity Shader文件的结构。这些结构包含了许多渲染所需的数据,例如Properties 语句块中定义了着色器所需的各种属性,这些属性将会出现在材质面板中。从设计上来说,ShaderLab类似于CgFx的Direct3D Effects (.Fx)语言,它们都定义了要显示一个材质所需要的所有东西,而不仅仅是着色器代码。

一个Unity Shader 的基础结构如下所示

技术分享

Unity 会在背后根据使用的平台来把这些结构编译成真正的代码和Shader 文件,而开发者只需要和Unity Shader 打交道即可。

 

Unity Shader 的结构

每个Unity Shader文件的第一行都需要通过Shader语义来指定该Unity Shader的名字。这个名字由一个字符串来定义,例如"MyShader"。当为材质选择使用的Unity Shader 时,这些名称就会出现在材质面板的下拉面板里。通过在字符串中添加斜杠(“/”),可以控制Unity Shader在材质面板中出现的位置。

 

Properties语义块中包含了一系列属性,这些属性将会出现在材质面板中。

Properties语义块通常定义如下:

 


 
Properties  
{  
    Name ("display name",PropertyType) = DefaultValue  
}  

 


开发者们声明这些属性是为了在材质面板中能够方便地调整各种材质属性。如果我们需要再Shader 中访问它们,就需要使用每个属性的名字。在Unity中,这些属性的名字通常由一个下划线开始。显示的名称则是出现在材质面板上的名字。我们需要为每个属性指定它的类型,常见的属性类型如下表。除此之外,我们还需要为每个属性指定一个默认值,在我们第一次把该Unity Shader赋给某个材质,材质面板上显示的就是这些默认值。

 

技术分享

 

每个Unity Shader 文件可以包含多个SubShader语义块,但最少要有一个,当Unity需要加载这个Unity Shader时,Unity 会扫描所有的SubShader 语义块,然后选择第一个能够在目标平台上运行的SubShader。如果都不支持的话,Unity就会使用Fallback语义指定的Unity Shader。

Unity提供这种语义的原因在于,不同的显卡具有不同的能力。

SubShader语义块中包含的定义通常如下。

技术分享

SubShader中定义了一系列Pass以及可选的状态和标签设置。每个Pass定义了一次完整的渲染流程,但如果Pass的数目过多,往往会造成渲染性能的下降。因此,我们应尽量使用最小数目的Pass。状态和标签同样可以在Pass声明。不同的是,SubShader中的一些标签设置是特定的。也就是说,这些标签设置和Pass中使用的标签是不一样的。而对于状态设置来说,其使用的语法是相同的。但是,如果我们在SubShader进行了这些而设置,那么将会用于所有的Pass。

ShaderLab提供了一系列渲染状态的设置指令,这些指令可以设置显卡的各种状态,下表给出了ShaderLab中常见的渲染状态设置选项。

技术分享

当在SubShader块中设置了上述渲染状态时,将会应用到所有的Pass。如果我们不想这样(例如在双面渲染中,我们希望在第一个Pass中剔除正面来对背面进行渲染,在第二个Pass中剔除背面来对正面进行渲染),可以在Pass语义块中单独进行上面的设置。

SubShader的标签是一个键值对,它的键和值都是字符串类型。这些键值对是SubShader和渲染引擎之间的沟通桥梁。它们用来告诉Unity的引擎:SubShader我希望怎样以及何时渲染这个对象。支持的标签类型如下:

技术分享

需要注意的是,上述标签仅可以在SubShader中声明,而不可再Pass块中声明。Pass块虽然也可以定义标签,但这些标签是不同于SubShader的标签类型。

 

Pass语义块的定义如下:

技术分享

首先,我们可以在Pass中定义该Pass的名称,例如:

 

Name "MyPassName"  

 

通过这个名称,我们可以使用ShaderLab的UsePass命令来直接使用其他Unity Shader 中的Pass,例如:

 

UsePass "MyShader/MYPASSNAME"  

 

这样可以提高代码的复用性。需要注意的是,由于Unity内部会把所有Pass的名称转换成大写字母的表示,因此,在使用UsePass命令时必须使用大写形式的名字。

 

其次,我们可以对Pass设置渲染状态。SubShader的状态设置通用适用于Pass。除了上面提到的状态设置外,在Pass中我们还可以使用固定渲染管线的着色器命令。

Pass同样可是设置标签,但它的标签不同于SubShader的标签。这些标签页是用于告诉渲染引擎我们希望怎样来渲染该物体。下表给出了Pass中使用的标签类型。

技术分享

除了上面普通的Pass定义外,Unity Shader 还支持一些特殊的Pass,以便进行代码复用或实现更复杂的效果。

 

紧跟着在各个SubShader语义块后面的,可以是一个Fallback指令。它用于告诉Unity,如果上面所有的SubShader在这块显卡上都不能允许,那就使用这个最低级的吧。

它的语义如下:

Fallback "name"  
//或者  
Fallback off  

 

如上所述,我们可以通过一个字符串来告诉Unity这个最低级的Shader是谁,我们也可以关闭Fallback 功能。

 

事实上,Fallback 还会影响阴影的投射。在渲染阴影纹理时,Unity会在每个Unity Shader中寻找一个阴影投射的Pass。通常情况下,我们不需要自己专门实现一个Pass,这是因为Fallback使用的内置Shader包含了这样一个通用的Pass。因此,为每个Unity Shader正确设置Fallback是非常重要的。

 

Unity Shader 的形式

尽管Unity Shader可以做的事情非常多,但其最重要的任务还是指定各种着色器所需的代码。这些着色器代码可以写在SubShader语义块中(表面着色器的做法),也可以写在Pass语义块中(顶点/片元着色器和固定函数着色器的做法)。

在Unity中,我们可以使用下面3种形式来编写Unity Shader。而不管使用哪种形式,真正意义上的Shader代码都需要包含在ShaderLab 语义块中,如下所示:

 

Shader "MyShader"  
{  
    Properties  
    {  
        //所需的各种属性  
    }  
    SubShader  
    {  
        //真正意义上的Shader代码会出现在这里  
        //表面着色器或者顶点/片元着色器或者固定函数着色器  
    }  

 

表面着色器是Unity自己创造的一种着色器代码类型。它需要的代码量很少,Unity在背后做了很多工作,但渲染的代价比较大。它在本质上和下面要讲到的顶点/片元着色器是一样的。也就是说,当给Unity提供一个表面着色器的时候,它在背后仍旧把它转换成对应的顶点/片元着色器。我们可以理解成,表面着色器是Unity对顶点/片元着色器的更高一层的抽象。它存在的价值在于,Unity为我们处理了很多光照细节,使得我们不需要操心这些烦人的事情。

 

一个非常简单表面着色器实例代码如下:

 

Shader "Custom/Simple Surface Shader"  
{  
    SubShader  
    {  
        Tags { "RenderType" = "Opaque" }  
        CGPROGRAM  
        #pragma surface surf Lambert  
        struct Input  
        {  
            float4 color : COLOR;  
        };  
        void surf(INPUT IN,inout SurfaceOutput o)  
        {  
            o.Albedo = 1;  
        }  
        ENDCG  
    }  
    Fallback "Diffuse"  
  
}  

 


从上述程序中可以看出,表面着色器被定义在SubShader语义块中的CGPROGRAM 和 ENDCG之间。原因是,表面着色器不需要开发者关心使用多少个Pass、每个Pass如何渲染等问题,Unity会在背后为我们做好这些事情。我们要做的只是告诉它:“使用这些纹理去填充颜色,使用这个法线纹理去填充法线,使用Lambert光照模型。”

 

CGPROGRAM 和 ENDCG之间的代码是使用CG/HLSL 编写的,也就是说,我们需要把CG/HLSL语言嵌套在ShaderLab语言中。值得注意的是,这里的CG/HLSL是Unity经封装后提供的,它的语法和标准的CG/HLSL语法几乎一样,但还是由细微不同的,例如有些原声的函数和用法Unity并没有提供支持。

在Unity中我们可以使用CG/HLSL 语言来编写顶点/片元着色器。它们更加复杂,但灵活性也更高。

一个非常简单的顶点/片元着色器示例代码如下:

 

Shader "Custom/Simple VertextFrament Shader"  
{  
    SubShader  
    {  
        Pass  
        {  
            CGPROGRAM  
            #pragma vertext vert  
            #pragma frament frag  
  
            float4 vert(float4 v : POSITION) : SV_POSITION  
            {  
                return mul (UNITY_MAX_MVP,v);  
            }  
  
            float4 frag() : SV_Target  
            {  
                return fixed(1.0,1.0,1.0,1.0);  
            }  
  
            ENDCG  
        }  
    }  
}  

 


和表面着色器类似,顶点/片元着色器的代码也需要定义在CGPROGRAM 和 ENDCG之间,但不同的是,顶点/片元着色器是写在Pass语义块内,而非SubShader内的。原因是,我们需要自己定义每个Pass需要使用的Shader代码。虽然我们可能需要编写更多的代码,但带来的好处是灵活性很高。更重要的是,我们可以控制渲染的实现细节。同样,这里的CGPROGRAM和ENDCG之间的代码也是使用CG/HLSL编写的。

 

上述两种Unity Shader 形式都使用了可编程管线。而对于一些较旧的设备,它们不支持可编程管线着色器,因此,这时候我们就需要使用固定函数着色器来完成渲染。这些着色器往往只可以完成一些非常简单的效果。

一个非常简单的固定函数着色器示例代码如下:

 

Shader "Tutorial/Basic"  
{  
    Properties{  
        _Color ( "Main Color" , Color) = (1,0.5,0.5,1)  
    }  
    SubShader{  
        Pass{  
            Material{  
                Diffuse[_Color]  
            }  
            Lightinh On  
        }  
    }  
}  

 

可以看出,固定函数着色器的代码被定义在Pass语义块中,这些代码相当于Pass中的一些渲染设置。

 

对于固定函数着色器来说,我们需要完全使用ShaderLab的语法来编写,而非使用CG/HLSL。
由于现在绝大多数GPU都支持可编程的渲染管线,这种固定管线 编程方式已经逐渐被抛弃。实际上,在Unity5.2中,所有固定函数着色器都会在背后Unity被编译成对应的顶点/片元着色器,因此真正意义上的固定函数着色器已经不存在了。

 

如何选择哪种Unity Shader:

除非有非常明确的需求必须要使用固定函数着色器,例如需要在非常旧的设备上运行游戏,否则不要使用固定函数着色器。

如果想和各种光源打交道,你可能更喜欢使用表面着色器,但需要小心它在移动平台的性能表现。

如果光照数量少,例如只有一个平行光,那么使用顶点/片元着色器是一个更好的选择。

更重要的是,如果有很多自定义的渲染效果,那么顶点/片元着色器更好。








以上是关于Unity Shader 概述的主要内容,如果未能解决你的问题,请参考以下文章

「游戏引擎 浅入浅出」4.1 Unity Shader和OpenGL Shader

Unity3D引擎之Shader Forge应用

unity学习啥最重要?

unity shader有啥用

unity-shader-游戏渲染效果逆向分析

unity-shader-游戏渲染效果逆向分析