U3D_Shader编程(第二篇:基础夯实篇)

U3D_Shader编程》

##U3D_Shader编程》发布说明:程序员

++++Shader一个高大上的领域,无论怎么样,我来了。算法

++++立钻哥哥从2018年开始正式对Shader进行战略布局。数据库

++++U3D_Shader编程》将从零开始,按部就班探索,仍是先探索一段时间吧,后期根据须要再推出一个精品博文,这篇就算一个杂谈吧。编程

##U3D_Shader编程》目录:设计模式

#第一篇:快速入门篇数组

#第二篇:基础夯实篇缓存

#第三篇:基础进阶篇网络

#第四篇:中级挑战篇数据结构

#第五篇:高级成魔篇app



#第二篇:基础夯实篇

#第二篇:基础夯实篇

++++通过“第一篇:快速入门篇”,咱们了解了Shader的基本结构,可是还不够,经过这一篇的熟悉,咱们将夯实Shader基本结构和相关原理。在这里立钻哥哥已经总结梳理了5个阶段(基础、初级、中级、高级、扩展等)对Shader进行较为全面的剖析,也为后续Shader进阶、Shader成魔夯实基础。

++++经过这一篇的学习,了解Unity中的一些渲染机制以及如何使用UnityShader实现各类自定义的渲染效果。(实际上,Shader仅是整个渲染流程的一个子部分,所以,任何脱离渲染流程的对Shader的介绍都会让咱们更加困惑。)(向量运算、矩阵变换等数学知识在Shader编写中无处不在。数学知识在Shader中也很是重要的。)

++++Shader能够:渲染游戏模型、模拟波动的海面、实现各类屏幕特效等。(让Shader和其余游戏开发元素(例如,模型、纹理、脚本等)相配合,实现游戏中常见的渲染效果。)

++++Shader其实比咱们想象的要强大得多。(这个须要咱们共同努力去发掘。)

++++学好Shader须要:1、有必定(或少许)的编程经验;2、对Unity引擎的操做界面比较熟悉;3、保持必定的耐心;4、有必定的数学基础。

++++想要完全理解Shader,必须了解整个渲染流水线的工做方式。(要知道为何这么写,它们是怎么执行的等一系列基础问题。)

++++有必定的数学基础,包括了解基本的代数运算(如结合律、交换律等)、三角运算(如正弦、余弦计算等),要具备大学水平的线性代数、微积分等数学知识。

##1章:Shader基础篇

##2章:Shader初级篇

##3章:Shader中级篇

##4章:Shader高级篇

##5章:Shader扩展篇



##1章:Shader基础篇

++1章:Shader基础篇

++++1.1、了解Shader

++++1.2、渲染流水线

++++1.3UnityShader基础

++++1.4Shader中的数学基础



###1.1、了解Shader

++1.1、了解Shader

++++程序员的三大浪漫:编译原理、操做系统和图形学。

++++学习Shader比学习C#这样的编程语言更加困难,由于Shader须要牵扯到整个渲染流程。(Shader更多地是面向GPU的工做方式。)(创建一个渲染流程的总体体系,是跨越Shader学习中层层障碍的重要因素。)

++++Unity在原有的渲染流程中进行了封装,并提供给开发者新的图像编程接口:UnityShader

++++了解Shader中的基础的光照模型、纹理和透明效果等初级渲染效果。(了解Shader中实现基本的光照模型,如漫反射、高光反射等。)(纹理的使用给渲染的世界带来了更多的变化,)(透明是游戏中经常使用的渲染效果。)



###1.2、渲染流水线

++1.2、渲染流水线

++++不了解渲染流水线的工做流程,就永远没法说本身对Shader已经入门。

++++渲染流水线的最终目的在于生成或者说是渲染一张二维纹理,即咱们在屏幕上看到的全部效果。(它的输入是一个虚拟摄像机、一些光源、一些Shader以及纹理等。)

++++Shader仅仅是渲染流水线中的一个环节,想要让咱们的Shader发挥出它的做用,就须要知道Shader在渲染流水线中扮演的角色。

++++渲染流程通常分为3个阶段:应用阶段(Application Stage)、几何阶段(Geometry Stage)、光栅化阶段(Rasterizer Stage)。


++1.2.1、渲染流程

++++渲染流程通常分为3个阶段:应用阶段(Application Stage)、几何阶段(Geometry Stage)、光栅化阶段(Rasterizer Stage)。

 

++++应用阶段(Application Stage):此阶段由应用主导,一般由CPU负责实现(开发者具备这个阶段的绝对控制权)。开发者有如下主要任务:

--首先,须要准备好场景数据,例如摄像机的位置、视锥体、场景中包含了哪些模型、使用了哪些光源等等;

--其次,为了提升渲染性能,须要作一个粗粒度剔除(culling)工做,以把那些不可见的物体剔除出去,这样就不须要移交给几何阶段进行处理;

--最后,须要设置好每一个模型的渲染状态。(这些渲染状态包括但不限于它使用的材质(漫反射颜色、高光反射颜色)、使用的纹理、使用的Shader等。)

--应用阶段最重要的输出是:渲染所需的几何信息,即渲染图元(rendering primitives)。(渲染图元能够是点、线、三角面等。)(这些渲染图元将会被传递给下一阶段:几何阶段)

++++几何阶段(Geometry Stage):用于处理全部和咱们要绘制的几何相关的事情。(例如,决定须要绘制的图元是什么,怎样绘制它们,在哪里绘制它们。)(这一阶段一般在GPU上进行。)(几何阶段负责和每一个渲染图元打交道,进行逐顶点、逐多边形的操做。)(几何阶段能够进一步分红更小的流水线阶段。)

--几何阶段的一个重要任务是把顶点坐标变换到屏幕空间中,再交给光栅器进行处理。

--经过对输入的渲染图元进行多不处理后,这一阶段将会输出屏幕空间的二维顶点坐标、每一个顶点对应的深度值、着色等相关信息,并传递给下一个阶段(光栅化阶段)。

++++光栅化阶段(Rasterizer Stage):使用上一阶段(几何阶段)传递的数据来产生屏幕上的像素,并渲染出最终的图像。(光栅化阶段是在GPU上运行。)

--光栅化的任务主要是决定每一个渲染图元中的哪些像素应该被绘制在屏幕上。

--光栅化须要对上一阶段(几何阶段)获得的逐顶点数据(例如纹理坐标、顶点颜色等)进行插值,而后再进行逐像素处理。

--光栅化阶段也能够分红更小的流水线阶段。

++1.2.2CPUGPU之间的通讯

++++渲染流水线的起点是CPU,即应用阶段。应用阶段大体可分为如下阶段:

--1、把数据加载到显存中;

--2、设置渲染状态;

--3、调用Draw Call

++++1、把数据加载到显存中】:全部渲染所需的数据都须要从硬盘(Hard Disk Drive, HDD)中加载到系统内存(Random Access Memory, RAM)中。而后,网格和纹理等数据又被加载到显卡上的存储空间显存(Video Random Access MemoryVRAM)中。这是由于,显卡对于显存的访问速度更快,并且大多数显卡对于RAM没有直接的访问权限。(真实渲染中须要加载到显卡中的数据比较复杂,好比顶点的位置信息、法线方向、顶点颜色、纹理坐标等。)

++++2、设置渲染状态】:渲染状态定义了场景中的网格是怎样被渲染的。(使用哪一个顶点着色器(Vertex Shader/片元着色器(Fragment Shader)、光源属性、材质等。)(若是咱们没有更改渲染状态,那么全部的网格都将使用同一种渲染状态。)

--渲染状态:不开启混合、使用这张纹理、使用这个顶点着色器、使用这个片元着色器等。

++++3、调用Draw Call】:Draw Call就是一个命令,它的发起方是CPU,接收方是GPU。(这个命令仅仅会指向一个须要被渲染的图元(primitives)列表,而不会再包含任何材质信息。)(在给定一个Draw Call时,GPU就会根据渲染状态(例如材质、纹理、着色器等)和全部输入的顶点数据来进行计算,最终输出成屏幕上显示的那些漂亮的像素。这个过程,就是GPU流水线。)

 

 

++1.2.3GPU流水线

++++GPUCPU那里获得渲染命令后,就会进行一系列流水线操做,最终把图元渲染到屏幕上。(GPU渲染的过程就是GPU流水线。)(几何阶段和光栅化阶段,开发者没法拥有绝对的控制权,其实现的载体是GPU。但GPU向开发者开放了不少控制权。)(GPU经过实现流水线化,大大加快了渲染速度。)

++++几何阶段和光栅化阶段能够分红若干更小的流水线阶段,这些流水线阶段由GPU来实现,每一个阶段GPU提供了不一样的可配置性或可编程性。

++++GPU渲染流水线实现:

 

--颜色不一样表示了不一样阶段的可配置性或可编程性。

--【绿色】:表示该流水线阶段是彻底可编程控制的;(顶点着色器、曲面细分着色器、几何着色器、片元着色器等)

--【黄色】:表示该流水线阶段能够配置但不是可编程的;(剪裁、逐片元操做等)

--【蓝色】:表示该流水线阶段是由GPU固定实现的,开发者没有任何控制权。(顶点数据、屏幕映射、三角形设置、三角形遍历、屏幕图像等)

--【实线】:表示该Shader必须由开发者编程实现。(顶点数据、顶点着色器、剪裁、屏幕映射、三角形设置、三角形遍历、逐片元操做、屏幕图像等)

--【虚线】:表示该Shader是可选的。(曲面细分着色器、几何着色器、片元着色器等)

--几何阶段:顶点着色器、曲面细分着色器、几何着色器、裁剪、屏幕映射等。

--光栅化阶段:三角形设置、三角形遍历、片元着色器、逐片元操做、屏幕图像等。

++1.2.4、顶点着色器(Vertex Shader

++++顶点着色器(Vertex Shader是流水线的第一个阶段。(它的输入来自于CPU。)

++++顶点着色器的处理单元是顶点。(输入进来的每一个顶点都会调用一次顶点着色器。)(顶点着色器自己不能够建立后者销毁任何顶点,并且没法获得顶点与顶点之间的关系。)(此阶段的处理速度会很快。)

++++顶点着色器须要完成的工做主要有:坐标变换、逐顶点光照和输出后续阶段所需的数据。

++++GPU在每一个输入的网格顶点上都会调用顶点着色器。顶点着色器必须进行顶点的坐标变换,须要时还能够计算和输出顶点的颜色。(可能须要进行逐顶点的光照。)

++++坐标变换,就是对顶点的坐标(即位置)进行某种变换。(顶点着色器能够在这一步中改变顶点的位置,这在顶点动画中是很是有用的。例如,咱们能够经过改变顶点位置来模拟水面、布料等。)(不管咱们在顶点着色器中怎样改变顶点的位置,一个最基本的顶点着色器必须完成一个工做:把顶点坐标从模型空间转换到齐次裁剪空间。)

++++顶点着色器会将模型顶点的位置变换到齐次裁剪坐标空间下,进行输出后再由硬件作透视除法获得NDC下的坐标。(NDCNormalized Device Coordinates,归一化的设备坐标。)

++1.2.5、裁剪(Clipping

++++咱们的游戏场景可能会很大,而摄像机的视野范围不会覆盖全部的场景物体,那些不在摄像机视野范围的物体须要被裁剪(Clipping

++++一个图元和摄像机视野的关系有3种:彻底在视野内、部分在视野内、彻底在视野外。

--彻底在视野内的图元就继续传递给下一个流水线阶段。

--彻底在视野外的图元不会继续向下传递,由于他们不须要被渲染。

--部分在视野内的图元须要进行一个处理,就是裁剪。(在视野外的顶点应该使用一个新的顶点来代替,这个新的顶点位于视野边界的交点处。)

++1.2.6、屏幕映射(Screen Mapping

++++屏幕映射(Screen Mapping的任务是把每一个图元的xy坐标转换到屏幕坐标系(Screen Coordinates)下。(屏幕坐标系是一个二维坐标系,它和咱们用于显示画面的分辨率有很大关系。)

++++屏幕映射获得的屏幕坐标决定了这个顶点对应屏幕上哪些像素以及距离这个像素有多远。

++++注意1OpenGL把屏幕左下角做为(0,0)原点,而DirectX定义左上角为(0,0)原点。(若是发现获得的图像是倒转的,颇有多是屏幕坐标系的差别问题。)

++1.2.7、三角形设置(Triangle Setup

++++光栅化的第一个流水线阶段是三角形设置(Triangle Setup。(这个阶段会计算光栅化一个三角网络所需的信息。)(光栅化阶段有两个重要的目标:计算每一个图元覆盖了哪些像素,以及为这些像素计算它们的颜色。)(上一阶段输出的信息是屏幕坐标系下的顶点位置以及和它们相关的额外信息,如深度值(z坐标)、法线方向、视角方向等。)

++1.2.8、三角形遍历(Triangle Traversal

++++三角形遍历(Triangle Traversal阶段将会检查每一个像素是否被一个三角网格所覆盖。(若是被覆盖的话,就会生成一个片元(fragment)。)(这样一个找到哪些像素被三角网格覆盖的过程就是三角形遍历,这个阶段也被称为扫描变换(Scan Conversion))。

++++三角形遍历的输出就是获得一个片元序列。(一个片元并非真正意义上的像素,而是包含了不少状态的集合,这些状态用于计算每一个像素的最终颜色。这些状态包括了它的屏幕坐标、深度信息,以及其余从几何阶段输出的顶点信息,如法线、纹理坐标等。)

++1.2.9、片元着色器(Fragment Shader

++++片元着色器(Fragment Shader:一个很是重要的可编程着色器阶段。(在DirectX中,片元着色器被称为像素着色器(Pixel Shader),但片元着色器更合适些,由于此时的片元并非一个真正意义上的像素。)

++++片元着色器的输入是上一阶段对顶点信息插值获得的结果,输出是一个或多个颜色值。

++++片元着色器阶段能够完成不少重要的渲染技术,其中最重要的技术之一就是纹理采样。(为了在片元着色器中进行纹理采样,咱们一般会在顶点着色器阶段输出每一个顶点对应的纹理坐标,而后经过光栅化阶段对三角网格的3个顶点对应的纹理坐标进行插值后,就能够获得其覆盖的片元的纹理坐标了。)


++1.2.10、逐片元操做(Per-Fragment Operations

++++渲染流水线的最后一步:逐片元操做(Per-Fragment Operations,对每个片元进行一些操做。(在DirectX中,这一阶段被称为输出合并阶段(Output-Merger))

++++逐片元操做阶段有几个主要任务:

--1)决定每一个片元的可见性。(这涉及到不少测试工做,例如深度测试、模板测试等。)

--2)若是一个片元经过了全部的测试,就须要把这个片元的颜色值和已经存储在颜色缓冲区中的颜色进行合并,或者说是混合(Blend)。

++++【片元】=>【模板测试】=>【深度测试】=>【混合】=>【颜色缓冲区】。

++++逐片元操做阶段是高度可配置性的,咱们能够设置每一步的操做细节。(混合操做也是能够高度配置的,开发者能够选择开启/关闭混合功能。)

++1.2.11Unity Shader

++++渲染流水线比较复杂,Unity为咱们封装了不少功能。(在Unity Shader设置一些输入、编写顶点着色器和片元着色器、设置一些状态就能够达到大部分常见的屏幕效果。)

++++Unity Shader缺点:封装性会致使编程自由度降低,没法掌握其背后的原理,在出现问题时,没法找到错误缘由。

++++Shader就是:

--GPU流水线上一些可高度编程的阶段,而由着色器编译出来的最终代码是会在GPU上运行的(对于固定管线的渲染来讲,着色器有时等同于一些特定的渲染设置);

--有一些特定类型的着色器,如顶点着色器、片元着色器等;

--依靠着色器咱们能够控制流水线中的渲染细节,例如用顶点着色器来进行顶点变换以及传递数据,用片元着色器来进行逐像素的渲染。

++++要获得出色的游戏画面是须要包括Shader在内的全部渲染流水线阶段的共同参与才可完成:设置适当的渲染状态,使用合适的混合函数,开启仍是关闭深度测试/深度写入等。

++++Unity提供了一个既能够方便地编写着色器,同时又可设置渲染状态的地方:Unity Shader

++1.2.12OpenGL/DirectX

++++OpenGLDirectX是图像应用编程接口,这些接口用于渲染二维或三维图形。这些接口架起了上层应用程序和底层GPU的沟通桥梁。(一个应用程序向这些接口发送渲染命令,而这些接口会依次向显卡驱动(Graphics Driver)发送渲染指令,这些显卡驱动是真正知道如何和GPU通讯的角色,正是它们把OpenGL或者DirectX的函数调用翻译成了GPU可以听懂的语言,同时它们也负责把纹理等数据转换成GPU所支持的格式。)(显卡驱动就是显卡的操做系统。)

++1.2.13HLSLGLSLCG

++++HLSLGLSLCG是着色器语言。

--HLSLHigh Level Shading Language):DirectX着色语言。(微软控制着色器的编译。)

--GLSLOpenGL Shading Language):OpenGL着色语言。(GLSL是依赖硬件,而非操做系统层级的。)

--CGC for Graphic):NVIDIA着色语言。(CG是真正意义上的跨平台,会根据不一样的平台,编译成相应的中间语言。)(CG语言可无缝移植成HLSL代码,但没法彻底发挥出OpenGL的最新特性。)

++1.2.14Draw Call

++++Draw Call就是CPU调用图像编程接口,以命令GPU进行渲染的操做。

++++Draw Call多了会影响帧率:在每次调用Draw Call以前,CPU须要向GPU发送不少内容,包括数据、状态和命令的等。(若是Draw Call是数量太多,CPU就会把大量时间花费在提交Draw Call上,形成CPU的过载。)

++++如何减小Draw Call:使用批处理(Batching的方法。(把不少小的Draw Call合并成一个大的Draw Call,这就是批处理的思想。)(批处理技术更加适合于静态的物体,例如不会移动的大地、石头等,对于这些静态物体咱们只须要合并一次便可。)

++++利用批处理,CPURAM把多个网格合并成一个更大的网格,再发送给GPU,而后在一个Draw Call中渲染它们。(使用批处理合并的网格将会使用同一种渲染状态,若是网格之间须要使用不一样的渲染状态,那么就没法使用批处理技术。)

++++在游戏开发过程当中,为了减小Draw Call的开销,注意点:

--注意1:避免使用大量很小的网格。(当不可避免地须要使用很小的网格结构时,考虑是否能够合并它们。)

--注意2:避免使用过多的材质。(尽可能在不一样的网格之间共用同一个材质。)

++1.2.15、什么是固定管线渲染

++++固定函数的流水线(Fixed-Function Pipeline,也简称固定管线,一般是指在较旧的GPU上实现的渲染流水线。(这种流水线只给开发者提供一些配置操做,但开发者没有对流水线阶段的彻底控制权。)

++++固定管线一般提供了一系列接口,这些接口包含了一个函数入口点(Function Entry Points)集合,这些函数入口点会匹配GPU上的一个特定的逻辑功能。(固定渲染管线是只可配置的管线。)

++++第一个支持可编程管线的版本:OpenGL2.0OpenGL ES2.0DirectX8.0等。

++++随着GPU的发展,固定管线已经逐渐退出历史舞台。

++1.2.16、立钻哥哥推荐的一个Shader示例

//立钻哥哥:一个简单的Shader示例

Shader YanlzShaderDemo/SurfaceShade03{

    Properties{

        _MainTex(Base(RGB), 2D) = white{ }

    }

 

    SubShader{

        Tags{ RenderType=Opaque }

        LOD 200

    

        CGPROGRAM

        #pragma surface surf Lambert addshadow

        #pragma shader_feature REDIFY_ON

 

        sampler2D _MainTex;

    

        struct Input{

            float2 uv_MainTex;

        };

 

        void surf(Input IN, inout SurfaceOutput o){

             half4 c = tex2D(_MainTex, IN.uv_MainTex);

             o.Albedo = c.rgb;

            o.Alpha = c.a;

 

            #if REDIFY_ON

            o.Albedo.gb *= 0.5;

            #endif

        }

 

        ENDCG

    }

 

    CustomEditor YanlzCustomShaderGUI

}



###1.3UnityShader基础

++1.3UnityShader基础

++++Shader就是渲染流水线中的某些特定阶段,如顶点着色器阶段、片元着色器阶段等。

++++UnityShader提供了更加轻松地管理着色器代码以及渲染设置(如开启/关闭混合、深度测试、设置渲染顺序等)。

++1.3.1、材质和Unity Shader

++++Unity中咱们须要配合使用材质(Material)和Unity Shader才能达到须要的效果。常见的流程是:

--1)建立一个材质;

--2)建立一个Unity Shader,并不它赋给建立的材质;

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

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

++++Unity Shader定义了渲染所需的各类代码(如顶点着色器和片元着色器)、属性(如使用哪些纹理等)和指令(渲染和标签设置等),而材质则容许咱们调节这些属性,并将其最终赋给相应的模型。

++1.3.2Unity中的材质

++++Unity中的材质须要结合一个GameObjectMesh或者Particle Systems组件来工做。(材质决定了咱们游戏对象看起来是什么样子的。(这固然也须要Unity Shader的配合。))

++++建立材质:【Create=>Material】。(当建立一个材质后,就能够把它赋给一个对象。)

++++Unity5.x后续版本,默认一个新建的材质将使用Unity内置的Standard Shader。(这是一种基于物理渲染的着色器。)

++1.3.3Unity中的Shader

++++Unity Shader和渲染管线的Shader是不一样的。

++++新建Unity Shader:【Create=>Shader】。

++++Unity Shader类型:“Standard Surface Shader”、“Unlit Shader”、“Image Effect Shader”、“Compute Shader”等。

--Standard Surface Shader】:会产生一个包含了标准光照模型(使用了Unity5中新添加的基于物理的渲染方法)的表面着色器模板。(我咱们提供了典型的表面着色器的实现方法。)

--Unlit Shader】:会产生一个不包含光照(但包含雾效)的基本的顶点/片元着色器。(使用Unlit Shader来生成一个基本的顶点/片元着色器模板。)

--Image Effect Shader】:为咱们实现各类屏幕后处理效果提供一个基本模板。

--Compute Shader】:会产生一种特殊的Shader文件,这类Shader旨在利用GPU的并行性来进行一些与常规渲染流水线无关的计算。

++++一个单独的Unity Shader是没法发挥任何做用的,它必须和材质结合起来使用。

++++Unity Shader的导入面板可查看:渲染队列(Render queue)、是否关闭批处理(Disable batching)、属性列表(Properties)等信息。

++1.3.4ShaderLab

++++ShaderLab is a friend you can afford。”

++++Unity提供了一层抽象:Unity Shader,而咱们和这层抽象打交道的途径就是使用Unity提供的一种专门为Unity Shader服务的语言:ShaderLab。(Unity ShaderUnity为开发者提供的高层次的渲染抽象层。Unity但愿经过这种方式来让开发者更加轻松地控制渲染。)

++++Unity中,全部的Unity Shader都是使用ShaderLab来编写的。(ShaderLabUnity提供的编写Unity Shader的一种说明性语言。)(ShaderLab使用了一些嵌套的花括号内部的语义来描述一个Unity Shader文件的结构。它们定义了要显示一个材质所需的全部东西,而不只仅是着色器代码。)

 

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

++++Unity Shader的基础结构:

//立钻哥哥:Unity Shader基础结构

Shader YanlzShaderDemo/ShaderName{

    Properties{

        //属性

    }

 

    SubShader{

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

    }

 

    SubShader{

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

    }

 

    Fallback YanlzVertexLit

}

++1.3.5立钻哥哥推荐的一个简单Unity Shader示例:

Shader Custom/YanlzShaderDemo_shader2{

    //属性

    Properties{

        _Color(主颜色, Color) = (1,1,1,0)

        _SpecColor(高光颜色, Color) = (1,1,1,1)

        _Emission(光泽颜色, Color) = (0,0,0,0)

        _Shininess(光泽度, Range(0.01, 1)) = 0.7

        _MainTex(基础纹理(RGB-透明度(A, 2D) = white{ }

    }

 

    //子着色器

    SubShader{

        //定义材质

        Material{

            Diffuse[_Color]

            Ambient[_Color]

            Shininess[_Shininess]

            Specular[_SpecColor]

            Emission[_Emission]

        }

 

        Lighting On    //开启光照

        SeparateSpecular On    //开启独立镜面反射

        Blend SrcAlpha OneMinusSrcAlpha    //开启透明度混合(alpha blending

 

        //通道一:渲染对象的背面部分

        Pass{

            //若是对象是凸型,那么老是离镜头离得比前面更远

            Cull Front    //不绘制面向观察者的几何面

            SetTexture[_MainTex]{

                 Combine Primary * Texture

            }

        }

 

        //通道二:渲染对象背对咱们的部分

        Pass{

            //若是对象是凸型,那么老是离镜头离得比背面更远

            Cull Back    //不绘制背离观察者的几何面

            SetTexture[_MainTex]{

                Combine Primary * Texture

           }

        }

    }

}

++1.3.6Unity Shader的名字

++++Shader Custom/YanlzShader{  }

++++经过在字符串中添加斜杠“/”,能够控制Unity Shader在材质面板中出现的位置:【Shader=>Custom=>YanlzShader】。

++1.3.7Properties属性(材质和Unity Shader的桥梁)

++++Properties语义块中包含了一系列属性(property),这些属性将会出如今材质面板中。

++++Properties语义块的定义:

Properties{

    _Name(display name, PropertyType) = DefaultValue

    _Name(display name, PropertyType) = DefaultValue

    ....    //更多属性

}

--【名字(_Name)】:这些属性的名字一般有一个下划线开始。(若是咱们须要在Shader中访问属性,就须要使用每一个属性的名字。)

--【显示的名称(display name)】:出如今材质面板上的名字。

--【类型(PropertyType)】:指定属性的类型。

--【默认值(DefaultValue)】:属性指定的默认值。(在咱们第一次把该Unity Shader赋给某个材质时,材质面板上显示的就是这些默认值。)

++++Properties语义块支持的属性类型:IntFloatRange(min, max)ColorVector2DCube3D等。

--IntFloatRange这些数字类型的属性,默认值就是一个单独的数字;

--ColorVector这类属性,默认值是用圆括号包围的一个思惟向量;

--2DCube3D这三种纹理类型,默认值是经过一个字符串后跟一个花括号来指定的。(字符串要么是空,要么是内置的纹理名称,如white”、“black”、“gray”、“bump”等;花括号的用处本来是用于指定一些纹理属性的。)

++++立钻哥哥推荐的一个简单示例:

Shader YanlzShader/Properties/MyTestShader{

    Properties{

        //Numbers and Sliders

        _Int(Int, Int) = 2

        _Float(Float, Float) = 1.5

        _Range(Range, Range(0.0, 5.0)) = 3.0

    

        //Colors and Vectors

        _Color(Color, Color) = (1,1,1,1)

        _Vector(Vector, Vector) = (2,3,6,1)

 

        //Textures

        _2D(2D, 2D) = “”{}

        _Cube(Cube, Cube) = white{}

        _3D(3D, 3D) = black{}

    }

 

    SubShader{

        Pass{

        }

    }

 

    FallBack Diffuse

}


++1.3.8SubShader子着色器

++++每一个Unity Shader文件能够包含多个SubShader语义块,但最少要有一个。

++++Unity须要加载这个Unity Shader时,Unity会扫描全部的SubShader语义块,而后选择第一个可以在目标平台上运行的SubShader。(若是都不支持的话,Unity就会使用Fallback语义指定的Unity Shader。)(因为不一样的显卡具备不一样的能力,让低端显卡使用计算复杂度较低的着色器,在高端显卡上使用计算复杂度较高的着色器。)

++++SubShader语义块定义:

SubShader{

    [Tags]    //可选的

    [RenderSetup]    //可选的

 

    Pass{

    }

 

    //Other Passes

}

++++SubShader中定义了一系列Pass以及可选的状态([RenderSetup])和标签([Tags])设置。(每一个Pass定义了一次完整的渲染流程,可是若是Pass数目过多,每每会形成渲染性能的降低。(所以,咱们应尽可能使用最小数目的Pass))

++++状态设置([RenderSetup]ShaderLab提供了一系列渲染状态的设置指令,这些指令能够设置显卡的各类状态。常见的渲染状态设置选项:

--Cull Back/Front/Off】:设置剔除模式,剔除背面(Back)、正面(Front)、关闭剔除(Off);

--ZTest Less Greater/LEqual/GEqual/Equal/NotEqual/Always】:设置深度测试时使用的函数;

--ZWrite On/Off】:开启/关闭深度写入。

--Blend SrcFactor DstFactor】:开启并设置混合模式。

--说明:当在SubShader块中设置渲染状态,将会应用到全部的Pass,也可在Pass语义块中单独进行状态设置

++++标签([Tags]:是一个键值对(Key/Value Pair),它的键和值都是字符串类型。(这些键值对是SubShader和渲染引擎之间的沟通桥梁。标签用来告诉Unity的渲染引擎怎样以及什么时候渲染这个对象。)

++++标签的结构:Tags{ TagName1=Value1 TagName2=Value2 }

++++SubShader的标签类型:QueueRenderTypeDisableBatchingForceNoShadowCastingIgnoreProjectorCanUseSpriteAtlasPreviewType等:

--Queue】:Tags{Queue=Transparent} ,控制渲染顺序,指定该物体属于哪个渲染队列,经过这种方式能够保证全部的透明物体能够在全部不透明物体后面被渲染,咱们也能够自定义使用的渲染队列来控制物体的渲染顺序。

--RenderType】:Tags{ RenderType=Opaque } ,对着色器进行分类,例如这是一个不透明的着色器,或者一个透明的着色器等。这能够被用于着色器替换(Shader Replacement)功能。

--DisableBatching】:Tags{ DisableBatching=True } ,一些SubShader在使用Unity的批处理功能时会出现问题,例如使用了模型空间下的坐标进行顶点动画。这时能够经过该标签来直接指明是否对该SubShader使用批处理。

--ForceNoShadowCasting】:Tags{ ForceNoShadowCasting=True } ,控制使用该SubShader的物体是否会投射阴影。

--IgnoreProjector】:Tags{ IgnoreProjector=True } ,若是该标签为True”,那么使用该SubShader的物体将不会受Projector的影响。一般用于半透明物体。

--CanUseSpriteAtlas】:Tags{ CanUseSpriteAtlas = False } ,当该SubShader是用于精灵(sprites)时,将该标签设置为“False”。

--PreviewType】:Tags{ PreviewType=Plane } ,指明材质面板将如何预览该材质。默认状况下,材质将显示为一个球形,咱们能够经过把该标签的值设为Plane”“SkyBox”来改变预览类型。

--注意:标签仅能够在SubShader中声明,不能够在Pass块中声明

++1.3.9Pass(渲染通道)

++++Pass语义块结构:

Pass{

    [Name]

    [Tags]

    [RenderSetup]

    //Other code

}

++++Pass名称: Name “MyPassName”】:UsePass MyShader/MYPASSNAME

--因为Unity内部会把全部Pass的名称转换成大写字母,所以,在使用UsePass命令时必须使用大写形式的名字。

++++Pass的标签类型:LightModeRequireOptions

--LightMode】:Tags{ LightMode=ForwardBase } ,定义该PassUnity的渲染流水线中的角色。

--RequireOptions】:Tags{ RequireOptions=SoftVegetation } ,用于指定当知足某些条件时才渲染该Pass,它的值是一个由空格分隔的字符串。(目前,Unity支持的选项有:SoftVegetation

++++UsePass】:能够使用该命令来复用其余Unity Shader中的Pass

++++GrabPass】:该Pass负责抓取屏幕并将结果存储在一张纹理中,用于后续的Pass处理。

++1.3.10Fallback回滚

++++紧跟在各个SubShader语义块后面的,能够是一个Fallback指令。(Fallback指令告诉Unity:若是上面全部的SubShader在这块显卡上都不能运行,那么就使用这个最低级的Shader吧。)

++++为每一个Unity Shader正确设置Fallback是很是重要的。(Fallback还会影响阴影的投射。在渲染阴影纹理时,Unity会在每一个Unity Shader中寻找一个阴影投射的Pass。一般状况下,咱们不须要本身专门实现一个Pass,这是由于Fallback使用的内置Shader中包含了这样一个通用的Pass。)

++1.3.11Unity Shader的形式(表面着色器、顶点/片元着色器、固定函数着色器)

++++Unity Shader能够作的事情很是多(例如设置渲染状态等),但其最重要的任务仍是指定各类着色器所需的代码。这些着色器代码能够写在SubShader语义块中(表面着色器的作法),也能够写在Pass语义块中(顶点/片元着色器和固定函数着色器的作法。)

++++真正意义上的Shader代码须要包含在ShaderLab语义块中:

Shader YanlzShaderDemo/MyShader{

    Properties{

        //所需的各类属性

    }

 

    SubShader{

        //立钻哥哥:真正意义上的Shader代码会出如今这里

        //表面着色器(Surface Shader)或者顶点/片元着色器(Vertex/Fragment Shader)或者固定函数着色器(Fixed Function Shader

    }

 

    SubShader{

    }

}

++++选择哪一种Unity Shader形式:

--除非有很是明确的要求必需要使用固定函数着色器,例如须要在很是旧的设备上运行游戏,不然请使用可编程管线的着色器,即代表着色器或顶点/片元着色器。

--若是想和光源打交道,可能更喜欢使用代表着色器,但它在移动平台的性能表现很差。

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

--有不少自定义的渲染效果,请选择顶点/片元着色器。

++1.3.12、表面着色器(Surface Shader

++++表面着色器(Surface Shader)是Unity本身创造的一种着色器代码类型。(它须要的代码量不多,Unity在背后作了不少工做,但渲染的代价比较大。)

++++当给Unity提供一个表面着色器的时候,它在背后仍旧把它转换成对应的顶点/片元着色器。(表面着色器是Unity对顶点/片元着色器的更高一层的抽象。)

++++表面着色器存在的价值在于,Unity为咱们处理了不少光照细节。

++++一个简单的表面着色器示例:

Shader YanlzShaderDemo/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语义块(而非Pass语义块)中的CGPROGRAMENDCG之间。(表面着色器不须要开发者关心使用多少个Pass、每一个Pass如何渲染等问题,Unity会在背后为咱们作好这些事情。咱们只要告诉:使用哪些纹理去填充颜色,使用哪一个法线纹理去填充法线,使用Lambert光照模型等信息便可。)

++++CGPROGRAMENDCG之间的代码是使用CG/HLSL编写的,也就是说,咱们须要把CG/HLSL语言嵌套在ShaderLab语言中。

++1.3.13、顶点/片元着色器(Vertex/Fragment Shader

++++Unity中咱们能够使用CG/HLSL语言来编写顶点/片元着色器(Vertex/Fragment Shader)。它们更加复杂,但灵活性也更高。

++++一个简单的顶点/片元着色器示例:

//立钻哥哥:一个简单的顶点/片元着色器

Shader YanlzShaderDemo/VertexFragment Shader{

    SubShader{

        Pass{

            CGPROGRAM

            #pragma vertex vert

            #pragma fragment frag

        

            float4 vert(float4 v : POSITION) : SV_POSITION{

                return mul(UNITY_MATRIX_MVP, v);

            }

 

            fixed4 frag() : SV_Target{

                return fixed4(1.0, 0.0, 0.0, 1.0);

            }

 

            ENDCG

        }

    }

}

++++和表面着色器相似,顶点/片元着色器的代码也须要定义在CGPROGRAMENDCG之间,但不一样的是,顶点/片元着色器是写在Pass语义块内,而非SubShader内的。(咱们须要本身定义每一个Pass须要使用的Shader代码。)(咱们须要编写更多的代码,灵活性很高,能够控制渲染的实现细节。)

++1.3.14、固定函数着色器(Fixed Function Shader

++++这是一个被弃用的着色器。对于那些来设备不支持可编程管线着色器的,须要使用固定函数着色器(Fixed Function Shader)来完成渲染。(这些着色器能够完成一些很是简单的效果。)

++++一个简单的固定函数着色器示例:

//立钻哥哥:一个简单的固定函数着色器

Shader YanlzShaderDemo/FixedFunctionShader{

    Properties{

        _Color(Main Color, Color) = (1,0.5,0.5,1)

    }

 

    SubShader{

        Pass{

            Material{

                Diffuse[_Color]

            }

 

            Lighting On

        }

    }

}

++++固定函数着色器的代码被定义在Pass语义块中,这些代码至关于Pass中的一些渲染设置。

++++对于固定函数着色器来讲,咱们须要彻底使用ShaderLab的语法(即便用ShaderLab的渲染设置命令)来编写,而非使用CG/HLSL

++++因为如今绝大多数GPU都支持可编程的渲染管线,这种固定管线的编程方式已经逐渐被抛弃。(实际上,在Unity5.2中,全部固定函数着色器都会在背后被Unity编译成对应的顶点/片元着色器,所以真正意义上的固定函数着色器已经不存在了。)

1.3.15Unity Shader不是真正的Shader

++++Unity Shader并不等同于Shader。(在Unity里,Unity Shader实际上指的就是一个ShaderLab文件(硬盘上以.shader做为文件后缀的一种文件。))

++++ShaderLab文件中,咱们能够作更多的事情:

--在传统的Shader中,仅能够编写特定类型的Shader,例如顶点着色器、片元着色器等。而在ShaderLab中,咱们能够在同一个文件里同时包含须要的顶点着色器和片元着色器代码。

--在传统的Shader中,咱们没法设置一些渲染设置,例如是否开启混合、深度测试等,这些是开发者在另外的代码中自行设置的。而在ShaderLab中,咱们经过一行特定的指令就能够完成这些设置。

--在传统的Shader中,咱们须要编写冗长的代码来设置着色器的输入和输出,要当心地处理这些输入输出的位置对应关系等。而在ShaderLab中,咱们只须要在特定语句块中声明一些属性,就能够依靠材质来方便地改变这些属性。(对于模型自带的数据(如顶点位置、纹理坐标、法线等),ShaderLab也提供了直接访问的方法,不须要开发者自行编码来传给着色器。)

++++ShaderLab的缺点:因为Unity Shader的高度封装性,咱们能够编写的Shader类型和语法被限制了。(对于一些类型的Shader,例如曲面细分着色器(Tessellation Shader)、几何着色器(Geometry Shader)等,Unity的支持就相对差一些。)(一些高级的Shader语法ShaderLab也不支持。)

++++ShaderLab提供了一种让开发者同时控制渲染流水线中多个阶段的一种方式,不只仅是提供Shader代码。(做为开发者而言,咱们绝大部分时候只须要和ShaderLab打交道,而不须要关心渲染引擎底层的实现细节。)

++1.3.16Unity ShaderCG/HLSL之间的关系

++++Unity Shader使用ShaderLab语言编写的,但对于表面着色器和顶点/片元着色器,咱们能够在ShaderLab内部嵌套CG/HLSL语言来编写这些着色器代码。(这些CG/HLSL代码是嵌套在CGPROGRAMENDCG之间的。)

++++CG代码片断是位于Pass语义块内部的:

Pass{

    //Pass的标签和状态设置

 

    CGPROGRAM

    //编译指令,例如:

    #pragma vertex vert

    #pragma fragment frag

 

    //CG代码

 

    ENDCG

 

    //其余一些设置

}

++++在表面着色器中,CG/HLSL代码是写在SubShader语义块内,但在背后,Unity仍是会把它转化成一个包含多Pass的顶点/片元着色器。

++++从本质上来说:Unity Shader只有两种形式:顶点/片元着色器和固定函数着色器。(Unity5.2之后的版本中,固定函数着色器也会在背后被转化成顶点/片元着色器,所以从本质上来讲:Unity只存在顶点/片元着色器。)

++++在提供给编程人员这些便利的背后,Unity编译器会把这些CG片断编译成低级语言,如汇编语言等。(一般,Unity会自动把这些CG片断编译到全部相关平台上。)



###1.4Shader中的数学基础

++1.4Shader中的数学基础

++++“不懂数学者不得入内。”

++++计算机图形学之因此深奥难懂,很大缘由在于它是创建在虚拟世界上的数学模型。(数学渗透到图形学的方方面面,固然也包括Shader。)

++++在学习Shader的过程当中,咱们最常使用的就是矢量和矩阵(即数学的线性代数)。

++1.4.1、笛卡尔坐标系

++++在游戏制做中,咱们使用数学绝大部分都是为了计算位置、距离和角度等变量。(而这些计算大部分都是在笛卡尔坐标系(Cartesian Coordinate System)下进行的。)

++++传说,笛卡尔坐标系来源于笛卡尔对天花板上一只苍蝇的运动轨迹的观察。(笛卡尔发现,能够使用苍蝇距不一样墙面的距离来描述它的当前位置。这个坐标平面后来逐渐发展造成了坐标系系统。)

++1.4.2、二维笛卡尔坐标系

++++一个二维的笛卡尔坐标系包含的信息:

--原点:它是整个坐标系中的中心。

--x轴和y轴:两条过原点的相互垂直的矢量。(这些坐标轴也被称为该坐标系的基矢量。)

++1.4.3、三维笛卡尔坐标系

++++在三维笛卡尔坐标系中,咱们须要定义3个坐标轴和一个原点。

++++XYZ三个坐标轴被称为该坐标系的基矢量(basic vector

++++一般状况下,这3个坐标轴之间是相互垂直的,且长度为1,这样的基矢量被称为标准正交基(orthonormal basis

++++三维笛卡儿坐标系中的坐标轴方向也不是固定的,这种不一样致使了两种不一样种类的坐标系:左手坐标系(left-handed coordinate space右手坐标系(right-handed coordinate space

++1.4.4Unity使用的坐标系

++++Unity使用的左手坐标系。

++++对于观察空间来讲,Unity使用的右手坐标系。(观察空间,通俗来说就是以摄像机为原点的坐标系。在这个坐标系中,摄像机的前向是z轴的负方向,这与在模型空间和世界空间中的定义相反。)

++++观察空间中,z轴坐标的减小意味着场景深度的增长:


++1.4.5、点和矢量

++++点(Pointn维空间(游戏中主要使用二维和三维空间)中的一个位置,它没有大小、宽度这类概念。

++++矢量(vector,也称为向量)是指n维空间中一种包含了模(magnitude方向(direction有向线段(好比速度)。(标量只要模没有方向,好比距离)(矢量的模是一个标量,能够理解为矢量在空间中的长度。)

++++点是一个没有大小之分的空间中的位置,而矢量是一个有模和方向但没有位置的量。

++++矢量一般用于描述偏移量,而一个点点能够用于指定空间中的一个位置(即相对于原点的位置。)

++++单位矢量(unit vector指的是那些模为1的矢量。(单位矢量也被称为被归一化的矢量(normalized vector。)(对任何给定的非零矢量,把它转换成单位矢量的过程就被称为归一化(normalization

++++法线方向(法矢量)、光源方向等,这些矢量不必定是归一化后的矢量。(因为咱们的计算每每要求矢量是单位矢量,所以在使用前应先对这些矢量进行归一化运算。)

++++矢量之间也能够进行乘法。矢量的乘法有两种:点积(dot product,也称内积(inner product))叉积(cross product,也被外积(outer product))

++1.4.6、矢量的运算

++++从几何意义上看,把一个矢量v和一个标量k相乘,意味着对矢量v进行一个大小为|K|的缩放。

++++从图形学中矢量一般用于描述位置偏移(简称位移)。(咱们能够利用矢量的加法和减法来计算一点相对于另外一点的位移。)

++++从几何意义上看,对于加法,能够把矢量a的头链接到矢量b的尾,而后画一条从a的尾到b的头的矢量,来获得ab相加后的矢量。(也就是说,若是咱们从一个起点开始进行了一个位置偏移a,而后又进行一个位置偏移b,那么就等于进行了一个a+b的位置偏移。这被称为矢量加法的三角形定则(triangle rule。矢量的减法是相似的。)


++1.4.7、矢量的点积

++++点积(dot product,也称内积(inner product))

++++Unity Shader中,咱们能够直接使用形如 dot(a, b)的代码来对两个矢量值进行点积的运算。

++++矢量的点积知足交换律:a.b = b.a

++++点积公式1a.b=(ax,ay,az).(bx,by,bz)=axbx+ayby+azbz

++++点积公式2a.b=|a||b|cosA

++++点积性质1:点积能够结合标量乘法:(ka).b=a.(kb)=k(a.b)

++++点积性质2:点积可结合矢量加法和减法:a.(b+c)=a.b+a.c

++++点积性质3:一个矢量和自己进行点积的结果,是该矢量的模的平方:v.v=vxvx+vyvy+vzvz=|v|^2

++++点积的几何意义很重要,由于点积几乎应用到图形学的各个方面。(其中一个几何意义就是投影(projection)(投影的值多是负数。)

++++能够直接利用点积来求矢量的模,而不须要使用模的计算公式。(在只想要比较两个矢量的长度大小时,能够直接使用点积的结果。)

++++两个矢量的点积能够表示为两个矢量的模相乘,再乘以它们之间夹角的余弦值。(a.b=|a||b|cosA)。(两个矢量的方向不一样会获得不一样符号的投影值:当夹角小于90度时,cosA>0;当夹角等于90度时,cosA=0;当夹角大于90度时,cosA<0。)(利用arcos反余弦操做求得两个向量之间的夹角(在0~180度):A=arcos(a^.b^)

++++点积的符号可让咱们知道两个矢量的方向关系。(投影结果的正负号与a^b的方向有关:当它们的方向相反(夹角大于90度)时,结果小于0;当它们的方向相互垂直(夹角为90度)时,结果等于0;当它们的方向相同(夹角小于90度)时,结果大于0。)


++1.4.8、矢量的叉积

++++叉积(cross product,也被外积(outer product))

++++叉积与点积不一样的是,矢量叉积的结果还是一个矢量,而非标量。

++++两个矢量的叉积:aXb=(ax,ay,az)X(bx,by,bz)=(aybz-azby, azbx-axbz, axby-aybx)

 

++++叉积知足反交换律的:axb=-(bxa)

++++对两个矢量进行叉积的结果会获得一个同时垂直于这两个矢量的新矢量

++++叉积axb的长度等于ab的模的乘积再乘以它们之间夹角的正弦值:|axb|=|a||b|sinA

++++叉积最多见的应用就是:计算垂直于一个平面、三角形的矢量。(还能够用于判断三角面片的朝向。)

++1.4.9、矩阵(Matrix

++++矩阵就是由m*n个标量组成的长方形数组。(网格结构,意味着矩阵有行(row)和列(column))

++++矢量就是一个数组,矩阵也是一个数组。

++++矩阵和矢量相似,也能够和标量相乘,它的结果仍然是一个相同维度的矩阵。(它们之间的乘法很是简单,就是矩阵的每一个元素和该标量相乘。)

++++两个矩阵的乘法结果会是一个新的矩阵

++++特殊的矩阵:方块矩阵(square matrix)、单位矩阵(identity matrix)、转置矩阵(transposed matrix)、逆矩阵(inverse matrix)、正交矩阵(orthogonal matrix)等。

++++方块矩阵(square matrix】:简称方阵,是指那些行和列数目相等的矩阵。(在三维渲染里,最常使用就是3*34*4的方阵。)

++++单位矩阵(identity matrix】:一个特殊的对角矩阵是单位矩阵(identity matrix)。(任何矩阵和它相乘的结果仍是原来的矩阵。)

++++转置矩阵(transposed matrix】:实际是对原矩阵的一种运算,即转置运算。

++++逆矩阵(inverse matrix】:不是全部矩阵都有逆矩阵,第一个前提就是该矩阵必须是一个方阵。(矩阵最复杂的一种操做。)

++++正交矩阵(orthogonal matrix】:正交是矩阵的一种属性。若是一个方阵M和它的转置矩阵的乘积是单位矩阵的话,咱们就说这个矩阵是正交的(orthogonal)。

++1.4.10、矩阵的几何意义:变换

++++在游戏的世界中,这些变换通常包括了旋转、缩放和平移。(给定一个点或矢量,再给定一个变换,就能够经过某个数学运算来求得新的点和矢量,能够使用矩阵来完美地解决这些问题。)

++++变换(transform,是指把一些数据,如点、方向矢量甚至是颜色等,经过某种方式进行转换的过程。(在计算机图形学领域,变换很是重要。)

++++线性变换(linear transform】:是指那些能够保留矢量加和标量乘的变换。(缩放(scale就是一种线性变换。)(旋转(rotation也是一种线性变换。)(对于线性变换来讲,若是咱们要对一个三维的矢量进行变换,那么使用3*3的矩阵就能够表示全部的线性变换。)

++++线性变换除了缩放和旋转外,还有错切(shear镜像(mirroring,也称reflection正交投影(orthographic projection等等。

++++平移变换(f(x)=x+(1,2,3)),这个变换就不是一个线性变换,它知足标量乘法,但不知足矢量加法。(平移变换不会对方向矢量产生任何影响,矢量没有位置属性。)(平移矩阵并非一个正交矩阵。)

++++仿射变换(affine transform就是合并线性变换和平移变换的变换类型。(仿射变换能够使用一个4*4的矩阵来表示,把矢量扩展到思惟空间下,这就是齐次坐标空间(homogeneous space

++++能够使用一个4*4的矩阵来表示平移、旋转和缩放。(把表示纯平移、纯旋转和纯缩放的变换矩阵叫作基础变换矩阵。)(平移矩阵并非一个正交矩阵。)(缩放矩阵通常不是正交矩阵。)(旋转矩阵的逆矩阵是旋转相反角度获得的变换矩阵。旋转矩阵是正交矩阵,并且多个旋转矩阵之间的串联一样是正交的。)

++++在绝大多数状况下,约定变换的顺序:先缩放,再旋转,最后平移。(复合变换能够经过矩阵的串联来实现。把平移、旋转和缩放组合起来,造成一个复杂的变换过程。)

++++分别绕x轴、y轴、z轴旋转的变换矩阵,Unity的旋转顺序是:zxy顺序旋转

++1.4.11、齐次坐标

++++因为3*3矩阵不能表示平移操做,扩展到4*4的矩阵就能够实现对平移的表示。

++++把三维矢量转换成四维矢量,也就是齐次坐标(homogeneous coordinate,齐次坐标泛指四维齐次坐标(事实上齐次坐标的维度能够超过四维)。

++++齐次坐标是一个四维矢量。

++1.4.12、坐标空间

++++顶点着色器最基本的功能就是把模型的顶点坐标从模型空间转换到齐次裁剪坐标空间

++++渲染游戏的过程能够理解为:把一个个顶点通过层层处理最终转化到屏幕上的过程。

++++在编写Shader的过程当中,不少看起来很难理解和复杂的数学运算都是为了在不一样坐标空间之间转换点和矢量。(单一坐标系其实会很麻烦:好比用公司大门为原点的坐标系描述办公桌上的电脑。)

++++在渲染流水线中,须要把一个点或方向矢量从坐标空间转换到另外一个坐标空间。(坐标空间,指明其原点位置和3个坐标轴的方向。)

++++坐标空间会造成一个层次结构:每一个坐标空间都是另外一个坐标空间的子空间,每一个空间都有一个父(parent)坐标空间。(对坐标空间的变换实质上就是在父空间和子空间之间对点和矢量进行变换。)

++++Shader中,经常截取变换矩阵的前3行前3列对法线方向、光照方向等进行空间变换。(矢量是没有位置的,坐标空间的原点变换是能够忽略的,仅仅平移坐标系的原点是不会对矢量形成任何影响的。)(对矢量的坐标空间变换能够使用3*3的矩阵来表示。)

++++在渲染流水线中,一个顶点要通过多个坐标空间的变换才能最终被画在屏幕上。(一个顶点最开始是在模型空间中定义的,最后它将会变换到屏幕空间中,获得真正的屏幕像素坐标。)

++1.4.13、模型空间(model space

++++模型空间(model space是和某个模型或者说是对象有关的。(模型空间也称对象空间(object space局部空间(local space

++++每一个模型都有本身独立的坐标空间,当它移动或旋转的时候,模型空间也会跟着它移动和旋转。(在模型空间中,常用的一些方向概念:前(forward)、后(back)、左(left)、右(right)、上(up)、下(down)等。)

++++Unity在模型空间中使用的是左手坐标系:+x轴(右)、+y轴(上)、+z轴(前)等。

++++模型空间的原点和坐标轴一般是由美术人员在建模软件里肯定好的。当导入到Unity中后,咱们能够在顶点着色器中访问到模型的顶点信息,其中包括了每一个顶点的坐标。(这些坐标都是相对于模型空间中的原点(一般位于模型的重心)定义的。)

++++模型坐标空间中的位置能够经过访问顶点属性来获得。(一个位置是(0,3,4),因为顶点变换中每每包含了平移变换,所以须要把其扩展到齐次坐标系下,获得顶点坐标是(0,3,4,1))。

++1.4.14、世界空间(world space

++++世界空间(world space是一个特殊的坐标系,它创建了咱们所关心的最大的空间。

++++世界空间能够被用于描述绝对位置。(绝对位置指的就是在世界坐标系中的位置。)

++++一般,咱们会把世界空间的原点放置在游戏空间的中心。

++++Unity中,世界空间使用左手坐标系,但它的x轴,y轴,z轴是固定不变的。(在Unity中,咱们能够经过调整Transform组件中的Position属性来改变模型的位置,这里的位置值相对于这个Transform的父节点(parent)的模型坐标空间中的原点定义的。)(若是一个Transform没有任何父节点,那么这个位置就是在世界坐标系中的位置。)

++++顶点变换的第一步:就是将顶点坐标从模型空间变换到世界空间中。(这个变换一般叫作模型变换(model transform

++1.4.15、观察空间(view space

++++观察空间(view space也被称为摄像机空间(camera space。(观察空间能够认为是模型空间的一个特例,在全部的模型中有一个很是特殊的模型,即摄像机(一般来讲摄像机本事是不可见的),它的模型空间就是观察空间。)

++++摄像机决定了咱们渲染游戏所使用的视角。(在观察空间中,摄像机位于原点,其坐标轴的选择能够是任意的,在Unity中观察空间的坐标轴:+x轴(右)、+y轴(上)、+z轴(摄像机后方))(Unity在模型空间和世界空间中选用的都是左手坐标系,而在观察空间中使用的是右手坐标系。)

++++OpenGL传统中,观察空间中摄像机的正前方指向的是-z轴方向。

++++Unity为咱们作了不少渲染的底层工做,包括不少坐标空间的转换。(调用相似Camera.cameraToWorldMatrixCamera.worldToCameraMatrix等接口自行计算某模型在观察空间中的位置。)

++++提示:观察空间和屏幕空间是不一样的,观察空间是一个三维空间,而屏幕空间是一个二维空间。(从观察空间到屏幕空间的转换须要一个投影(projection)操做。)

++++获得顶点在观察空间中的位置,有两种方法:

--第一种方法:是计算观察空间的3个坐标轴在世界空间下的表示,构建出从观察空间变换到世界空间的变换矩阵,再对该矩阵求逆来获得从世界空间变换到观察空间的变换矩阵。

--第二种方法:想象平移整个观察空间,让摄像机原点位于世界坐标的原点,坐标轴与世界空间中的坐标轴重合便可。

++1.4.16、裁剪空间

++++顶点要从观察空间转换到裁剪空间(clip space,也称齐次裁剪空间)中,这个用于变换的矩阵叫作裁剪矩阵(clip matrix,也称投影矩阵(projection matrix

++++裁剪空间的目标是可以方便地对渲染图元进行裁剪:彻底位于这块空间内部的图元将会被保留,彻底位于这块空间外部的图元将会被剔除,与这块空间边界相交的图元就会被裁剪。(这块空间是由视锥体(view frustum来决定的。)

++++视锥体指的是空间中的一块区域,这块区域决定了摄像机能够看到的空间。(视锥体由六个平面包围而成,这些平面也称裁剪平面(clip planes)。(视锥体有两种类型:正交投影(orthographic projection透视投影(perspective projection)(透视投影模拟了人眼看世界的方式,近大远小。)(正交投影彻底保留了物体的距离和角度,在2D游戏或渲染小地图等其余HUD元素时,使用正交投影。)

++++在视锥体的6块裁剪平面中,有两块裁剪平面比较特殊:近裁剪平面(near clip plane远裁剪平面(far clip plane。(它们决定了摄像机能够看到的深度范围。)

 

++++投影矩阵有两个目的:

--投影矩阵目的1:为投影作准备。(虽然投影矩阵包含投影二字,可是它并无进行真正的投影工做,而是在为投影作准备。)(真正的投影发生在后面的齐次除法(homogeneous division过程当中。通过投影矩阵的变换后,顶点的w份量将会具备特殊的意义。)(投影可理解成一个空间的降维,而投影矩阵实际上并不会真的进行这个步骤,它会为真正的投影作准备工做。真正的投影会在屏幕映射时发生,经过齐次除法来获得二维坐标。)

--投影矩阵目的2:对xyz份量进行缩放。(直接使用视锥体的6个裁剪平面来进行裁剪会比较麻烦。而通过投影矩阵的缩放后,咱们能够直接使用w份量做为一个范围值,若是xyz份量都位于这个范围内,就说明该顶点位于裁剪空间内。)

++++透视投影:视锥体的意义在于定义了场景中的一块三维空间。(全部位于这块空间内的物体将会被渲染,不然就会被剔除或裁剪。)

++++Unity中,裁剪空间是由Camera组件中的参数和Game视图的横纵比共同决定。(能够经过Camera组件的Field of ViewFOV)属性来改变视锥体竖直方向的张开角度,而Clipping Planes中的NearFar参数能够控制视锥体的近裁剪平面和远裁剪平面距离摄像机的远近。)


++++透视投影perspective projection的视锥体是一个金字塔形。(侧面的4个裁剪平面将会在摄像机处相交。)

++++正交投影orthographic projection的视锥体是一个长方体。

++1.4.17、屏幕空间(screen space

++++通过投影矩阵的变换后,能够进行裁剪操做了。(当完成了全部的裁剪工做后,就须要进行真正的投影了,须要把视锥体投影到屏幕空间(screen space中。)(通过这一步变换,会获得真正的像素位置,而不是虚拟的三维坐标。)

++++屏幕空间是一个二维空间,必须把顶点从裁剪空间投影到屏幕空间中,来生成对应的2D坐标,这个过程能够理解成两个步骤:齐次除法和映射到屏幕空间。

++++进行标准齐次除法(homogeneous division,也称透视除法(perspective division。(就是用齐次坐标系的w份量去除以xyz份量)。(在OpenGL中,这一步获得的坐标叫作归一化的设备坐标(Normalized Device Coordinates, NDC)(通过这一步,能够把坐标从齐次裁剪坐标空间转换到NDC中。)(通过透视投影变换后的裁剪空间,通过齐次除法后就变换到一个立方体内。Unity选择了OpenGL这样的齐次裁剪空间,这个立方体的xyz份量的范围都是[-1, 1]


++++Unity中,从裁剪空间到屏幕空间的转换是由Unity帮咱们完成的。(顶点着色器只须要把顶点转换到裁剪空间便可。)

++++顶点着色器的最基本的任务就是把顶点坐标从模型空间转换到裁剪空间中

++++片元着色器中能够获得该片元在屏幕空间的像素位置

++1.4.18、渲染流水线中顶点的空间变换过程


++1.4.19、法线变换

++++一种特殊的变换:法线变换。

++++法线(normal,也称法矢量(normal vector

++++在游戏中,模型的一个顶点每每会携带额外的信息,而顶点法线就是其中一种信息。(当咱们变换一个模型的时候,不只须要变换它的顶点,还须要变换顶点法线,以便在后续处理(如片元着色器)中计算光照等。)

++++切线(tangent,也称切矢量(tangent vector。(与法线相似,切线每每也是模型顶点携带的一种信息。它一般与纹理空间对齐,并且与法线方向垂直。)

++1.4.20Unity Shader的内置变量

++++使用UnityShader的一个好处在于,它提供了不少内置的参数,这使得咱们再也不须要本身动手计算一些值。

++++Unity内置的变换矩阵:UNITY_MATRIX_MVPUNITY_MATRIX_MVUNITY_MATRIX_VUNITY_MATRIX_PUNITY_MATRIX_VPUNITY_MATRIX_T_MVUNITY_MATRIX_IT_MV_Object2World_World2Object等。

  --UNITY_MATRIX_MVP】:当前的模型.观察.投影矩阵,用于将顶点/方向矢量从模型空间变换到裁剪空间

  --UNITY_MATRIX_MV】:当前的模型.观察矩阵,用于将顶点/方向矢量从模型空间变换到观察空间

  --UNITY_MATRIX_V】:当前的观察矩阵,用于将顶点/方向矢量从世界空间变换到观察空间

  --UNITY_MATRIX_P】:当前的投影矩阵,用于将顶点/方向矢量从观察空间变换到裁剪空间

  --UNITY_MATRIX_VP】:当前的观察.投影矩阵,用于将顶点/方向矢量从世界空间变换到裁剪空间

  --UNITY_MATRIX_T_MV】:UNITY_MATRIX_MV的转置矩阵。

  --UNITY_MATRIX_IT_MV】:UNITY_MATRIX_MV的逆转置矩阵,用于将法线从模型空间变换到观察空间,也可用于获得UNITY_MATRIX_MV的逆矩阵。

  --_Object2World】:当前的模型矩阵,用于将顶点/方向矢量从模型空间变换到世界空间。

  --_World2Object】:_Object2World的逆矩阵,用于将顶点/方向矢量从世界空间变换到模型空间。

++++Unity内置的摄像机和屏幕参数:_WorldSpaceCameraPos_ProjectionParams_ScreenParams_ZBufferParamsunity_OrthoParamsunity_CameraProjectionunity_CameraInvProjectionunity_CameraWorldClipPlanes[6]等。

--_WorldSpaceCameraPos】:float3:该摄像机在世界空间中的位置。

--_ProjectionParams】:float4x=1.0(或-1.0,若是正在使用一个翻转的投影矩阵进行渲染),y=Nearz=Farw-1.0+1.0/Far,其中NearFar分别是近裁剪平面和远裁剪平面和摄像机的距离。

--_ScreenParams】:float4x=widthy=heightz=1.0+1.0/widthw=1.0+1.0/height,其中widthheight分别是该摄像机的渲染目标(render target)的像素宽度和高度。

--_ZBufferParams】:float4x=1-Far/Near, y=Far/Near, z=x/Far, w=y/Far,该变量用于线性化Z缓存中的深度值。

--unity_OrthoParams】:float4x=width, y-height, z没有定义,w=1.0(该摄像机是正交摄像机)或w=0.0(该摄像机是透视摄像机),其中widthheight是正交投影摄像机的宽度和高度。

--unity_CameraProjection】:float4x4:该摄像机的投影矩阵。

--unity_CameraInvProjection】:float4x4:该摄像机的投影矩阵的逆矩阵。

--unity_CameraWorldClipPlanes[6]】:float4:该摄像机的6个裁剪平面在世界空间下的等式,按以下顺序:左、右、下、上、近、远裁剪平面。

++1.4.21、使用3*3仍是4*4的变换矩阵?

++++对于线性变换(例如旋转和缩放)来讲,仅使用3*3的矩阵就足够表示全部的变换了。(若是存在平移变换,就须要使用4*4的矩阵。)

++++在对顶点的变换中,一般使用4*4的变换矩阵。(在变换前须要把点坐标转换成齐次坐标的表示,即把顶点的w份量设为1。)

++++在对方向矢量的变换中,一般使用3*3的矩阵就足够了。(平移变换对方向矢量是没有影响的。)

++1.4.22CG中的矢量和矩阵类型

++++Unity Shader中一般使用CG做为着色器编程语言。

++++CG中,矩阵类型是由float3x3float4x4等关键词进行声明和定义的。(对于float3float4等类型的变量,能够把它当成一个矢量,也能够把它当成是一个1*n的行矩阵或者一个n*1的列矩阵。)

++++CG使用的是行优先的方法,便是一行一行地填充矩阵的。(咱们在CG中访问一个矩阵中的元素时,也是按行来索引的。)(提示:Unity在脚本中提供一种矩阵类型:Matrix4x4,脚本中这个矩阵类型时采用列优先的方式。这与Unity Shader中的规定不同,需多加注意)

++++CG优先按行来索引一个矩阵中的元素:

//立钻哥哥:行行优先的方式初始化矩阵M

float3x3 M = float3x3(1.0,2.0,3.0,   4.0,5.0,6.0,  7.0,8.0,9.0);

float3 row = M[0];    //获得M的第一行:(1.0,2.0,3.0

float e1e = M[1][0];    //获得M的第2行第1列的元素:4.0

++1.4.23Unity中的屏幕坐标:ComputeScreenPos/VPOS/WPOS

++++在写Shader的过程当中,咱们有时候但愿可以得到片元在屏幕上的像素位置。

++++在顶点/片元着色器中,有两种方式来得到片元的屏幕坐标:

--第一种:是在片元着色器的输入中声明VPOSWPOS语义。(VPOSHLSL中对屏幕坐标的语义,而WPOSCG中对屏幕坐标的语义。二者在Unity Shader中是等价的。)(咱们能够在HLSL/CG中经过语义的方式来定义顶点/片元着色器的默认输入,而不须要本身定义输入输出的数据结构。)

//立钻哥哥:在片元着色器的输入中声明VPOSWPOS语义

fixed4 frag(float4 sp : VPOS) : SV_Target{

    //用屏幕坐标除以屏幕分辨率_ScreenParams.xy,获得空间中的坐标

    return fixed4(sp.xy/_ScreenParams.xy, 0.0, 1.0);

}

--第二种:是经过Unity提供的ComputeScreenPos函数。(这个函数在UnityCG.cginc里被定义。)(一般的用法:首先在顶点着色器中将ComputeScreenPos的结果保存在输出结构体中,而后在片元着色器中进行一次齐次除法运算后获得空间下的坐标。)

//立钻哥哥:经过Unity提供的ComputeScreenPos函数

struct vertOut{

    float4 pos : SV_POSITION;

    float4 scrPos : TEXCOORD0;

};

 

vertOut vert(appdata_base v){

    vertOut o;

    o.pos = mul(UNITY_MATRIX_MVP, v.vertex);

 

    //第一步:把ComputeScreenPos的结果保存到scrPos

    o.scrPos = ComputeScreenPos(o.pos);    

 

    return o;

}

 

fixed4 frag(vertOut i) : SV_Target{

    //第二步:用scrPos.xy除以scrPos.w获得空间中的坐标

    float2 wcoord = (i.scrPos.xy/i.scrPos.w);

    return fixed4(wcoord, 0.0, 1.0);

}




##2章:Shader初级篇

++2章:Shader初级篇

++++2.1Shader入门

++++2.2Unity中的基础光照

++++2.3、基础纹理

++++2.4、透明效果

++++2.5、更复杂的光照

++++2.6、高级纹理

++++2.7、让画面动起来

++++2.8、立钻哥哥带咱们学Shader



###2.1Shader入门

++2.1Shader入门

++++在第1章(Shader基础篇)中,立钻哥哥带咱们学习了渲染流水线,并给出了Unity Shader的基本概况,同时还打下了必定的数学基础。

++2.1.1、顶点/片元着色器的基本结构

++++顶点/片元着色器的结构与Unity Shader基本结构大致相似,包括了ShaderPropertiesSubShaderFallback等语义块:

//立钻哥哥:顶点/片元着色器的基本结构

Shader YanlzShaderDemoName{

    Properties{

        //属性

    }

 

    SubShader{

        //针对显卡ASubShader

        Pass{

            //设置渲染状态和标签

 

            CGPROGRAM    //开始CG代码片断

            #pragma vertex vert    //该代码片断的编译指令

            #pragma fragment frag

 

            //立钻哥哥:CG代码写在这里

 

            ENGCG

 

            //其余设置

        }

 

        //其余须要的Pass

     }

 

    SubShader{

        //针对显卡BSubShader

    }

 

    //立钻哥哥:上述SubShader都失败后用于回调的Unity Shader

    Fallback VertexLit

}

++1.2.2、剖析一个最简单的顶点/片元着色器代码

++++一个简单顶点/片元着色器示例:

//立钻哥哥:一个最简单的顶点/片元着色器示例

Shader YanlzShaderDemo/VertexFragmentDemo01{

    SubShader{

        Pass{

            CGPROGRAM

            #pragma vertex vert

            #pragma fragment frag

 

            float4 vert(float4 v : POSITION) : SV_POSITION{

                return mul(UNITY_MATRIX_MVP, v);

            }

 

            fixed4 frag() : SV_Target{

                return fixed4(1.0, 1.0, 1.0, 1.0);

            }

 

            ENGCG

        }

    }

}

++++Properties属性语义并非必需的。(咱们能够选择不声明任何材质属性。)

++++没有进行任何渲染设置和标签设置时,SubShader将使用默认的渲染设置和标签设置。

++++#pragma vertex vert  :告诉Unity哪一个函数包含了顶点着色器代码。

++++#pragma fragment frag  :告诉Unity哪一个函数包含了片元着色器代码。

++++立钻哥哥带咱们剖析vert函数:

float4 vert(float4 v : POSITION) : SV_POSITION{

    return mul(UNITY_MATRIX_MVP, v);    //把顶点坐标从模型空间转换到裁剪空间

}

--函数的输入v包含了这个顶点的位置,这是经过POSITION语义指定的。

--函数的返回值是一个float4类型的变量,它是该顶点在裁剪空间中的位置。

--POSITIONSV_POSITION都是CG/HLSL中的语义(semantics),它们是不可省略的,这些语义将告诉系统用户须要哪些输入值,以及用户的输出是什么。(POSITION告诉Unity,把模型的顶点坐标填充到输入参数v中。)(SV_POSITION告诉Unity,顶点着色器的输出是裁剪空间中的顶点坐标。)

--UNITY_MATRIX_MVP矩阵是Unity内置的模型.观察.投影矩阵

++++立钻哥哥带咱们剖析frag函数:

fixed4 frag() : SV_Target{

    return fixed4(1.0, 1.0, 1.0, 1.0);    //返回一个表示白色的fixed4类型的变量

}

--frag函数没有任何输入。

--函数的输出是一个fixed4类型的变量,而且使用了SV_Target语义进行限定。(SV_Target也是HLSL中的一个系统语义,它等同于告诉渲染器,把用户的输出颜色存储到一个渲染目标(render target)中,这里将输出到默认的帧缓存中。)

--片元着色器输出的颜色的每一个份量范围在[0,1],其中(0,0,0)表示黑色,而(1,1,1)表示白色。

++1.2.3、模型数据从哪里来

++++在顶点着色器中使用POSITION语义获得模型的顶点位置。

++++想要获得更多模型数据,好比每一个顶点的纹理坐标和法线方向等(使用纹理坐标访问纹理,使用法线计算光照),那就须要为顶点着色器定义个新的输入参数,这个参数是一个结构体。

Shader YanlzShaderDemo/VertexFragmentShade02{

    SubShader{

        Pass{

            CGPROGRAM

            #pragma vertex vert

            #pragma fragment frag

 

            //使用一个结构体来定义顶点着色器的输入

            struct a2v{

                //POSITION语义告诉Unity,用模型空间的顶点坐标填充vertex变量

                float4 vertex : POSITION;

 

                //NORMAL语义告诉Unity,用模型空间的法线方向填充normal变量

                float3 normal : NORMAL;

 

                //TEXCOORD0语义告诉Unity,用模型的第一套纹理坐标填充texcoord变量

                float4 texcoord : TEXCOORD0;

            };

 

            float4 vert(a2v v) : SV_POSITION{

                //使用v.vertex来访问模型空间的顶点坐标

                return mul(UNITY_MATRIX_MVP, v.vertex);

            }

 

            fixed4 frag() : SV_Target{

                return fixed4(1.0, 1.0, 1.0, 1.0);

            }

 

            ENDCG

        }

    }

}

++++在上面代码中,咱们声明了一个新的结构体a2v,它包含了顶点着色器须要的模型数据。(在a2v的定义中,咱们用到了更多Unity支持的语义,如NORMALTEXCOORD0,当它们做为顶点着色器的输入时都是有特定含义的,Unity会根据这些语义来填充这个结构体。)

++++vert函数的输入参数类型是一个新定义的结构体a2v,经过这个自定义结构体的方式,能够在顶点着色器中访问模型数据。(a表示应用(application),v表示顶点着色器(vertex shader),a2v就是把数据从应用阶段传递到顶点着色器中。)

++++顶点着色器的输出,Unity支持的语义有:POSITIONTANGENTNORMALTEXCOORD0TEXCOORD1TEXCOORD2TEXCOORD3COLOR等。

++++Unity中,POSITIONTANGENTNORMAL等这些语义中的数据是由使用该材质的Mesh Render组件提供的。(在每帧调用Draw Call的时候,Mesh Render组件会把它负责渲染的模型数据发送给Unity Shader。一个模型一般包含了一组三角面片,每一个三角面片由3个顶点构成,而每一个顶点又包含了一些数据,例如顶点位置、法线、切线、纹理坐标、顶点颜色等。经过语义方法能够在顶点着色器中访问顶点的这些模型数据。)

++++建立自定义的结构体,格式以下:

struct StructName{

    Type Name : Semantic;

    Type Name : Semantic;

    ....

}

++1.2.4、顶点着色器和片元着色器之间如何通讯

++++在实践中,从顶点着色器输出一些数据,例如把模型的法线、纹理坐标等传递给片元着色器,这就涉及顶点着色器和片元着色器之间的通讯。

++++再定义一个新的结构体,代码:

Shader YanlzShaderDemo/VertexFragmentShader03{

    SubShader{

        Pass{

            CGPROGRAM

            #pragma vertex vert

            #pragma fragment frag

 

            struct a2v{

                float4 vertex : POSITION;

                float3 normal : NORMAL;

                float4 texcoord : TEXCOORD0;

            };

 

            //使用一个结构体来定义顶点着色器的输出

            struct v2f{

                //SV_POSITION语义告诉Unitypos里包含了顶点在裁剪空间中的位置信息

                float4 pos : SV_POSITION;

 

                //COLOR0语义能够用于存储颜色信息

                fixed3 color : COLOR0;

            };

 

            v2f vert(a2v v) : SV_POSITION{

                v2f o;    //声明输出结构

                o.pos = mul(UNITY_MATRIX_MVP, v.vertex);

 

                //v.normal包含了顶点的法线方向,其份量范围在[-1.0, 1.0]

                //下面的代码把份量范围映射到了[0.0, 1.0],存储到o.color中传递给片元着色器

                o.color = v.normal * 0.5 + fixed3(0.5, 0.5, 0.5);

                return o;

            }

 

            fixed4 frag(v2f i) : SV_Target{

                return fixed4(i.color, 1.0);    //将插值后的i.color显示到屏幕上

            }

 

            ENDCG

        }

    }

}

++++在上面的代码中,声明了一个新的结构体v2f。(v2f用于在顶点着色器和片元着色器之间传递信息。)(v2f也须要指定每一个变量的语义。例如SV_POSITIONCOLOR0等语义)

++++顶点着色器的输出结构中,必须包含一个变量,它的语义是SV_POSITION。(不然,渲染器将没法获得裁剪空间中的顶点坐标,也就没法把顶点渲染到屏幕上。)

++++COLOR0语义中的数据能够由用户自行定义,但通常都是存储颜色,例如逐顶点的漫反射颜色或逐顶点的高光反射颜色。

++++完成了顶点着色器和片元着色器之间的通讯,顶点着色器是逐顶点调用的,而片元着色器是逐片元调用的。(片元着色器中的输入其实是把顶点着色器的输出进行插值后获得的结果。)

++1.2.5、如何使用属性

++++材质和Unity Shader之间有着紧密联系。(材质提供给咱们一个能够方便地调节Unity Shader中参数的方式,经过这些参数,咱们能够随时调整材质的效果。这些参数就写在Properties语义块中。)

++++一个小需求:在材质面板显示一个颜色拾取器,从而能够直接控制模型在屏幕上显示的颜色:

Shader YanlzShaderDemo/VertexFragmentShader04{

    Properties{

        //声明一个Color类型的属性

        _Color (Color Tint, Color) = (1.0, 1.0, 1.0, 1.0)

    }

 

    SubShader{

        Pass{

            CGPROGRAM

            #pragma vertex vert

            #pragma fragment frag

 

            //定义一个与属性名称和类型都匹配的变量

            fixed4 _Color;

 

            struct a2v{

                float4 vertex : POSITION;

                float3 normal : NORMAL;

                float4 texcoord : TEXCOORD0;

            };

 

            struct v2f{

                float4 pos : SV_POSITION;

                fixed3 color : COLOR0;

            };

 

            v2f vert(a2v v) : SV_POSITION{

                v2f o;

                 o.pos = mul(UNITY_MATRIX_MVP, v.vertex);

                o.color = v.normal * 0.5 + fixed3(0.5, 0.5, 0.5);

                return o;

            }

 

            fixed4 frag(v2f i) : SV_Target{

                fixed3 c = i.color;

                c *= _Color.rgb;    //使用_Color属性来控制输出颜色

                return fixed4(c, 1.0);

            }

 

            ENDCG

        }

    }

}

++++在上面的代码中,咱们添加了Properties语义块,并声明了一个属性_Color,它的类型是Color,初始值是(1.0, 1.0, 1.0, 1.0),对应白色。

++++CG代码中须要提早定义一个新变量,这个变量的名称和类型必须与Properties语义块中的属性定义相匹配,就能够访问它了。

++1.2.6ShaderLab属性类型和CG变量类型的匹配关系

++++ShaderLab属性类型】VSCG变量类型】

++++ColorVectorVSfloat4half4fixed4

++++RangeFloatVSfloathalffixed

++++2DVSsampler2D

++++CubeVSsamplerCube

++++3DVSsampler3D

++++uniform】关键词是CG中修饰变量和参数的一种修饰词,它仅仅用于提供一些关于该变量的初始值是如何指定和存储的相关信息。(在Unity Shader中,uniform关键词是能够省略的。)(这和其余一些图像编程接口中的uniform关键词的做用不太同样。)

++++uniform fixed4 _Color;

++1.2.7Unity提供的内置文件和变量

++++顶点/片元着色的复杂之处在于,不少事情都须要本身转换法线方向,本身处理光照、阴影等。Unity提供了不少的内置文件,这些文件包含了不少提早定义的函数、变量和宏等。

++++内置的包含文件(include file),是相似于C++中头文件的一种文件。(在Unity中,它们的文件后缀是.cginc。)

++++在编写Shader时,咱们能够使用#include指令把这些文件包含进来:

CGPROGRAM

//...

#include UnityCG.cginc

//...

ENDCG

++++能够从官网下载这些文件:【官网】=>【下载】=>【内置着色器】:

 

--CGIncludes】文件夹中包含了全部的内置包含文件;

--DefaultResources】文件夹中包含了一些内置组件或功能所须要的Unity Shader,例如一些GUI元素使用的Shader

--DefaultResourcesExtra】:包含了全部Unity中内置的Unity Shader

--Editor】文件夹目前只包含一个脚本文件,它用于定义Unity5引入的Standard Shader所用的材质面板。

--这些文件都是很是好的参考资料,在咱们想要学习内置着色器的实现或是寻找内置函数的实现时,均可以在这里找到内部实现。

--Windows上的位置是:Unity的安装路径/Data/CGIncludes

++++Unity中一些经常使用的包含文件:UnityCG.cgincUnityShaderVariables.cgincLighting.cgincHLSLSupport.cginc等:

--UnityGC.cginc】:包含了最常使用的帮助函数、宏和结构体等;

--UnityShaderVariables.cginc】:在编译Unity Shader时,会被自动包含进来。包含了许多内置的全局变量,如UNITY_MATRIX_MVP等;

--Lighting.cginc】:包含了各类内置的光照模型,若是编写的是Surface Shader的话,会自动包含进来;

--HLSLSupport.cginc】:在编译Unity Shader时,会被自动包含进来。声明了不少用于跨平台编译的宏和定义。

++++Unity5引入了许多新的重要的包含文件,如UnityStandardBRDF.cgincUnityStandardCore.cginc等,这些包含文件用于实现基于物理的渲染。

++++UnityCG.cginc等文件能够帮助咱们提供代码的复用率。(UnityCG.cginc还包含了不少宏。)

++++Unity还提供了用于访问时间、光照、雾效和环境光等目的的变量,这些内置变量大多位于UnityShaderVariables.cginc中。(与光照有关的内置变量还会位于Lighting.cgincAutoLight.cginc等文件中。)

++1.2.8UnityCG.cginc中一些经常使用的结构体

++++UnityCG.cginc是最经常使用的一个包含文件。(会常用该文件提供的结构体和函数,例如能够直接使用UnityCG.cginc中预约义的结构体做为顶点着色器的输入和输出。)

++++appdata_base】:可用于顶点着色器的输入:包含顶点位置、顶点法线、第一组纹理坐标;

++++appdata_tan】:可用于顶点着色器的输入:包含顶点位置、顶点切线、顶点法线、第一组纹理坐标;

++++appdata_full】:可用于顶点着色器的输入:包含顶点位置、顶点切线、顶点法线、四组(或更多)纹理坐标;

++++appdata_img】:可用于顶点着色器的输入:包含顶点位置、第一组纹理坐标;

++++v2f_img】:可用于顶点着色器的输出:裁剪空间中的位置、纹理坐标;

++++强烈建议找到UnityCG.cginc文件并查看相关结构体的声明,能够帮助咱们更好理解Unity中一些内置变量的工做原理:

//立钻哥哥带咱们剖析UnityCG.cginc

//...\Unityxxx\Editor\Data\CGIncludes\UnityCG.cginc

#ifndef UNITY_CG_INCLUDED

#define UNITY_CG_INCLUDED

 

#define UNITY_PI 3.14159265359f

....

 

#include UnityShaderVariables.cginc

#include UnityInstancing.cginc

 

....

 

struct appdata_base{

    float4 vertex : POSITION;

    float3 normal : NORMAL;

    float4 texcoord : TEXCOORD0;

    UNITY_VERTEX_INPUT_INSTANCE_ID

};

 

struct appdata_tan{

    float4 vertex : POSITION;

    float4 tangent : TANGENT;

    float3 normal : NORMAL;

    float4 texcoord : TEXCOORD0;

    UNITY_VERTEX_INPUT_INSTANCE_ID

};

 

struct appdata_full{

    float4 vertex : POSITION;

    float4 tangent : TANGENT;

    float3 normal : NORMAL;

    float4 texcoord : TEXCOORD0;

    float4 texcoord1 : TEXCOORD1;

    float4 texcoord2 : TEXCOORD2;

    float4 texcoord3 : TEXCOORD3;

    fixed4 color : COLOR;

    UNITY_VERTEX_INPUT_INSTANCE_ID

};

 

...

 

struct v2f_vertex_lit{

    float2 uv : TEXCOORD0;

    fixed4 diff : COLOR0;

    fixed4 spec : COLOR1;

};

 

...

 

struct appdata_img{

    float4 vertex : POSITION;

    half2 texcoord : TEXCOORD0;

};

 

struct v2f_img{

    float4 pos : SV_POSITION;

    half2 uv : TEXCOORD0;

}

 

....

 

#endif  //立钻哥哥:UNITY_CG_INCLUDED

++1.2.9UnityCG.cginc中一些经常使用的帮助函数

++++UnityCG.cginc是最经常使用的一个包含文件。(会常用该文件提供的结构体和函数,例如能够直接使用UnityCG.cginc中预约义的结构体做为顶点着色器的输入和输出。)

++++float3 WorldSpaceViewDir(float4 v)】:输入一个模型空间中的顶点位置,返回世界空间中从该点到摄像机的观察方向;

++++float3 ObjSpaceViewDir(float4 v)】:输入一个模型空间中的顶点位置,返回模型空间中从该点到摄像机的观察方向;

++++float3 WorldSpaceLightDir(float4 v)】:仅可用于前向渲染中。输入一个模型空间中的顶点位置,返回世界空间中从该点到光源的光照方向。没有被归一化。

++++float3 ObjSpaceLightDir(float4 v)】:仅可用于前向渲染中。输入一个模型空间中的顶点位置,返回模型空间中从该点到光源的光照方向。没有被归一化。

++++float3 UnityObjectToWorldNormal(float3 norm)】:把法线方向从模型空间转换到世界空间中;

++++float3 UnityObjectToWorldDir(in float3 dir)】:把方向矢量从模型空间变换到世界空间中;

++++float3 UnityWorldToObjectDir(float3 dir)】:把方向矢量从世界空间变换到模型空间中;

++++强烈建议找到UnityCG.cginc文件并查看相关函数的定义,能够帮助咱们更好理解Unity中这些函数(一些函数咱们彻底是能够本身实现的,好比UnityObjectToWorldDirUnityWorldToObjectDir,这两个函数实际上就是对方向矢量进行了一次坐标空间变换):

//立钻哥哥带咱们剖析UnityCG.cginc

//...\Unityxxx\Editor\Data\CGIncludes\UnityCG.cginc

#ifndef UNITY_CG_INCLUDED

#define UNITY_CG_INCLUDED

 

#define UNITY_PI 3.14159265359f

....

 

#include UnityShaderVariables.cginc

#include UnityInstancing.cginc

 

....

 

inline bool IsGammaSpace(){

    #ifdef UNITY_COLORSPACE_GAMMA

        return true;

    #else

        return false;

    #endif

}

 

....

 

//Computes world space view direction, from object space position

inline float3 WorldSpaceViewDir(in float4 localPos){

    float3 worldPos = mul(unity_ObjectToWorld, localPos).xyz;

    return UnityWorldSpaceViewDir(worldPos);

}

 

//Computes object space view direction

inline float3 ObjSpaceViewDir(in float4 v){

    float3 objSpaceCameraPos = mul(unity_WorldToObject, float4(_WorldSpaceCameraPos.xyz, 1)).xyz;

    return objSpaceCameraPos - v.xyz;

}

 

....

 

//Computes world space light direction, from object space position

inline float3 WorldSpaceLightDir(in float4 localPos){

    float3 worldPos = mul(unity_ObjectToWorld, localPos).xyz;

    return UnityWorldSpaceLightDir(worldPos);

}

 

//Computes object space light direction

inline float3 ObjSpaceLightDir(in float4 v){

    float3 objSpaceLightPos = mul(unity_WorldToObject, _WorldSpaceLightPos0).xyz;

    #ifndef USING_LIGHT_MULTI_COMPILE

        return objSpaceLightPos.xyz - v.xyz * _WorldSpaceLightPos0.w;

    #else

        #ifndef USING_DIRECTIONAL_LIGHT

            return objSpaceLightPos.xyz - v.xyz;

        #else

            return objSpaceLightPos.xyz;

        #endif

    #endif

}

 

....

 

//Transforms normal from object to world space

inline float3 UnityObjectToWorldNormal(in float3 norm){

    #ifdef UNITY_ASSUME_UNIFORM_SCALING

        return UnityObjectToWorldDir(norm);

    #else

        //mul(IT_M, norm)=>mul(norm, I_M)=>{dot(norm, I_M.col0), dot(norm, I_M.col1), dot(norm, I_M.col2)}

        return normalize(mul(norm, (float3x3)unity_WorldToObject));

    #endif

}

 

....

 

//Transforms direction from object to world space

inline float3 UnityObjectToWorldDir(in float3 dir){

    return normalize(mul((float3x3)unity_ObjectToWorld, dir));

}

 

//Transforms direction from world to object space

inline float3 UnityWorldToObjectDir(in float3 dir){

    return normalize(mul((float3x3)unity_WorldToObject, dir));

}

 

....

 

#endif    //立钻哥哥:UNITY_CG_INCLUDED

++1.2.10Unity提供的CG/HLSL语义

++++Shader中常常能够看到:在顶点着色器和片元着色器的输入输出变量后还有一个冒号以及一个所有大写的名称,例如SV_POSITIONPOSITIONCOLOR0等,这些是CG/HLSL提供的语义(semantics

++++语义实际上就是一个赋给Shader输入和输出的字符串,这个字符串表达了这个参数的含义。(这些语义可让Shader知道从哪里读取数据,并把数据输出到哪里,它们在CG/HLSLShader流水线中是不可或缺的。)(注意:Unity并无支持全部的语义。)

++++一种新的语义类型:系统数值语义(system-value semantics,这类语义是以SV开头的。(SV表明的含义就是系统数值(system-value

++++从应用阶段传递模型数据给顶点着色器时Unity支持的经常使用语义:POSITIONNORMALTANGENTTEXCOORDnCOLOR等:

--POSITION】:模型空间中的顶点位置,一般是float4类型;

--NORAML】:顶点法线,一般是float3类型;

--TANGENT】:顶点切线,一般是float4类型;

--TEXCOORDn,如TEXCOORD0TEXCOORD1】:该顶点的纹理坐标,TEXCOORD0表示第一组纹理坐标,依次类推。一般是float3float4类型;(一般状况下,一个模型的纹理坐标组数通常不超过2,即TEXCOORD0TEXCOORD1。在Unity内置的数据结构体appdata_full中,它最多使用了6个坐标纹理组。)

--COLOR】:顶点颜色,一般是fixed4或者float4类型;

++++从顶点着色器传递数据给片元着色器时Unity使用的经常使用语义:SV_POSITIONCOLOR0COLOR1TEXCOORD0~TEXCOORD7等:

--SV_POSITION】:裁剪空间中的顶点坐标,结构体中必须包含一个用该语义修饰的变量。等同于DirectX9中的POSITION,但最好使用SV_POSITION

--COLOR0】:一般用于输出第一组顶点颜色,但不是必需的;

--COLOR1】:一般用于输出第二组顶点颜色,但不是必需的;

--TEXCOORD0~TEXCOORD7】:一般用于输出纹理坐标,但不是必需的;

--上面的语义中,除了SV_POSITION是由特别含义外,其余语义对变量的含义没有明确要求,也就是说,咱们能够存储任意值到这些语义描述的变量中。(一般,若是咱们须要把一些自定义的数据从顶点着色器传递给片元着色器,通常选用TEXCOORD0等)

++++片元着色器输出时Unity支持的经常使用语义:SV_Target

--SV_Target】:输出值将会存储到渲染目标(render target)中。(等同于DirectX9中的COLOR语义,但最好使用SV_Target

++1.2.11、定义复杂的变量类型

++++语义绝大部分用于描述标量或矢量类型的变量,例如fixed2floatfloat4fixed4等。

++++也能够使用语义来修饰不一样类型变量:

struct v2f{

    float4 pos : SV_POSITION;

    fixed3 color0 : COLOR0;

    fixed4 color1 : COLOR1;

    half value0 : TEXCOORD0;

    float2 value2 : TEXCOORD1;

};

++++注意:一个语义能够使用的寄存器只能处理4个浮点值(float)。所以,若是咱们想要定义矩阵类型,如float3X4float4X4等变量就须要使用更多的空间。(一种方法是:把这些变量拆分红多个变量,例如对于float4X4的矩阵类型,咱们能够拆分红4float4类型的变量,每一个变量存储了矩阵中的一行数据。)

++1.2.12Unity Shader调试工具

++++调试(debug)是全部程序员的噩梦,对一个Shader进行调试更是噩梦中的噩梦。

++++使用假彩色图像】:假彩色图像(false-color image指的是用假彩色技术生成的一种图像。(与假彩色图像对应的是照片这种真彩色图像(true-color image)(一张假彩色图像能够用于可视化一些数据。)(主要思想是:咱们能够把须要调试的变量映射到[0,1]之间,把它们做为颜色输出到屏幕上,而后经过屏幕上显示的像素颜色来判断这个值是否正确。这种方法获得的调试信息很模糊,可以获得的信息颇有限,但在很长一段时间内,这种方法的确是惟一的可选方法。)

++++利用神器:Visual Studio】:Visual Studio做为Windows系统下的开发利器,也提供了对Unity Shader的调试功能:Graphics Debugger。(经过Graphics Debugger能够查看每一个像素的最终颜色、位置等信息,还能够对顶点着色器和片元着色器进行单步调试。)

++++最新利器:帧调试器】:Unity5除了带来全新的UI系统外,还给咱们带来了一个新的针对渲染的调试器:帧调试器(Frame Debugger(【Window=>Frame Debugger】)。(与其余调试工具的复杂性相比,Unity原生的帧调试器很是简单快捷。咱们能够使用它来看到游戏图像的某一帧是如何一步步渲染出来的。)(帧调试器能够用于查看渲染该帧时进行的各类渲染事件(event),这些事件包含了Draw Call序列,也包括了相似清空帧缓存等操做。)(Unity5提供的帧调试器实际上并无实现一个真正的帧拾取(frame capture)的功能,而是仅仅使用中止渲染的方法来查看渲染事件的结果。)

++1.2.13、渲染平台的差别

++++Unity的优势之一是其强大的跨平台性:写一份代码能够运行在不少平台上。(绝大多数状况下,Unity为咱们隐藏了这些细节,但有些时候咱们须要本身处理它们。)

++++渲染纹理的坐标差别】:在OpenGL中,(0,0)点对应了屏幕的左下角;在DirectX中,(0,0)点对应了左上角。(若是不采起任何措施的话,就会出现纹理翻转的状况。)(咱们不只能够把渲染结果输出到屏幕上,还能够输出到不一样的渲染目标(Render Target)中。这时,咱们须要使用渲染纹理(Render Texture)来保存这些渲染结果。)

++++Shader的语法差别】:DirectX9/11Shader的语义更加严格。

--报错1incorrect number of arguments to numeric-type constructor(compiling for d3dll)

    ----float4 v = float4(0.0);    //OpenGL平台上,此代码是合法的

    ----float4 v = float4(0.0, 0.0, 0.0, 0.0);   //提供和变量类型相匹配的参数数目

--报错2output parameter o not completely initialized(compiling for d3dll)

    ----表面着色器的顶点函数有一个使用了out修饰符的参数,没有对这个参数的全部成员变量进行初始化。

    ----使用相似下面代码对这些参数进行初始化:

        void vert(inout appdata_full v, out Input o){

            //使用Unity内置的UNITY_INITIALIZE_OUTPUT宏对初始化结构体o

            UNITY_INITIALIZE_OUTPUT(Input, o);

            //....

        }

++++Shader的语义差别】:例如SV_POSITIONPOSITION在某些平台下是等价的,但在另外一些平台上这些语义是不等价的。为了让Shader可以在全部平台上正常工做,咱们应该尽量使用下面的语义来描述Shader的输入输出变量:

--使用SV_POSITION来描述顶点着色器输出的顶点位置。(一些Shader使用了POSITION语义,但这些Shader没法在索尼PS4平台上或使用了细分着色器的状况下正常工做。)

--使用SV_Target来描述片元着色器的输出颜色。(一些Shader使用了COLOR或者COLOR0语义,一样的,这些Shader没法在索尼PS4上正常工做。)

++1.2.14Shader代码规范

++++写出规范的代码让代码变得漂亮易懂,养成良好习惯有助于写出高效的代码。

++++floathalf仍是fixed】:在CG/HLSL中,有3种精度的数据类型:float, halffixed

--float:最高精度的浮点值。一般使用32位来存储。

--half:中等精度的浮点值。一般使用16位来存储,精确范围是:-60000~+60000

--fixed:最低精度的浮点值。一般使用11位来存储,精确范围是:-2.0~+2.0

--大多数现代的桌面GPU会把全部计算按最高的浮点精度进行计算。(也就是说,floathalffixed在这些平台上实际是等价的。)

--在移动平台GPU上,会有不一样的精度范围,不一样精度的浮点值的运算速度会有差别。

--fixed精度实际上只在一些较旧的移动平台上有用,大多数现代的GPU上,它们内部把fixedhalf当成同等精度来对待。

--建议:尽量使用精度较低的类型,由于这能够优化Shader的性能,这一点在移动平台上尤为重要。(能够使用fixed类型来存储颜色和单位矢量,若是要存储更大范围的数据能够选择half类型,最差状况下再选择使用float。)

++++规范语法】:DirectX平台对Shader的语义有更加严格的要求,例如,使用和变量类型相匹配的参数数目来对变量进行初始化。

++++避免没必要要的计算】:若是咱们毫无节制地在Shader(尤为是片元着色器)中进行大量计算,那么很快就会收到Unity的错误提示:

--错误1temporary register limit of 8 exceeded

--错误2Arithmetic instruction limit of 64 exceeded; 65 arithmetic instructions needed to compile program

--出现这些错误信息大可能是由于咱们在Shader中进行了过多的运算,使得须要的临时寄存器数目或指令数目超过了当前可支持的数目。

++++慎用分支和循环语句】:最开始,GPU是不支持在顶点着色器和片元着色器中使用流程控制语句的。随着GPU的发展,能够使用if-elseforwhile这种流程控制指令了。(分支语句在GPU上的实现和CPU上有很大不一样:GPU使用了不一样于CPU的技术来实现分支语句,在最坏的状况下,花在一个分支语句的时间至关于运行全部分支语句的时间。所以,不鼓励在Shader中使用流程控制语句,由于它们会下降GPU的并行处理操做(尽管在现代的GPU上已经有了改进))(若是咱们在Shader中使用了大量的流程控制语句,那么这个Shader的性能可能会成倍降低。咱们应该尽可能把计算向流水线上端移动,例如把放在片元着色器中的计算放到顶点着色器中,或者直接在CPU中进行预计算,再把结果传递给Shader。)(在不可避免使用分支语句进行计算时:第一,分支判断语句中使用的条件变量最好是常数,即在Shader运行过程当中不会发生变化;第二,每一个分支包含的操做指令数尽量少;第三,分支的嵌套层数尽量少。)

++++不要除以0】:在某些渲染平台上,除以0的代码不会形成Shader的崩溃,但获得的结果也是不肯定的;在另外一些平台上可能会直接崩溃。(对那些除数可能为0的状况,强制截取到非0范围。)

++1.2.15Shader Model

++++Shader Model是由微软提出的一套规范,Shader Model决定了Shader中各个特性(feature)的能力(capability)。这些特性和能力体如今Shader能使用的运算指令数目、寄存器个数等各个方面。(Shader Model等级越高,Shader的能力就越大。)

++++虽然更高等级的Shader Target可让咱们使用更多的临时寄存器和运算指令,但一个更好的方法是尽量减小Shader中的运算,或者经过预计算的方式来提供更多的数据。

++++Unity支持的Shader Target

--#pragma target 2.0】:默认的Shader Target等级。至关于Direct3D9上的Shader Model 2.0

--#pragma target 3.0】:至关于Direct3D9上的Shader Model 3.0

--#pragma target 4.0】:至关于Direct3D10上的Shader Model4.0,目前只在DirectX11XboxOne/PS4平台上提供了支持;

--#pragma target 5.0】:至关于Direct3D11上的Shader Model5.0,目前只在DirectX11XboxOne/PS4平台上提供了支持;




###2.2Unity中的基础光照

++2.2Unity中的基础光照

++++渲染包含了两大部分:决定一个像素的可见性,决定这个像素上的光照计算。(光照模型就是用于决定在一个像素上进行怎样的光照计算。)

++++红色物体其实是由于这个物体会反射更多的红光波长,而吸取了其余波长。(黑色物体其实是由于它吸取了绝大部分的波长。)

++++光源:在实时渲染中,咱们一般把光源当成一个没有体积的点。(在光学里,使用辐照度(irradiance来量化光。)

++++吸取和散射:光线由光源发射出来后,会与物体相交的结果:散射(scattering吸取(absorption等。(散射只改变光线的方向,但不改变光线的密度和颜色。)(吸取只改变光线的密度和颜色,不改变光线的方向。)(光线在物体表面通过散射后,有两种方向:一种将会散射到物体内部,这种现象称为折射(refraction透射(transmission;另外一种将会散射到外部,这种现象称为反射(reflection。)

++++在光照模型中使用不一样的部分来计算散射方向:高光反射(specular部分表示物体表面是如何反射光线的,而漫反射(diffuse部分则表示有多少光线会被折射、吸取和散射出表面。

++++根据入射光线的数量和方向,能够计算出射光线的数量和方向,一般使用出射度(exitance来描述它。(辐照度和出射度之间是知足线性关系的,而它们之间的比值就是材质的漫反射和高光反射属性。)

++2.2.1、着色(shading

++++着色(shading指的是,根据材质属性(如漫反射属性等)、光源信息(如光源方向、辐照度等),使用一个等式去计算沿某个观察方向的出射度的过程。

++++咱们把这个等式称为光照模型(Lighting Model。不一样的光照模型有不一样的目的。(例如,一些用于描述粗糙的物体表面,一些用于描述金属表面等。)

++2.2.2BRDF光照模型

++++BRDFBidirectional Reflectance Distribution Function双向反射分布函数。(它用来定义给定入射方向上的辐射照度(irradiance)如何影响给定出射方向上的辐射率(radiance)。更笼统地说,它描述了入射光线通过某个表面反射后如何在各个出射方向上分布:这能够是从理想镜面反射到漫反射、各向同性(isotropic)或者各向异性(anisotropic)的各类反射。)

++++当给定模型表面上的一个点时,BRDF包含了对该点外观的完整的描述。

++++在图形学中,BRDF大多使用一个数学公式来表示,而且提供了一些参数来调整材质属性。(通俗来讲,当给定入射光线的方向盒辐照度后,BRDF能够给出在某个出射方向上的光照能量分布。)

++2.2.3、标准光照模型

++++标准光照模型只关心直接光照(direct light),也就是那些直接从光源发射出来照射到物体表面后,通过物体表面的一次反射直接进入射线机的光线。

++++标准光照模型的基本方法,把进入到摄像机内的光线分为4个部分,每一个部分使用一种方法来计算它的贡献度。这4个部分是:自发光(emissive部分、高光反射(specular部分、漫反射(diffuse部分、环境光(ambient部分。

--自发光(emissive部分】:这个部分用来描述当给定一个方向时,一个表面自己会向该方向发射多少辐射量。(注意:若是没有使用全局光照(global illumination)技术,这些自发光的表面并不会真的照亮周围的物体,而是它自己看起来更亮了而已。)

--高光反射(specular部分】:这个部分用于描述当光线从光源照射到模型表面时,该表面会在彻底镜面反射方向散射多少辐射量。

--漫反射(diffuse部分】:这个部分用于描述,当光线从光源照射到模型表面时,该表面会向每一个方向散射多少辐射量。

--环境光(ambient部分】:它用于描述其余全部的间接光照。

++2.2.4、环境光

++++虽然标准光照模型的重点在于描述直接光照,但在真实的世界中,物体也能够被间接光照(indirect light)所照亮。

++++间接光照指的是,光线一般会在多个物体之间反射,最后进入摄像机,也就是说,在光线进入摄像机以前,通过了不止一次的物体反射。

++++在标准光照模型中,咱们使用了一种被称为环境光的部分来近似模拟间接光照。(环境光的计算很是简单,它一般是一个全局变量,即场景中的全部物体都使用这个环境光。)

++2.2.5、自发光

++++光线也能够直接由光源发射进入摄像机,而不须要通过任何物体的反射。(标准光照模型使用自发光来计算这个部分的贡献度。)

++++一般在实时渲染中,自发光的表面每每并不会照亮周围的表面,也就是说,这个物体并不会被当成一个光源。(Unity5引入了全新的全局光照系统则能够模拟这类自发光物体对周围物体的影响。)

++2.2.6、漫反射

+++++漫反射光照是用于对那些被表面随机散射到各个方向的辐射度进行建模的。

++++在漫反射中,视角的位置是不重要的,由于反射是彻底随机的,所以能够认为在任何反射方向上的分布都是同样的。(入射光线的角度很重要。)

++2.2.7、高光反射

++++这里的高光反射是一种经验模型,它并不彻底符合真实世界中的高光反射现象。

++++高光反射可用于计算那些沿着彻底镜面反射方向被反射的光线,这可让物体看起来是有光泽的,例如金属材质。

++++计算高光反射须要知道的信息比较多,如表面法线、视角方向、光源方向、反射方向等。

++2.2.8、逐像素仍是逐顶点

++++基本光照模型使用的数学公式:逐像素光照、逐顶点光照。

++++逐顶点光照(per-vertex lighting】:在顶点着色器中计算。(逐顶点光照,也称为高洛德(Gouraud shading)。)(在逐顶点光照中,咱们在每一个顶点上计算光照,而后会在渲染图元内部进行线性插值,最后输出成像素颜色。)

++++逐像素光照(per-pixel lighting】:在片元着色器中计算。(在逐像素光照中,咱们会以每一个像素为基础,获得它的法线(能够是顶点法线插值获得的,也能够是从法线纹理中采样获得的),而后进行光照模型的计算。)(这种在面片之间对顶点法线进行的插值的技术被称为Phong着色(Phong shading,也被称为Phong插值或法线插值着色技术。)

++2.2.9Unity中的环境光和自发光

++++在标准光照模型中,环境光和自发光的计算是最简单的。

++++Unity场景中的环境光能够在【Window=>Lighting=>Ambient Source/Ambient Color/Ambient Intensity】中控制。

++++Shader中,咱们只须要经过Unity的内置变量UNITY_LIGHTMODEL_AMBIENT就能够获得环境光的颜色和强度信息。

++++大多数物体是没有自发光特性的,若是要计算自发光只须要在片元着色器输出最后的颜色以前,把材质的自发光颜色添加到输出颜色上便可。

++2.2.10、逐顶点光照的实践:逐顶点的漫反射光照效果

++++咱们来看如何实现一个逐顶点的漫反射光照效果。

//立钻哥哥:逐顶点的漫反射光照效果

Shader YanlzShaderDemo/DiffuseVertexLevel_ShaderDemo01{

    Properites{

        _Diffuse(Diffuse, Color) = (1,1,1,1)

    }

 

    SubShader{

        Pass{

            Tags{  LightMode=ForwardBase  }

        

            CGPROGRAM

            #pragma vertex vert

            #pragma fragment frag

            #include Lighting.cginc

 

            fixed4 _Diffuse;

 

            struct a2v{

                float4 vertex : POSITION;

                float3 normal : NORMAL;

            };

 

            struct v2f{

                float4 pos : SV_POSITION;

                fixed3 color : COLOR;

            };

 

            v2f vert(a2v v){

                v2f o;

                o.pos = mul(UNITY_MATRIX_MVP, v.vertex);

    

                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;

                fixed3 worldNormal = normalize(mul(v.normal, (float3x3)unity_WorldToObject));

                fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);

                fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNorml, worldLight));

                o.color = ambient + diffuse;

    

                return o;

            }

 

            fixed4 frag(v2f i) : SV_Target{

                return fixed4(i.color, 1.0);

            }

 

            ENGCG

        }

   }

 

    FallBack Diffuse

}

++++为了获得而且控制材质的漫反射颜色,在ShaderProperties语义块中声明了一个Color类型的属性,并把它的初始值设为白色:【_Diffuse (Diffuse, Color) = (1,1,1,1)

++++SubShader语义块中定义了一个Pass语义块。(顶点/片元着色器的代码须要写在Pass语义块,而非SubShader语义块中。)

++++Pass的第一行指明了该Pass的光照模式:【Tags { LightMode = ForwardBase }

--LightMode标签是用于定义该PassUnity的光照流水线中的角色,只有定义了正确的LightMode,才能获得一些Unity的内置光照变量。

++++使用CGPROGRAMENDCG来包围CG代码片断,以定义最重要的顶点着色器和片元着色器代码。(使用#pragma指令来告诉Unity,咱们定义的顶点着色器和片元着色器叫什么名字。)

++++为了使用Unity内置的一些变量,须要包含内置文件Lighting.cginc:【#include Lighting.cginc

++++为了在Shader中使用Properties语义块中声明的属性,咱们须要定义一个和该属性类型相匹配的变量:【fixed4 _Diffuse】:经过这种方式,咱们就能够获得漫反射公式中须要的参数之一:材质的漫反射属性。(因为颜色属性的范围在01之间,所以咱们能够使用fixed精度的变量来存储它。)

++++定义顶点着色器的输入和输出结构体(输出结构体同时也是片元着色器的输入结构体):【struct a2v{ };】、【struct v2f{ };

struct a2v{

    float4 vertex : POSITION;

    float3 normal : NORMAL;

};

 

struct v2f{

    float4 pos : SV_POSITION;

    fixed3 color : COLOR;

};

--为了访问顶点的法线,须要在a2v中定义一个normal变量,并经过使用NORMAL语义来告诉Unity要把模型顶点的法线信息存储到normal变量中。

--为了把在顶点着色器中计算获得的光照颜色传递给片元着色器,咱们须要在v2f中定义一个color变量,且并非必须使用COLOR语义,一些资料中会使用TEXCOORD0语义。

++++顶点着色器实现一个逐顶点的漫反射光照(漫反射部分的计算都将在顶点着色器中进行):

v2f vert(a2v v){

    v2f o;

    o.pos = mul(UNITY_MATRIX_MVP, v.vertex);

 

    fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;

    fixed3 worldNormal = normalize(mul(v.normal, (float3x3)_World2Object));

    fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);

    fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLight));

    o.color = ambient + diffuse;

 

    return o;

}

--首先定义返回值o:【v2f o;

--顶点着色器最基本的任务就是把顶点位置从模型空间转换到裁剪空间中,所以咱们须要使用Unity内置的模型*世界*投影矩阵UNITY_MATRIX_MVP来完成这样的坐标变换。

--经过Unity内置变量UNITY_LIGHTMODEL_AMBIENT获得了环境光部分。

--材质的漫反射颜色_Diffuse以及顶点法线v.normal

--Unity提供了一个内置变量_LightColor0来访问该Pass处理的光源和强度信息以及光源方向(想要获得正确的值须要定义合适的LightMode标签)。

--光源方向能够由_WorldSpaceLightPos0来获得。(注意:若是场景中有多个光源而且类型多是点光源等其余类型,直接使用_WorldSpaceLightPos0就不能获得正确的结果。)

--在计算法线和光源方向之间的点积时,咱们须要选择它们所在的坐标系,只有二者处于同一坐标系空间下,它们的点积才有意义。(咱们选择了世界坐标空间,由a2v获得的顶点法线是位于模型空间下的,所以咱们首先须要把法线转换到世界空间中。)(能够使用顶点变换矩阵的逆转置矩阵对法线进行相同的变换,首先获得模型空间到世界空间的变换矩阵的逆矩阵_World2Object,而后经过调换它在mul函数中的位置,获得和转置矩阵相同的矩阵乘法。)(因为法线是一个三维矢量,须要截取_World2Object的前三行前三列便可。)

--在获得了世界空间中的法线和光源受,咱们须要对它进行归一化操做。(在获得它们点积的结果后,须要防止这个结果为负值,使用saturate函数。)(saturate函数是CG提供的一种函数,它的做用是能够把参数截取到[0,1]的范围内。)

--光源的颜色和强度以及材质的漫反射颜色相乘便可获得最终的漫反射光照部分。

--对环境光和漫反射光部分相加,获得最终的光照结果。

++++全部的计算在顶点着色器中都已经完成了,片元着色器的代码只须要直接把顶点颜色输出便可:

fixed4 frag(v2f i) : SV_Target{

    return fixed4(i.color, 1.0);

}

++++UnityShader的回调shader设置为内置的Diffuse:【Fallback Diffuse

++++逐顶点的漫反射光照,对于细分程度较高的模型,逐顶点光照已经能够获得比较好的光照效果了。但对于一些细分程序较低的模型,逐顶点光照就会出现一些视觉问题:例如背光面与向光面交界处有一些锯齿等,为了解决这些问题,就能够使用逐像素的漫反射光照。

++2.2.11、逐像素光照实践:逐像素的漫反射光照效果

++++逐像素的漫反射效果。

//立钻哥哥:逐像素的漫反射光照效果

Shader YanlzShaderDemo/DiffusePixelLevel_ShaderDemo02{

    Properties{

        _Diffuse(Diffuse, Color) = (1,1,1,1)

    }

 

    SubShader{

        Pass{

            Tags{  LightMode=ForwardBase  }

 

            CGPROGRAM

            #pragma vertex vert

            #pragma fragment frag

            #include Lighting.cginc

 

            fixed4 _Diffuse;

 

            struct a2v{

                float4 vertex : POSITION;

                float3 normal : NORMAL;

            };

 

            struct v2f{

                float4 pos : SV_POSITION;

                float3 worldNormal : TEXCOORD0;

            };

 

            v2f vert(a2v v){

                v2f o;

                o.pos = mul(UNITY_MATRIX_MVP, v.vertex);

                o.worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);

 

                return o;

            }

 

            fixed4 frag(v2f i) : SV_Target{

                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;

                fixed3 worldNormal = normalize(i.worldNormal);

                fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);

                fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLightDir));

                fixed3 color = ambient + diffuse;

 

                return fixed4(color, 1.0);

            }

 

            ENDCG

        }

    }

 

    FallBack Diffuse

}

++++顶点着色器不须要计算光照模型,只须要把世界空间下的法线传递给片元着色器便可。

++++逐像素光照能够获得更加平滑的光照效果。(可是,即使使用了逐像素漫反射光照,有一个问题依然存在:当光照没法达到的区域,模型的外观一般是全黑的,没有任何明暗变化,这会使背光区域看起来就像一个平面同样,失去了模型细节表现。)

++++能够经过添加环境光来获得非全黑的效果,但即使这样仍然没法解决背光面明暗同样的缺点。

++++有一种改善技术被提出来,这就是半兰伯特(Half Lambert)光照模型

++2.2.12、半兰伯特模型实践:半兰伯特漫反射光照效果

++++漫反射光照模型也被称为兰伯特光照模型,由于它符合兰伯特定律:在平面某点漫反射光的光强与该反射点的法向量和入射光角度的余弦值成正比。

++++半兰伯特是没有任何物理依据的,它仅仅是一个视觉增强技术。

//立钻哥哥:半兰伯特漫反射光照

Shader YanlzShaderDemo/HalfLambert_ShaderDemo03{

    Properties{

        _Diffuse(Diffuse, Color) = (1,1,1,1)

    }

 

    SubShader{

        Pass{

            Tags{  LightMode=ForwardBase  }

 

            CGPROGRAM

            #pragma vertex vert

            #pragma fragment frag

            #include Lighting.cginc

        

            fixed4 _Diffuse;

 

            struct a2v{

                float4 vertex : POSITION;

                float3 normal : NORMAL;

            };

 

            struct v2f{

                float4 pos : SV_POSITION;

                float3 worldNormal : TEXCOORD0;

            };

 

            v2f vert(a2v v){

                float4 pos : SV_POSITION;

                float3 worldNormal : TEXCOORD0;

            }

 

            v2f vert(a2v v){

                v2f o;

                o.pos = mul(UNITY_MATRIX_MVP, v.vertex);

                o.worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);

 

                return o;

            }

 

            fixed4 frag(v2f i) : SV_Target{

                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;

                fixed3 worldNormal = normalize(i.worldNormal);

                fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);

                fixed3 halfLambert = dot(worldNormal, worldLightDir) * 0.5 + 0.5;

                fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * halfLambert;

                fixed3 color = ambient + diffuse;

 

                return fixed4(color, 1.0);

            }

 

            ENGCG

        }

    }

 

    FallBack Diffuse

}

++++使用半兰伯特公式修改片元着色器中计算漫反射光照的部分。

++2.2.13、漫反射光照对比

++++逐顶点漫反射光照、逐像素漫反射光照、半兰伯特光照的对比效果:


++2.2.14、逐顶点光照实践:逐顶点的高光反射光照效果

++++实现一个逐顶点的高光反射效果。

//立钻哥哥:逐顶点的高光反射效果

Shader YanlzShaderDemo/SpecularVertexLevel_ShaderDemo01{

    Properties{

        _Diffuse (Diffuse, Color) = (1,1,1,1)

        _Specular (Specular, Color) = (1,1,1,1)

        _Gloss (Gloss, Range(8.0, 256)) = 20

    }

 

    SubShader{

        Pass{

            Tags{  LightMode=ForwardBase }

 

            CGPROGRAM

            #pragma vertex vert

            #pragma fragment frag

            #include Lighting.cginc

 

            fixed4 _Diffuse;

            fixed4 _Specular;

            float _Gloss;

 

            struct a2v{

                float4 vertex : POSITION;

                float3 normal : NORMAL;

            };

 

            struct v2f{

                float4 pos : SV_POSITION;

                fixed3 color : COLOR;

            };

 

            v2f vert(a2v v){

                v2f o;

                o.pos = mul(UNITY_MATRIX_MVP, v.vertex);

    

                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;

                fixed3 worldNormal = normalize(mul(v.normal, (float3x3)unity_WorldToObject));

                fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);

                fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLightDir));

                fixed3 reflectDir = normalize(reflect(-worldLightDir, worldNormal));

                fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - mul(unity_ObjectToWorld, v.vertex).xyz);

                fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(reflectDir, viewDir)), _Gloss);

 

                o.color = ambient + diffuse + specular;

 

                return o;

            }

 

            fixed4 frag(v2f i) : SV_Target{

                return fixed4(i.color, 1.0);

            }

 

           ENGCG

        }

    }

 

    FallBack Specular

}

++++为了在材质面板上可以方便地控制高光反射属性,在ShaderProperties语义中声明了三个属性:_Diffuse_Specular_Gloss等。(新添加的_Specular用于控制材质的高光反射颜色)(新增的_Gloss用于控制高光区域的大小)

++++SubShader语义块中定义了一个Pass语义块。定义了光照模式:【Tags { LightMode=ForwardBase }

++++对于高光反射部分,咱们首先计算了入射光线方向关于表面法线的反射方向reflectDir。(因为CGreflect函数的入射方向要求是由光源指向交点处的,所以咱们须要对worldLightDir取反后再传给reflect函数。)

++++咱们经过_WorldSpaceCameraPos获得了世界空间中的摄像机位置,再把顶点位置从模型空间变换到世界空间下,再经过和_WorldSpaceCameraPos相减便可获得世界空间下的视角方向。

++++使用逐顶点的方法获得的高光效果有比较大的问题,高光部分明显不平滑。(是由于:高光反射部分的计算是非线性的,而在顶点着色器中计算光照再进行插值的过程是线性的,破坏了原计算的非线性关系,就会出现较大的视觉问题。)=>所以,咱们就须要使用逐像素的方法来计算高光反射。

++2.2.15、逐像素光照实践:逐像素的高光反射光照效果

++++能够使用逐像素光照来获得更加平滑的高光效果。

//立钻哥哥:逐像素的高光反射光照

Shader YanlzShaderDemo/SpecularPixelLevel_ShaderDemo02{

    Properties{

        _Diffuse (Diffuse, Color) = (1,1,1,1)

        _Specular (Specular, Color) = (1,1,1,1)

        _Gloss (Gloss, Range(8.0, 256)) = 20

    }

 

    SubShader{

        Pass{

            Tags {  LightMode=ForwardBase }

 

            CGPROGRAM

            #pragma vertex vert

            #pragma fragment frag

            #include Lighting.cginc

 

            fixed4 _Diffuse;

            fixed4 _Specular;

            float _Gloss;

 

            struct a2v{

                float4 vertex : POSITION;

                float3 normal : NORMAL;

            };

 

            struct v2f{

                float4 pos : SV_POSITION;

                float3 worldNormal : TEXCOORD0;

                float3 worldPos : TEXCOORD1;

            };

 

            v2f vert(a2v v){

                v2f o;

                o.pos = mul(UNITY_MATRIX_MVP, v.vertex);

                o.worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);

                o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;

 

                return o;

            }

 

            fixed4 frag(v2f i) : SV_Target{

                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;

                fixed3 worldNormal = normalize(i.worldNormal);

                fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);

                fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLightDir));

                fixed3 reflectDir = normalize(reflect(-worldLightDir, worldNormal));

                fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);

                fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(reflectDir, viewDir)), _Gloss);

 

                 return fixed4(ambient + diffuse + specular, 1.0);

            }

 

            ENGCG

        }

    }

 

    FallBack Specular

}

++++顶点着色器只须要计算世界空间下的法线方向和顶点坐标,并把它们传递给片元着色器便可。

++++片元着色器须要计算关键的光照模型:【fixed4 frag(v2f i) : SV_Target{  }

++++按逐像素的方式处理光照能够获得更加平滑的高光效果。=>能够实现一个完整的Phong光照模型。

++2.2.16Blinn-Phong光照模型

++++另外一种高光反射的实现方法:Blinn光照模型。

//立钻哥哥:Blinn-Phong高光反射光照

Shader YanlzShaderDemo/BlinnPhong_ShaderDemo03{

    Properties{

        _Diffuse (Diffuse, Color) = (1,1,1,1)

        _Specular (Specular, Color) = (1,1,1,1)

        _Gloss (Gloss, Range(8.0, 256)) = 20

    }

 

    SubShader{

        Pass{

            Tags {  LightMode=ForwardBase  }

  

            CGPROGRAM

            #pragma vertex vert

            #pragma fragment frag

            #include Lighting.cginc

      

            fixed4 _Diffuse;

            fixed4 _Specular;

            float _Gloss;

 

            struct a2v{

                float4 vertex : POSITION;

                float3 normal : NORMAL;

            };

 

             struct v2f{

                float4 pos : SV_POSITION;

                float3 worldNormal : TEXCOORD0;

                float3 worldPos : TEXCOORD1;

             };

 

            v2f vert(a2v v){

                v2f o;

                o.pos = mul(UNITY_MATRIX_MVP, v.vertex);

                o.worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);

                o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;

 

                return o;

             }

 

            fixed4 frag(v2f i) : SV_Target{

                 fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;

                 fixed3 worldNormal = normalize(i.worldNormal);

                 fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);

                 fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(worldNormal, worldLightDir));

                 fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);

                 fixed3 halfDir = normalize(worldLightDir + viewDir);

                 fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);

 

                 return fixed4(ambient + diffuse + specular, 1.0);

             }

 

            ENDCG

        }

    }

 

    FallBack Specular

}

++++Blinn-Phong光照模型的高光反射部分看起来更大、更亮一些。(在实际渲染中,绝大多数状况咱们都会选择Blinn-Phong光照模型。)

++++说明:这两种光照模型都是经验模型,不该该认为Blinn-Phong模型是对“正确的”Phong模型的近似。(实际上,在一些状况下,Blinn-Phong模型更符合实验结果。)

2.2.17、高光反射光照对比

++++逐顶点的高光反射光照、逐像素的高光反射光照(Phong光照模型)、Blinn-Phong高光反射光照的对比参考:


++2.2.18、使用Unity内置的函数

++++在计算光照模型的时候,须要获得光源方向、视角方向这两个基本信息。

++++使用normalize(_WorldSpaceLightPos0.xyz)来获得光源方向:这种方法实际只适用于平行光。

++++使用normalize(_WorldSpaceCameraPos.xyz - i.worldPosition.xyz)来获得视角方向。

++++手动计算这些光源信息的过程相对比较麻烦(但并不意味着须要了解它们的原理),Unity提供了一些内置函数来帮助咱们计算这些信息。

++++UnityCG.cginc中一些经常使用的帮助函数:

--float3 WorldSpceViewDir(float4 v)】:输入一个模型空间中的顶点位置,返回世界空间中从该点到摄像机的观察方向。内部实现使用了UnityWorldSpaceViewDir函数。

--float3 UnityWorldSpaceViewDir(float4 v)】:输入一个世界空间中的顶点位置,返回世界空间中从该点到摄像机的观察方向。

--float3 ObjSpaceViewDir(float4 v)】:输入一个模型空间中的顶点位置,返回模型空间中从该点到摄像机的观察方向。

--float3 WorldSpaceLightDir(float4 v)】:仅可用于前向渲染中,输入一个模型空间中的顶点位置,返回世界空间中从该点到光源的光照方向。内部实现使用了UnityWorldSpaceLightDir函数。没有被归一化。

--float3 UnityWorldSpaceLightDir(float4 v)】:仅可用于前向渲染中,输入一个世界空间中的顶点位置,返回世界空间中从该点到光源的光照方向。没有被归一化。

--float3 ObjSpaceLightDir(float4 v)】:仅可用于前向渲染中。输入一个模型空间中的顶点位置,返回模型空间中从该点到光源的光照方向。没有被归一化。

--float3 UnityObjectToWorldNormal(float3 norm)】:把法线方向从模型空间转换到世界空间中。

--float3 UnityObjectToWorldDir(in float3 dir)】:把方向矢量从模型空间变换到世界空间中。

--float3 UnityWorldToObjectDir(float3 dir)】:把方向矢量从世界空间变换到模型空间中。

++++相似UnityXXX的几个函数是Unity5中新添加的内置函数。(这些帮助函数使得咱们不须要跟各类变换矩阵、内置变量打交道,也不须要考虑各类不一样的状况(例如使用了哪一种光源),而仅仅调用一个函数就能够获得须要的信息。)

++++说明:这些函数都没有保证获得的方向矢量是单位矢量,所以,咱们须要在使用前把它们归一化。

++++计算光源方向的3个函数:WorldSpaceLightDirUnityWorldSpaceLightDirObjSpaceLightDir,稍微复杂一些,是由于:Unity帮咱们处理了不一样种类光源的状况。(这3个函数仅可用于前向渲染,这是由于只有在前向渲染时,这3个函数里使用的内置变量_WorldSpaceLightPos0等才会被正确赋值。)

++2.2.19、使用Unity内置函数改写Blinn-Phong高光反射光照

++++在实际编写过程当中,每每会借助于Unity的内置函数来帮助咱们进行各类计算,这能够减轻很多咱们的“痛苦”。

//立钻哥哥:使用Unity内置函数的Blinn-Phong高光反射光照

Shader YanlzShaderDemo/BlinnPhongUseBuiltInFunctions_ShaderDemo04{

    Properties{

        _Diffuse (Diffuse, Color) = (1,1,1,1)

        _Specular (Specular, Color) = (1,1,1,1)

        _Gloss (Gloss, Range(1.0, 500)) = 20

    }

 

    SubShader{

        Pass{

            Tags {  LightMode=ForwardBase  }

        

            CGPROGRAM

            #pragma vertex vert

            #pragma fragment frag

            #include Lighting.cginc

 

            fixed4 _Diffuse;

            fixed4 _Specular;

            float _Gloss;

 

            struct a2v{

                float4 vertex : POSITION;

                float3 normal : NORMAL;

            };

 

            struct v2f{

                 float4 pos : SV_POSITION;

                float3 worldNormal : TEXCOORD0;

                float4 worldPos : TEXCOORD1;

            };

 

            v2f vert(a2v v){

                v2f o;

                o.pos = mul(UNITY_MATRIX_MVP, v.vertex);

                o.worldNormal = UnityObjectToWorldNormal(v.normal);

                o.worldPos = mul(unity_ObjectToWorld, v.vertex);

 

                return o;

            }

 

             fixed4 frag(v2f i) : SV_Target{

                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;

                fixed3 worldNormal = normalize(i.worldNormal);

                fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));

                fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(worldNormal, worldLightDir));

                fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));

                fixed3 halfDir = normalize(worldLightDir + viewDir);

                fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);

 

                return fixed4(ambient + diffuse + specular, 1.0);

            }

 

            ENGCG

        }

    }

 

    FallBack Specular

}

++++在顶点着色器中,咱们使用内置的UnityObjectToWorldNormal函数计算世界空间下的法线方向。

++++在片元着色器中,咱们使用内置的UnityWorldSpaceLightDir函数和UnityWorldSpaceViewDir函数来分别计算世界空间的光照方向和视角方向。

++++说明:内置函数获得的方向是没有归一化的,所以须要使用normalize函数来对结果进行归一化,在进行光照模型的计算。





###2.3、基础纹理

++2.3、基础纹理

++++纹理最初的目的就是使用一张图片来控制模型的外观。(使用纹理映射(texture mapping)技术,咱们能够把一张图“黏”在模型表面,逐纹素(texel地控制模型的颜色。)

++++在美术人员建模的时候,一般会在建模软件中利用纹理展开技术把纹理映射坐标(texture-mapping coordinates存储在每一个顶点上。(纹理映射坐标定义了该顶点在纹理中对应的2D坐标。)(一般,这些坐标使用一个二维变量(u,v)来表示,其中u是横向坐标,而v是纵向坐标。所以,纹理映射坐标也被称为UV坐标)

++++纹理的大小能够是多种多样的,但顶点UV坐标的范围一般被归一化到[0,1]范围内。(纹理采样时使用的纹理坐标不必定是在[0,1]范围内。)

++2.3.1、单张纹理实践:Blinn-Phong光照模型来计算光照

++++一般会使用一张纹理来代替物体的漫反射颜色。

//立钻哥哥:使用单张纹理

Shader YanlzShaderDemo/SingleTexture_ShaderDemo01{

    Properties{

        _Color (Color Tint, Color) = (1,1,1,1)

        _MainTex (Main Tex, 2D) = white{}

        _Specular (Specular, Color) = (1,1,1,1)

        _Gloss (Gloss, Range(8.0, 256)) = 20

    }

 

    SubShader{

        Pass{

            Tags {  LightMode=ForwardBase }

 

            CGPROGRAM

            #pragma vertex vert

            #pragma fragment frag

            #include Lighting.cginc

 

            fixed4 _Color;

            sampler2D _MainTex;

            float4 _MainTex_ST;

            fixed4 _Specular;

            float _Gloss;

 

            struct a2v{

                float4 vertex : POSITION;

                float3 normal : NORMAL;

                float4 texcoord : TEXCOORD0;

            };

 

            struct v2f{

                float4 pos : SV_POSITION;

                float3 worldNormal : TEXCOORD0;

                float3 worldPos : TEXCOORD1;

                float2 uv : TEXCOORD2;

            };

 

            v2f vert(a2v v){

                v2f o;

                o.pos = mul(UNITY_MATRIX_MVP, v.vertex);

                o.worldNormal = UnityObjectToWorldNormal(v.normal);

                o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;

                o.uv = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;

                //o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);   //built-in function

 

               return o;

            }

 

             fixed4 frag(v2f i) : SV_Target{

                fixed3 worldNormal = normalize(i.worldNormal);

                fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));

                fixed3 albedo = tex2D(_MainTex, i.uv).rgb * _Color.rgb;

                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;

                fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(worldNormal, worldLightDir));

                fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));

                fixed3 halfDir = normalize(worldLightDir + viewDir);

                fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);

 

                return fixed4(ambient +diffuse + specular, 1.0);

            }

 

            ENDCG

        }

    }

 

    FallBack Specular

}

++++为了使用纹理,须要在Properties语义块中添加纹理属性:_Color_MainTex_Specular_Gloss等。(【_MainTex (Main Tex, 2D) = white { }】:2D是纹理属性的声明方式。使用一个字符串后跟一个花括号做为它的初始值,“white”是内置纹理的名字,也就是一个全白的纹理。)(为了控制物体的总体色调,声明一个_Color属性:【_Color (Color Tint, Color) = (1,1,1,1)】)

++++为纹理类型的属性声明一个float4类型的变量_MainTex_ST:【float4 _MainTex_ST;】:在Unity中使用 纹理名_ST的方式来声明某个纹理的属性。(ST缩放(scale平移(translation的缩写。)

++++_MainTex_ST】:可让咱们获得该纹理的缩放和平移(偏移)值,_MainTex_ST.xy存储的是缩放值(Tilling),_MainTex_ST.zw存储的是偏移值(Offset)。

++++a2v结构体中使用TEXCOORD0语义声明一个新的变量texcoord(【float4 texcoord : TEXCOORD0;】):Unity就会将模型的第一组纹理坐标存储到该变量中。

++++v2f结构体中添加了用于存储纹理坐标的变量uv(【float2 uv : TEXCOORD2;】),以便在片元着色器中使用该坐标进行纹理采样。

++++在顶点着色器中,使用纹理的属性值_MainTex_ST来对顶点纹理坐标进行变换,获得最终的纹理坐标。(计算过程是:首先使用缩放属性_MainTex_ST.xy对顶点纹理坐标进行缩放,而后再使用偏移属性_MainTex_ST.zw对结果进行偏移。)

++++Unity提供了一个内置宏TRANSFORM_TEX来帮咱们计算上述过程:【o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);

++++TRANSFORM_TEXUnityCG.cginc中的定义:

//立钻哥哥:...\UnityCG.cginc

//Transforms 2D UV by scale/bias property

#define TRANSFORM_TEX(tex, name) (tex.xy * name##_ST.xy + name##_ST.zw)

++++TRANSFORM_TEX(tex, name)】:接受两个参数,第一个参数是顶点纹理坐标,第二个参数是纹理名,它的实现中,利用纹理名_ST的方式来计算变换后的纹理坐标。

++++实现片元着色器,并在计算漫反射时使用纹理中的纹素值。(首先计算世界空间下的法线方向和光照方向。而后,使用CGtex2D函数对纹理进行采样。【tex2D(_MainTex, i.uv)】:第一个参数是须要被采样的纹理,第二个参数是一个float2类型的纹理坐标,它将返回计算获得的纹素值。)(使用采样结果和颜色属性_Color的乘积来做为材质的反射率albedo:【fixed3 albedo = tex2D(_MainTex, i.uv).rgb * _Color.rgb;】,并把发射率albedo和环境光照相乘获得环境光部分:【fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;】)

++2.3.2、纹理的属性

++++Unity的纹理映射:声明一个纹理变量,再使用tex2D函数采样。

++++实际上,在渲染流水线中,纹理映射的实现远比咱们想象的复杂。

++++纹理缩小的过程比放大更加复杂:原纹理中的多个像素将会对应一个目标像素。(须要处理抗锯齿问题:使用多级渐远纹理(mipmapping)技术。)

++2.3.3、凹凸映射

++++纹理的另外一种常见的应用就是凹凸映射(bump mapping

++++凹凸映射的目的是使用一张纹理来修改模型表面的法线,以便为模型提供更多的细节。(这种方法不会真的改变模型的顶点位置,只是让模型看起来好像是“凹凸不平”的,但能够从模型的轮廓处看出“破绽”。)

++++有两种主要的方法能够用来进行凹凸映射:一种方法是使用一张高度纹理(height map来模拟表面位移(displacement,而后获得一个修改后的法线值,这种方法也被称为高度映射(height mapping;另外一种方法则是使用一张法线纹理(normal map来直接存储表面法线,这种方法又被称为法线映射(normal mapping

++++经常把凹凸映射法线映射当成是相同的技术。

++2.3.4、高度纹理

++++使用一张高度纹理(height map来模拟表面位移(displacement,而后获得一个修改后的法线值,这种方法也被称为高度映射(height mapping

++++高度图中存储的是强度值(intensity),它用于表示模型表面局部的海拔高度。(颜色越浅代表该位置的表面越向外凸起,颜色越深表面该位置越向里凹。这种方法的好处是很是直观,能够从高度图中明确地知道一个模型表面的凹凸状况,但缺点是计算更加复杂,在实时计算时不能直接获得表面法线,而是须要由像素的灰度值计算而得,所以须要消耗更多的性能。)

++++高度图一般会和法线映射一块儿使用,用于该出表面凹凸的额外信息。(一般会使用法线映射来修改光照。)

++2.3.5、法线纹理

++++使用一张法线纹理(normal map来直接存储表面法线,这种方法又被称为法线映射(normal mapping

++++法线纹理中存储的就是表面的法线方向。

++++对于模型顶点自带的法线,它们是定义在模型空间中的,所以一种直接的想法就是将修改后的模型空间中的表面法线存储在一张纹理中,这种纹理被称为是模型空间的法线纹理(object-space normal map。(在实际制做中,每每会采用另外一种坐标空间,即模型顶点的切线空间(tangent space来存储法线。)

++++法线自己存储在哪一个坐标系中都是能够的,甚至能够选择存储在世界空间下。

++++使用模型空间来存储法线的优势:1、实现简单,更加直观;2、在纹理坐标的缝合处和尖锐的边角部分,能够的突变(缝隙)较少,便可以提供平滑的边界。

++++使用切线空间有更多优势:1、自由度很高;2、可进行UV动画;3、能够重用法线纹理;4、可压缩。(切线空间在不少状况下都优于模型空间。)

++++计算光照模型中统一各个方向矢量所在的坐标空间。因为法线纹理中存储的法线是切线空间下的方向,有两种选择:

--第一种选择是在切线空间下进行光照计算,此时咱们须要把光照方向、视角方向变换到切线空间下;

--另外一种选择是世界空间下进行光照计算,此时咱们须要把采样获得的法线方向变换到世界空间下,再和世界空间下的光照方向和视角方向进行计算。

--从效率上来讲,第一种方法每每要优于第二种方法,由于咱们能够在顶点着色器中就完成对光照方向盒视角方向的变换,而第二种方法因为要先对法线纹理进行采样,因此变换过程必须在片元着色器中实现,这意味着咱们须要在片元着色器中进行一次矩阵操做。

--从通用性角度来讲,第二种方法要因为第一种方法,由于有时咱们须要在世界空间下进行一些计算,例如在使用Cubemap进行环境映射时,咱们须要使用世界空间下的反射方向对Cubemap进行采样。(若是同时须要进行法线映射,咱们就须要把法线方向变换到世界空间下。)

++2.3.6、使用法线纹理实践:在切线空间下计算光照模型

++++在切线空间下计算光照模型的基本思路是:在片元着色器中经过纹理采样获得切线空间下的法线,而后再与切线空间下的视角方向、光照方向等进行计算,获得最终的光照结果。

++++首先须要在顶点着色器中把视角方向和光照方向从模型空间变换到切线空间中,即咱们须要知道从模型空间到切线空间的变换矩阵。(从模型空间到切线空间的变换矩阵就是从切线空间到模型空间的变换矩阵的转置矩阵,把切线(x轴)、副切线(y轴)、法线(z轴)的顺序按行排列便可获得。)

//立钻哥哥:在切线空间下计算光照模型

Shader YanlzShaderDemo/NormalMapTangentSpaceMat_ShaderDemo01{

    Properties{

        _Color (Color Tint, Color) = (1,1,1,1)

        _MainTex (Main Tex, 2D) = white{ }

        _BumpMap (Normal Map, 2D) = bump{ }

        _BumpScale (Bump Scale, Float) = 1.0

        _Specular (Specular, Color) = (1,1,1,1)

        _Gloss (Gloss, Range(8.0, 256)) = 20

    }

 

    SubShader{

        Pass{

            Tags {  LightMode=ForwardBase  }

        

            CGPROGRAM

            #pragma vertex vert

            #pragma fragment frag

            #include Lighting.cginc

   

            fixed4 _Color;

            sampler2D _MainTex;

            float4 _MainTex_ST;

            sampler2D _BumpMap;

            float4 _BumpMap_ST;

            float _BumpScale;

            float4 _Specular;

            float _Glosss;

 

            struct a2v{

                float4 vertex : POSITION;

                float3 normal : NORMAL;

                float4 tangent : TANGENT;

                float4 texcoord : TEXCOORD0;

            };

 

            struct v2f{

                float4 pos : SV_POSITION;

                float4 uv : TEXCOORD0;

                float3 lightDir : TEXCOORD1;

                float3 viewDir : TEXCOORD2;

            };

 

            v2f vert(a2v v){

                v2f o;

                o.pos = mul(UNITY_MATRIX_MVP, v.vertex);

                o.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;

                o.uv.zw = v.texcoord.xy * _BumpMap_ST.xy + _BumpMap_ST.zw;

 

                //float3 binormal = cross(normalize(v.normal), normalize(v.tangent.xyz)) * v.tangent.w;

                //float3 rotation = float3x3(v.tangent.xyz, binormal, v.normal);

                TANGENT_SPACE_ROTATION;    //use the built-in macro

 

                o.lightDir = mul(rotation, ObjSpaceLightDir(v.vertex)).xyz;

                o.viewDir = mul(rotation, ObjSpaceViewDir(v.vertex)).xyz;

 

                return o;

            }

 

            fixed4 frag(v2f i) : SV_Target{

                fixed3 tangentLightDir = normalize(i.lightDir);

                fixed3 tangentViewDir = normalize(i.viewDir);

                fixed4 packedNormal = tex2D(_BumpMap, i.uv.zw);

 

                 fixed3 tangentNormal;

                 tangentNormal = UnpackNormal(packedNormal);

                 tangentNormal.xy *= _BumpScale;

                 tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));

 

                 fixed3 albedo = tex2D(_MainTex, i.uv).rgb * _Color.rgb;

                 fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;

                 fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(tangentNormal, tangentLightDir));

                 fixed3 halfDir = normalize(tangentLightDir + tangentViewDir);

                 fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(tangentNormal, halfDir)), _Gloss);

 

                 return fixed4(ambient + diffuse + specular, 1.0);

            }

 

             ENDCG

        }

    }

 

    FallBack Specular

}

++++Properties语义块中添加法线纹理的属性,以及用于控制凹凸程度的属性:_Color_MainTex_BumpMap_BumpScale_Specular_Gloss等。(对于法线纹理_BumpMap:【_BumpMap (Normal Map, 2D) = bump { }】:使用bump做为它的默认值。(bumpUnity内置的法线纹理,当没有提供任何法线纹理时,bump就对应了模型自带的法线信息。))(_BumpScale:【_BumpScale (Bump Scale, Float) = 1.0】:用于控制凹凸程度的,当它为0时,意味着该法线纹理不会对光照产生任何影响。)

++++为了使用Unity内置的一些变量,如_LightColor0,还须要包含进Unity的内置文件Lighting.cginc=>#include Lighting.cginc

++++为了获得该纹理的属性(平铺和偏移系数),为_MainTex_BumpMap定义了_MainTex_ST_BumpMap_ST变量。

++++切线空间是顶点法线和切线构建出的一个坐标空间,须要获得顶点的切线信息。顶点着色器的输入结构体a2v

struct a2v{

    float4 vertex : POSITION;

    float3 normal : NORMAL;

    float4 tangent : TANGENT;

    float4 texcoord : TEXCOORD0;

};

--使用TANGENT语义来描述float4类型的tangent变量,以告诉Unity把顶点的切线方向填充到tangent变量中。(注意:和法线方向normal不一样,tangent的类型是float4,而非float3,这是由于咱们须要使用tangent.w份量来决定切线空间中的第三个坐标轴:副切线的方向性。)

++++在顶点着色器中计算切线空间下的光照和视角方向,在v2f结构体中添加变量来存储变换后的光照和视角方向:

struct v2f{

    float4 pos : SV_POSITION;

    float4 uv : TEXCOORD0;

    float3 lightDir : TEXCOORD1;

    float3 viewDir : TEXCOORD2;

};

++++定义顶点着色器:【v2f vert(a2v v){  }】:使用了两张纹理,所以须要存储两个纹理坐标。(咱们把v2f中的uv变量的类型定义为float4类型,其中xy份量存储了_MainTex的纹理坐标,而zw份量存储了_BumpMap的纹理坐标(实际上,_MainTex_BumpMap一般会使用同一组纹理坐标,出于减小插值寄存器的使用数目的目的,咱们每每只计算和存储一个纹理坐标便可。))(而后,把模型空间下切线方向、副切线方向和法线方向按行排列来获得从模型空间到切线空间的变换矩阵rotation。)

++++Unity提供了一个内置宏TANGENT_SPACE_ROTATION(在UnityCG.cginc中定义)来帮助咱们直接计算获得rotation变换矩阵。

++++使用Unity的内置函数ObjSpaceLightDirObjSpaceViewDir来获得模型空间下的光照和视角方向,再利用变换矩阵rotation把它们从模型空间变换到切线空间中。

++++在顶点着色器中完成了大部分工做,所以片元着色器中只须要采样获得切线空间下的法线方向,再在切线空间下进行光照计算便可。

++++fixed4 frag(v2f i) : SV_Target{  }】:首先利用tex2D对法线纹理_BumpMap进行采样。(法线纹理中存储的是把法线通过映射后获得的像素值,所以咱们须要把它们反映射回来。)

(若是没有在Unity里把该法线纹理的类型设置成Normal map,就须要在代码中手动进行这个过程。)(首先把packedNormalxy份量按公式映射回法线方向,而后乘以_BumpScale(控制凹凸程度)来获得tangentNormalxy份量。因为法线都是单位矢量,所以tangentNormal.z份量能够由tangentNormal.xy计算而得。因为咱们使用的是切线空间下的法线纹理,能够保证法线方向的z份量为正。)(在Unity中,为了方便对法线纹理的存储进行优化,一般会把法线纹理的纹理类型标识成Normal mapUnity会根据平台来选择不一样的压缩方法。=>能够使用Unity内置函数UnpackNormal来获得正确的法线方向。)

++2.3.7、使用法线纹理实践:在世界空间下计算光照模型

++++在世界空间下计算光照模型的基本思路:在顶点着色器中计算从切线空间到世界空间的变换矩阵,并把它传递给片元着色器。(变换矩阵的计算能够由顶点的切线、副切线和法线在世界空间下的表示来获得。)(最后,只须要在片元着色器中把法线纹理中的法线方向从切线空间变换到世界空间下便可。)(尽管这种方法须要更多的计算,但在须要使用Cubemap进行环境映射等状况下,咱们就须要使用这种方法。)

//立钻哥哥:在世界空间下计算光照模型

Shader YanlzShaderDemo/NormalMapWorldSpace_ShaderDemo02{

    Properties{

        _Color (Color Tint, Color) = (1,1,1,1)

        _MainTex (Main Tex, 2D) = white { }

        _BumpMap (Normal Map, 2D) = bump { }

        _BumpScale (Bump Scale, Float) = 1.0

        _Specular (Specular, Color) = (1,1,1,1)

        _Gloss (Gloss, Range(8.0, 256)) = 20

    }

 

    SubShader{

        Pass{

            Tags {  LightMode=ForwardBase }

 

            CGPROGRAM

            #pragma vertex vert

            #pragma fragment frag

            #include Lighting.cginc”    //立钻哥哥

 

            fixed4 _Color;

            sampler2D _MainTex;

            float4 _MainTex_ST;

            sampler2D _BumpMap;

            float4 _BumpMap_ST;

            float _BumpScale;

            fixed4 _Specular;

            float _Gloss;

 

            struct a2v{

                float4 vertex : POSITION;

                float3 normal : NORMAL;

                float4 tangent : TANGENT;

                float4 texcoord : TEXCOORD0;

            };

 

            struct v2f{

                float4 pos : SV_POSITION;

                float4 uv : TEXCOORD0;

                float4 TtoW0 : TEXCOORD1;

                float4 TtoW1 : TEXCOORD2;

                float4 TtoW2 : TEXCOORD3;

            };

 

             v2f vert(a2v v){

                v2f o;

                o.pos = mul(UNITY_MATRIX_MVP, v.vertex);

                o.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;

                o.uv.zw = v.texcoord.xy * _BumpMap_ST.xy + _BumpMap_ST.zw;

 

                float3 worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;

                fixed3 worldNormal = UnityObjcetToWorldNormal(v.normal);

                fixed3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz);

                fixed3 worldBinormal = cross(worldNormal, worldTangent) * v.tangent.w;

                o.TtoW0 = float4(worldTangent.x, worldBinormal.x, worldNormal.x, worldPos.x);

                o.TtoW1 = float4(worldTangent.y, worldBinormal.y, worldNormal.y, worldPos.y);

                o.TtoW2 = float4(worldTangent.z, worldBinormal.z, worldNormal.z, worldPos.z);

 

                return o;

            }

 

            fixed4 frag(v2f i) : SV_Target{

                float3 worldPos = float3(i.TtoW0.w, i.TtoW1.w, i.TtoW2.w);

                fixed3 lightDir = normalize(UnityWorldSpaceLightDir(worldPos));

                fixed3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos));

                fixed3 bump = UnpackNormal(tex2D(_BumpMap, i.uv.zw));

                bump.xy *= _BumpScale;

                bump.z = sqrt(1.0 - saturate(dot(bump.xy, bump.xy)));

                bump = normalize(half3(dot(i.TtoW0.xyz, bump), dot(i.TtoW1.xyz, bump), dot(i.TtoW2.xyz, bump)));

                fixed3 albedo = tex2D(_MainTex, i.uv).rgb * _Color.rgb;

                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;

                fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(bump, lightDir));

                fixed3 halfDir = normalize(lightDir + viewDir);

                fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(bump, halfDir)), _Gloss);

 

                return fixed4(ambient + diffuse + specular, 1.0);

            }

 

            ENDCG

        }

    }

 

    FallBack Specular

}

++++顶点着色器的输出结构体v2f,使它包含从切线空间到世界空间的变换矩阵:

struct v2f{

    float4 pos : SV_POSITION;

    float4 uv : TEXCOORD0;

    float4 TtoW0 : TEXCOORD1;

    float4 TtoW1 : TEXCOORD2;

    float4 TtoW2 : TEXCOORD3;

};

    --一个插值寄存器最多只能存储float4大小的变量,对于矩阵这样的变量,咱们能够把它们按行拆成多个变量再进行存储。(TtoW0TtoW1TtoW2就依次存储了从切线空间到世界空间的变换矩阵的每一行。)(实际上,对方向矢量的变换只须要使用3x3大小的矩阵,也就是说,每一行只须要使用float3类型的变量便可。但为了充分利用插值寄存器的存储空间,把世界空间下的顶点位置存储在这些变量的w份量中。)

++++顶点着色器,计算从切线空间到世界空间的变换矩阵:【v2f vert(a2v v){  }

--首先从TtoW0TtoW1TtoW2w份量中构建世界空间下的坐标。

--而后,使用内置的UnityWorldSpaceLightDirUnityWorldSpaceViewDir函数获得世界空间下的光照和视角方向。

--接着,使用内置的UnpackNormal函数对法线纹理进行采样和解码(须要把法线纹理的格式标识成Normal map),并使用_BumpScale对其进行缩放。

--最后,咱们使用TtoW0TtoW1TtoW2存储的变换矩阵把法线变换到世界空间下。【bump = normalize(half3(dot(i.TtoW0.xyz, bump), dot(i.TtoW1.xyz, bump), dot(i.TtoW2.xyz, bump)));】这是经过使用点乘操做来实现矩阵的每一行和法线相乘来获得的。

++++从视觉表现上,在切线空间下和世界空间下计算光照几乎没有任何差异。

--Unity 4.x版本中,在不须要使用Cubemap进行环境映射的状况下,内置的Unity Shader使用的是切线空间来进行法线映射和光照计算。

--Unity 5.x中,全部内置的Unity Shader都使用了世界空间来进行光照计算。(这也是为何Unity5.x中表面着色器更容易报错,由于它们使用了更多的插值寄存器来存储变换矩阵(还有一些额外的插值寄存器是用来辅助计算雾效的。))

++2.3.8Unity中的法线纹理类型

++++当把法线纹理的纹理类型标识成Normal map时,能够使用Unity的内置函数UnpackNormal来获得正确的法线方向。

++++当咱们须要使用那些包含了法线映射的内置的Unity Shader时,必须把使用的法线纹理标识成Normal map才能获得正确结果。(即使忘了,Unity也会在材质面板中提醒修正这个问题。)(这是由于这些Unity Shader都使用了内置的UnpackNormal函数采样法线方向。)

++++把纹理类型设置成Normal map,可让Unity根据不一样平台对纹理进行压缩,再经过UnpackNormal函数来针对不一样的压缩格式对法线纹理进行正确的采样。

++++Normal map=>Create from Grayscale】:用于从高度图中生成法线纹理。(高度图自己记录的是相对高度,是一张灰度图,白色表示相对更高,黑色表示相对更低。)

++++Create from Grayscale=>Bumpiness】、【Filtering=>Smooth】、【Sharp

--Bumpiness】:用于控制凹凸程度;【Filtering】决定咱们使用哪一种方式来计算凹凸程度。

--Smooth】:这使得生成后的法线纹理会比较平滑;【Sharp】:它会使用Sobel滤波(一种边缘检测时使用的滤波器)来生成法线。

--Sobel滤波的实现很是简单:只须要在一个3x3的滤波器中计算xy方向上的导数,而后从中获得法线便可。(具体方法是:对于高度图中的每一个像素,咱们考虑它与水平方向和竖直方向上的像素差,把它们的差当成该点对应的法线在xy方向上的位移,而后使用以前提到的映射函数存储到法线纹理的rg份量便可。)

++2.3.9、渐变纹理

++++在渲染中使用纹理是为了定义一个物体的颜色,其实纹理能够用于存储任何表面属性。(一种常见的用法就是:使用渐变纹理来控制漫反射光照的结果。)(漫反射光照一般是使用表面法线和光照方向的点积结果与材质的反射率相乘来获得表面的漫反射光照。也能够用冷到暖色调的着色技术。)

++++冷到暖色调(cool-to-warm tones的着色技术:用来获得一种插画风格的渲染效果。(使用这种技术,能够保证物体的轮廓线相比于以前使用的传统漫反射光照更加明显,并且可以提供多种色调变化。)(不少卡通风格的渲染中都使用这种技术。)

++++使用一张渐变纹理来控制漫反射光照效果实例:

//立钻哥哥:使用一张渐变纹理来控制漫反射光照

Shader YanlzShaderDemo/RampTexture_ShaderDemo01{

    Properties{

        _Color (Color Tint, Color) = (1,1,1,1)

        _RampTex (Ramp Tex, 2D) = white {  }

        _Specular (Specular, Color) = (1,1,1,1)

        _Gloss (Gloss, Range(8.0, 256)) = 20

    }

 

    SubShader{

        Pass{

            Tags {  LightMode=ForwardBase }

        

            CGPROGRAM

            #pragma vertex vert

            #pragma fragment frag

            #include Lighting.cginc

 

            fixed4 _Color;

            sampler2D _RampTex;

            float4 _RampTex_ST;

            fixed4 _Specular;

            float _Gloss;

 

            struct a2v{

                float4 vertex : POSITION;

                float3 normal : NORMAL;

                float4 texcoord : TEXCOORD0;

            };

 

            struct v2f{

                float4 pos : SV_POSITION;

                float3 worldNormal : TEXCOORD0;

                float3 worldPos : TEXCOORD1;

                float2 uv : TEXCOORD2;

             };

 

            v2f vert(a2v v){

                v2f o;

                o.pos = mul(UNITY_MATRIX_MVP, v.vertex);

                o.worldNormal = UnityObjectToWorldNormal(v.normal);

                o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;

                o.uv = TRANSFORM_TEX(v.texcoord, _RampTex);

 

                return o;

            }

 

            fixed4 frag(v2f i) : SV_Target{

                fixed3 worldNormal = normalize(i.worldNormal);

                fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));

                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;

                fixed halfLambert = 0.5 * dot(worldNormal, worldLightDir) + 0.5;

                fixed3 diffuseColor = tex2D(_RampTex, fixed2(halfLambert, halfLambert)).rgb * _Color.rgb;

                fixed3 diffuse = _LightColor0.rgb * diffuseColor;

                fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));

                fixed3 halfDir = normalize(worldLightDir + viewDir);

                fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);

 

                return fixed4(ambient + diffuse + specular, 1.0);

            }

 

            ENDCG

        }

    }

 

    FallBack Specular

}

++++Properties语义块中声明一个纹理属性来存储渐变纹理。

++++半兰伯特模型:经过对法线方向和光照方向的点积作一次0.5倍的缩放以及一个0.5大小的偏移来计算半兰伯特部分halfLambert=>获得halfLambert的范围被映射到了[0,1]之间。

++++使用halfLambert来构建一个纹理坐标,并用这个纹理坐标对渐变纹理_RampTex进行采样。(因为_RampTex实际就是一个一维纹理(它在纵轴方向上颜色不变),所以纹理坐标的uv方向咱们都使用了halfLambert。而后,把从渐变纹理采样获得的颜色和材质颜色_Color相乘,获得最终的漫反射颜色。)


++2.3.10、遮罩纹理(mask texture

++++遮罩容许咱们能够保护某些区域,使它们免于某些修改。能够一张遮罩纹理来控制光照:但愿模型表面某些区域的反光强烈一些,某些区域弱一些。(另外一种常见的应用:制做地形材质时须要混合多种图片,例如表现草地的纹理、表现石子的纹理、表现裸露土地的纹理等,使用遮罩纹理能够控制如何混合这些纹理。))

++++使用遮罩纹理的流程通常是:经过采样获得遮罩纹理的纹素值,而后使用其中某个(或某几个)通道的值来与某种表面属性进行相乘,这样,当该通道的值为0时,能够保护表面不受该属性的影响。(使用遮罩纹理可让美术人员更加精准(像素级别)地控制模型表面的各类性质。)

++++拓展:遮罩纹理已经不止限于保护某些区域使它们免于某些修改,而是能够存储任何咱们但愿逐像素控制的表面属性。(一般,咱们会充分利用一张纹理的RGBA四个通道,用于存储不一样的属性。例如:能够把高光反射的强度存储在R通道,把边缘光照的强度存储在G通道,把高光反射的指数部分存储在B通道,把自发光强度存储在A通道。)

++++最佳实践:开发人员能够为每一个模型使用4张纹理:一张用于定义模型颜色,一张用于定义表面法线,另外两张则是遮罩纹理。这样,两张遮罩纹理提供了共8种额外的表面属性,这使得游戏中的人物材质自由度很强,能够支持不少高级的模型属性。

++++高光遮罩纹理实践:

//立钻哥哥:高光遮罩纹理(漫反射+高光反射+遮罩)

Shader YanlzShaderDemo/MaskTexture_ShaderDemo02{

    Properties{

        _Color (Color Tint, Color) = (1,1,1,1)

        _MainTex (Main Tex, 2D) = white{ }

        _BumpMap (Normal Map, 2D) = bump { }

        _BumpScale (Bump Scale, Float) = 1.0

        _SpecularMask (Specular Mask, 2D) = white {  }

        _SpecularScale (Specular Scale, Float) = 1.0

        _Specular (Specular, Color) = (1,1,1,1)

        _Gloss (Gloss, Range(8.0, 26)) = 20

    }

 

    SubShader{

        Pass{

            Tags {  LightMode=ForwardBase  }

        

            CGPROGRAM

            #pragma vertex vert

            #pragma fragment frag

            #include Lighting.cginc

 

            fixed4 _Color;

            sampler2D _MainTex;

            float4 _MainTex_ST;

            sampler2D _BumpMap;

            float _BumpScale;

            sampler2D _SpecularMask;

            float _SpecularScale;

            fixed4 _Specular;

            float _Gloss;

 

            struct a2v{

                float4 vertex : POSITION;

                float3 normal : NORMAL;

                float4 tangent : TANGENT;

                float4 texcoord : TEXCOORD0;

            };

 

            struct v2f{

                float4 pos : SV_POSITION;

                float2 uv : TEXCOORD0;

                float3 lightDir : TEXCOORD1;

                float3 viewDir : TEXCOORD2;

            };

 

            v2f vert(a2v v){

                v2f o;

                o.pos = mul(UNITY_MATRIX_MVP, v.vertex);

                o.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;

  

                TANGENT_SPACE_ROTATION;

                o.lightDir = mul(rotation, ObjSpaceLightDir(v.vertex)).xyz;

                o.viewDir = mul(rotation, ObjSpaceViewDir(v.vertex)).xyz;

 

                return o;

            }

 

            fixed4 frag(v2f i) : SV_Target{

                fixed3 tangentLightDir = normalize(i.lightDir);

                fixed3 tangentViewDir = normalize(i.viewDir);

 

                fixed3 tangentNormal = UnpackNormal(tex2D(_BumpMap), i.uv);

                tangentNormal.xy *= _BumpScale;

                tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));

 

                fixed3 albedo = tex2D(_MainTex, i.uv).rgb * _Color.rgb;

                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;

                fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(tangentNormal, tangentLightDir));

                fixed3 halfDir = normalize(tangentLightDir + tangentViewDir);

                fixed specularMask = tex2D(_SpecularMask, i.uv).r * _SpecularScale;

                fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(tangentNormal, halfDir)), _Gloss);

 

                return fixed4(ambient + diffuse + specular, 1.0);

            }

 

            ENDCG

        }

    }

 

    FallBack Specular

}

++++Properties语义块中声明变量来控制高光反射。(_SpecularMask是须要使用的高光反射遮罩纹理,_SpecularScale是用于控制遮罩影响度的系数。)

++++为主纹理_MainTex、法线纹理_BumpMap、遮罩纹理_SpecularMask定义了它们共同使用的纹理属性变量_MainTex_ST。(这意味着:在材质面板中修改主纹理的平铺系数和偏移系数会同时影响3个纹理的采样。)(使用这种方式可让咱们节省须要存储的纹理坐标数目,若是咱们为每个纹理都使用一个单独的属性变量TextureName_ST,那么随着使用的纹理数目的增长,咱们会迅速占满顶点着色器中能够使用的插值寄存器。而不少时候,咱们不须要对纹理进行平铺和位移操做,或者不少纹理能够使用同一种平铺和位移操做,此时咱们就能够对这些纹理使用同一个变换后的纹理坐标进行采样。)

++++在顶点着色器中,咱们对光照方向和视角方向进行了坐标空间的变换,把它们从模型空间变换到了切线空间中,以便在片元着色器中和法线进行光照运算。

++++使用遮罩纹理的地方是片元着色器。咱们使用它来控制模型表面的高光反射强度。

++++在计算高光反射时,咱们首先对遮罩纹理_SpecularMask进行采样。(使用的遮罩纹理中每一个纹素的rgb份量其实都是同样的,代表了该点对应的高光反射强度,在这里咱们选择使用r份量来计算掩码值(【fixed specularMask = tex2D(_SpecularMask, i.uv).r * _SpecularScale;】)。而后,咱们用获得的掩码值和_SpecularScale相乘,一块儿来控制高光反射的强度。)

++++说明:咱们使用的这张遮罩纹理其实有不少空间被浪费了:它的rgb份量存储的都是同一个值。(在实际的游戏制做中,咱们会充分利用遮罩纹理中的每个颜色通道来存储不一样的表面属性。)





###2.4、透明效果

++2.4、透明效果

++++透明是游戏中常常要使用的一种效果。在实时渲染中要实现透明效果,一般会在渲染模型时控制它的透明通道(Alpha Channel。(当开启透明混合后,当一个物体被渲染到屏幕上时,每一个片元除了颜色值和深度值以外,它还有另外一个属性:透明度。)(当透明度为1时,表示该像素是彻底不透明的,而当其为0时,则表示该像素彻底不会显示。)

++++Unity中,实现透明效果方法有:透明度测试(Alpha Test透明度混合(Alpha Blending等。

++++对于不透明(opaque物体,不考虑它们的渲染顺序也能获得正确的排序效果,这是因为强大的深度缓冲(depth buffer,也称z-buffer的存在。(在实时渲染中,深度缓冲是用于解决可见性(visibility)问题的,它能够决定哪一个物体的哪些部分会被渲染在前面,而哪些部分会被其余物体遮挡。基本思想是:根据深度缓存中的值来判断该片元距离摄像机的距离,当渲染一个片元时,须要把它的深度值和已经存在于深度缓冲中的值进行比较(有物体挡住了它);不然,这个片元应该覆盖掉此时颜色缓冲中的像素值,并把它的深度值更新到深度缓冲中(若是开启了深度写入。))

++++使用深度缓冲,可让咱们不用关心不透明物体的渲染顺序。(例如A挡住B,即使咱们先渲染A再渲染B也不用担忧B会遮盖掉A,由于在进行深度测试时会判断出B距离摄像机更远,也就不会写入到颜色缓冲中。)(但若是想要实现透明效果,事情就不那么简单了,这是由于,当使用透明度混合时,咱们关闭了深度写入(ZWrite))

++2.4.1、透明度测试的基本原理

++++透明度测试(Alpha Test:它采用一种“霸道极端”的机制,只要一个片元的透明度不知足条件(一般是小于某个阀值),那么它对应的片元就会被舍弃。被舍弃的片元将不会再进行任何处理,也不会对颜色缓冲产生任何影响;不然,就会按照普通的不透明物体的处理方式来处理它,即进行深度测试、深度写入等。

++++透明度测试是不须要关闭深度写入的,它和其余不透明物体最大的不一样就是它会根据透明度来舍弃一些片元。

++++虽然简单,但它产生的效果也很极端,要么彻底透明,即看不到,要么彻底不透明,就像不透明物体那样。

++2.4.2、透明度混合的基本原理

++++透明度混合(Alpha Blending:这种方法能够获得真正的半透明效果。它会使用当前片元的透明度做为混合因子,与已经存储在颜色缓冲中的颜色值进行混合,获得新的颜色。

++++透明度混合须要关闭深度写入,这使得咱们要很是当心物体的渲染顺序。

++++说明:透明度混合只关闭了深度写入,但没有关闭深度测试。这意味着,当使用透明度混合渲染一个片元时,仍是会比较它的深度值与当前深度缓冲中的深度值,若是它的深度值距离摄像机更远,那么就不会再进行混合操做。(这一点决定了:当一个不透明物体出如今一个透明物体的前面,而咱们先渲染了不透明物体,它仍然能够正常得遮挡住透明物体。也就是说,对于透明度混合来讲,深度缓冲是只读的。)

++2.4.3、渲染顺序很重要

++++对于透明度混合技术,须要关闭深度写入,此时咱们就须要当心处理透明物体的渲染顺序。(若是不关闭深度写入,一个半透明表面背后的表面原本是能够透过它被看到的,但因为深度测试时判断结果是该半透明表面距离摄像机更近,致使后面的表面将会被剔除,咱们就没法透过半透明表面看到后面的物体了。)=>这就破坏了深度缓冲的工做机制,而这是一个很是很是很是糟糕的事情,尽管咱们不得不这样作。

++++关闭深度写入致使渲染顺序将变得很是重要

++++渲染引擎通常都会先对物体进行排序,再渲染。经常使用的方法是:

--1)先渲染全部不透明物体,并开启它们的深度测试和深度写入。

--2)把半透明物体按它们距离摄像机的远近进行排序,而后按照从后往前的顺序渲染这些半透明物体,并开启它们的深度测试,但关闭深度写入。

++2.4.4Unity Shader的渲染顺序

++++Unity为了解决渲染顺序的问题提供了渲染队列(render queue这一解决方案。使用SubShaderQueue标签来决定咱们的模型将归于哪一个渲染队列。(Unity在内部使用一系列整数索引来表示每一个渲染队列,且索引号越小表示越早被渲染。)

++++Unity定义了5个渲染队列:BackgroundGeometryAlphaTestTransparentOverlay(固然在每一个队列中间咱们能够使用其余队列)。

++++Background】:1000:这个渲染队列会在任何其余队列以前被渲染,咱们一般使用该队列来渲染那些须要绘制在背景上的物体。

++++Geometry】:2000:默认的渲染队列,大多数物体都使用这个队列。不透明物体使用这个队列。

++++AlphaTest】:2450:须要透明度测试的物体使用这个队列。(在Unity5中它从Geometry队列中被单独分出来,这是由于在全部不透明物体渲染以后再渲染它们会更加高效。)(Queue=AlphaTest

++++Transparent】:3000:这个队列中的物体会在全部GeometryAlphaTest物体渲染后,再按从后往前的顺序进行渲染。(任何使用了透明度混合(例如关闭了深度写入的Shader)的物体都应该使用该队列。)

++++Overlay】:4000:该队列用于实现一些叠加效果。(任何须要在最后渲染的物体都应该使用该队列。)

++++经过透明度混合来实现透明效果,代码结构以下:

SubShader{

    Tags {  Queue=Transparent  }

    Pass{

        ZWrite off    //立钻哥哥:关闭深度写入

        ....

    }

}

++2.4.5、透明度测试(Alpha Test

++++透明度测试(Alpha Test:只要一个片元的透明度不知足条件(一般是小于某个阀值),那么它对应的片元就会被舍弃。(被舍弃的片元将不会再进行任何处理,也不会对颜色缓冲产生任何影响;不然,就会按照普通的不透明物体的处理方式来处理它。)

++++一般,咱们会在片元着色器中使用clip函数来进行透明度测试。(clipCG中的一个函数)

++++void clip(float4 x);】:参数:剪裁时使用的标量或矢量条件。

++++void clip(float3 x);

++++void clip(float2 x);

++++void clip(float1 x);

++++void clip(float x);

++++说明:若是给定参数的任何一个份量是负数,就会舍弃当前像素的输出颜色。

//立钻哥哥:透明度测试

Shader YanlzShaderDemo/AlphaTest_ShaderDemo01{

    Properties{

        _Color (Color Tint, Color) = (1,1,1,1)

        _MainTex (Main Tex, 2D) = white {  }

        _Cutoff (Alpha Cutoff, Range(0, 1)) = 0.5

    }

 

    SubShader{

        Tags {  Queue=AlphaTest IgnoreProjector=True RenderType=TransparentCutout  }

 

        Pass{

            Tags {  LightMode=ForwardBase  }    //立钻哥哥

 

            CGPROGRAM

            #pragma vertex vert

            #pragma fragment frag

            #include Lighting.cginc

 

            fixed4 _Color;

            sampler2D _MainTex;

            float4 _MainTex_ST;

            fixed _Cutoff;

 

            struct a2v{

                float4 vertex : POSITION;

                float3 normal : NORMAL;

                float4 texcoord : TEXCOORD0;

            };

 

            struct v2f{

                float4 pos : SV_POSITION;

                float3 worldNormal : TEXCOORD0;

                float3 worldPos : TEXCOORD1;

                float2 uv : TEXCOORD2;

            };

 

            v2f vert(a2v v){

                v2f o;

                o.pos = mul(UNITY_MATRIX_MVP, v.vertex);

                o.worldNormal = UnityObjectToWorldNormal(v.normal);

                o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;

                o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);

 

                return o;

             }

 

            fixed4 frag(v2f i) : SV_Target{

                fixed3 wordNormal = normalize(i.worldNormal);

                fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));

                fixed4 texColor = tex2D(_MainTex, i.uv);

    

                clip(texColor.a - _Cutoff);    //Alpha test

 

                fixed3 albedo = texColor.rgb * _Color.rgb;

                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;

                fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(worldNormal, worldLightDir));

 

                return fixed4(ambient + diffuse, 1.0);

            }

 

            ENGCG

        }

    }

 

    FallBack Transparent/Cutout/VertexLit;

}

++++为了在材质面板中控制透明度测试时使用的阀值,在Properties语义块中声明一个范围在[0,1]之间的属性:_Cutoff:【_Cutoff (Alpha Cutoff, Range(0, 1)) = 0.5 】:_Cutoff参数用于决定咱们调用clip进行透明度测试时使用的判断条件。(它的范围是[0, 1],这是由于纹理像素的透明度就是在此范围内。)

++++Tags {  Queue=AlphaTest IgnoreProjector=True RenderType=TransparentCutout} 】:

--Queue=AlphaTest】:在Unity中透明度测试使用的渲染队列是名为AlphaTest的队列,所以须要把Queue标签设置为AlphaTest

--RenderType=TransparentCutout】:RenderType标签可让Unity把这个Shader纳入到提早定义的组(这里就是TransparentCutout组)中,以指明该Shader是一个使用了透明度测试的Shader。(RenderType标签一般被用于着色器替换功能。)

--IgnoreProjector=True】:这意味着这个Shader不会受到投射器(Projectors)的影响。

--一般,使用了透明度测试的Shader都应该在SubShader中设置这三个标签。

++++Tags {  LightMode=ForwardBase  }】:LightMode标签是Pass标签中的一种,它用于定义该PassUnity的光照流水线中的角色。(只有定义了正确的LightMode,才能正确获得一些Unity的内置光照变量,例如_LightColor0

++++材质面板中的Alpha cutoff参数用于调整透明度测试时使用的阀值,当纹理像素的透明度小于该值时,对应的片元就会被舍弃。(当咱们逐渐调大该值时,立方体上的网格会逐渐消失。)

++++透明度测试获得的透明效果很“极端”:要么彻底透明,要么彻底不透明,它的效果每每像在一个不透明物体上挖了一个空洞。(获得的透明效果在边缘处每每良莠不齐,有锯齿,这是由于在边界处纹理的透明度的变化精度问题。)

++++为了获得更加柔滑的透明效果,就能够使用透明度混合。


++2.4.6、透明度混合(Alpha Blending

++++透明度混合(Alpha Blending:透明度混合的实现要比透明度测试复杂一些,这是由于咱们在处理透明度测试时,实际上跟对待普通的不透明物体几乎是同样的,只是在片元着色器中增长了对透明度判断并裁剪片元的代码。

++++透明度混合:这种方法能够获得真正的半透明效果。(它会使用当前片元的透明度做为混合因子,与已经存储在颜色缓冲中的颜色值进行混合,获得新的颜色。可是,透明度混合须要关闭深度写入,这使得咱们要很是当心物体的渲染顺序。)

++++为了进行混合,咱们使用Unity提供的混合命令:Blend。(BlendUnity提供的设置混合模式的命令。)(想要实现半透明的效果就须要把当前自身的颜色和已经存在于颜色缓冲中的颜色值进行混合。)

++++ShaderLabBlend命名:Blend OffBlend SrcFactor DstFactorBlend SrcFactor DstFactor, ScrFactorA DstFactorABlendOp BlendOperation等:

--Blend Off】:关闭混合。

--Blend SrcFactor DstFactor】:开启混合,并设置混合因子。(源颜色(该片元产生的颜色)会乘以SrcFactor,而目标颜色(已经存在于颜色缓存的颜色)会乘以DstFactor,而后把二者相加后再存入颜色缓冲中。)

--Blend SrcFactor DstFactor, SrcFactorA DstFactorA】:同上,只是使用不一样的因子来混合透明通道。

--BlendOp BlendOperation】:并不是是把源颜色和目标颜色简单相加后混合,而是使用BlendOperation对它们进行其余操做。

++++咱们使用第二种语义:【Blend SrcFactor DstFactor】:这个命令在设置混合因子的同时也开启了混合模式。(这是由于,只有开启了混合以后,设置片元的透明通道才有意义,而Unity在咱们使用Blend命令的时候就自动帮咱们打开了。)

++++模型没有任何透明效果:由于没有在Pass中使用Blend命令,一方面是没有设置混合因子,更重要的是,根本没有打开混合模式。(咱们会把源颜色的混合因子SrcFactor设为SrcAlpha,而目标颜色的混合因子DstFactor设为OneMinusSrcAlpha。)(通过混合后的新颜色是:DstColornew = SrcAlpha * SrcColor + (1-SrcAlpha) * DstColorold

//立钻哥哥:透明度混合实现模型透明效果

Shader YanlzShaderDemo/AlphaBlend_ShaderDemo02{

    Properties{

        _Color (Color Tint, Color) = (1,1,1,1)

        _MainTex (Main Tex, 2D) = white { }

        _AlphaScale (Alpha Scale, Range(0, 1)) = 1

    }

 

    SubShader{

        Tags {  Queue=Transparent IgnoreProjector=True RenderType=Transparent  }

 

        Pass{

            Tags {  LightMode=ForwardBase  }

 

            ZWirte off

            Blend SrcAlpha OneMinusSrcAlpha

 

            CGPROGRAM

            #pragma vertex vert

            #pragma fragment frag

            #include Lighting.cginc

        

            fixed4 _Color;

            sampler2D _MainTex;

            float4 _MainTex_ST;

            fixed _AlphaScale;

 

            struct a2v{

                float4 vertex : POSITION;

                float3 normal : NORMAL;

                float4 texcoord : TEXCOORD0;

            };

 

            struct v2f{

                float4 pos : SV_POSITION;

                float3 worldNormal : TEXCOORD0;

                float3 worldPos : TEXCOORD1;

                float2 uv : TEXCOORD2;

            };

 

            v2f vert(a2v v){

                v2f o;

                o.pos = mul(UNITY_MATRIX_MVP, v.vertex);

                o.worldNormal = UnityObjectToWorldNormal(v.normal);

                o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;

                o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);

 

                return o;

            }

 

            fixed4 frag(v2f i) : SV_Target{

                fixed3 worldNormal = normalize(i.worldNormal);

                fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));

                fixed4 texColor = tex2D(_MainTex, i.uv);

                fixed3 albedo = texColor.rgb * _Color.rgb;

                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;

                fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(worldNormal, worldLightDir));

 

                return fixed4(ambient + diffuse, texColor.a * _AlphaScale);

            }

 

            ENDCG

        }

    }

 

    FallBack Transparent/VertexLit

}

++++Properties语义块中使用一个新的属性_AlphaScale。(_AlphaScale用于在透明纹理的基础上控制总体的透明度。)

++++与透明度测试不一样,须要在Pass中为透明度混合进行合适的混合状态设置:

Pass{

    Tags { LightMode=ForwardBase  }

 

    ZWrite Off

    Blend SrcAlpha OneMinusSrcAlpha

    ....

}

--Pass的标签,即把LightMode设为ForwardBase,这是为了让Unity可以按前向渲染路径的方式为咱们正确提供各个光照变量。

--把该Pass的深度写入(ZWrite)设置为关闭状态(Off)。

--开启并设置该Pass的混合模式:将源颜色(该片元着色器产生的颜色)的混合因子设为SrcAlpha,把目标颜色(已经存在于颜色缓冲中的颜色)的混合因子设为OneMinusSrcAlpha,以获得合适的半透明效果。


++2.4.7、开启深度写入的半透明效果

++++因为关闭深度写入而形成的错误排序的状况,一种解决方法是使用两个Pass来渲染模型:第一个Pass开启深度写入,但不输出颜色,它的目的仅仅是为了把该模型的深度值写入深度缓冲中;第二个Pass进行正常的透明度混合,因为上一个Pass已经获得了逐像素的正确的深度信息,该Pass就能够按照像素级别的深度排序结果进行透明渲染。(这种方法的缺点是:多使用一个Pass会对性能形成必定的影响。)

++++使用这种方法,能够实现模型与它后面的背景混合的效果,但模型内部之间不会有任何真正的半透明效果。

//立钻哥哥:开启深度写入的半透明效果

Shader YanlzShaderDemo/AlphaBlendZWrite_ShaderDemo03{

    ....

 

    SubShader{

        ....

    

        Pass{

            Zwrite On

            ColorMask 0

        }

 

        Pass{

            ....

        }

    }

 

    ....

}

++++这个新添加的Pass的目的仅仅是为了把模型的深度信息写入深度缓冲中,从而剔除模型中被自身遮挡的片元。

++++Pass第一行开启了深度写入。

++++Pass第二行使用了一个新的渲染命令:ColorMask

++++ShaderLab中,ColorMask用于设置颜色通道的写掩码(write mask):

--ColorMask RGB | A | 0| 其余任何RGBA的组合

--ColorMask设为0时,意味着该Pass不写入任何颜色通道,即不会输出任何颜色。(这正是咱们须要的:该Pass只需写入深度缓存便可。)


++2.4.8ShaderLab的混合命令

++++混合还有不少其余用处,不只仅是用于透明度混合。

++++混合的实现原理:当片元着色器产生一个颜色的时候,能够选择与颜色缓存中的颜色进行混合。(混合就和两个操做数有关:源颜色(source color)和目标颜色(destination color))(源颜色,用S表示,指的是由片元着色器产生的颜色值;目标颜色,用D表示,指的是从颜色缓冲中读取到的颜色值。对它们进行混合后获得的输出颜色,用O表示,它会从新写入到颜色缓冲中。)(混合中的源颜色、目标颜色、输出颜色,它们都包含了RGBA四个通道的值。)

++++Blend SrcFactor DstFactor】:开启混合,并设置混合因子。(源颜色(该片元产生的颜色)会乘以SrcFactor,而目标颜色(已经存在于颜色缓存的颜色)会乘以DstFactor,而后把二者相加后再存入颜色缓冲中。)

++++Blend SrcFactor DstFactor, SrcFactorA DstFactorA】:同上,只是使用不一样的因子来混合透明通道。

++++ShaderLab中的混合因子:OneZeroSrcColorSrcAlphaDstColorDstAlphaOneMinusSrcColorOneMinusSrcAlphaOneMinusDstColorOneMinusDstAlpha

++++ShaderLab中的混合操做:AddSubRevSubMinMax、其余逻辑操做等。

++2.4.9、常见的混合类型

++++经过混合操做和混合因子命令的组合,能够获得一些混合模式中的混合效果。

++++Blend SrcAlpha OneMinusSrcAlpha】:正常(Normal),即透明度混合。

++++Blend OneMinusDstColor One】:柔和相加(Soft Additive)。

++++Blend DstColor Zero】:正片叠底(Multiply),即相乘。

++++Blend DstColor SrcColor】:两倍相乘(2x Multiply)。

++++BlendOp Min    Blend One One】:变暗(Darken)。

++++BlendOp Max    Blend One One】:变亮(Lighten)。

++++Blend OneMinusDstColor One】:滤色(Screen)。(等同于:【Blend One OneMinusSrcColor】)

++++Blend One One】:线性减淡(Linear Dodge)。

++++说明1:虽然上面使用MinMax混合操做时仍然设置了混合因子,但实际上它们并不会对结果有任何影响,由于MinMax混合操做会忽略混合因子。

++++说明2:虽然上面有些混合模式没有设置混合操做的种类,可是它们默认就是使用加法操做,至关于设置了BlendOp Add

++2.4.10、双面渲染的透明效果

++++在现实生活中,若是一个物体时透明的,意味着咱们不只能够透过它看到其余物体的样子,也能够看到它内部的结构。(透明度测试和透明度混合:没法观察到正方体内部及其背面的形状,致使物体看起来就好像只有半个同样。这是由于,默认状况下渲染引擎剔除了物体背面(相对于摄像机的方向)的渲染图元,而只渲染了物体的正面。)

++++若是咱们想要获得双面渲染的效果,能够使用Cull指令来控制须要剔除哪一个面的渲染图元。

++++Unity中,Cull指令的语法:【Cull Back/Front/Off

--若是设置为Back,那么那些背对着摄像机的渲染图元就不会被渲染,这也是默认状况下的剔除状态;

--若是设置为Front,那么那些朝向摄像机的渲染图元就不会被渲染;

--若是设置为Off,就会关闭剔除功能,那么全部的渲染图元都会被渲染,但因为这时须要渲染的图元数目会成倍增长,所以除非是用于特殊效果,一般状况是不会关闭剔除功能的。

++++透明度测试的双面渲染:只添加一行代码便可:

Cull Off    //Turn off culling

--这行代码的做用是关闭剔除功能,使得该物体的全部的渲染图元都会被渲染。(能够透过正方体的镂空区域看到内部的渲染结果)


++2.4.11、透明度混合的双面渲染

++++和透明度测试相比,想要让透明度混合实现双面渲染会更复杂一些,这是由于透明度混合须要关闭深度写入,而这是“一切混乱的开端”。(想要获得正确的透明效果,渲染顺序时很是重要的:咱们想要保证图元是从后往前渲染的。)(对于透明度测试来讲,因为咱们没有关闭深度写入,所以能够利用深度缓冲按逐像素的粒度进行深度排序,从而保证渲染的正确性。)

++++关闭了深度写入,就须要当心地控制渲染顺序来获得正确的深度关系。(若是直接关闭剔除功能,就没法保证同一个物体的正面和背面图元的渲染顺序,就有可能获得错误的半透明效果。)

++++透明度混合的双面渲染的策略:把双面渲染分红两个Pass:第一个Pass只渲染背面,第二个Pass只渲染正面。(因为Unity会顺序执行SubShader中的各个Pass,所以咱们能够保证背面老是在正面被渲染以后渲染,从而能够保证正确的深度渲染关系。)

//立钻哥哥:透明度混合的双面渲染

Shader YanlzShaderDemo/AlphaBlendWithBothSide_ShaderDemo04{

    Properties{

        _Color (Color Tint, Color) = (1,1,1,1)

        _MainTex (Main Tex, 2D) = white {  }

        _AlphaScale (Alpha Scale, Range(0, 1)) = 1

    }

 

    SubShader{

        Tags {  Queue=Transparent IgnoreProjector=True RenderType=Transparent  }

 

        Pass{

            Tags{  LightMode=ForwardBase  }

        

            Cull Front    //立钻哥哥:First pass renders only back faces

 

            ZWrite Off

            Blend ScrAlpha OneMinusSrcAlpha

 

            CGPROGRAM

            #pragma vertex vert

            #pragma fragment frag

            #include Lighting.cginc

 

            fixed4 _Color;

            sampler2D _MainTex;

            float4 _MainTex_ST;

            fixed _AlphaScale;

 

            struct a2v{

                float4 vertex : POSITION;

                float3 normal : NORMAL;

                float4 texcoord : TEXCOORD0;

            };

 

            struct v2f{

                float4 pos : SV_POSITION;

                float3 worldNormal : TEXCOORD0;

                float3 worldPos : TEXCOORD1;

                float2 uv : TEXCOORD2;

            };

 

            v2f vert(a2v v){

                v2f o;

                o.pos = mul(UNITY_MATRIX_MVP v.vertex);

                o.worldNormal = UnityObjectToWorldNormal(v.normal);

                o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;

                o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);

 

                return o;

            }

 

            fixed4 frag(v2f i) : SV_Target{

                 fixed3 worldNormal = normalize(i.worldNormal);

                 fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));

                 fixed4 texColor = tex2D(_MainTex, i.uv);

                 fixed3 albedo = texColor.rgb * _Color.rgb;

                 fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;

                 fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(worldNormal, worldLightDir));

 

                 return fixed4(ambient + diffuse, texColor.a * _AlphaScale);

            }

 

            ENDCG

        }

 

        Pass{

            Tags{  LightMode=ForwardBase  }

 

            Cull Back    //立钻哥哥:Second pass renders only front faces

 

            ZWrite Off

            Blend SrcAlpha OneMinusSrcAlpha

 

            CGPROGRAM

            #pragma vertex vert

            #pragma fragment frag

            #include Lighting.cginc

    

            fixed4 _Color;

            sampler2D _MainTex;

            float4 _MainTex_ST;

            fixed _AlphaScale;

 

            struct a2v{

                float4 vertex : POSITION;

                float3 normal : NORMAL;

                float4 texcoord : TEXCOORD0;

            };

 

            struct v2f{

                float4 pos : SV_POSITION;

                float3 worldNormal : TEXCOORD0;

                float3 worldPos : TEXCOORD1;

                float2 uv : TEXCOORD2;

            };

 

            v2f vert(a2v v){

                v2f o;

                o.pos = mul(UNITY_MATRIX_MVP, v.vertex);

                o.worldNormal = UnityObjectToWorldNormal(v.normal);

                o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;

                o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);

 

                return o;

            }

 

            fixed4 frag(v2f i ) : SV_Target{

                fixed3 worldNormal = normalize(i.worldNormal);

                fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));

                fixed4 texColor = tex2D(_MainTex, i.uv);

                fixed3 albedo = texColor.rgb * _Color.rgb;

                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;

                fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(worldNormal, worldLightDir));

    

                return fixed4(ambient + diffuse, texColor.a * _AlphaScale);

            }

 

            ENDCG

        }

    }

 

    FallBack  Transparen/VertexLit

}

++++在两个Pass中分别使用Cull指令剔除不一样朝向的渲染图元。






##3章:Shader中级篇

++3章:Shader中级篇

++++3.1、更复杂的光照

++++3.2、高级纹理

++++3.3、让画面动起来







。。。未完待续。。。立钻哥哥。。。


#立钻哥哥Unity 学习空间: http://blog.csdn.net/VRunSoftYanlz/

++立钻哥哥推荐的拓展学习连接(Link_Url

++++立钻哥哥Unity 学习空间: http://blog.csdn.net/VRunSoftYanlz/

++++U3D_Shader编程(第一篇:快速入门篇)http://www.javashuo.com/article/p-kyppgrac-gz.html

++++U3D_Shader编程(第二篇:基础夯实篇)http://www.javashuo.com/article/p-qkyowtli-hv.html

++++设计模式简单整理http://www.javashuo.com/article/p-rngqugib-hg.html

++++U3D小项目参考https://blog.csdn.net/vrunsoftyanlz/article/details/80141811

++++UML类图http://www.javashuo.com/article/p-sxberuew-bm.html

++++Unity知识点0001http://www.javashuo.com/article/p-ryvdxxjr-ep.html

++++Unity引擎基础http://www.javashuo.com/article/p-beommoeb-ka.html

++++Unity面向组件开发http://www.javashuo.com/article/p-eigmuvut-dt.html

++++Unity物理系统http://www.javashuo.com/article/p-nqvvciwv-kd.html

++++Unity2D平台开发http://www.javashuo.com/article/p-ycaagdtj-hs.html

++++UGUI基础http://www.javashuo.com/article/p-rukxwckw-mc.html

++++UGUI进阶http://www.javashuo.com/article/p-wcatruhq-gt.html

++++UGUI综合http://www.javashuo.com/article/p-dkccmqii-gg.html

++++Unity动画系统基础http://www.javashuo.com/article/p-mbrdouxy-dq.html

++++Unity动画系统进阶http://www.javashuo.com/article/p-aqaqpbkh-bp.html

++++Navigation导航系统http://www.javashuo.com/article/p-dswwllas-t.html

++++Unity特效渲染http://www.javashuo.com/article/p-ckojjyfj-bp.html

++++Unity数据存储http://www.javashuo.com/article/p-bvlzynso-m.html

++++Unity中Sqlite数据库http://www.javashuo.com/article/p-ejutsbxl-ca.html

++++WWW类和协程http://www.javashuo.com/article/p-dbwmhsav-cy.html

++++Unity网络http://www.javashuo.com/article/p-sqrlntgh-dw.html

++++C#事件http://www.javashuo.com/article/p-zmwruvql-gm.html

++++C#委托http://www.javashuo.com/article/p-uozpymaf-gh.html

++++C#集合http://www.javashuo.com/article/p-sfqfdqsf-ex.html

++++C#泛型http://www.javashuo.com/article/p-xrttqngo-ee.html

++++C#接口http://www.javashuo.com/article/p-vhlfplgv-dm.html

++++C#静态类https://blog.csdn.net/vrunsoftyanlz/article/details/78630979

++++C#中System.String类http://www.javashuo.com/article/p-olslkfao-cq.html

++++C#数据类型http://www.javashuo.com/article/p-hmabbtmc-ba.html

++++Unity3D默认的快捷键http://www.javashuo.com/article/p-wuwcrclr-s.html

++++游戏相关缩写http://www.javashuo.com/article/p-mwacxwca-gm.html

++++立钻哥哥Unity 学习空间: http://blog.csdn.net/VRunSoftYanlz/


--_--VRunSoft : Lovezuanzuan--_--