• Shader开发之三大着色器


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

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

    Shader "Custom/VertexList" {
    
        Properties {
    
            _Color("Main Color",Color) = (0,1,1,0.5)
    
            _SpecColor("Spec Color",Color) = (1,1,1,1)
    
            _Emission("Emissive Color",Color) = (0,0,0,0)
    
            _Shininess("Shininess",Range(0.01,1)) = 0.7
    
            _MainTex ("Base (RGB)", 2D) = "white" {}
    
        }
    
        SubShader {
    
            Pass{
    
           
    
                    Material{
    
                       Diffuse[_Color]
    
                       Ambient[_Color]
    
                       Shininess[_Shininess]
    
                       Specular[_SpecColor]
    
                       Emission[_Emission]
    
                    }      
    
                    Lighting On
    
                    SeparateSpecular On//启用高光颜色
    
                    //设置纹理
    
                    SetTexture[_MainTex]{
    
                       //设置颜色常量
    
                       constantColor[_Color]
    
                       //混合命令
    
                       combine texture * primary DOUBLE,
    
                       texture *constant
    
                    }
    
                }
    
        }
    
    }

    表面着色器Surface Shaders

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

    编写表面着色器有几个规则:

    1)表面着色器的实现代码需要放在CGPROGRAM..ENDCG代码块中,而不是Pass结构中,它会自己编译到各个Pass。

    2)使用#pragma surface..命令来指明它是一个表面着色器。

    #pragma surface 表面函数光照模型[可选参数]

    其中表面函数用来说明哪个Cg函数包含有表面着色器代码,表面函数的形式为:

    void surf(Input IN,inoutSurfaceOutPut 0)

    光照模型可以是内置的Lambert和BlinnPhong,或者是自定义的光照模型。

    表面函数的作用是接收输入的UV或者附加数据,然后进行处理,最后将结构填充到输出结构体SurfaceOutPut中。

    输入结构体Input一般包含着色器所需的纹理坐标,纹理坐标的命名规则为uv加纹理名称。另外还可以在输入结构中设置一些附加数据:

     

    SurfaceOut描述了表面的各种参数,它的标准结构为:

      

    struct SurfaceOutput{
        half3 Albedo;//反射光
        half3 Normal;//法线
        half3 Emission;//自发光
        half Specular;//高光
        half Gloss;//光泽度
        half Alpha;//透明度
    };

    将输入数据处理完毕后,将结果填充到输出结构体中。

     

    相关示例:

    1、使用内置的Lambert光照模型,并设置表面颜色为白色。

     1 Shader "Custom/Diffuse Simple" {
     2 
     3     SubShader{
     4 
     5         Tags{"RenderType" = "Opaque"}
     6 
     7         CGPROGRAM//表面着色器的实现代码
     8 
     9         //指明着色器类型,表面函数和光照模型
    10 
    11         #pragma surface surf Lambert
    12 
    13        
    14 
    15         struct Input{//输入的数据结构体
    16 
    17             float color : COLOR;
    18 
    19         };
    20 
    21        
    22 
    23         void surf(Input IN,inoutSurfaceOutput o){//表面函数
    24 
    25             o.Albedo = 1;//输出颜色值
    26 
    27         }
    28 
    29         ENDCG;
    30 
    31    
    32 
    33     }
    34 
    35     Fallback "Diffuse"//备选着色器
    36 
    37 }

    渲染效果如下:

     

    2、在示例1的基础上添加纹理,代码如下:

     1 Shader "Custom/Diffuse Simple" {
     2 
     3  
     4 
     5     Properties{//添加纹理属性
     6 
     7         _MainTex("Texture",2D) = "White"{}
     8 
     9    
    10 
    11     }
    12 
    13     SubShader{
    14 
    15         Tags{"RenderType" = "Opaque"}
    16 
    17         CGPROGRAM   //表面着色器的实现代码
    18 
    19         //指明着色器类型,表面函数和光照模型
    20 
    21         #pragma surface surf Lambert
    22 
    23        
    24 
    25         struct Input{//输入的数据结构体
    26 
    27             float2 uv_MainTex;
    28 
    29         };
    30 
    31        
    32 
    33         sampler2D _MainTex;
    34 
    35        
    36 
    37         void surf(Input IN,inout SurfaceOutput o){//表面函数
    38 
    39             o.Albedo = tex2D(_MainTex,IN.uv_MainTex).rgb;
    40 
    41         }
    42 
    43         ENDCG
    44 
    45    
    46 
    47     }
    48 
    49     Fallback "Diffuse"//备选着色器
    50 
    51 }

    渲染效果如下:

     

    3、在示例2的基础上添加法线贴图

     1 Shader "Custom/Diffuse Simple"{
     2 
     3     Properties{//添加纹理属性
     4 
     5         _MainTex("Texture",2D) = "white"{}
     6 
     7         //添加法线贴图属性
     8 
     9         _BumpMap("Bumpmap",2d) = "bump"
    10 
    11     }
    12 
    13     SubShader{
    14 
    15         Tags{"RenderType" = "Opaque"}
    16 
    17         CGPROGRAM//表面着色器的实现代码
    18 
    19         //指明着色器类型,表面函数和光照模型
    20 
    21         #pragma surface surf Lambert
    22 
    23         struct Input{//输入的数据结构体
    24 
    25             float2 uv_MainTex;
    26 
    27             float2 uv_BumpMap;
    28 
    29         };
    30 
    31         sampler2D _MainTex;
    32 
    33         sampler2D _BumpMap;
    34 
    35         void surf(Input IN,inout SurfaceOutput o){//表面函数
    36 
    37             o.Albedo = tex2D(_MainTex,IN.uv_MainTex).rgb;
    38 
    39             o.Normal = UnpackNormal(tex2D(_BumpMap,IN.uv_BumpMap));
    40 
    41         }
    42 
    43         ENDCG
    44 
    45     }
    46 
    47     Fallback "Diffuse"//备选着色器
    48 
    49 }

    渲染效果如下:

     

    4、添加立方体贴图反射

     1 Shader "Surface Shader Example/Diffuse Simple"{
     2 
     3     Properties{//添加纹理属性
     4 
     5     _MainTex("Texture",2D) = "white"{}
     6 
     7     //立方体贴图属性
     8 
     9     _Cube("Cubemap",CUBE) = ""{}
    10 
    11 }
    12 
    13     SubShader{
    14 
    15             Tags{"RenderType" = "Opaque"}
    16 
    17             CGPROGRAM//表面着色器的实现代码
    18 
    19             //指明着色器类型,表面函数和光照模型
    20 
    21             #pragma surface surf Lambert
    22 
    23             struct Input{//输入的数据结构体
    24 
    25                 float2 uv_MainTex;
    26 
    27                 float3 worldRefl;//输入反射参数
    28 
    29         };
    30 
    31             sampler2D _MainTex;
    32 
    33             SamplerCUBE _Cube;
    34 
    35             void surf(Input IN,inout SurfaceOutput o){//表面函数
    36 
    37                 o.Albedo = tex2D(_MainTex,IN.uv_MainTex).rgb;
    38 
    39                 //将发射颜色设置给自发光颜色
    40 
    41                 o.Emission = texCUBE(_Cube,IN.worldRefl).rgb;
    42 
    43             }
    44 
    45             ENDCG
    46 
    47     }
    48 
    49     Fallback "Diffuse"//备选着色器
    50 
    51 }

    渲染效果如下:

     

    顶点片段着色器Vertex And Fragment Shaders

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

     与表面着色器一样,顶点片段着色器也需要用Cg/HLSL来编写核心的实现代码,代码用CGPROGRAM ENDCG语句包围起来,放在ShaderLab的Pass命令中,形式如下:

    Pass{
    //通道设置
    CGPROGRAM
    //本段Cg代码的编译命令
    #pragma vertexvert
    #pragma fragment frag
    //Cg代码
    ENDCG
    //其他通道设置
    }

    顶点着色程序从GPU前端模块(寄存器)中提取图元信息(顶点位置、法向量、纹理坐标等),并完成顶点坐标空间转换、法向量空间转换、光照计算等操作,最后将计算好的数据传送到指定寄存器中;然后片断着色程序从中获取需要的数据,通常为“纹理坐标、光照信息等”,并根据这些信息以及从应用程序传递的纹理信息(如果有的话)进行每个片断的颜色计算,最后将处理后的数据送光栅操作模块。

    顶点着色器和像素着色器的数据处理流程

     

    在应用程序中设定的图元信息(顶点位置坐标、颜色、纹理坐标等)传递到vertex buffer中;纹理信息传递到texture buffer中。其中虚线表示目前还没有实现的数据传递。当前的顶点程序还不能处理纹理信息,纹理信息只能在片断程序中读入。

    顶点着色程序与片断着色程序通常是同时存在,相互配合,前者的输出作为后者的输入。不过,也可以只有顶点着色程序。如果只有顶点着色程序,那么只对输入的顶点进行操作,而顶点内部的点则按照硬件默认的方式自动插值。例如,输入一个三角面片,顶点着色程序对其进行phong光照计算,只计算三个顶点的光照颜色,而三角面片内部点的颜色按照硬件默认的算法(Gourand明暗处理或者快速phong明暗处理)进行插值,如果图形硬件比较先进,默认的处理算法较好(快速phong明暗处理),则效果也会较好;如果图形硬件使用Gourand明暗处理算法,则会出现马赫带效应(条带化)。

    而片断着色程序是对每个片断进行独立的颜色计算,并且算法由自己编写,不但可控性好,而且可以达到更好的效果。

    由于GPU对数据进行并行处理,所以每个数据都会执行一次shader程序程序。即,每个顶点数据都会执行一次顶点程序;每个片段都会执行一次片段程序。

    片段就是所有三维顶点在光栅化之后的数据集合,这些数据没有经过深度值比较,而屏幕显示的像素是经过深度比较的。

    顶点片段着色器中编译命令的一些说明:

    编译命令Compilation directive

     

    编译目标Shader targets

     

    下面是一些顶点片段着色器的例子:

    1)根据发现方向设置模型表面颜色

     1 Shader"Tutorial/Display Normals"{
     2 
     3     SubShader{
     4 
     5         Pass{
     6 
     7             CGPROGRAM
     8 
     9             //CG代码块开始
    10 
    11             #pragma vertex vert
    12 
    13             #pragma fragment frag
    14 
    15             #include "UnityCG.cginc"
    16 
    17             struct v2f{
    18 
    19                 float4 pos:SV_POSITION;
    20 
    21                 float3 color:COLOR0;
    22 
    23             };
    24 
    25                v2f vert(appdata_base v)
    26 
    27                {//顶点程序代码,计算位置和颜色
    28 
    29                   v2f o;
    30 
    31                   o.pos = mul(UNITY_MATRIX_MVP,v.vertex);
    32 
    33                   o.color = v.normal*0.5+0.5;
    34 
    35                   return o;
    36 
    37                }
    38 
    39               half4 frag(v2f i):COLOR
    40 
    41              {
    42 
    43                  //片段程序代码,直接把输入的颜色返回,并把透明度设置为1
    44 
    45                    return half(i.color,1);
    46 
    47              }
    48 
    49             ENDCG
    50 
    51         }
    52 
    53     }
    54 
    55     Fallback "VertexLit"
    56 
    57 }

     

    渲染效果如下:

     

    2)根据切线方向设置模型表面颜色。

     

     1 Shader"vertex and fragment example/Tangents"{
     2 
     3     SubShader{
     4 
     5         Pass{
     6 
     7             Fog{Mode Off}
     8             CGPROGRAM
     9 
    10             //CG代码块开始
    11 
    12             #pragma vertex vert
    13 
    14             #pragma fragment frag
    15 
    16             //输入位置和切线数据
    17 
    18             struct appdata{
    19 
    20                 float4 vertex : POSITION;
    21 
    22                 float4 tangent : TANGENT;
    23 
    24             };
    25             
    26             struct v2f{
    27                 float4 pos : POSITION;
    28                 fixed4 color : COLOR;
    29             
    30             };
    31                v2f vert(appdata_base v)
    32 
    33                {//顶点程序代码,计算位置和颜色
    34 
    35                   v2f o;
    36 
    37                   o.pos = mul(UNITY_MATRIX_MVP,v.vertex);
    38 
    39                   o.color = v.tangent*0.5+0.5;
    40 
    41                   return o;
    42 
    43                }
    44                
    45               fixed4 frag(v2f i):COLOR0
    46               {
    47 
    48                  //片段程序代码,直接把输入的颜色返回,并把透明度设置为1
    49 
    50                     return i.color;
    51 
    52               }
    53 
    54              ENDCG
    55 
    56         }
    57 
    58     }
    59 
    60 }

     

     

    渲染效果如下:

     

  • 相关阅读:
    文字标签和注释标签
    HTML文档的组成和标签的规范
    HTML概述
    javaWeb
    web开发的三层架构
    ASCII码表
    JDK的新特性
    Editplus的运行JAVA的配置
    Eclipse的断点调试
    Eclipse工作空间的基本配置
  • 原文地址:https://www.cnblogs.com/tgycoder/p/4785553.html
Copyright © 2020-2023  润新知