精华内容
下载资源
问答
  • unity-shader-ShaderGraph可视化shader

    万次阅读 2019-05-05 11:33:28
    title: unity-shader-ShaderGraph可视化shader categories: Unity3d tags: [unity, shader, ShaderGraph, 可视化] date: 2019-05-03 18:04:23 comments: false unity-shader-ShaderGraph可视化shader, 和 shader ...

    title: unity-shader-ShaderGraph可视化shader
    categories: Unity3d
    tags: [unity, shader, ShaderGraph, 可视化]
    date: 2019-05-03 18:04:23
    comments: false

    可视化shader, 和 shader forge 插件, ue4 材质编辑器 类似的东西, 都是通过可视化链接节点来编写逻辑代码, 然后会自动 编译 就能看到效果.
    ShaderGraph 需要用到 lightweight rp. 里面内置了一些常用的公式 如: frensel, 很简单就可以使用. 可见写 shader 的门槛越来越低了, 如果知道基点的实现原理会更好, 这个可以通过官网文档查阅到, 每个节点都有对应的代码实现. 官网文档 - https://docs.unity3d.com/Packages/com.unity.shadergraph@6.5/manual/index.html


    前篇

    要点击左上角的 save asset 才会编译生效, 这个和 ue4 的 apply 一样

    疑问

    上面几个问题还是得用以前的写 xxx.shader 代码的方式去解决.


    相关使用方式

    shader 命名规则

    • reference 属性的 _MainClr 才是 uniform 变量的命名, 也就是程序设置参数的命名.

    替换预览模型

    预览节点右键 custom mesh 就可以选择别的模型


    安装 shader graph

    当前版本 unity2018.3.11f1. 安装了 shader graph 后, 之前写的 xxx.shader 将会无效 (变成紫色).
    所以最好还是另起一个lwrp的模板工程用来学习.

    参考: Unity-Shader Graph 安装 - https://blog.csdn.net/liquanyi007/article/details/80771441

    1. 默认的 3d 工程没有 shader graph, 需要通过 package manager 安装 lightweight rp, shadergraph 这两个包, 才能使用 shader graph.

      如果是 2019 之前的是预览版, 需要在 advance 勾选上 show preview packages 才能显示出来

    2. 创建一个 lwrp asset. create -> rendering -> lightweight pipeline asset

      然后指定项目使用这个 asset, 才肯使用 shader graph. Edit -> Project Setting -> Graphics

    3. 然后 create -> shader 才会出现有 graph 的选项


    自定义节点 custom node

    相关参考:

    1. 增加一个自定义节点脚本

      using System.Reflection;
      using UnityEditor.ShaderGraph;
      using UnityEngine;
      
      [Title("Custom", "My Custom Node1")] // 创建节点时 节点所在的组, 可有n多个层级
      public class MyCustomNode : CodeFunctionNode {
          public MyCustomNode() {
              name = "My Custom Node2"; // graph 中节点名字
          }
      
          protected override MethodInfo GetFunctionToConvert() {
              return GetType().GetMethod("MyCustomFunction",
                  BindingFlags.Static | BindingFlags.NonPublic);
          }
      
          // 计算公式
          static string MyCustomFunction(
              [Slot(0, Binding.None)] DynamicDimensionVector A, [Slot(1, Binding.None)] DynamicDimensionVector B, [Slot(2, Binding.None)] out DynamicDimensionVector Out) {
              return @"
      {
          Out = A + B;
      } 
      ";
          }
      }
      
    2. 然后就可以在 graph 中使用

      • 节点可以是 vector1,2,3,4, 只要 A 或 B 其中一个换成其他参数类型, 其他两个节点也会跟着变化

    数据类型

    Name Color Description
    Vector 1 Light Blue A Vector 1 or scalar value
    Vector 2 Green A Vector 2 value
    Vector 3 Yellow A Vector 3 value
    Vector 4 Pink A Vector 4 value
    Dynamic Vector Light Blue See Dynamic Data Types below
    Matrix 2 Blue A Matrix 2x2 value
    Matrix 3 Blue A Matrix 3x3 value
    Matrix 4 Blue A Matrix 4x4 value
    Dynamic Matrix Blue See Dynamic Data Types below
    Dynamic Blue See Dynamic Data Types below
    Boolean Purple A Boolean value. Defined as a float in the generated shader
    Texture 2D Red A Texture 2D asset
    Texture 2D Array Red A Texture 2D Array asset
    Texture 3D Red A Texture 3D asset
    Cubemap Red A Cubemap asset
    Gradient Grey A Gradient value. Defined as a struct in the generated shader
    SamplerState Grey A state used for sampling a texture

    常用节点

    法线节点

    sample texture 2dType 的设置为 Normal 才是 法线图 的采样, 得到蓝色的才是正确的
    Default 为普通贴图的采样


    边缘 rim

    fresnel effect, 使用很方便, 已经把 世界空间 下的 法线和观察方向 算好用来 dot 处理, 只需要填充一下 指数 Power 即可.
    ps: 这些节点如果了解过 fresnel 公式的话自然就比较清楚这些节点都干了什么事情.


    纹理uv偏移

    tilling and offset, 相当于 o.texcoord = TRANSFORM_TEX(v.texcoord,_MainTex); 中的 TRANSFORM_TEX

    做 uv流动 效果的时候就需要用到这个节点

    uv流动的几种方式

    1. 基于 纹理uv. 模型顶点的uv值. (看起来有点乱, 因为 uv值 的是在 uv展开 的时候决定的)

    2. 基于 顶点位置, 又分为 切线,对象,世界,观察 四个空间的

      • 观察空间下

    3. 基于 屏幕空间的位置. 简答的理解就是屏幕的 左下角是(0,0), 右下角是(1, 1), 顶点都位于这个区间内


    appdata 节点

    也就是 cpu 到 gpu 的数据结构中的字段, 比如

    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;
    };
    

    在 shader graph 中为以下节点

    Name Data Type Description
    Position Vector 3 Vertex or fragment position, label describes expected transform space
    Tangent Vector 3 Vertex or fragment tangent vector, label describes expected transform space
    Normal Vector 3 Vertex or fragment normal vector, label describes expected transform space
    Bitangent Vector 3 Vertex or fragment bitangent, label describes expected transform space
    UV Vector 2 UV0, UV1, UV2, UV3
    Vertex Color Vector 4 RGBA vertex color value.

    示例说明

    积雪

    step 函数使用的频率非常高, 常用于条件的判断 (替代 if-else, 更优于gpu并行计算). 之前也总结过 unity-shader-GPU优化_step函数替代if-else.

    step(a, b) : 如果 a <= b,返回 1 ;否则,返回 0

    官方示例中的 积雪 效果

    • snow direction : (0, 1, 0), 也就是y轴正方向
    • snow depth : 0, 这一步step也就是判断哪些 面 的 法线 与 (0, 1, 0) 的夹角小于 90 度

    卡通

    • 光的方向与法线的 点乘 结果从 [-1, 1] 映射到 [0, 1] (映射算法 (x+1)/2), 再去采样 Tamp 贴图作为光照的强度.

    常用快捷键

    various shortcut keys to use for better workflow.

    Hotkey Windows OSX Description
    Focus F F Focus the workspace on all or selected Nodes
    Create Node Spacebar Spacebar Opens the Create Node Menu
    展开全文
  • Shader开发从入门到精通

    万人学习 2015-09-17 09:32:32
    Shader编程从入门到精通视频教程,该课程主要分享2D中的Shader与3D中的Shader编程,具体功能包括颜色配置、纹理、UV动画、滤镜等。
  • Unity Shader 基础教程

    万次阅读 多人点赞 2018-05-27 19:14:23
    在Github上看到一篇关于Unity-Shader的教程,感觉还不错,作者写的很好,很适合Unity-Shader的基础入门,我在这里翻译一下,分享给大家,英文水平很烂,大致能明白Unity-Shader是什么,渲染管线的工作流程,以及...

    Unity-Shader-基础教程

    在Github上看到一篇关于Unity-Shader的教程,感觉还不错,作者写的很好,很适合Unity-Shader的基础入门,我在这里翻译一下,分享给大家,英文水平很烂,大致能明白Unity-Shader是什么,渲染管线的工作流程,以及Unity Shader的一些类型和怎样编写Unity Shader。
    原文链接

    第一部分:什么是Shader?

    Shader是计算机图形渲染管线的一部分,它是一小段应用程序,告诉计算机在场景中怎样对物体渲染和着色。这个过程包括计算颜色和光照值,并将其给予对象,以至于对象被显示在屏幕上。和上面一样,Shader也被用来创建游戏中的一些特殊的和后期处理效果。

    在现代游戏引擎中(包括Unity),Shader运行在可编程的GPU渲染管道中,在GPU中允许并行运行,并且能够很快速的进行许多着色计算。

    第二部分:渲染管道

    为了学习Shader,我们将简单的了解渲染管道,我们将在本教程中讨论下面这张图片:

    image

    我更加倾向把Shader看做是由一种信息类型(模型数据、颜色等等)到另外一种信息类型(像素/片元)的变换,对象数据继承与对象本身,例如模型中的点、法线、三角形、UV坐标等等。我们可以把自定义数据/传递到shader中使用,颜色、纹理、数字等等这些。

    着色器流水线的第一步是顶点函数。正如你所知的,顶点就是一些点。顶点函数将处理模型中的一些点(连同其它的一些数据诸如法线)并将它们渲染流水线的下一个阶段,片元函数。

    片元函数将使用这些顶点,并对它们着色。将它想象为一个画家和他们的画笔,它最终以(R,G,B,A)的格式输出像素数据。

    最后,将像素添加到帧缓冲中,在帧缓冲中这些数据有可能被进一步处理,直到这些数据被绘制到屏幕上。

    第三部分:Scene 配置

    在开始写Shader代码之前,需要在Unity中配置一下我们的场景。在Unity中创建一个工程,并导入所有的资源。

    • Bowl Model(碗模型)
    • Noise Texture(噪声纹理)
    • Bowl texture(碗模型纹理)

    在新场景中添加一个Cube、Sphere和Bowl Model(碗模型),并保存场景,之后,你的场景将向下面这样:

    image

    接下来,在Project视图中单击右键(或者点击Create)并添加一个新的Unlit Shader(无光照着色器),将其命名为Tutorial_Shader.

    如果你对其它类型的shaders好奇的话,我会在文章的末尾谈论它。

    image

    然后在刚才创建的shader上点击右键Create>Material 创建一个材质球,Unity将自动创建一个材质球并使用刚才创建的着色器的名字。

    Note:一个“Material”在Unity中仅仅是着色器的一个实例,它仅保存自定义数据/属性的值。

    image

    最后,通过单击或者拖动的方式将材质赋予我们在场景中创建的所有对象上。

    之后场景中的每个对象看起来是这样的,白色,并且没有阴影或者shading:

    image

    第四部分:一个Unlit Shader(无光照着色器)的大致骨架

    终于到了开始写我们自己的shader的时候了,在写之前,首先打开之前创建的Tutorial_Shader文件,你将看到Unity自动生成了一些代码供我们使用。为了继续本教程,删除所有代码并使文件变空白。

    Note:所有的shader在Unity中使用的是一种被称为shaderlab的语言写的。Shadrlab是HLSL/CG语法的包装器,为了使Unity能够跨平台编译Shader代码并且在Inspector面板中暴露一些属性结点。

    我们将添加一些初始代码,如下:

    Shader "Unlit/Tutorial_Shader"{
        ...
    }
    

    这行代码的作用是指定着色器代码存放在什么位置。双引号中的字符串告诉Unity在哪里查找Shader.

    例如:

    Shader "A/B/C/D/E_Shader"{
        ...
    }
    

    image

    如果你保存你的shader代码,并切回到Unity中,你将注意到所有使用这个材质的对象都已经变成了粉红色。

    当你的着色器中有错误时,Unity中将调用FallBack着色器,你将会得到一个粉红色的物体。你可以在Project中点击shader文件查看相应的错误。目前,我们得到一个粉红色的物体,因为我们的shader文件还没有完成。

    接下来是属性块,如下:

    Shader "Unlit/Tutorial_Shader"{
        Properties{
            ...
        }
    }
    

    在属性块中,我们可以传递一些自定义数据。我们在这里声明的数据将被显示在Unity Editor面板中,在Editor中更改也会驱动脚本更改。

    Shader "Unlit/Tutorial_Shader"{
        Properties{
            ...
        }
        SubShader{
            ...
        }
    }
    

    每一个shader有一个或者多个subshaders,如果你的应用将被部署到多种平台(移动、PC、主机),添加多个Subshader是非常有用的。例如:你可能想要写为PC/Desktop写一个高质量的Subshader,为移动端写一个低质量,但速度更快的Subshader.

    pass语句块:

    Shader "Unlit/Tutorial_Shader"{
        Properties{
            ...
        }
        Subshader{
            Pass{
                ...
            }
        }
    }
    

    每个Subshader至少有一个pass语句块,它实际上是对象渲染的位置。一些特效要求有多个pass语句块,目前,我们仅仅专注于一个。

    在pass语句块中,是一些实际渲染的代码:

    Shader "Unlit/Tutorial_Shader"{
        Properties{
            
        }
        Sunshader{
            pass{
                CGPROGRAM
                    ...
                ENDCG
            }
        }
    }
    

    我们实际写的所有Shader代码都在CGPROGRAM和ENDCG中,对于Unity来说,shaderlab是HLSL和CG的变体。

    下面,我们需要告诉Unity,顶点函数和片元函数是什么:

    CGPROGRAM
        #pragma vertex vertexFunction
        #pragma fragment fragmentFunction
    ENDCG
    

    这里,我们将vertex函数声明为vertexFunction,fragment函数声明为fragmentFunction.

    我们也将定义这些函数:

    CGPROGRAM 
        #pragma vertex vertexFunction
        #pragma fragment fragmentFunction
        
        void vertexFunction(){
            
        }
        void fragmentFunction(){
            
        }
    ENDCG
    

    在开始着色之前,我们需要设置一些数据结构和两个函数,这样,我们就可以使用Unity给定的数据,并把数据返回到Unity中。首先,添加UnityCG.cginc语句块,我们可以使用这个文件中包含的一些助手函数。

    我们将添加一个数据结构a2v(此处和原文不一致),并修改顶点函数,将a2v作为参数传递给顶点函数。

    CGPROGRAM
        #pragma vertex vertexFunction
        #pragma fragment fragmentFunction
        
        #include "UnityCG.cginc"
        
        struct a2v{
            
        };
        void vertexFunction(a2v v){
            
        }
        void fragmentFunction(){
            
        }
    ENDCG
    

    当传递一个参数到vertexFunction中时,Unity将解析这个函数的结构,并基于正在绘制的对象模型传递值。我们可以传递一些自定义的数据,如下面声明的那样:

    [type] [name] :[semantic]
    

    例如,可以要求Unity获取模型对象的顶点坐标,如下:

    flot4 vertex:POSITION;
    

    我们也可以从Unity中获取顶点坐标和UV纹理坐标,如下:

    struct a2v{
        float4 vertex:POSITION;
        float2 uv:TEXCOORD0;
    }
    

    最后配置顶点函数,创建一个结构体,并将其命名v2f(代表从vertex to fragment,顶点数据传递到片元),将vertex中包含的数据传递到片元函数,同时确保vertexFunction 返回 v2f的数据类型,在我们使用它时,创建并返回一个空白数据。

    CGPROGRAM
        #pragma vertex vertexFunction
        #pragma fragment fragmentFunction
        #include "UnityCG.cginc"
        
        struct a2v{
            float4 vertex:POSITION;
            float2 uv:TEXCOORD0;
        };
        struct v2f{
            
        };
        v2f vertexFunction(a2v v){
            v2f o;
            return o;
        }
        void fragmentFunction(){
            
        }
    
    ENDCG
    

    像之前一样,我们可以在v2f结构体中定义一些数据,我们可能想要把这些数据从顶点函数传递到片元函数。

    struct v2f{
        float4 position:SV_POSITION;
        float2 uv:TEXCOORD0;
    }
    

    如果你对SV_POSITION和POSITION 感到好奇,SV代表“system value”,在v2f结构中表示最终渲染的顶点的位置。

    现在基本准备好了,我们仅仅需要编辑片元函数,使它接受一个v2f结构并返回一个fixed4的值。

    fixed4 fragmentFunction(v2f i){
        
    }
    

    输出的片元函数将是一个有(R,G,B,A)代表的颜色值

    最后,我们将为片元函数添加一个SV_TARGET的输出语义,如下:

    fixed4 fragmentFunction(v2f i):SV_TARGET{
        
    }
    

    这个过程告诉Unity我们将输出一个color去渲染,现在准备开始实际的编码了,肉和土豆使我们的vertex和fragment函数,到这个点,大致的骨架已经出来了

    Shader "Unlit/Tutorial_Shader"{
        Properties{
            
        }
        Subshader{
            Pass{
                CGPROGRAM
                #pragma vertex vertexFunction
                #pragma fragment fragmentFunction
                #include "UnityCG.cginc"
                
                struct a2v{
                    float4 vertex:POSITION;
                    float2 uv:TEXCOORD0;
                };
                struct v2f{
                  float4 position:SV_POSITION;
                  float2 uv:TEXCOORD0;
                };
                v2f vertexFunction(a2v v){
                    v2f o;
                    return o;
                }
                fixed4 fragmentFunction(v2f i):SV_TARGET{
                    
                }
                ENDCG
            }
        }
    }
    

    第五部分:Shader 基础

    首选我们要做的是获取顶点的正确位置,使用Unity中提供的UnityObjectToClipPos()函数(这个函数的作用是将世界空间的模型坐标转换到裁剪空间,函数内部封装了实现顶点坐标变换的具体细节,如矩阵变换等等),如下:

    v2f vertexFunction(a2v v){
        v2f o;
        o.position=UNnityObjectToClipPos(v.vertex);
        return o;
    }
    

    这个函数将在局部空间中表示的顶点,变换到渲染相机的裁剪空间。注意,我们通过设置o.position的位置来传递转换的点。接下来,给片元函数一个输出。

    fixed4 fragmentFunction(v2f i):SV_TARGET{
        return fixed4(0,1,0,1);
    }
    

    现在,等待一会儿。保存你的shader并且返回到Unity,你将看到我们的精美的绿色的物体。如下:

    image

    当然,这对你来说可能印象并不深刻,因此,让我们继续构建,而不是返回一个基本的绿色,可能我们想要编辑shader使得其能返回一个我们想要的颜色,为了做到这一点,我们需要回到开始的自定义属性。

    我们可以使用如下语法添加一些属性:

    name ("display name",type)=default value
    

    如下,我们将暴露出一个颜色值,如下:

    Properties{
        _Color("Totally Rad Color",Color)=(1,1,1,1)
    }
    

    在这里定义了一个颜色供我们使用,将其称之为_Color并且它将显示为 “Totally Rad Color!”,在Unity面板中。我们也将给予一个默认白色的值,现在保存并返回Unity,在Inspect的材质面板中,你将看到如下:

    image

    在我们使用这个color之前,我们需要把它传递到CG代码中,Unity会通过变量的名字进行自动绑定,如下:

    CGPROGRAM
        #pragma vertex vertexFunction
    	#pragma fragment fragmentFunction
    
    	#include "UnityCG.cginc"
    	
    	struct a2v{
    	    float4 vertex:POSITION;
    	    float2 uv:TEXCOORD0;
    	};
    	struct v2f{
    	    float4 position:SV_POSITION;
    	    float2 uv:TEXCOORD0;
    	};
    	//从CG中获取属性
    	float4 _Color;
    	v2f vertexFunction(a2v v){
    	    v2f o;
    	    o.position=UnityObjectToClipPos(v.vertex);
    	    return o;
    	}
    	fixed4 fragmentFunction(v2f i):SV_TARGET{
    	    return fixed4(0,1,0,1);
    	}
    ENDCG
    

    现在,可以在片元函数中使用_Color值了,让它返回我们期待的颜色值,而不是返回一个绿色:

    fixed4 fragmentFunction(v2f i):SV_TARGET{
        return _Color;
    }   
    

    现在,保存并返回到Unity中,如果你在Inspect中的Material中改变_Color的值,你应该能看到所有对象做出了相应的改变。

    image

    现在我们知道了如何添加属性,让我们尝试添加一张标准的纹理贴图,这里需要添加一个新的属性给我们的纹理:

    Properties{
        _Color("_Color",Color)=(1,1,1,1)
        _MainTexture("Mian Texture",2D)="white"{}
    }
    

    注意它的类型是2D,默认给它一张白色的纹理,我们还需要获取这个属性在CG片段中使用它:

    float4 _Color;
    sampler2D _MainTexTure;
    

    然后,需要从模型传递UV纹理坐标到片元函数,我们可以通过返回顶点函数并将其传递v2f中,如下:

    v2f vertexFunction(a2v v){
        v2f o;
        o.position=UnityObjectToClipPos(v.vertex);
        o.ov=v.uv;
        return o;
    }
    

    为了能在片元函数中使用纹理的颜色,我们需要对纹理进行采样。谢天谢地,CG中已经有一个tex2D()函数帮我们做了一切。

    fixed4 fragmentFunction(v2f i):SV_TARGET{
        return tex2D(_MainTexture,i.uv);
    }
    

    tex2D获取我们想要采样的纹理以及我们想要采样的UV坐标,在这种情况下。我们提供了它的主纹理并给定模型的点,我们可以得到我们想要的颜色,最后返回的是最终的颜色。现在,保存并返回到Unity 的material insepct面板中,选择bowel纹理赋予"Main Texture"",你会发现,模型发生了改变,尤其是碗的模型看起来尤其像一碗汤。

    image

    提示:我们可以改变纹理在Unity中的采样方式,通过选择纹理文件并在Inspector面板中改变filter mode(过滤模式):

    imageimage

    第六部分:试着改变Shader

    现在,我们已经大致了解了一些基础,我们可以做一些有趣的效果并做一些简单的特效。首先,我们将使用一张噪声贴图实现“溶解” 或者“切断”效应,首先我们将添加另一个纹理属性和一个float 属性,如下:

    Properties{
        _Color("Color",Color)=(1,1,1,1)
        _MainTexture("Main Texture",2D)="white"{}
        _DissolveTexture("Dissolve Texture",2D)="white"{}
        _DissolveCutoff("Dissolve Cutoff",Range(0,1)=1
    }
    

    注意这里是如何设置_DissolveCutoff 为一个Range(0,1),它代表一个从(0,1)(包含)的float值,并且这种计数法允许我么容易的使用slider(滑动条)来设置值,接下来,让我们在CGPROGRAM中添加他们。

    float4 _Color;
    sampler2D _MainTexture;
    sampler2D _DissolveTexture;
    float _DissolveCutoff;
    

    现在能在片元函数中对溶解纹理采样:

    fixed4 fragmentFunction(v2f i):SV_TARGET{
        float4 textureColor=tex2D(_MainTexture,i.uv);
        float4 dissolveColor=tex2D(_DissolveCutoff,i.uv);
        return textureColor;
    }
    

    提示:我们将为我们的主纹理使用相同的UV纹理坐标,接下里,魔术发生了:

    fixed4 fragmentFunction(v2f i):SV_TARGET{
        float4 textureColor=tex2D(_MainTexture,i.iv);
        float4 dissolveColor=tex2D(_DissolveTexture,i.uv);
        clip(dissolveColor.rgb-_DissolveCutoff);
        return textureColor;
    }
    

    clip 函数检查这个给定的值是否小于0.如果小于0,我们将丢弃这个像素并且不绘制它。如果大于0,继续保持像素、正常的渲染,我们的代码的工作方式如下:

    1. 对主纹理的颜色进行采样
    2. 对纹理颜色进行裁剪采样
    3. 从裁剪纹理中减去裁剪值
    4. 如果小于0,不进行绘制
    5. 否则,返回主纹理采样颜色

    现在,保存并返回Unity,回到材质面板,赋予“Dissolve Texture"我们的noise纹理,移动”Dissolve Cutoff" 滑动条,你应该会看到一个效果,向下面这样:

    image

    很酷吧? 我们也能做更多。在将其传递给fragment函数之前,让我们尝试更改这些顶点,在Inspector面板中暴露出一些结点属性。

    Properties{
        _Color("Color",Color)=(1,1,1,1)
        _MainTexture("Main Texture",2D)="white"{}
        _DissolveTexture("Dissolve Texture",2D)="white"{}
        _DissolveCuroff("Dissolve Cutoff",Range(0,1))=1
        _ExtrudeAmount("Extrue Amount",float)=0
    }
    ...
    
    float4 _Color;
    sampler2D _MainTexture;
    sampler2D _DissolveTexture;
    float _DissolveCutoff;
    float _ExtrudeAmount;
    

    我们还将使用模型中的法线信息,因此,让我们添加这个字段到a2v的结构体中,以至于我们能访问它。

    struct a2v{
        float4 vertex:POSITION;
        float2 uv:TEXCOORD0;
        float3 normal:NORMAL;
    };
    

    现在,添加一个单行到顶点函数中:

    v2f vertexFunction(a2v v){
        v2f o;
        v.vertex.xyz+=v.normal.xyz*_ExtrudeAmount;
        o.position=UnityObjectToClipPos(v.vertex);
        o.uv=v.uv;
        return o;
    }
    

    我么在这里做的是,在将顶点转换为局部模型空间之前,我们将通过增加他们的法线方向时间来抵消它们的外加量,法线是一个向量代表顶点面向的方向,现在保存并返回Unity中,改变"Extrude Amount"的值,你应该看到下面这样的效果:

    image

    我们也能为这些属性制作动画:

    v2f vertexFunction(a2v v){
        v2f o;
        v.vertex.xyz+=v.normal.xyz*_ExtrudeAmount*sin(_Time.y);
        o.position=UnityObjectToClipPos(v.vertex);
        o.uv=v.uv;
        return o;
    }
    

    _Time是一个代表时间的变量被包含在UnityCH.cginc中,y值代表秒,确保“Animated Materials” 在场景视图中被勾选,如下:

    image

    下面是我们最终的代码:

    Shader "Unlit/Tutorial_Shader"{
        Properties{
            _Color("Color",Color)=(1,1,1,1)
            _MainTexture("Main Texture",2D)="white"{}
            _DissolveTexture("Dissolve Texture",2D)="white"{}
            _DissolveCutoff("Dissolve Cutoff",Range(0,1))=1
            _ExtrudeAmount("Extrue Amount",float)=0
        }
        Subshader{
            Pass{
                CGPROGRAM
                    #pragma vertex vertexFunction
                    #pragma fragment fragmentFunction
                    #include "UnityCG.cginc"
                    
                    struct a2v{
                        float4 vertex:POSITION;
                        float2 uv:TEXCOORD0;
                        float3 normal:NORMAL;
                    };
                    struct v2f{
                        float4 position:SV_POSITION;
                        flaot2 uv:TEXCOORD0;
                    };
                    float4 _Color;
                    sampler2D _MainTexture;
                    sampler2D _DissolveTexture;
                    float _DissolveCutoff;
                    float _ExtrudeAmount;
                    
                    v2f vertexFunction(a2v v){
                        v2f o;
                        v.vertex.xyz+=v.normal.xyz*_ExtrudeAmount*sin(_Time.y);
                        o.position=UnityObjectToClipPos(v.vertex);
                        o.uv=v.uv;
                        return o;
                    }
                    
                    fixed4 fragmentFunction(v2f i):SV_TARGET{
                        float4 textureColor=tex2D(_MainTexture,i.uv);
                        float4 dissolveColor=tex2D(_DissolveTexture,i.uv);
                        clip(dissolveColor.rgb-_DissolveCutoff);
                        return textureColor;
                    }
                ENDCG
            }
        }
    }
    

    第七部分:Scripting 和Shaders

    接下来,我们将讨论怎样使用Unity脚本来控制Shader(即C#和Shader的交互),例如,我们将再次使用之前添加的_Color属性。首先,我们再片元函数中让其为着色器的颜色进行着色,如下:

    fixed4 fragmentFunction(v2f i):SV_TARGET{
        float4 textureColor=tex2D(_MainTexture,i.uv);
        float4 dissolveColor=tex2D(_DissolveTexture,i.uv);
        clip(dissolveColor.rgb-_DissolveCutoff);
        return textureColor*_Color;
    }
    

    我们将输出颜色和_Color属性相乘,在Editor中如下:

    image

    现在,让我们开始写脚本吧,我们将为每一个对象添加一个名为RainbowColour.cs的脚本。

    image

    在脚本中,声明两个私有变量 Rendeerer和Materail:

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    public class RainbowColor:MonoBehaviour{
    
        Renderer rend;
        Material material;
        
        void Start(){
            
        }
        void Update(){
            
        }
    }
    

    在Satrt()函数中,设置引用值。

    void Start(){
        rend=GetComponent<Renderer>();
        material=rend.material;
    }
    

    我们将在shader中使用Material.SetColor()函数设置颜色值,这个函数的第一个参数是一个字符串,它的名字使我们想要设置的属性的名字,第二个参数是我们想要设置的颜色的值。

    void Start(){
        rend=GetComponent<Renderer>();
        material=rend.material;
        material.SetColor("_Color",Color.mangenta);
    }
    

    当我们运行游戏的时候,颜色变为品红。

    image

    第八部分:阴影? 表面着色器?

    到目前为止,我们写了一个Unlit Shader(无光照着色器),Unity还允许你写表面着色器,表面着色器实际上就像vertex/fragment着色器,除了它们去掉了许多使着色器与光照与阴影交互的示例代码。如果你对写光照和阴影感兴趣,这是一份很棒的教程 here

    在这个章节,我将展示的是,表面着色器的每个部分如何与我们的顶点/片元着色器相关联,如果你在Unity中创建一个新的“Standard Shader",你会看到一些自动生成的代码,如下:

    Shader "Custom/NewSurfaceShader" {
        Properties{
            _Color("Color",Color)=(1,1,1,1)
            _MainTex("Albedo(RGB)",2D)="white"{}
            _Glossiness("Smothness",Range(0,1))=0.5
            _Metallic("Metallic",Range(0,1))=0.0
        }
        SubShader{
            Tags{"RenderType="Opaque"}
            LOD 200
            
            CGPROGRAM
            //基于物理着色的光照模型,并且在所有光类型上启用阴影
            #pragma surface surf Standard fullforwardshadows
            //使用3.0着色器目标,获得更好的光照效果
            #pragma target 3.0
            sampler2D _MainTex;
            
            struct Input{
                float2 uv_MainTex;
            };
            half _Glossiness;
            half _Metrllic;
            fixed4 _Color;
            //为证着色器添加实例化支持,你需要在材质上检测“启用示例"
            
            UNITY_INSTANCING_CBUFFER_START(Props)
            
            UNITY_INSTANCING_CBUFFER_END
            
            void surf(Input in,inout SurfaceOutputStandard o){
                fixed4 c=tex2D(_MainTex,in.uv_MainTex)*_Color;
                o.Albedo=c.rgb;
                o.Metallic=_Metallic;
                o.Smoothness=_Glossiness;
                o.Alpha=c.a;
            }
            ENDCG
        }
        FallBack "Diffuse"
    }
    

    让我们看一看每一个部分并解释一下他们都做了什么。
    首先,tags标签:

    SubShader{
        Tags{"RenderType"="Opaque"}
        ...
    }
    

    标签帮助你告诉渲染引擎如何以及何时你的着色器被渲染。在这种情况下,我们只是指定我们的着色器是透明的,这个对于深度纹理/地图是非常有用的。

    LOD 200
    

    多细节层次或者(LOD)有助于指定再默写硬件上使用哪种着色器,LOD值越大,着色器越复杂且它的值与模型的LOD无关。

    #pragma surface surf Standard fullforwardshadows
    

    类似于我们定义顶点和片元函数,我们在这里定义了一个称之为surf的表面函数,Stadard指定Unity Shader使用标准光照模型,而fullforwardshadows指定着色器启用所有常规阴影类型。

    #pragma target 3.0
    

    这里指定编译使用的光照版本,值越大,效果越好,也越复杂,同时对系统有更高的要求。

    void surf(Input i,inout SurfaceOutputStandard o){
        fixed4 c=tex2D(+mAINtEX,i.uv_MianT)*_Color;
        o.Albedo=c.rgb;
        
        o.Metallic=_Metallic;
        
        o.Smoothness=_Glossiness;
        o.Alpha=c.a;
    }
    

    这是着色器的核心部分,Unity定义了一个SurfaceOutputStandard 结构体来替代指定像素的颜色值。你可以设置一些诸如“Albedo"的属性,由于我们正在处理光照和阴影,不单单是直接获取颜色值,需要能够通过SurfaceOutputStandard保存的值来进行计算,下面是SurfaceOutputStandard的所有属性值的一部分:

    struct SurfaceOutput{
        fixed3 Albedo;
        fixed3 Normal;
        fixed3 Emission;
        half Specular;
        fixed Gloss;
        fixed Alpha;
    }
    

    Okay,讨论一下关于verties把。
    standard surface 默认情况下不暴露编辑vertices属性的函数,我们可以手动添加一个。首先,添加pragma并定义一个vertex函数

    #pragma surface surf Standard fullforwardshadows vertex:vert
    

    定义vert函数:

    void vert(inout appdata_full v){
        v.vertex.xyz+=v.normal.xyz*_ExtrudeAmount*sin(_Time.y;
        )
    }
    

    提示:如果你在改变顶点坐标的时候,阴影没有随之改变,你需要确保添加了”addshadow“ paagma 片段声明,如下:

    #pragma surface surf Standard fullforwardshadows vetex:vert addshadow
    

    再表面着色器的内部是非常复杂的,但是,它最终会被编译成我们之前写的顶点和片元函数那样。我强烈的建议去读官方文档,以了解更多关于这方面的信息。

    更多内容,欢迎关注公众号:

    码码小虫

    展开全文
  • OpenGL-Shader

    万人学习 2017-07-09 22:06:20
    2.Tesslattion Shader应用/基础案例分析 3.Gemotry Shader应用/基础案例分析 4.Compute Shader应用/基础案例分析 5.通过大量案例讲解分析/结合新特性,介绍用法 6.性能调优,如果借助shader加速应用,让你的程序支撑...
  • 【Unity Shader实战】卡通风格的Shader(二)

    万次阅读 热门讨论 2014-11-14 17:58:54
    三个月以前,在一篇讲卡通风格的Shader的最后,我们说到在Surface Shader中实现描边效果的弊端,也就是只对表面平缓的模型有效。这是因为我们是依赖法线和视角的点乘结果来进行描边判断的,因此,对于那些平整的表面...

    写在前面


    本系列其他文章:


    好久没写博客了,一定是因为课程作业比较多,一定不是因为我懒,恩恩。


    三个月以前,在一篇讲卡通风格的Shader的最后,我们说到在Surface Shader中实现描边效果的弊端,也就是只对表面平缓的模型有效。这是因为我们是依赖法线和视角的点乘结果来进行描边判断的,因此,对于那些平整的表面,它们的法线通常是一个常量或者会发生突变(例如立方体的每个面),这样就会导致最后的效果并非如我们所愿。如下图所示:



    因此,我们有一个更好的方法来实现描边效果,也就是通过两个pass进行渲染——首先渲染对象的背面,用黑色略微向外扩展一点,就是我们的描边效果;然后正常渲染正面即可。而我们应该知道,surface shader是不可以使用pass的。


    在这篇里,我们就会学习如何使用Vertex & Fragment Shader来实现上述的过程。很显然,这样的一个过程包含了两个步骤——描边和正常的渲染。


    最后的效果如下:




    实现描边


    上一篇里,我们使用了边缘高光来实现描边。而这篇里,我们将使用一个单独的pass来得到一个更好的效果。这里说的“更好”指的是以下几个方面:

    • 首先是对平整表面的适应性,如上面正方体的例子,这种方法仍可以得到期望的效果;
    • 而且这种方法可以不破坏正面模型的逼真度,也就是说正面模型可以完全不受影响。与之产生对比的是上一篇中的方法,使用边缘光照来实现的描边效果会影响到正面模型的表面,即正面模型也会有强烈的描边效果,而这往往不是我们所期望的。
    因此,这个pass的第一个步骤就是剔除正面部分:
                Cull Front
        	    Lighting Off

    我们先来看frag函数,因此它的工作非常简单!就是输出黑色啦~当然如果你的描边不想要黑色可以在这里改写。
                float4 frag(v2f i) : COLOR  
                { 
                	return float4(0, 0, 0, 1);               
                } 

    然后,我们继续计算vert函数部分。我们将会沿着顶点的法线方法向外扩张该点来模拟描边。因此,我们需要在下面的结构体中声明position和normal属性:
                struct a2v
                {
                    float4 vertex : POSITION;
                    float3 normal : NORMAL;
                }; 
     
                struct v2f
                {
                    float4 pos : POSITION;
                };

    接下来,我们定义一个范围在0到1之间的_Outline的变量来控制描边的宽度。最后,vert函数如下:
                            float _Outline;
    
    			v2f vert (a2v v)
    			{
    				v2f o;
    				o.pos = mul( UNITY_MATRIX_MVP, v.vertex + (float4(v.normal,0) * _Outline)); 
    				return o;
    			}

    它的含义很好理解:把原先的顶点位置v.vertex沿着v.normal的方向扩展_Outline倍后,再转换到投影平面上输出最后的屏幕位置信息。

    效果如下:


    为了暴露问题,我把描边的宽度调的比较高。那么,问题来了。大家可以看出来哪里不太对吧?没错,就是眼睛和嘴巴的地方。为什么会有哪些很大的黑色色块呢?这是因为眼睛和嘴巴是独立于身体之外的两个网格,它们各自使用了一个新的材质,而它们的深度关系是眼睛和嘴巴在身体的后面(被身体的皮肤包裹嘛),因此在渲染的时候身体的渲染输出像素会覆盖眼睛和嘴巴的部分,也包括身体的描边部分,也就是说身体的黑色描边会覆盖眼睛和嘴巴的正常渲染,而这不是我们所希望的。一种暴力的解决方法就是直接关闭该pass的深度信息。即:
        		Cull Front
        		Lighting Off
        		ZWrite Off

    这样一来,这个pass的结果是不写入深度缓存中的,而后面只要有其他材质要渲染该点的像素就会覆盖它。这样的效果如下:


    那么,问题又来了。眼睛和嘴巴部分虽然对了,但小怪物的先后关系又乱了,即后面小怪物的身体挡住了前面小怪物的描边。而要解决这个问题,就要写入深度缓存。死循环了有木有!

    其实,这说明我们生成描边的方法需要改进。我们回想为什么会出现眼睛和嘴巴那样的错误,是因为我们把描边的宽度调的太大了。我们之所以会这么做(当然这里我是故意的。。。),是因为有时候一些相邻顶点的法线指向非常不同,而为了得到我们想要的感性宽度,我们不得不调整的很大。而上述过程的实质其实就是把背光面的模型放大了而已,我们可以理解成它实际相当于一个新的黑色模型。这样放大的太过分了会发生什么呢?就是穿透和遮挡了。

    而正确的方法应该是,把顶点当成轮廓处理而不是一个真正的模型。也就是说,当我们观察背面的某一个顶点时,要把它的Z方向的值扁平化,那么描边的结果就会主要受X和Y方向的影响。因此,

    扁平化背面


    首先,我们要在视角坐标系中处理描边。因为描边正是基于我们观察的角度而定的。因此,我们要把需要的变量都转换到视角坐标系下处理。这里面涉及两个变量——顶点的位置和顶点的法线。

    顶点很好处理,只要使用UNITY_MATRIX_MV即可。法线的转换麻烦一点,这是因为法线并不是真正定义在模型坐标系中的,而是和它是正交的,我们需要使用ModelView转换矩阵的转置矩阵来把法线转换到视角坐标系中。原因可以看这里这里

    因此,我们的工作包含下面几个步骤:
    • 把顶点位置转换到视角坐标系;
    • 把法线转换到视角坐标系;
    • 把转换后的法线的z值扁平化,即使其是一个较小的定值,这样所有的背面其实都在一个平面上;
    • 按描边的宽度放缩法线,并添加到转换后顶点的位置上,得到新的视角坐标系中的位置;
    • 把新的位置转换到投影坐标系中。
    代码如下:
                v2f vert (a2v v)
                {
                    v2f o;
    
                    float4 pos = mul( UNITY_MATRIX_MV, v.vertex); 
    				float3 normal = mul( (float3x3)UNITY_MATRIX_IT_MV, v.normal);  
    				normal.z = -0.4;
    				pos = pos + float4(normalize(normal),0) * _Outline;
    				o.pos = mul(UNITY_MATRIX_P, pos);
    				
                    return o;
                }


    卡通化


    后面这一部分没什么好解释的,和上一篇的方法一致,同样使用了简化颜色和渐变纹理来模拟卡通效果。


    弊端


    上一篇里的方法比,这里的方法解决了之前的两个弊端:一个是轮廓宽度无法精确保证,一个是对于法线突变的模型的不适应性。但它也有自己的弊端。最明显的就是,它无法和模型内部的褶皱添加轮廓。例如上面的小怪兽,只有它最外层的边界才有轮廓线,但其内部的肥肉褶皱则无法体现。而这种问题的解决方法,可以依靠第三种更高级的Shader来实现。具体请参见卡通风格的Shader(三)。


    代码


    我知道大家最想要的还是代码,上面很多人直接略过。Sigh~

    还是把完整的代码给出。代码有两种,一种使用了法线纹理,一种没有使用法线纹理。每种都包含了三个pass:第一个pass处理背面进行描边,第二个pass处理正面的forwardbase,第三个pass处理正面的forwardadd。

    在Vertex & Fragment中处理法线和光照,简直是噩梦啊!VF虐我千百遍,我却待她如初恋,哎。后面会写一篇这方面的文章,如果有时间的话。。。

    首先是没有使用法线纹理的代码:
    Shader "MyToon/Toon-Fragment" {
    	Properties {
            _MainTex ("Base (RGB)", 2D) = "white" {}
            _Ramp ("Ramp Texture", 2D) = "white" {}
            _Tooniness ("Tooniness", Range(0.1,20)) = 4
            _Outline ("Outline", Range(0,1)) = 0.1
        }
        SubShader {
            Tags { "RenderType"="Opaque" }
            LOD 200
     
            Pass {
            	Tags { "LightMode"="ForwardBase" }
            	
                Cull Front
        		Lighting Off
        		ZWrite On
     
                CGPROGRAM
                
                #pragma vertex vert
                #pragma fragment frag
                
                #pragma multi_compile_fwdbase
     
               	#include "UnityCG.cginc"
               	
                float _Outline;
     
                struct a2v
                {
                    float4 vertex : POSITION;
                    float3 normal : NORMAL;
                }; 
     
                struct v2f
                {
                    float4 pos : POSITION;
                };
     
                v2f vert (a2v v)
                {
                    v2f o;
    
                    float4 pos = mul( UNITY_MATRIX_MV, v.vertex); 
    				float3 normal = mul( (float3x3)UNITY_MATRIX_IT_MV, v.normal);  
    				normal.z = -0.5;
    				pos = pos + float4(normalize(normal),0) * _Outline;
    				o.pos = mul(UNITY_MATRIX_P, pos);
    				
                    return o;
                }
     
                float4 frag(v2f i) : COLOR  
                { 
                	return float4(0, 0, 0, 1);               
                } 
     
                ENDCG
            }
            
            Pass {
    			Tags { "LightMode"="ForwardBase" }
    			
    			Cull Back 
    			Lighting On
    
    			CGPROGRAM
    
    			#pragma vertex vert
    			#pragma fragment frag
    			
    			#pragma multi_compile_fwdbase
    
    			#include "UnityCG.cginc"
    			#include "Lighting.cginc"
    			#include "AutoLight.cginc"
    			#include "UnityShaderVariables.cginc"
    			
    
    			sampler2D _MainTex;
    			sampler2D _Ramp;
    
    			float4 _MainTex_ST;
    
    			float _Tooniness;
     
     			struct a2v
    			{
    				float4 vertex : POSITION;
    				float3 normal : NORMAL;
    				float4 texcoord : TEXCOORD0;
    				float4 tangent : TANGENT;
    			}; 
    
    			struct v2f
    			{
    				float4 pos : POSITION;
    				float2 uv : TEXCOORD0;
    				float3 normal : TEXCOORD1;
    				LIGHTING_COORDS(2,3)
    			};
    			
    			v2f vert (a2v v)
    			{
    				v2f o;
    
    				//Transform the vertex to projection space
    				o.pos = mul( UNITY_MATRIX_MVP, v.vertex); 
    				o.normal  = mul((float3x3)_Object2World, SCALED_NORMAL);
    				//Get the UV coordinates
    				o.uv = TRANSFORM_TEX (v.texcoord, _MainTex);  
    				
    				// pass lighting information to pixel shader
      				TRANSFER_VERTEX_TO_FRAGMENT(o);
    				return o;
    			}
    			
    			float4 frag(v2f i) : COLOR  
    			{ 
    				//Get the color of the pixel from the texture
    				float4 c = tex2D (_MainTex, i.uv);  
    				//Merge the colours
    				c.rgb = (floor(c.rgb*_Tooniness)/_Tooniness);
    
    				//Based on the ambient light
    				float3 lightColor = UNITY_LIGHTMODEL_AMBIENT.xyz;
    
    				//Work out this distance of the light
    				float atten = LIGHT_ATTENUATION(i);
    				//Angle to the light
    				float diff =  dot (normalize(i.normal), normalize(_WorldSpaceLightPos0.xyz));  
    				diff = diff * 0.5 + 0.5; 
    				//Perform our toon light mapping 
    				diff = tex2D(_Ramp, float2(diff, 0.5));
    				//Update the colour
    				lightColor += _LightColor0.rgb * (diff * atten); 
    				//Product the final color
    				c.rgb = lightColor * c.rgb * 2;
    				return c; 
    
    			} 
    
    			ENDCG
    		}
    		Pass {
    			Tags { "LightMode"="ForwardAdd" }
    			
    			Cull Back 
    			Lighting On
    			Blend One One
    
    			CGPROGRAM
    
    			#pragma vertex vert
    			#pragma fragment frag
    			
    			#pragma multi_compile_fwdadd
    
    			#include "UnityCG.cginc"
    			#include "Lighting.cginc"
    			#include "AutoLight.cginc"
    			#include "UnityShaderVariables.cginc"
    			
    
    			sampler2D _MainTex;
    			sampler2D _Ramp;
    
    			float4 _MainTex_ST;
    
    			float _Tooniness;
     
     			struct a2v
    			{
    				float4 vertex : POSITION;
    				float3 normal : NORMAL;
    				float4 texcoord : TEXCOORD0;
    				float4 tangent : TANGENT;
    			}; 
    
    			struct v2f
    			{
    				float4 pos : POSITION;
    				float2 uv : TEXCOORD0;
    				float3 normal : TEXCOORD1;
    				half3 lightDir : TEXCOORD2;
    				LIGHTING_COORDS(3,4)
    			};
    			
    			v2f vert (a2v v)
    			{
    				v2f o;
    
    				//Transform the vertex to projection space
    				o.pos = mul( UNITY_MATRIX_MVP, v.vertex); 
    				o.normal  = mul((float3x3)_Object2World, SCALED_NORMAL);
      				o.lightDir = WorldSpaceLightDir( v.vertex );
    				//Get the UV coordinates
    				o.uv = TRANSFORM_TEX (v.texcoord, _MainTex);  
    				
    				// pass lighting information to pixel shader
      				TRANSFER_VERTEX_TO_FRAGMENT(o);
    				return o;
    			}
    			
    			float4 frag(v2f i) : COLOR  
    			{ 
    				//Get the color of the pixel from the texture
    				float4 c = tex2D (_MainTex, i.uv);  
    				//Merge the colours
    				c.rgb = (floor(c.rgb*_Tooniness)/_Tooniness);
    
    				//Based on the ambient light
     				float3 lightColor = float3(0);
    
    				//Work out this distance of the light
    				float atten = LIGHT_ATTENUATION(i);
    				//Angle to the light
    				float diff =  dot (normalize(i.normal), normalize(i.lightDir));  
    				diff = diff * 0.5 + 0.5; 
    				//Perform our toon light mapping 
    				diff = tex2D(_Ramp, float2(diff, 0.5));
    				//Update the colour
    				lightColor += _LightColor0.rgb * (diff * atten); 
    				//Product the final color
    				c.rgb = lightColor * c.rgb * 2;
    				return c; 
    			} 
    
    			ENDCG
    		}
        }
        FallBack "Diffuse"	    
    }
    

    然后是使用了法线纹理的Shader:
    Shader "MyToon/Toon-Fragment_Normal" {
    	Properties {
            _MainTex ("Base (RGB)", 2D) = "white" {}
            _Bump ("Bump", 2D) = "bump" {}
            _Ramp ("Ramp Texture", 2D) = "white" {}
            _Tooniness ("Tooniness", Range(0.1,20)) = 4
            _Outline ("Outline", Range(0,1)) = 0.1
        }
        SubShader {
            Tags { "RenderType"="Opaque" }
            LOD 200
     
            Pass {
            	Tags { "LightMode"="ForwardBase" }
            	
                Cull Front
        		Lighting Off
        		ZWrite On
     
                CGPROGRAM
                
                #pragma vertex vert
                #pragma fragment frag
                
                #pragma multi_compile_fwdbase
     
               	#include "UnityCG.cginc"
               	
                float _Outline;
     
                struct a2v
                {
                    float4 vertex : POSITION;
                    float3 normal : NORMAL;
                }; 
     
                struct v2f
                {
                    float4 pos : POSITION;
                };
     
                v2f vert (a2v v)
                {
                    v2f o;
                    
                    float4 pos = mul( UNITY_MATRIX_MV, v.vertex); 
    				float3 normal = mul( (float3x3)UNITY_MATRIX_IT_MV, v.normal);  
    				normal.z = -0.5;
    				pos = pos + float4(normalize(normal),0) * _Outline;
    				o.pos = mul(UNITY_MATRIX_P, pos);
    				
                    return o;
                }
     
                float4 frag(v2f i) : COLOR  
                { 
                	return float4(0, 0, 0, 1);               
                } 
     
                ENDCG
            }
            
            Pass {
    			Tags { "LightMode"="ForwardBase" }
    			
    			Cull Back 
    			Lighting On
    
    			CGPROGRAM
    
    			#pragma vertex vert
    			#pragma fragment frag
    			
    			#pragma multi_compile_fwdbase
    
    			#include "UnityCG.cginc"
    			#include "Lighting.cginc"
    			#include "AutoLight.cginc"
    			#include "UnityShaderVariables.cginc"
    
    			sampler2D _MainTex;
    			sampler2D _Bump;
    			sampler2D _Ramp;
    
    			float4 _MainTex_ST;
    			float4 _Bump_ST;
    
    			float _Tooniness;
     
     			struct a2v
    			{
    				float4 vertex : POSITION;
    				float3 normal : NORMAL;
    				float4 texcoord : TEXCOORD0;
    				float4 tangent : TANGENT;
    			}; 
    
    			struct v2f
    			{
    				float4 pos : POSITION;
    				float2 uv : TEXCOORD0;
    				float2 uv2 : TEXCOORD1;
    				float3 lightDirection : TEXCOORD2;
    				LIGHTING_COORDS(3,4)
    			};
    			
    			v2f vert (a2v v)
    			{
    				v2f o;
    				//Create a rotation matrix for tangent space
    				TANGENT_SPACE_ROTATION; 
    				//Store the light's direction in tangent space
    				o.lightDirection = mul(rotation, ObjSpaceLightDir(v.vertex));
    				//Transform the vertex to projection space
    				o.pos = mul( UNITY_MATRIX_MVP, v.vertex); 
    				//Get the UV coordinates
    				o.uv = TRANSFORM_TEX (v.texcoord, _MainTex);  
    				o.uv2 = TRANSFORM_TEX (v.texcoord, _Bump);
    				
    				// pass lighting information to pixel shader
      				TRANSFER_VERTEX_TO_FRAGMENT(o);
    				return o;
    			}
    			
    			float4 frag(v2f i) : COLOR  
    			{ 
    				//Get the color of the pixel from the texture
    				float4 c = tex2D (_MainTex, i.uv);  
    				//Merge the colours
    				c.rgb = (floor(c.rgb*_Tooniness)/_Tooniness);
    
    				//Get the normal from the bump map
    				float3 n =  UnpackNormal(tex2D (_Bump, i.uv2)); 
    
    				//Based on the ambient light
    				float3 lightColor = UNITY_LIGHTMODEL_AMBIENT.xyz;
    
    				//Work out this distance of the light
    				float atten = LIGHT_ATTENUATION(i);
    				//Angle to the light
    				float diff = saturate (dot (n, normalize(i.lightDirection)));  
    				//Perform our toon light mapping 
    				diff = tex2D(_Ramp, float2(diff, 0.5));
    				//Update the colour
    				lightColor += _LightColor0.rgb * (diff * atten); 
    				//Product the final color
    				c.rgb = lightColor * c.rgb * 2;
    				return c; 
    
    			} 
    
    			ENDCG
    		}
    		Pass {
    			Tags { "LightMode"="ForwardAdd" }
    			
    			Cull Back 
    			Lighting On
    			Blend One One
    
    			CGPROGRAM
    
    			#pragma vertex vert
    			#pragma fragment frag
    			
    			#pragma multi_compile_fwdadd
    
    			#include "UnityCG.cginc"
    			#include "Lighting.cginc"
    			#include "AutoLight.cginc"
    			#include "UnityShaderVariables.cginc"
    
    			sampler2D _MainTex;
    			sampler2D _Bump;
    			sampler2D _Ramp;
    
    			float4 _MainTex_ST;
    			float4 _Bump_ST;
    
    			float _Tooniness;
     
     			struct a2v
    			{
    				float4 vertex : POSITION;
    				float3 normal : NORMAL;
    				float4 texcoord : TEXCOORD0;
    				float4 tangent : TANGENT;
    			}; 
    
    			struct v2f
    			{
    				float4 pos : POSITION;
    				float2 uv : TEXCOORD0;
    				float2 uv2 : TEXCOORD1;
    				float3 lightDirection : TEXCOORD2;
    				LIGHTING_COORDS(3,4)
    			};
    			
    			v2f vert (a2v v)
    			{
    				v2f o;
    				//Create a rotation matrix for tangent space
    				TANGENT_SPACE_ROTATION; 
    				//Store the light's direction in tangent space
    				o.lightDirection = mul(rotation, ObjSpaceLightDir(v.vertex));
    				//Transform the vertex to projection space
    				o.pos = mul( UNITY_MATRIX_MVP, v.vertex); 
    				//Get the UV coordinates
    				o.uv = TRANSFORM_TEX (v.texcoord, _MainTex);  
    				o.uv2 = TRANSFORM_TEX (v.texcoord, _Bump);
    				
    				// pass lighting information to pixel shader
      				TRANSFER_VERTEX_TO_FRAGMENT(o);
      				
    				return o;
    			}
    			
    			float4 frag(v2f i) : COLOR  
    			{ 
    				//Get the color of the pixel from the texture
    				float4 c = tex2D (_MainTex, i.uv);  
    				//Merge the colours
    				c.rgb = (floor(c.rgb*_Tooniness)/_Tooniness);
    
    				//Get the normal from the bump map
    				float3 n =  UnpackNormal(tex2D (_Bump, i.uv2)); 
    
    				//Based on the ambient light
    				float3 lightColor = float3(0);
    
    				//Work out this distance of the light
    				float atten = LIGHT_ATTENUATION(i);
    				//Angle to the light
    				float diff = saturate (dot (n, normalize(i.lightDirection)));  
    				//Perform our toon light mapping 
    				diff = tex2D(_Ramp, float2(diff, 0.5));
    				//Update the colour
    				lightColor += _LightColor0.rgb * (diff * atten); 
    				//Product the final color
    				c.rgb = lightColor * c.rgb * 2;
    				return c; 
    
    			} 
    
    			ENDCG
    		}
        }
        FallBack "Diffuse"	    
    }
    



    写在最后


    上一篇里的代码我也更新过了,添加了有无法线的两种shader。和上一篇里的还有一点不一样,上一篇里的渐变纹理使用了假的双向反射分布函数,即除了漫反射方向还有视角方向进行采样。这一篇里仅仅使用了漫反射方向对渐变纹理进行采样,对应的渐变纹理也是一维的,如下:


    读者有需要的可以自己添加上视角方向的采样,也就是说在v2f里添加一个新的变量viewDir,然后逐顶点计算后传递给frag函数。如果我后面有时间的话可能会回头添加上。当然,大家还是靠自己比较好。


    展开全文
  • 最近工作遇到shader 打包问题,最后解决发现shader变体没有被包含进来所致。 一直以来对shader变体的了解还不够系统全面。现在觉得好好完整的总结下。 一、概念 ShaderVariant是啥,*.shadervariants 是干嘛的,#...

        最近工作遇到shader 打包问题,最后解决发现shader变体没有被包含进来所致。 一直以来对shader变体的了解还不够系统全面。现在觉得好好完整的总结下。

    一、概念

      ShaderVariant是啥,*.shadervariants 是干嘛的, #pragma shader_feature,#pragma multi_compile 有什么作用。

    ShaderVariant 表示一个shader其中一个特定的变种。个人理解 有点像C 语言的预编译宏一样,shader源代码写好后,编译时会根据 ShaderVariant 的关键字生成多个 最终shader。材质使用shader时,可以通过关键字使用其中那个最终shader.

    #pragma shader_feature,#pragma multi_compile 二者作用本质上是一样的,都是声明一个ShaderVariant关键字,之后Shader代码里就可以用#if 来判断此关键字是否启用,二者不同的地方就是前者在性能上做了优化。#shader_feature 声明的关键字,只在有材质球使用了其中关键字,则关键字的变体才会被包含近shader。*.shadervariants 文件可以手动编辑那些变体被包含进来。

    二、为啥要了解、使用变体。

    在实际工作,我们经常要根据外部条件的不同,对着色器有不同的处理方式。比如,如果有法线贴图的时候,会运用法线贴图,没有时候,会是一种处理逻辑,一般方法就是,在shader 属性上加个开关参数,针对有法线和没有法线的情况传入不同的值,再在shader代码里用if分支判断处理。这样虽然没有啥问题,但性能却下降很多,gpu运算都是并行的,分支结构对性能影响很大。所以要针对这两种情况,分别用两个不同shader来处理。变体,就可以针对这些情况用一份shader源代码通过使用不同关键字编译后生成不同的 最终shader。好处就是只要一份shader源代码,就可以生成多份目标shader.缺点就是,会增加 目标shader体积大小。影响最终包大小和加载变体的时间。

    三、如何正确使用变体

     1. 声明Keyword,用来产生Shader的变体(Variant)

       #pragma multi_compile A B 或者 #pragma shader_feature A B    全局变体,最多只能声明256个这样的Keyword;

      #pragma multi_complie_local 声明局部的、只在该Shader内部起作用的Keyword 每个Shader最多可以包含64个local Keyword

      local Keyword   Material.EnableKeyword仍是有效的,但对Shader.EnableKeyword 或CommandBuffer.EnableShaderKeyword这种全局开关说拜拜

        #if A

       return fixed(1,1,1,1)

       #endif

       #if B

       return fixed(1,0,0,1)

      #endif

     2. 变体组合

    变体组合 变种个数
    #pragma shader_feature A __, A  (非A 变体, A变体,) 默认:__
    #pragma shader_feature A B A,B 默认:A
    #pragma shader_feature A B C A,B,C 默认:A
    #pragma shader_feature  __ A __, A (非A 变体, A变体,) 默认:__
    #pragma multi_compile A A 默认:A
    #pragma multi_compile A B A,B 默认:A
    #pragma multi_compile A B C A, B,C 默认:A

    #pragma multi_compile  A B C

    #pragma multi_compile  D E

    AD,BD,CD,AE,BE,CE 默认:AD

    3. 通过shader 监视面板查看 ShaderVariant 包含的数量.

    3. 材质球如何选择变体组合。

     a. 默认选择第一个变体。 

      如 #pragma multi_compile A B 或者 #pragma shader_feature A B,默认选择 A

    b. 材质球监视面板,切换到Debug模式,Shader keywords 填入变体关键字组合

     

      

       c. 代码设置 

    Material. EnableKeyword(); Material. EnableKeyword();

    Shader.EnableKeyword();  Shader.DisableKeyword();

    四、shader变体打包

    1. shader_feature声明变种时,打包只会打包被资源引用的keyword变种,

    2. multi_compile声明变种时,打包会把所有变种都打进去

    3. ProjectSetting->Graphics->Always Include Shaders 列表里的shader ,打包时会把所有的变种打包进取。

    4. Resources目录的shader 打包时会把所有的变种打包进去

    5. *.shadervariantcollection 文件指定所有shader变体,会在打包时包含所有变体。

     

    五、自定义材质监视器

    1. Space、Heaer 标记
    [Space(50)][Header(MaterialPropertyDrawer)]
      Space 标记,可以带参数也可以不带,参数表示距离。

     Header 标记, 增加一个title,注意没有引号

    2. KeywordEnum 标记
    [KeywordEnum(None, Add, Multiply, Screen)] _BlendMode ("Blend Mode", Float) = 0 或者
    [Enum(None, 0, Add, 1, Multiply, 2, Screen ,3)] _BlendMode ("Blend Mode", Float) = 0

    生成一个下拉框,可以选择None, Add, Multiply, Screen

    shader 代码可以 

     

    #if _BLENDMODE_NONE , #if _BLENDMODE_ADD, #if _BLENDMODE_Multiply, #if _BLENDMODE_Screen

    命名规则为变量名的大写形式+枚举名的大写形式。而需要注意的是,Enum标记无法使用这些宏。

    3.Toggle

    [Toggle] _Invert ("Invert?", Float) = 0     //_INVERT_ON

    [Toggle(ENABLE_DOUBLE)] _Double ("Double?", Float) = 0             //ENABLE_DOUBLE

    shader 代码可以 

    #if _INVERT_ON  #if ENABLE_DOUBLE 

    命名规则是变量名的大写形式+_ON

    4. 继承ShaderGUI

    public class TestShaderGUI : ShaderGUI
    {
        public override void OnGUI(MaterialEditor materialEditor, MaterialProperty[] properties)
        {
            base.OnGUI(materialEditor, properties); // 显示默认面板

            Material targetMat = materialEditor.target as Material;

        }

    }

    展开全文
  • Vertex Shader

    千次阅读 2019-02-04 19:30:50
    Vertex Shader
  • Shader入门教程(一)

    万次阅读 多人点赞 2018-05-05 11:08:46
    学习Unity有一段时间了,都说Unity想要进阶得学会Shader编程,因此花了一阵子来学习Shader编程。学了之后才发现,Shader并没有我原先想的那么复杂,掌握它的原理和语法后,我们也能用shader编写出自己想要的特效,好...
  • Unity Shader 之Geometry Shader

    千次阅读 2017-06-19 10:59:09
    其实unity shader除了vertex shader和frag shader之外,还有一个geom shader,处理流程在vextex shader和frag shader之间,主要是能获取到一个顶点临近的一些顶点,处理三角形之类的,下面看一个例子 Shader ...
  • compute shader

    千次阅读 2018-01-12 18:11:45
    computer shader
  • android-gpuimage里给出了很多滤镜效果,本质都是用shader处理图像。 使用OpenGL做图像处理,最主要的就是FragShader的实现,以下是几种已经实现了的shader。有黑白照处理,Sephia特效,反相,高斯模糊,Median模糊...
  • Shader|什么是Shader

    千次阅读 2018-10-12 18:01:38
     Shader所在的阶段就是GPU渲染流水线的一部分。要得出出色的游戏画面是需要包括Shader在内的、CPU在内的所有的渲染流水线的共同参与才可完成:设置适当的渲染状态、使用混合函数或者开/关闭深度测试等。   具体的...
  • 本次更新放出的Shader为透明系列的3个Shader和标准的镜面高光Shader的两个Shader。由易到难,由入门级到应用级,难度梯度合理。 依然是先放出游戏场景的exe和运行截图。 本期用的模型为妙蛙草。 【可运行...
  • 一、UnityShaderShader    在Unity里,Unity Shader实际上指的就是一个ShaderLab文件——硬盘上以.shader作为文件后缀的一种文件。  Unity Shader相比于Shader,有优点也有缺点。 优点有: 在传统的...
  • 目录:Unity Shader - 知识点目录(先占位,后续持续更新) 原文:Making multiple shader program variants 版本:2019.1 Making multiple shader program variants 制作shader程序多变体 通常它将方便保留大部分...
  • 作为一个系统介绍Unity3D中Shader编写的系列文章的开篇,本文的第一部分系列文章的前言,然后第二部分介绍了这个系列文章中我们会使用的游戏场景创建方式,最后一部分讲解了如何在Unity中创建和使用Shader,为后面...
  • ShaderShaderToy开篇

    千次阅读 2018-07-24 10:49:46
    写在前面  Unity从学习到工作至今已经有三个年头了,开发中每次遇到难点第一件事就是百度搜索,从来没有自己的想法,甚是惭愧。想着不能再这样浑浑噩噩下去... 说起来跟Shader也很有渊源,因为以前自学过C#,第一...
  • shader实例(unity内置shader

    千次阅读 2019-05-20 11:12:04
    熟悉Unity3d内置shader应该算是学习unity shader不可缺少的一步,Unity3d自带60多个shader,这些shader被分为五大类Normal,Transparent,Transparent,Cutout,Self-llluminated,Reflective。 (一) Normal Shader ...
  • Unity Shader 自定义SurfaceShader

    千次阅读 2017-12-15 18:13:48
    这里只做记录用,不进行...Shader "Custom/SurfaceShader" { Properties { _Color ("Color", Color) = (1,1,1,1) _MainTex ("Albedo (RGB)", 2D) = "white" {} _Glossiness ("Smoothness", Range(0,1)) = 0.5
  • shader插件学习AmplifyShaderEditor

    千次阅读 2018-06-01 21:45:19
    lerp(a,b,c): a(1-c)+bc图片的lerp shader editor: 按键5+鼠标左键 新建颜色 右键搜索想要一个物体正反面都渲染 比如古风人物大袖子里外效果不同 有三种方法:1.插件写shader2.改shader源码 流程: unity官网...
  • ShaderShader官方示例

    千次阅读 2018-12-20 11:02:54
    Surface Shader示例 在表面着色器 。此页面上的示例显示如何使用内置照明模型。有关如何实现自定义光照模型的示例,请参阅Surface Shader光照示例。 简单着色器 例 我们将从一个非常简单的Shader开始,并在此基础上...
  • Shader Stages

    2015-11-20 17:54:02
    The Direct3D 10 and higher pipeline contains three programmable-shader stages (the rounded blocks in the pipeline functional diagram). Each of these shader stages exposes its own unique ...
  • UnityShader——初探Compute Shader

    千次阅读 2017-07-20 16:43:38
    Compute Shader是基于DX11(SM4.5+)的在GPU上运行的程序,通过Compute Shader我们可以将大量可以并行的计算放到GPU中计算从而节省CPU资源,Unity 5.6版本提供的 Graphics.DrawMeshInstancedIndirect 接口可以非常...
  • UnityShader入门 积雪Shader

    千次阅读 2016-05-26 23:09:15
    积雪Shader 如果你是一个shader编程的新手,并且你想学到下面这些酷炫的技术,我觉得你可以看看这篇教程: 实现一个积雪效果的shader创建一个具有凹凸纹理的shader为每个像素修改其对应纹理值在表面着色...
  • 这篇文章主要讲解了如何在Unity3D中分别使用Surface Shader和Vertex & Fragment Shader实现边缘发光Shader。 一、最终实现的效果 边缘发光Shader比较直观的一个运用便是模拟宇宙中的星球效果。将本文实现的边缘...
  • Tessellation Shader

    2017-01-05 17:10:50
    Tessellation Control Shader(TCS): Tessellation Evaluation Shader(TES):Tessellation Shader的GLSL入门实现: 曲线 Tessellation Shader的GLSL入门实现: 平面 OpenGL的各个Shader的作用与区别
  • Shader2D: 一些2D效果的Shader实现

    万次阅读 多人点赞 2017-04-09 01:59:37
    Shader2D: 一些2D效果的Shader实现 包括:模糊,锐化,圆形裁剪,正六边形裁剪,圆角,UV动画,百叶窗,马赛克,浮雕,铅笔画,水彩画,灰化,老照片,饱和度,HDR,内发光,外发光,扭曲,旋涡,波浪,水滴散开等
  • 本篇文章中,我们学习了Unity Shader的基本写法框架,以及学习了Shader中Properties(属性)的详细写法,光照、材质与颜色的具体写法。写了6个Shader作为本文Shader讲解的实战内容,最后创建了一个逼真的暴风雪场景...
  • Shader Graph】Shader Graph入门

    千次阅读 2019-09-27 18:15:54
    Unity2018引用了ShaderGraph:通过可视化界面拖拽就可以实现着色器的创建和编辑,听起来很简单,那我们一起来尝试下吧。 我用的unity版本为2019.1.0f2 一.如何引入ShaderGraph? 方法有两种: 1.新工程: 新建...
  • Unity Shader 二 发光Shader

    千次阅读 2016-04-17 19:46:17
    哈哈,我又回来了,自从上篇的特效Shader之后,这是这个系列的第二弹。老实说这次想写的内容,我也考虑了很久,最终还是将内容暂定为使用的频率较多的外发光。其实外发光可以说是一个烂大街的Shader了,网上也有很多...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 40,798
精华内容 16,319
关键字:

shader