zoukankan      html  css  js  c++  java
  • Unity3d——编写Unity3D着色器的方式(转)

    不管你会不会写Unity3D的shader,估计你会知道,Unity3D编写shader有三种方式,这篇东西主要就是说一下这三种东西有什么区别,和大概是怎样用的。
    先来列一下这三种方式:
    fixed function shader
    vertex and fragment shader
    surface shader
    为什么Unity3D要提供三种shader的编写方式呢?那是因为三种方式的编写的难易度有区别,对应着不同的使用人群。其实我觉得这是Uniy3D想得有点多了,着色器不单止是为了实现效果,还要效果实现的效率高,才适合真正使用在项目中,单纯实现一个效果,那只是demo级别的,只适合unity3D自身的产品推广,对实际使用者的意义不会很大。基于这种想法,我下面只会大概的介绍三种方式的使用方法,以后会通过其他文章有针对性的对某种方式进行详细的说明。
     
    先来看看三种方式的说明:
    fixed function shader (固定功能着色器)
    最简单的着色器类型,只能使用Unity3D自带的固定语法和提供的方法,适用于任何硬件,使用难度最小
     vertex and fragment shader (顶点片段程序着色器)
    顶点片段着色器,效果最为丰富的着色器类型,使用Cg/HLSL语言规范,着色器由顶点程序和片段程序组成。所有效果都需要自己编写,使用难度相对较大。
    surface shader (表面着色器)
    同样使用Cg/HLSL语言规范的着色器类型,不过把光照模型提取出来,可以使用Unity3D自带的一些光照模型,也可以自己编写光照模型,着色器同样由顶点程序和片段程序组成,不过本身有默认的程序方法,使用者可以只针对自己关系的效果部分进行编写。由于选择性比较大,所以可以编写出较为丰富的效果,使用难度相对vertex and fragment shader小。
     
    由上面的三种方式的说明可以看出来了,如果你是由丰富的的Cg开发经验的,好的选择就是直接使用vertex and fragment shader了。fixed function shader虽然简单,但能实现的效果非常有限。而surface shader是Unity3D提供的一种较为折中的方式,同样能实现较丰富的效果,但难度相对小很多。不过surface shader有一个问题,它不支持SubShader内部的多pass,所以某些需要多pass的效果要实现起来会比较困难。
     
    接下来是主要内容,每一种方式的编写规范和举例:
     
    1、Fixed Function shaders
    1.标准范例:
    复制代码
     1 Shader "VertexLit" {
     2     Properties {
     3         _Color ("Main Color", Color) = (1,1,1,0.5)
     4         _SpecColor ("Spec Color", Color) = (1,1,1,1)
     5         _Emission ("Emmisive Color", Color) = (0,0,0,0)
     6         _Shininess ("Shininess", Range (0.01, 1)) = 0.7
     7         _MainTex ("Base (RGB)", 2D) = "white" { }
     8     }
     9 
    10     SubShader {
    11         Pass {
    12             Material {
    13                 Diffuse [_Color]
    14                 Ambient [_Color]
    15                 Shininess [_Shininess]
    16                 Specular [_SpecColor]
    17                 Emission [_Emission]
    18             }
    19             Lighting On
    20             SeparateSpecular On
    21             SetTexture [_MainTex] {
    22                 constantColor [_Color]
    23                 Combine texture * primary DOUBLE, texture * constant
    24             }
    25         }
    26     }
    27 } 
    复制代码
    2.具体说明
    具体变量、Material模块参考《Unity3D的着色器介绍(二)——Unity3D的Shader基本结构说明》,主要区别是使用SetTexture 模块来控制贴图
    SetTexture的具体写法:
              SetTexture [贴图名称] {
    ConstantColor color
                Combine ColorPart, AlphaPart
                }
     
    ConstantColor color
    定义一个常量颜色在combine command里面使用。
     
    combine command
    combine src1 * src2
    将src1和src2相乘,结果会比两者更暗。
    combine src1 + src2
    将src1和src2相加,结果会比两者更亮
    combine src1 - src2
    从src1里减去src2
    combine src1 +- src2
    把src1加到src2,然后减去0.5
    combine src1 lerp (src2) src3
     src3和src1之间的插值,使用src2的透明值。插值是相对的。
    当透明值是1时显示src1,当透明值是0时显示src3
    combine src1 * src2 + src3
    将src1和src2的透明度相乘,然后加上src3
    combine src1 * src2 +- src3
    将src1和src2的透明度相乘,然后与src3相加减去0.5
    Multiplies src1 with the alpha component of src2, then does a signed add with src3. 
    combine src1 * src2 - src3
    将src1和src2的透明度相乘,然后减去src3
     
    matrix command
    matrix [MatrixPropertyName]
    Transforms texture coordinates used in this command with the given matrix
     
    其中Combine部分是可选的,作用是设定颜色部分和透明部分的叠加方式。
    其中透明部分如果不写,将会默认使用颜色部分相同的设置。
    一般的写法是Combine texture * primary,这样的意思是把贴图乘以顶点颜色作为最终颜色。
    也可以写成Combine texture * primary DOUBLE,加一个DOUBLE的作用是让光照强度增幅成2倍。primary代表的是初始的顶点颜色,也就是在没有加贴图之前的顶点颜色。
    如果需要叠加第二张或者更多贴图,那么就不能再乘以primary了,要改为previous,也就是Combine texture * previous。previous代表的是之前的颜色,也就是你前一张贴图乘以初始颜色之后的颜色。
     
    从Fixed Function shaders的写法可以看出,这种类型的shader非常简单易用,基本上所有平台设备都能支持,不过能做的东西确实不多。你可以用来简单的表现一些基本的贴图和颜色叠加的效果,也可以使用TexGen的贴图来实现一些贴图特效,不过不能控制顶点,也不能较为复杂的控制表面着色。
     
     
    2、Vertex and Fragment Shader
    1.标准例子:
    复制代码
     1 Shader "Custom/Exam1" {
     2 Properties {
     3 _MainTex ("Texture", 2D) = "white" { }
     4 }
     5 SubShader
     6 {
     7 pass
     8 {
     9 CGPROGRAM
    10 #pragma vertex vert
    11 #pragma fragment frag
    12 #include "UnityCG.cginc"
    13 sampler2D _MainTex;
    14 float4 _MainTex_ST;
    15 struct v2f {
    16     float4  pos : SV_POSITION;
    17     float2  uv : TEXCOORD0;
    18 } ;
    19 v2f vert (appdata_base v)
    20 {
    21     v2f o;
    22    o.pos = mul(UNITY_MATRIX_MVP,v.vertex);
    23 o.uv = TRANSFORM_TEX(v.texcoord,_MainTex);
    24     return o;
    25 }
    26 float4 frag (v2f i) : COLOR
    27 {
    28 float4 texCol = tex2D(_MainTex,i.uv);
    29     float4 outp = texCol;
    30     return outp;
    31 }
    32 ENDCG
    33 }
    34 }
    35 }
    复制代码
    2.细节解释:
     
    1、Cg小片段
    Cg小片段以CGPROGRAM开头,以ENDCG结尾。中间的内容使用Cg/HLSL语言。
    Cg小片段的开始处,可以添加:
    #pragma vertex name - 表明这是个以name为名字的函数的顶点程序。 
     
    #pragma fragment name- 表明这是个以name为名字的函数的片段程序。 
     
    #pragma fragmentoption option - 添加选项到编译的OpenGL 片段程序。 通过 ARB 片段程序 可以查询到所允许的规范的选项列表。 这个指令对顶点程序或者不是以OpenGL为编译目标的程序无效。
     
    #pragma target name - 着色器目标编译。
     
    #pragma target default 编译默认目标:
    在Direct3D 9环境中:顶点着色器1.1和像索着色器2.0
    ARB顶点程序 有128位指令限制,ARB片段程序 有96位指令限制(32位纹理 + 64位算术运算),16个临时寄存器和4个间接纹理。
     
    #pragma target 3.0 编译着色器模式3.0:
    在Direct3D 9环境中:顶点着色器3.0和像索着色器 3.0
    ARB顶点程序 没有指令限制,ARB片段程序 有1024位指令限制(512位纹理 + 512位算术运算),32个临时寄存器和4个间接纹理。可以通过使用 #pragma profileoption 命令扩展限制值。例如: #pragma profileoption MaxTexIndirections=256增加间接纹理上限到256个。需要注意的是某些着色器模式3.0中的特性不支持ARB顶点程序和ARB片段程序,比如派生指令这样的。你可以使用 #pragma glsl命令转换到GLSL中,这样子限制较少。
     
    #pragma only_renderers space separated names - 仅用给定的渲染器编译着色器。默认情况下用所有的渲染器都编译着色器。
    ?d3d9 - Direct3D 9.
    ?opengl - OpenGL.
    ?gles - OpenGL ES 2.0.
    ?xbox360 - Xbox 360.
    ?ps3 - PlayStation 3.
    ?flash - Flash.
     
    #pragma exclude_renderers space separated names - 不用给定的渲染器编译着色器。 默认情况下用所有的渲染器都编译着色器。参考上一点。
     
    #pragma glsl - 用桌面OpenGL平台编译着色器时,转换成GLSL里面的Cg/HLSL(而不是默认设置的ARB顶点/片段程序)。
     
    2、参数对应声明
    如有以下着色器输入参数
    _MyColor ("Some Color", Color) = (1,1,1,1) 
    _MyVector ("Some Vector", Vector) = (0,0,0,0) 
    _MyFloat ("My float", Float) = 0.5 
    _MyTexture ("Texture", 2D) = "white" {} 
    _MyCubemap ("Cubemap", CUBE) = "" {} 
    那么在cg程序里面应该再次声明这些参数,才能在cg程序里面使用
    float4 _MyColor;
    float4 _MyVector;
    float _MyFloat; 
    sampler2D _MyTexture;
    samplerCUBE _MyCubemap;
     
    类型对应关系:
    Color和Vector属性对应 float4 类型。 
    Range和Float属性对应 float 类型。
    Texture属性对应 普通2D纹理的sampler2D 类型。 
    CUBE和RECT纹理对应 samplerCUBE 和 samplerRECT类型。 
     
    3、顶点程序
    需要获取顶点的信息,可以先引用#include "UnityCG.cginc"
    然后在这个包里面有2种类型包含了顶点的信息:
    appdata_base: 包含顶点位置,法线 和一个纹理坐标。
    appdata_tan:包含顶点位置,切线,法线 和一个纹理坐标。
    其中包含以下的参数:
    float4 vertex 是顶点位置
    float3 normal 是顶点法线
    float4 texcoord 是第一UV坐标
    float4 texcoord1 是第二UV坐标
    float4 tangent 是切线向量(用在法线贴图中)
    float4 color 是每个顶点(per-vertex)颜色
     
    当然也可以不引用#include "UnityCG.cginc",直接自己定义appdata
    struct appdata {
        float4 vertex : POSITION;
        float4 texcoord : TEXCOORD0;
    };
    其中常用类型:
    POSITION:顶点位置
    COLOR:顶点颜色
    NORMAL:法线
    TANGENT:切线
    TEXCOORD0:UV1
    TEXCOORD1:UV2
     
     
    声明顶点程序名称
    #pragma vertex vert
    下面就可以写顶点程序vert了
    struct v2f {
        float4 pos : SV_POSITION;
        fixed4 color : COLOR;
    };
     
    v2f vert (appdata_base v)
    {
        v2f o;
        o.pos = mul (UNITY_MATRIX_MVP, v.vertex);
        o.color.xyz = v.normal * 0.5 + 0.5;
        return o;
    }
     
    其中v2f是一个自定义块,里面定义了顶点程序返回时包含的信息,也可以传递给片段程序用,其中位置改成SV_POSITION。也可以自定义一些其他的属性。
     
    在顶点程序里面的顶点与矩阵相乘得到最终位置,相关矩阵解释:
    UNITY_MATRIX_MVP 
    当前模型*视*投影矩阵。(注:模型矩阵为 本地->世界)
    UNITY_MATRIX_MV 
    当前模型*视图矩阵 
    UNITY_MATRIX_V 
    当前视图矩阵
    UNITY_MATRIX_P 
    当前投影矩阵
    UNITY_MATRIX_VP 
    当前视图*投影矩阵
    UNITY_MATRIX_T_MV 
    转置模型*视图矩阵
    UNITY_MATRIX_IT_MV 
    -逆转置模型*视矩阵 
    UNITY_MATRIX_TEXTURE0 to UNITY_MATRIX_TEXTURE3 
    纹理变换矩阵 
     
     
     
    当每一帧渲染的时候,每一个需要渲染的物体会自动的把顶点信息输入到shader的指定顶点程序,在vert顶点程序里面处理完,把顶点需要的信息赋值之后,就可以把v2f返回,这时候gpu会自动接收到顶点信息,并作处理。这个是GPU编程的特点。
     
     
    3、片段程序
    #pragma fragment frag
    然后写
    half4 frag (v2f i) : COLOR
    {
        half4 texcol = tex2D (_MainTex, i.uv);
        return texcol * _Color;
    }
    片段程序不需要自定义块作为返回值,因为片段程序的返回值就直接是颜色了。
    不过在片段程序里面,可以通过对之前顶点程序做处理获得的顶点位置、uv、法线等信息进行各种处理,让最终该点的颜色发生各种各样的改变。
     
    3、Surface Shaders
    1.标准例子:
    复制代码
    Shader "Example/Diffuse Texture" {
        Properties {
          _MainTex ("Texture", 2D) = "white" {}
        }
        SubShader {
          Tags { "RenderType" = "Opaque" }
          CGPROGRAM
          #pragma surface surf Lambert
          struct Input {
              float2 uv_MainTex;
          };
          sampler2D _MainTex;
          void surf (Input IN, inout SurfaceOutput o) {
              o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;
          }
          ENDCG
        } 
        Fallback "Diffuse"
      }
    复制代码
    2.详细说明
    程序声明写法:
    #pragma surface surfaceFunction lightModel [optionalparams]
     
    surfaceFunction就是程序名称,该程序应该写成:
    void surf (Input IN,inout SurfaceOutput o),
    Input是你自己定义的结构。Input结构中应该包含所有纹理坐标(texture coordinates)和和表面函数(surfaceFunction)所需要的额外的必需变量。
     
    lightModel 光照模型:
    可选:
    Lambert
    BlinnPhong
    或者自定义
     
    Optional parameters:
    可选参数:
    alpha -透明( Alpha)混合模式。使用它可以写出半透明的着色器。
    alphatest:VariableName -透明( Alpha)测试模式。使用它可以写出 镂空效果的着色器。镂空大小的变量(VariableName)是一个float型的变量。
    finalcolor:ColorFunction - 自定义的最终颜色函数(final color function)。 请参考范例:表面着色器例子(Surface Shader Examples)。
    exclude_path:prepass 或者 exclude_path:forward - 使用指定的渲染路径,不需要生成通道。
    addshadow - 添加阴影投射 & 收集通道(collector passes)。通常用自定义顶点修改,使阴影也能投射在任何程序的顶点动画上。 
    dualforward - 在正向(forward)渲染路径中使用 双重光照贴图(dual lightmaps)。
    fullforwardshadows - 在正向(forward)渲染路径中支持所有阴影类型。
    decal:add - 添加贴花着色器(decal shader) (例如: terrain AddPass)。
    decal:blend - 混合半透明的贴花着色器(Semitransparent decal shader)。
    softvegetation - 使表面着色器(surface shader)仅能在Soft Vegetation打开时渲染。 
    noambient - 不适用于任何环境光照(ambient lighting)或者球面调和光照(spherical harmonics lights)。 
    novertexlights - 在正向渲染(Forward rendering)中不适用于球面调和光照(spherical harmonics lights)或者每个顶点光照(per-vertex lights)。 
    nolightmap - 在这个着色器上禁用光照贴图(lightmap)。(适合写一些小着色器) 
    nodirlightmap - 在这个着色器上禁用方向光照贴图(directional lightmaps)。 (适合写一些小着色器)。
    noforwardadd - 禁用正向渲染添加通道(Forward rendering additive pass)。 这会使这个着色器支持一个完整的方向光和所有光照的per-vertex/SH计算。(也是适合写一些小着色器). 
    approxview - 着色器需要计算标准视图的每个顶点(per-vertex)方向而不是每个像索(per-pixel)方向。 这样更快,但是视图方向不完全是当前摄像机(camera) 所接近的表面。
    halfasview - 在光照函数(lighting function)中传递进来的是half-direction向量,而不是视图方向(view-direction)向量。 Half-direction会计算且会把每个顶点(per vertex)标准化。这样做会很快,但不完全准确。
     
    注意:surface由于使用了光照模型,所以是不能写pass,直接写在subshader里面的。
     
    输入端:
    uv_贴图变量名称 - 贴图的uv
    float3 viewDir - 视图方向( view direction)值。为了计算视差效果(Parallax effects),边缘光照(rim lighting)等,需要包含视图方向( view direction)值。
    float4 with COLOR semantic -每个顶点(per-vertex)颜色的插值。
    float4 screenPos - 屏幕空间中的位置。 为了反射效果,需要包含屏幕空间中的位置信息。比如在Dark Unity中所使用的 WetStreet着色器。
    float3 worldPos - 世界空间中的位置。
    float3 worldRefl - 世界空间中的反射向量。如果表面着色器(surface shader)不写入法线(o.Normal)参数,将包含这个参数。 请参考这个例子:Reflect-Diffuse 着色器。 
    float3 worldNormal - 世界空间中的法线向量(normal vector)。如果表面着色器(surface shader)不写入法线(o.Normal)参数,将包含这个参数。
    float3 worldRefl; INTERNAL_DATA - 世界空间中的反射向量。如果表面着色器(surface shader)不写入法线(o.Normal)参数,将包含这个参数。为了获得基于每个顶点法线贴图( per-pixel normal map)的反射向量(reflection vector)需要使用世界反射向量(WorldReflectionVector (IN, o.Normal))。请参考这个例子: Reflect-Bumped着色器。
    float3 worldNormal; INTERNAL_DATA -世界空间中的法线向量(normal vector)。如果表面着色器(surface shader)不写入法线(o.Normal)参数,将包含这个参数。为了获得基于每个顶点法线贴图( per-pixel normal map)的法线向量(normal vector)需要使用世界法线向量(WorldNormalVector (IN, o.Normal))。 
     
     
    输出端:
    struct SurfaceOutput {
        half3 Albedo;
        half3 Normal;
        half3 Emission;
        half Specular;
        half Gloss;
        half Alpha;
    };
     
    最后说两句我觉得比较重要的话:
    1、着色器都有它规定的指令限制、寄存器、和纹理的限制,有一些复杂的效果需要使用到较多的寄存器和指令,可能会超出限制,比如使用着色器来实现骨骼动画就很容易超出限制了。不论在什么平台编写着色器(unity3D或者stage3D之类)都需要先了解清楚有什么限制,不然等到编译的时候就哭了。
    2、着色器的限制有时候是跟发布平台的硬件支持有关系的,某些发布的平台(比如手机端)不支持你指定的高级别着色器模式,它有可能会根据实际情况降低级别,或者甚至显示不出来。所以编写着色器效果之前要考虑一下需要发布的平台,不要老是问一些诸如“为什么我在Unity3D的编辑器看的效果是正常的,为什么发布到手机就看不到效果”之类的问题了。
     
     原文地址:http://blog.csdn.net/a6627651/article/details/50545643
  • 相关阅读:
    一文快速入门分库分表(必修课)
    MySql分库分表与分区的区别和思考
    常用分库分表方案汇总
    分区分表分库
    MySQL分区和分表
    MySQL的聚集索引和非聚集索引
    PHP大文件上传支持断点上传组件
    PHP大文件上传支持断点上传工具
    Nginx大文件上传支持断点上传
    百度WebUploader大文件上传支持断点上传
  • 原文地址:https://www.cnblogs.com/xingchong/p/12877947.html
Copyright © 2011-2022 走看看