精华内容
下载资源
问答
  • unityshader教程

    2017-10-13 12:14:19
    unityshader教程 unityshader教程 unityshader教程 unityshader教程
  • u3d场景中用于实现水流动的效果的shader
  • unity shader资源包

    热门讨论 2012-12-15 09:58:00
    unity shader 材质资源包,各种材质效果。效果丰富
  • Unity Shader入门

    2021-09-02 20:23:47
    文章目录前言环境Unity的帧调试器Unity Shader 概述详解基于Unity编写的Blinn-Phong Shader 前言 本文对应《Unity Shader入门精要》一书的初级篇,为自己的学习笔记与思考补充。 环境 Unity版本:2020.1.6f1c1 编写...

    前言

    本文对应《Unity Shader入门精要》一书的初级篇,为自己的学习笔记与思考补充。
    书籍配套源码:
    https://github.com/candycat1992/Unity_Shaders_Book
    配套插图:
    http://candycat1992.github.io/unity_shaders_book/unity_shaders_book_images.html

    环境

    Unity版本:2020.1.6f1c1

    编写shader:VS2019+ShaderlabVS

    其中ShaderlabVS为VS的一个插件,安装地址:
    https://marketplace.visualstudio.com/items?itemName=ShaderlabVS2019.ShaderlabVS

    Unity的帧调试器

    位置:Window->Analysis->Frame Debugger
    在这里插入图片描述
    以书的资源Scene_17_4为例,打开Frame Debugger,可以看到如下画面:
    在这里插入图片描述
    帧调试器可以用于查看渲染该帧时进行的各种渲染事件(event),这些事件包含了Draw Call序列,也包括了类似清空帧缓存等操作。

    在Frame Debug所显示的所有事件的树状图中,每个叶子节点就是一个事件,右边不带数字;而每个父节点右侧带数字,代表该节点下的事件数目。

    比如图中的Camera.ImageEffects右侧显示数字为2,表示其中包含两个事件,分别是Resolve Color与Draw Dynamic

    以Draw开头的事件通常是一个Draw Call;当单机某个事件的时候(如上图点击了Draw Mesh)右侧的窗口中就会显示出该事件的细节(比如图中告诉了使用了哪个shader、剔除方式是背面剔除之类的),同时在Game窗口里也可以看到对应的效果。

    值得一提的是,帧调试器实际上并没有实现一个真正的帧拾取(frame capture)的功能,而是仅仅使用停止渲染的方法来查看渲染事件的结果,所以得到的信息也就相对有限。所以有时还需要一些外部工具来辅助使用,比如RenderDoc

    Unity Shader 概述

    Unity中的材质需要结合一个GameObject的Mesh或者Particle Systems组件来工作,而shader则必须要和材质结合起来才能工作。

    一个常见的流程:

    1. 创建一个材质
    2. 创建一个Unity Shader,并把它赋给上一步中创建的材质
    3. 把材质赋给要渲染的对象
    4. 在材质面板中调整Unity Shader的属性,以达到满意的效果

    这里我们讲的Unity Shader指的是硬盘上的 .shader 文件。实际上Unity Shader != 真正的shader,而是用Unity自己定义的shaderlab语言去写的,其实际上就是对整个渲染过程的一层抽象,开发者只需要和Unity Shader也就是ShaderLab语言去打交道,Unity会在背后根据所使用的平台来把你所编写的 .shader 文件编译成真正的代码和 Shader 文件。

    在这里插入图片描述
    如上图,Unity提供了五种Unity Shader 模板,Standard Surface Shader会产生一个包含了标准光照模型(使用了PBR)的表面着色器模板,Unlit Shader则会产生一个不包含光照(但包含雾效)的基本的顶点/片元着色器,Image Effect Shader则为我们实现各种屏幕后处理效果提供了一个基本模板,Compute Shader旨在利用GPU的并行性来进行一些与常规渲染流水线无关的计算,详可见官方手册:https://docs.unity.cn/cn/current/Manual/class-ComputeShader.html,最后顾名思义为Ray Tracing Shader。

    比如官方提供的一个Unlit Shader模板:

    Shader "Unlit/NewUnlitShader"
    {
        Properties
        {
            _MainTex ("Texture", 2D) = "white" {}
        }
        SubShader
        {
            Tags { "RenderType"="Opaque" }
            LOD 100
    
            Pass
            {
                CGPROGRAM
                #pragma vertex vert
                #pragma fragment frag
                // make fog work
                #pragma multi_compile_fog
    
                #include "UnityCG.cginc"
    
                struct appdata
                {
                    float4 vertex : POSITION;
                    float2 uv : TEXCOORD0;
                };
    
                struct v2f
                {
                    float2 uv : TEXCOORD0;
                    UNITY_FOG_COORDS(1)
                    float4 vertex : SV_POSITION;
                };
    
                sampler2D _MainTex;
                float4 _MainTex_ST;
    
                v2f vert (appdata v)
                {
                    v2f o;
                    o.vertex = UnityObjectToClipPos(v.vertex);
                    o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                    UNITY_TRANSFER_FOG(o,o.vertex);
                    return o;
                }
    
                fixed4 frag (v2f i) : SV_Target
                {
                    // sample the texture
                    fixed4 col = tex2D(_MainTex, i.uv);
                    // apply fog
                    UNITY_APPLY_FOG(i.fogCoord, col);
                    return col;
                }
                ENDCG
            }
        }
    }
    

    详解基于Unity编写的Blinn-Phong Shader

    在这里插入图片描述
    新建一个场景,可以看到场景中默认包含一个摄像机、一个平行光。而且场景背景不是纯色,而是一个天空盒子(Skybox)。

    为了得到更加原始的效果,我们选择去掉这个天空盒。我们在Window->Rendering->Lighting中把Skybox Material选为None即可。
    在这里插入图片描述
    在这里插入图片描述

    然后我们编写shader代码如下:

    Shader "Unity Shader Learn/test/Blinn-Phong Use Built-in Functions" 
    {
    	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 appdata 
    			{
    				float4 vertex : POSITION;
    				float3 normal : NORMAL;
    			};
    			
    			struct v2f 
    			{
    				float4 pos : SV_POSITION;
    				float3 worldNormal : TEXCOORD0;
    				float4 worldPos : TEXCOORD1;
    			};
    			
    			v2f vert(appdata v) 
    			{
    				v2f o;
    				o.pos = UnityObjectToClipPos(v.vertex);
    				
    				// Use the build-in funtion to compute the normal in world space
    				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);
    				//  Use the build-in funtion to compute the light direction in world space
    				// Remember to normalize the result
    				fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
    				
    				fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(worldNormal, worldLightDir));
    				
    				// Use the build-in funtion to compute the view direction in world space
    				// Remember to normalize the result
    				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"
    }
    

    接下来进行逐行拆解:

    每个Unity Shader第一行都需要通过一个字符串去定义这个shader的名字,反斜杠是为了控制在材质面板中的位置,比如这里:
    在这里插入图片描述

    之后的Properties则是声明一系列属性以在材质面板中显示调整,比如这里我的_Diffuse 、_Specular 、_Gloss,就可以在材质面板进行调整:
    在这里插入图片描述
    分别对应Blinn-Phong中的diffuse材质的漫反射颜色、specular材质的高光反射颜色、gloss材质的反光度(用于控制高光区域的“亮点”有多宽)。

    为了使用这些属性(在Cg代码中访问它),在后面我们仍需在 SubShader 的pass块中定义出来:

    			fixed4 _Diffuse;
    			fixed4 _Specular;
    			float _Gloss;
    

    这里变量的名称和类型必须与Properties语义块中的属性定义相匹配。

    比如这里颜色我们常用fixed4,控制高光区域大小的gloss我们用float。

    接着往下,我们到了SubShader语义块。每一个Unity Shader文件可以包含多个SubShader语义块,但至少要有一个。

    当Unity需要加载这个Unity Shader时,就会去扫描所有的SubShader语义块,然后选择第一个能够在目标平台运行的SubShader。如果都不支持的话就会去使用Fallback语义指定的Unity Shader。

    因此可以知道,这里的FallBack就是为了“留一条后路”。

    事实上FallBack还会影响阴影的投射。为每个Unity Shader正确设置Fallback是非常重要的。

    FallBack "Specular"
    

    再往下到了pass块。SubShader也可以定义标签Tags、LOD、渲染状态RenderSetup,当然也可以不定义。而一个pass则对应一次完整的渲染流程。

    因此SubShader定义的标签是描述其内所有pass的,比如渲染顺序Queue,而pass中的标签则是对应这一趟渲染流程的,比如LightMode。

    再往下,我们写了 CGPROGRAM 与结尾的 ENDCG,顶点/片元着色器代码需要定义在这之间,表明这之间的代码使用CG/HLSL去编写的。

    这两行是为了用#pragma指令来告诉Unity我们定义的顶点着色器和片元着色器叫什么名字:

    #pragma vertex vert
    #pragma fragment frag
    

    #include “Lighting.cginc” 是Unity的内置文件,这些内置文件的后缀都是 .cginc ,常见的还有 UnityCG.cginc, 是为了使用一些非常有用的变量和帮助函数。

    之后的:

    			struct appdata 
    			{
    				float4 vertex : POSITION;
    				float3 normal : NORMAL;
    			};
    			
    			struct v2f 
    			{
    				float4 pos : SV_POSITION;
    				float3 worldNormal : TEXCOORD0;
    				float4 worldPos : TEXCOORD1;
    			};
    

    定义了两个结构体,分别表示从应用层输入到顶点着色器 和 从顶点着色器输入到片元着色器的结构体,v2f表示vertex to fragment,这里的命名我都是去模仿unity官方提供的模板的,同样的模仿还有大括号风格。

    这里的POSITION、NORMAL、SV_POSITION等都是Cg/HLSL中的语义(semantics),它们是不可省略的,这些语义将告诉系统用户需要哪些输入值,以及用户的输出是什么。这样渲染器就知道用户的输入输出是什么,以便后续的插值等操作。

    比如这里的POSITION表示要把模型的顶点坐标填充到参数vertex中,法线向量填充到normal 中,SV_POSITION则告诉Unity顶点着色器输出的是裁剪空间中的顶点坐标,之后渲染引擎就会把SV_POSITION修饰的变量经过光栅化后显示到屏幕上,因此这些语义描述的变量不可随便赋值。这里SV表示system-value,即系统语义。后面片元着色器的SV_Target也是HLSL中的一个系统语义,是为了告诉渲染器要把用户的输出颜色存储到一个渲染目标(render target)中,这里将输出到默认的帧缓存中。

    接着来看顶点着色器:

    v2f vert(appdata v) 
    {
    	v2f o;
    	o.pos = UnityObjectToClipPos(v.vertex);
    	
    	// Use the build-in funtion to compute the normal in world space
    	o.worldNormal = UnityObjectToWorldNormal(v.normal);
    	
    	o.worldPos = mul(unity_ObjectToWorld, v.vertex);
    	
    	return o;
    }
    			
    

    首先定义要输出的结构体 v2f o,接着把输入的模型坐标用unity内置函数UnityObjectToClipPos作用转换到裁剪空间坐标(以前是mul(UNITY_MATRIX_MVP,*)),并赋值给o.pos;接着是法线的变换,使用内置函数UnityObjectToWorldNormal;世界空间下的顶点坐标则是unity_ObjectToWorld与v.vertex相乘,即从模型空间转换到世界空间。

    接着看片元着色器:

    fixed4 frag(v2f i) : SV_Target 
    {
    	fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
    	
    	fixed3 worldNormal = normalize(i.worldNormal);
    	//  Use the build-in funtion to compute the light direction in world space
    	// Remember to normalize the result
    	fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
    	
    	fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(worldNormal, worldLightDir));
    	
    	// Use the build-in funtion to compute the view direction in world space
    	// Remember to normalize the result
    	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);
    }
    

    环境光ambient使用了UNITY的内置变量UNITY_LIGHTMODEL_AMBIENT,worldNormal世界坐标的法向量则是把传进来的参数标准化一下,worldLightDir世界坐标下的光线方向则是用UnityWorldSpaceLightDir实现的,UnityWorldSpaceLightDir仅可用于前向渲染中,这个函数的输入是一个世界空间中的顶点位置(比如这里为i.worldPos),输出为世界空间中该点到光源的光照方向。没有被归一化,所以这里还用了normalize归一化了一下。

    接着计算diffuse,为了防止法线和光源方向点乘结果为负(防止物体被后面来的光源照亮),这里我们与0取了max,接着乘以光源颜色和漫反射颜色。

    接着计算高光反射,为了使用Blinn-Phong模型,我们用UnityWorldSpaceViewDir得到该顶点到观察方向的向量viewDir(世界坐标下),用worldLightDir和viewDir相加得到半程向量,然后用公式
    c s p e c u l a r = ( c l i g h t ⋅ m s p e c u l a r ) m a x ( 0 , v ⃗ ⋅ c ⃗ ) m g l o s s c_{specular} = (c_{light} \cdot m_{specular}) max(0, \vec{v} \cdot \vec{c})^{m_{gloss}} cspecular=(clightmspecular)max(0,v c )mgloss
    去计算高光反射部分的颜色和强度。

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

    最后返回像素的颜色值:

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

    注:
    Blinn-Phong光照模型和Phong光照模型(没有使用半程向量)都是经验模型,没有谁比谁更好一说。这种模型有很多局限性,比如无法表现菲涅尔反射(Fresnel reflection)等重要的物理现象。
    其次Blinn-Phong模型是各项同性(isotropic)的,即固定视角和光源方向旋转这个表面时,反射不会发生任何改变。但是比如拉丝金属和毛发等就是各项异性(anisotropic)的。

    纹理

    纹理面板与属性解析

    在这里插入图片描述
    如图,Wrap Mode有几种模式可以选择,决定了当纹理坐标超过[0, 1]范围后将会如何被平铺:
    在这里插入图片描述
    Repeat模式会不断重复,Clamp模式则是截取到0到1之间,如果纹理坐标大于1则为1,小于0则截取到0。

    这里还展示了平铺(Tiling)属性,如上图为(3,3),而为(1,1)即原纹理则如下:
    在这里插入图片描述
    Filter Mode则决定了当纹理由于变换而产生拉伸时将会采用哪种滤波模式。可以选
    Point:no filter,放大或缩小时采样的像素数目只有一个,呈现一种像素风格的效果。
    Bilinear:双线性插值
    Trilinear:三线性插值,还会在不同的Mipmap层次间作插值。

    既然说到Mipmap,一般我们纹理都是用2的幂大小,Format决定Unity内部使用哪种格式来存储纹理。Advanced内可以选择:
    在这里插入图片描述
    来开启多级渐进纹理技术,通常这会使纹理占用空间多33%,比如不开启多级渐进纹理技术纹理的占用空间:
    在这里插入图片描述
    开启后占用空间:
    在这里插入图片描述
    170.7 / 128 − 1 ≈ 33.3 % 170.7 / 128 - 1 \approx 33.3\% 170.7/128133.3%
    原因其实是:
    1 4 + ( 1 4 ) 2 + ( 1 4 ) 3 + . . . = 1 3 \frac{1}{4} + (\frac{1}{4})^{2} + (\frac{1}{4})^{3} + ... = \frac{1}{3} 41+(41)2+(41)3+...=31

    代码使用纹理

    Properties中声明:
    _MainTex (“Main Tex”, 2D) = “white” {}
    这里纹理的名字也可以改变,比如法线纹理可以声明为:
    _BumpMap (“Normal Map”, 2D) = “bump” {}
    white和bump都是Unity的内置纹理,前者为内置的纯白纹理,后者为内置的法线纹理。2D则是纹理属性的声明方式。

    之后的pass中则需要声明:

    sampler2D _MainTex;
    float4 _MainTex_ST;
    

    与之前的Properties内的属性不同,这里我们还声明了_MainTex_ST,_MainTex_ST不是随便起的,Unity中使用纹理名_ST的方式来声明某个纹理的属性。ST表示scale和translation,即缩放和平移。_MainTex_ST.xy存储缩放值,_MainTex_ST.zw存储偏移值。

    比如之后我们可以在顶点着色器中写代码:

    o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
    

    这一行等效于:

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

    这样才能正确使用面板中的Tilling和Offset

    凹凸映射

    纹理的另一常见应用是凹凸映射(bump mapping)。有两种主要方法:

    1. 使用一张高度纹理(height map)来模拟表面位移(displacement),然后得到一个修改后的法线值。
      高度纹理常使用灰度图,颜色越浅则表面越向外凸,越深则越向里凹。这种方法导致实时计算要得到表面法线计算较困难。因此我们常使用第二种方法:
    2. 使用一张法线纹理(normal map)来直接存储表面法线,这种方法又被称为法线映射(normal mapping)。一般我们把凹凸映射和法线映射作等同。

    由于法线分量范围[-1, 1]而像素分量范围[0, 1],因此会做一个映射。

    法线纹理还分模型空间下和切线空间下,如图:
    在这里插入图片描述
    模型空间中的法线纹理,每个顶点对应的法线都是在同一坐标空间——模型空间中的,因此每个点所存的法线方向是各异的,因此看起来五颜六色;

    切线空间的z轴是顶点的法线方向,x轴为切线方向,y轴为二者作叉积得到(y轴也被称为副切线bitangent或副法线),因此每个顶点都有自身的切线空间,而实际上切线空间下的法线纹理所存的是一个法线的扰动方向。要是一个点的法线方向不变,那么对应在切线空间就是(0, 0, 1),映射后所存的就是(0.5,0.5,1),就是浅蓝色。因此看上去会有大片蓝色,其实就是说明顶点的大部分法线和模型本身法线是一样的,不需要改变(偏移)。
    在这里插入图片描述

    由于法线是单位向量,且切线空间下法线的z分量始终为正,即法线纹理的第三个通道的值可以由前两个通道推导出来,因此法线可以进行如DXT5nm格式去压缩。使用时再针对不同的压缩格式去对法线纹理进行正确的采样(Unity内置UnpackNormal函数)。

    使用切线空间下的法线纹理有如下优点:

    1. 自由度高。因为模型空间存法线纹理记录的是绝对法线信息,仅可用于创建它的那个模型。而切线空间则存的是相对法线信息。
    2. 可进行UV动画。由于存的是相对法线信息,所以可以简单地移动一个纹理的UV坐标来实现一个凹凸移动的效果。
    3. 可以重用法线纹理。
    4. 可以压缩

    实际上,法线本身可以存储在任意坐标系,得到法线是为了后续的光照计算。在这里我们采用切线空间,在光照计算中则有两种手法:

    1. 在切线空间下计算光照模型:
      在片元着色器中通过纹理采样得到切线空间下的法线,然后再与切线空间下的视角方向、光照方向等进行计算,得到最终的光照结果。
    2. 在世界空间下计算光照模型:
      在顶点着色器中计算从切线空间到世界空间的变换矩阵,并传递给片元着色器。最后在片元着色器中把法线纹理的法线方向从切线空间变换到世界空间下即可。

    注意:这里涉及坐标系变换,从效率而言第一种手法优于第二种,但是从通用性而言第二种更好(因为我们有时需要在世界空间下进行一些计算)。
    并且这里的坐标系变换,我们只需要把光线向量、视线向量等做出变换,因此并不需要坐标系平移使原点对齐,只要把三个轴旋转对齐即可。

    渐变纹理

    使用一张纹理(渐变纹理)去控制漫反射光照的结果。
    在这里插入图片描述
    比如这里我们使用半兰伯特模型,根据法线方向和光照方向计算出halfLambert值并映射到[0, 1]之后,我们把这个计算出来的值当作UV坐标(u和v此时相同)去在渐变纹理种进行采样。然后把这个采样出来的结果和材质颜色相乘以当作最终的漫反射颜色。

    需要注意的是,这里我们需要把渐变纹理的Wrap Mode设置为Clamp模式,以防止对纹理进行采样时由于浮点数精度而造成的问题。
    在这里插入图片描述

    遮罩纹理

    mask texture

    什么是遮罩(mask)呢?简言之,遮罩允许我们可以保护某些区域,使他们免于某些修改。

    使用遮罩纹理的一般流程是:通过采样得到遮罩纹理的纹素值,然后使用其中某个或某几个通道的值(比如texel.r)来与某种表面属性进行相乘,这样在该通道值为0的时候就可以保护表面不受该属性的影响。
    在这里插入图片描述

    注:

    1. 代码实现中,我们为主纹理_MainTex、法线纹理_BumpMap和遮罩纹理_SpecularMask定义了它们共同使用的纹理属性变量_MainTex_ST,这意味着在材质面板中修改主纹理的平铺系数和偏移系数会同时影响3个纹理的采样。
      使用这种方式可以让我们节省需要存储的纹理坐标数目,因为顶点着色器可以使用的插值寄存器是有限的。
    2. 真实的游戏制作过程中,遮罩纹理已经不止限于保护某些区域使它们免于某些修改,而是可以存储任何我们希望逐像素控制的表面属性。通常我们会充分利用一张纹理的RGBA四个通道,用于存储不同的属性。

    透明效果

    alpha test与alpha blending

    在unity中有两种方法来实现透明效果:

    1. 透明度测试(alpha test):
      只要一个片元的透明度不满足条件(通常是小于某个阈值)就舍弃它。
      比如HLSL中用clip,GLSL中用discard,一段伪代码:
    if (texColor.a < 0.1)
    {
    	discard;
    }
    

    而实际上,对于Cg中的函数clip等同于如下伪代码:

    void clip(float4 x)
    {
    	if (any(x < 0))
    		discard;
    }
    

    透明度测试不需要关闭深度写入(即仍然可以把深度值更新到深度缓冲中)。和其他不透明物体最大的不同就是会根据透明度来舍弃一些片元。因此产生的效果要么完全透明(即看不到),要么完全不透明。

    通常,使用alpha test的shader都应该在subshader中设置这三个标签:
    在这里插入图片描述
    分别表示渲染队列为AlphaTest、不受投影器(Projectors)的影响、把这个Shader归入到提前定义的TransparentCutout组以指明该shader是一个使用了透明度测试的shader(RenderType标签通常被用于着色器替换功能)。

    在片元着色器中我们开启透明度测试:

    clip (texColor.a - _Cutoff);
    //  Equal to 
    //	if ((texColor.a - _Cutoff) < 0.0) {
    //		discard;
    //	}
    

    这里_Cutoff是我们定义在Properties的参数:

    _Cutoff ("Alpha Cutoff", Range(0, 1)) = 0.5
    

    最后的回调:

    FallBack "Transparent/Cutout/VertexLit"
    

    这保证了使用透明度测试的物体可以正确地向其他物体投射阴影。

    测试结果:
    透明效果很“极端”:要么全透明要么完全不透明。而且得到的透明效果在边缘处参差不齐,有锯齿,这是因为在边界处纹理的透明度的变化精度问题。
    为了得到更柔滑的透明效果,就可以使用透明度混合。

    1. 透明度混合(alpha blending):
      这种方法可以得到真正的半透明效果。
      它会使用当前片元的透明度作为混合因子,与已经存储在颜色缓冲区的颜色值进行混合,得到新的颜色。但是需要关闭深入写入(即不会把深度值更新到深度缓冲中,否则还怎么透明,当然不透明物体是要进行深度写入的),不过并不会关闭深度测试(即仍然会比较当前片元的深度值和当前深度缓冲中的深度值)。
      即对于透明度混合来说,深度缓冲是只读的。

    混合是一个逐片元的操作,它不是可编程的,但却是高度可配置的。

    为了进行混合,我们需要使用unity提供的混合命令——Blend。
    ShaderLab的Blend命令:

    1. Blend Off
    2. Blend SrcFactor DstFactor
    3. Blend SrcFactor DstFactor, SrcFactorA DstFactorA
    4. BlendOp BlendOperation

    详细描述可见书的P169.

    通常,使用alpha blending的shader都应该在subshader中设置这三个标签:
    在这里插入图片描述
    并且可以看到,Pass中我们还把深度写入ZWrite设置为关闭状态Off,然后开启并设置了该Pass的混合模式。

    这里我们使用Blend SrcFactor DstFactor来进行混合,这个命令在设置混合因子的同时也开启了混合模式。而只有使用Blend命令打开混合后,我们在这里设置透明通道才有意义,否则这些透明度并不会对片元的透明效果有任何影响。

    Blend SrcFactor DstFactor:我们会把源颜色(该片元产生的颜色)乘以SrcFactor,而目标颜色(已经存在于颜色缓存的颜色)会乘以DstFactor。
    Blend SrcAlpha OneMinusSrcAlpha 这一句其实内含了把DstFactor设为OneMinusSrcAlpha,这意味着混合后新的颜色是:
    D s t C o l o r n e w = S r c A l p h a × S r c C o l o r + ( 1 − S r c A l p h a ) × D s t C o l o r o l d DstColor_{new} = SrcAlpha \times SrcColor + (1 - SrcAlpha) \times DstColor_{old} DstColornew=SrcAlpha×SrcColor+(1SrcAlpha)×DstColorold

    而对于Blend SrcFactor DstFactor, SrcFactorA DstFactorA,其实就是区分了RGB通道的混合因子和Alpha通道的混合因子,这里SrcFactor、DstFactor为RGB通道的混合因子,而SrcFactorA 、DstFactorA为Alpha通道的混合因子。

    当设置混合状态时,我们实际上设置的是混合等式(即从源颜色和目标颜色得到输出颜色的等式)的操作和因子,而一般操作都是默认加操作,否则可以用混合操作命令 BlendOp BlendOperation 改为其他操作。因此很多时候我们只需要设置混合因子即可。

    一个例子:

    Blend SrcAlpha OneMinusSrcAlpha, One Zero
    

    关于混合操作和混合因子的更多设定可以看书本P174,这里仅放一个结果:
    在这里插入图片描述

    渲染顺序的重要性

    由于半透明物体关闭了深度写入,也就破坏了深度缓冲的机制,这是一个非常糟糕的事情。关闭深度写入也使得渲染顺序变得无比重要。
    在这里插入图片描述
    比如上图的两个场景,我们都需要先渲染在后面的B物体,再渲染在前面的半透明的A物体,否则就会出错。

    因此渲染引擎一般都会先对物体排序再渲染,常用的方法是:

    1. 先渲染所有不透明物体,并开启深度测试和深度写入。
    2. 把半透明物体按它们距离摄像机的远近进行排序,然后按照从后往前的顺序渲染这些半透明物体,并开启它们的深度测试,但关闭深度写入。

    然而即使是这样,仍然会有情况发生错误,比如下图:
    在这里插入图片描述
    图8.4,无论物体的深度排序是离摄像机最近的点、中心点还是离摄像机最远的点,图8.4中A始终在B之前,但是B的的确确遮挡了A。这就会造成错误。

    这种问题的解决方法通常是分割网格。为了减少错误排序的方法,我们可以尽可能让模型是凸面体,并且尽量考虑将复杂的模型拆分成可以独立排序的多个子模型等。其实就算排序错误结果有时也不会非常糟糕,如果不想分割网格,还可以试着让透明通道更加柔和,使穿插看起来并不是那么明显。我们也可以使用开启了深度写入的半透明效果来近似模拟物体的半透明。

    渲染队列

    unity为了解决渲染顺序问题提供了渲染队列(render queue)这一解决方案。可以用ySubShader的Queue标签来决定我们的模型将归于哪个渲染队列。这一部分在书的165页。

    官方文档:
    在这里插入图片描述
    https://docs.unity.cn/cn/current/Manual/SL-SubShaderTags.html

    开启深度写入的半透明效果

    如之前所述,半透明效果需要Alpha Blending,但是由于不开启深度写入,在半透明对象之间遮挡时可能会出现错误:
    在这里插入图片描述

    这里我们的解决方法是两趟pass:
    第一个Pass开启深度写入,但不输出颜色,它的目的仅仅是把该模型的深度值写入深度缓冲中,代码如下:

    Pass 
    {
    	ZWrite On
    	ColorMask 0
    }
    

    这里第一行ZWrite On开启了深度写入,第二行ColorMask 0意味着该Pass不写入任何颜色通道,即不会输出任何颜色。因此该Pass仅写入深度缓存。

    ColorMask在ShaderLab中用于设置颜色通道的写掩码(write mask),它的语义如下:

    ColorMask RGB | A | 0 | 其他任何R、G、B、A的组合
    

    第二个Pass则进行正常的透明度混合即可。

    由于第一个Pass已经得到了逐像素的正确的深度信息,第二个Pass就可以按照像素级别的深度排序结果进行透明度渲染。

    当然这样做缺点是多了一个Pass,影响性能,但是效果还是不错的:
    在这里插入图片描述

    双面渲染的透明效果

    由于默认情况下渲染引擎剔除了物体背面(相对于摄像机的方向)的渲染图元,而只渲染了物体的正面。因此如果我们想要得到双面渲染的效果,可以使用Cull指令来控制需要剔除哪个面的渲染图元。

    Unity中Cull指令的语法:

    Cull Back | Front | Off
    

    透明度测试的双面渲染

    只需要在Pass中使用 Cull Off 去掉剔除即可:
    在这里插入图片描述
    得到效果:
    在这里插入图片描述

    透明度混合的双面渲染

    由于此时同一个物体正面和背面还有一个渲染顺序,所以不能简单地Cull Off,我们这里采用两个Pass:
    第一个Pass只渲染背面,因此正面剔除,使用代码:
    在这里插入图片描述
    第二个Pass只渲染正面,因此背面剔除,使用代码(事实上由于默认背面剔除,这里不加这行代码也没关系):
    在这里插入图片描述
    得到效果:
    在这里插入图片描述

    展开全文
  • Unity Shader简介

    2021-09-13 09:52:31
    这里有unity学习交流小组点击可以直接加入,一起学习交流吧 一、什么是Shader Shader其实就是专门用来渲染图形的一种技术,通过shader,我们可以自定义显卡渲染画面的算法,使画面达到我们想要的效果。小到每一个...

    Shader,中文名为着色器,对很多开发者来说它是一个神秘的存在,想学但是又不知道如何去学,或者学了一段时间发现Get不到它的点,始终感觉游离在外,无法开窍。

    本文来讲下如何从零基础入门Shader,目的在于让初学Shader的程序或者美术可以快速进入这个丰富多彩的世界。

    这里有unity学习交流小组点击可以直接加入,一起学习交流吧

    一、 什么是Shader

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

    二、 Shader分类

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

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

    几个不同的图形API都有各自的Shader语言,在DirectX中,顶点shader叫做 Vertex Shader ,像素Shader叫做 Pixel Shader; 在OpenGL中,顶点Shader也叫做 Vertex Shader ,但像素Shader叫做 Fragment Shader,也就是我们常说的片断Shader或者片元Shader。

    说白了,Shader其实就是一段代码,这段代码的作用是告诉GPU具体怎样去绘制模型的每一个顶点的颜色以及最终每一个像素点的颜色。

    三、 Shader编程语言

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

    基于OpenGL的OpenGL Shading Language,简称GLSL

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

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

    GLSL与HLSL分别是基于OpenGL和Direct3D的接口,两者不能混用。而Cg语言是用于图形的C语言,这其实说明了当时设计人员的一个初衷,就是让基于图形硬件的编程变得和C语言编程一样方便,自由。正如C++和 Java的语法是基于C的,Cg语言本身也是基于C语言的。如果您使用过C、C++、Java其中任意一个,那么Cg的语法也是比较容易掌握的。Cg语言极力保留了C语言的大部分语义,力图让开发人员从硬件细节中解脱出来,Cg同时拥有高级语言的好处,如代码的易重用性,可读性高等。

    Cg语言是Microsoft和NVIDIA相互协作在标准硬件光照语言的语法和语义上达成了一致,所以,HLSL和Cg其实是同一种语言。

    一般来说为了跨游戏平台方便,一般学CG语言。

    四、 什么是Unity Shader

    显卡有NVIDIA、ATI、Intel等等。

    图形API有OpenGL、DirectX、OpenglES、Vulkan、Metal等等。

    Shader编程语言有GLSL、HLSL、Cg等等。

    是不是有点头晕,该怎么去选择呢?在Unity中我们又应该如何做呢?

    其实在Unity中反而一切变的简单起来了,我们只需关心如何去这实现我们想要的效果就好了,其余的事情全部交给Unity来自动处理。因为我们在Unity中编写的Shader最终会根据不同的平台来编绎成不同的着色器语言,那么我们在Unity中应该用什么语言来书写Shader呢?

    官方的建议是用Cg/HLSL来编写,当然你也可以使用GLSL,主要是因为Cg/HLSL有更好的跨平台性,更倾向于使用Cg/HLSL来编写Shader程序。

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

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

    Surface Shaders 表面着色器;

    Vertex/Fragment Shaders 顶点/片断着色器;

    Fixed Function Shaders 固定管线着色器;

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

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

    但是Surface Shader也有它的局限性,就是Vertex/Fragment Shader能实现的效果,Surface Shader不一定能实现,反过来则成立,Surface Shader能实现的Vertex/Fragment Shader则一定可以实现。

    并且在Unity2018后的版本中推出了Unity官方自己的可视化Shader工具(Shader Graph)。从生成的代码上来看,也全部是用的Vertex/Fragment Shader,那是不是可以理解为在今后的可编程渲染管线中,Unity自己也抛弃了Surface Shader,而全部采用了Vertex/Fragment Shader?

    总之,在今后的学习过程中,也会涉及一些Surface Shader的内容,但主要还是会以Vertex/Fragment为主。

    另外,学会Shader也会给我们带来很多的好处:

    游戏中模型显示粉色的情况你一定碰到过吧,是Shader丢失呢,还是Shader不符合当前平台呢,又或者是Shader上有语法的错误呢?如果我们有了解并学会Shader的话,这些问题就不会再是一脸懵逼啦。

    內建Unity Shader仅仅只是“通用”用例,不足以满足我们所有的画面表现需求。

    一旦掌握Shader,可以为游戏/应用创造独一无二的视觉享受。根据实际需求,为游戏和应用实现特定功能的Shader。

    能极大的帮助我们做渲染上的性能优化,因为通过Shader可以控制渲染什么以及如何渲染。

    撰写Shader的能力对于游戏团队非常重要,掌握Shader技能的开发一直是炙手可热的职位。现在一个不争的事实就是,技术美术永远是各大厂商的稀缺资源。

    五、 Shader核心知识

    1. 渲染管线

    又叫渲染流水线,是显示芯片内部处理图形信号相互独立的并行处理单元。一个流水线是一序列可以并行和按照固定顺序进行的阶段,遵循着前入后出的流程。类比来说,就像是一个工厂,同时生产宝马和宾利两种汽车,这两款汽车的每个零件又是同时制作的,就可以说这个车间内有两个不同的渲染管线。

    根据上图进行说明。3D软件或是游戏在运行的时候,会调用图形API,OpenGL或者是DirectX。而顶点着色器和片段着色器就在图中GPU运算框选部产生作用。

    上图就是针对到unity引擎中。Geometry部分可以理解为建模,这个过程就是把Mesh数据导入到unity之中,unity引擎再调用图形API,而调用图形API的过程就是在驱动GPU进行处理运算。

    进入到GPU后,首先进行的是顶点处理程序(Vertex Processor)对应顶点shader,顶点运算的结果会传递给像素处理器(Pixel Processor相当于片段处理器),其对应的便是像素shader(也就是片段shader),最后输出可以在屏幕上显示的像素信息,即Frame Buffer(帧缓冲),Frame Buffer不仅可以储存颜色信息还可以储存深度值。

    2. shader 材质 贴图

    着色器实际就是一小段程序,他负责将输入的顶点数据以指定的方式和输入的贴图或者颜色等组合起来,然后进行输出。绘图单元可以以及这个输出来将图像绘制到屏幕上。

    输入的贴图或者颜色等,加上对应的shader,以及对shader的特定的参数设置,将这些内容(shader及输入参数)打包储存在一起,得到的就是一个Material(材质),这些包里其实还有其他东西比如向量、矩阵。之后,我们便将材质赋予到三维模型上进行渲染(输出)了。

    材质,就像是游戏引擎最终使用的商品,shader就好比是生产这种商品的加工方法,而贴图则是这商品的原材料。如果我们不用Unity或是其他引擎的话,实现材质就需要利用OpenGL或是DirectX的API的调用,手动组织出个shader,这样就很麻烦,所以引擎提供了便捷。

    六、 unity中shader的编写形式

    有三种:surface shader vertex and Fragment shader fixed function shader。我们知道,硬件只识别顶点着色和片段着色,那么unity引擎中为何还会多出来一个surface shader(以下简称SS)和Fixed Function shader(以下简称FFS)呢。

    对于FFS,主要是对于固定管线的硬件的操作,是特别保守的shader能得到绝大部分硬件的支持;而SS呢,是unity推荐用的,创建一个shader在unity中的时候,默认的代码就是SS。实际上SS是对顶点和片段着色器的一种“包装”,在SS编程后unity会将SS代码编译成硬件可识别的顶点和片段着色器代码

    书写shader的主要结构:

    七、 ShaderLab

    ShaderLab是为unity定制的专门用于编写Shader的语法,可以容纳其他三种shader。

    shaderLab的主要结构:

    花括号内的三个部分:Properties,Subshaders,Fallback。

    什么是Properties呢,属性。我们在unity中新建一个材质球和一个shader,打开编译器将shader命名为“111”,再将111shader拖拽给材质球,再点击材质球观察其显示与代码中Properties的内容。我们发现,shader编程中的Properties(属性)会让拥有该shader的材质球的属性以列表的形式显示出来,进行参数调节。

    subshader是啥呢,算法,就是写给GPU渲染的shader片段了,这里记住,一个shader当中至少有一个subshader。每一次显卡进行处理的时候呢,只能选择其中一个subshader去执行。那为什么会有多个subshader呢?这和硬件有关。

    在读取shader的时候,会先从第一个subshader读取,如果第一个能适配当前硬件,就不会往下读了;如果硬件太老跟不上,第一个读取不了,就会读取第二个看能不能与我适配。也就是说,subshader的所有方案会向下简化。如果这些列举的subshader都用不了怎么办?那就是第三个Fallback了。

    FallBack就是在subshader都用不了的时候可以回滚到指定的unity中一个都能适配的shader。unity常用的内建shader的如下:

    展开全文
  • UnityShader的调试方法

    2021-08-19 19:12:43
    使用假彩色图像 假彩色图像指的是用假彩色技术生成的一种图像。与假彩色图像对应的是照片这种真彩色图像。一张假彩色图像可以用于可视化一些...如果不知道一个变量的范围 (往往说明你对这个Shader中的运算并不了解),

    使用假彩色图像

    假彩色图像指的是用假彩色技术生成的一种图像。与假彩色图像对应的是照片这种真彩色图像。一张假彩色图像可以用于可视化一些数据。

    主要思想是:我们可以把需要调试的变量映射到[0,1]之间,把它们作为颜色输出到屏幕上,然后通过屏幕上显示的像素颜色来判断这个值是否正确。
    需要注意的是,由于颜色的分量范围在[0,1],因此我们需要小心处理需要调试的变量的范围。如果我们已知它的值域范围,可以先把它映射到[0,1]之间再进行输出。如果不知道一个变量的范围 (往往说明你对这个Shader中的运算并不了解),我们就只能不停的实验。
    Tips:颜色分量中,任何大于1的数值将会被设置为1,而任何小于0的数值会被设置为0。因此,我们可以尝试使用不同的映射,直到法线颜色出现了变化(得到了[0,1]的值)

    如果我们要调试的数据是一维数据,那么可以选择一个单独的颜色分量进行输出,而把其他颜色分量置为0。如果是多维数据,可以选择对每一个颜色分量进行单独调试,或者选择多个颜色分量进行输出。

    实例:

    Shader"ShaderLearn/Chapter5/5_5_1_FalseColor"{
        SubShader{
            
            Pass{
                CGPROGRAM
    
                #pragma vertex vert
                #pragma fragment frag
    
                #include "UnityCG.cginc"
    
                struct v2f{
                    float4 pos : SV_POSITION;
                    fixed4 color : COLOR0;
                };
    
                v2f vert(appdata_full v){
                    v2f o;
                    o.pos = UnityObjectToClipPos(v.vertex);
    
                    // //可视化法线方向
                    // o.color = fixed4(v.normal * 0.5 + fixed3(0.5,0.5,0.5),1.0);
    
                    // 可视化切线方向
                    // o.color = fixed4(v.tangent * 0.5 + fixed3(0.5,0.5,0.5),1.0);
    
                    // //可视化副切线方向
                    // fixed3 binormal = cross(v.normal,v.tangent.xyz) * v.tangent.w;
                    // o.color = fixed4(binormal * 0.5 + fixed3(0.5,0.5,0.5),1.0);
    
                    // //可视化第一组纹理坐标
                    // o.color = fixed4(v.texcoord.xy, 0.0, 1.0);
    
                    // //可视化第二组纹理坐标
                    // o.color = fixed4(v.texcoord1.xy, 0.0, 1.0);
    
                    // //可视化第一组纹理坐标的小数部分
                    // o.color = frac(v.texcoord);
                    // if(any(saturate(v.TEXCOORD) - v.texcoord)){
                    //     o.color.b = 0.5;
                    // }
                    // o.color.a = 1.0;
    
                    // //可视化第二组纹理坐标的小数部分
                    o.color = frac(v.texcoord1);
                    if(any(saturate(v.texcoord1) - v.texcoord)){
                        o.color.b = 0.5;
                    }
                    o.color.a = 1.0;
    
                    // //可视化顶点颜色
                    // o.color = v.color;
    
                    return o;
                }
    
                fixed4 frag(v2f i) : SV_Target{
                    return i.color;
                }
    
                ENDCG
            }
        }
    }
    

    利用Visual Studio

    VisualStudio提供了对UnityShader的调试功能——Graphic Debugger
    通过Graphic Debugger,我们不仅可以查看每个像素的最终颜色,位置等信息,还可以对顶点着色器和片元着色器进行单步调试。

    这个方法有限制条件:Unity必须运行在DirectX11平台上

    帧调试器

    可以查看渲染该帧时进行的各种渲染事件,这些事件包含了Drawcall序列,也包括了类似清空帧缓存等操作
    这种方法较为简单,但得到的信息也有限

    展开全文
  • Unity shader阴影

    2020-12-26 21:12:21
    Unity shader阴影 参考书籍:《Unity Shader入门精要》 阴影是如何实现的 在实时渲染中,我们最常使用的是一种名为Shadow Map的技术。这种技术的原理是将摄像机放到与光源重合的位置上,那么场景中该光源的...

    Unity shader阴影

    参考书籍:《Unity Shader入门精要》

    阴影是如何实现的

     

    在实时渲染中,我们最常使用的是一种名为Shadow Map的技术。这种技术的原理是将摄像机放到与光源重合的位置上,那么场景中该光源的阴影区域就是那些摄像机看不到的地方。Unity使用的就是这种技术。

    在前向渲染路径中,如果场景中最重要的平行光开启了阴影,unity就会为该光源计算它的阴影映射纹理(shadowMap)。这张阴影映射纹理本质上也是一张深度图,它记录了从该光源的位置出发、能看到的场景中距离它最近的表面位置。

    在计算阴影映射纹理时,unity选择使用一个额外的Pass来专门更新光源的阴影映射纹理,这个Pass就是标签为ShadowCaster的Pass。这个Pass的渲染目标不是帧缓存,而是阴影映射纹理。当光源开启了阴影效果后,渲染引擎首先会在当前渲染物体的Unity Shader中查找标签为ShadowCaster的Pass,如果没有找到,就在Fallback中查找,如果最终仍未找到,那么该物体就无法向其他物体投射阴影。

     

    Unity的阴影映射纹理是通过屏幕空间的阴影映射技术来实现的。unity使用这种技术需要显卡支持MRT。

     

    *屏幕空间的阴影映射技术:当使用这种技术时,Unity会首先通过调用LightMode为shadowCaster的Pass来得到可投射阴影的光源的阴影映射纹理以及摄像机的深度纹理。然后,根据光源的阴影映射纹理和摄像机的深度纹理来得到屏幕空间的阴影图。如果摄像机的深度图中记录的表面深度大于转换得到阴影映射纹理中的深度值,就说明该表面虽然是可见的,但是却处于该光源的阴影中。通过这样的方式,阴影图就包含了屏幕空间中所有有阴影的区域。如果我们想要一个物体接收来自其他物体的阴影,只需要在Shader中对阴影图进行采样。由于阴影图是屏幕空间下的,因此,我们首先需要把表面坐标从模型空间变换到屏幕空间中,然后使用这个坐标对阴影图进行采样即可。

     

    总结:

    一个物体接受来自其他物体的阴影,以及它向其他物体投射阴影是两个过程:

    • 如果我们想要一个物体接受来自其他物体的阴影,就必须在shader中对阴影映射纹理进行采样,最后把采样结果和最后的光照结果相乘来得到阴影效果
    • 如果我们想要让物体向其他物体投射阴影,就必须把该物体加入光源的阴影映射纹理的计算中,从而让其他的物体能对阴影映射纹理采样时得到该物体的信息,这个过程是通过为该物体执行LightMode为ShadowCaster的Pass来实现的。

     

    让物体投射阴影

     

    修改Mesh Renderer下的Cast Shadows来控制物体能否投射阴影,选择“On”代表能投射阴影,此时unity会把该物体计算到光源的阴影映射纹理中。选择“Off”表示不会投射。

    而Receive Shadow则代表能否接收阴影。勾选后投射的阴影细节需要通过shader中来实现。

     

    *通常unity会在Fallback中的回调shader中自动帮我们实现

     

    unity内置的LightMode为ShadowCaster的Pass:

    Pass{
                Name "ShadowCaster"
                Tags {"LightMode"="ShadowCaster"}
    
                CGPROGRAM
                #pragma vertex vert
                #pragma fragment frag
                #pragma multi_compile_shadowcaster
                #include "UnityCG.cginc"
    
                struct a2f{
                    V2F_SHADOW_CASTER;
                }
    
                v2f vert(appdata_base v){
                    v2f o;
                    TRANSFER_SHADOW_CASTER_NORMALODDSET(o);
                    return o;
                }
    
                float4 frag(v2f i):SV_Target{
                    SHADOW_CASTER_FRAGMENT(i);
                }
    
                ENDCG
            }

     

    让物体可以接收阴影

     

    在前向渲染下的shader上进行实现,代码如下:

    Shader "Unlit/Chapter9-AttenuationAndShadowUseBuildInFuncitons"
    {
        Properties
        {
        //材质属性 BlinnPhong模型中需要的三个常量参数
        _Diffuse("Diffuse",Color)=(1,1,1,1)    //漫反射中所需的diffuse值(漫反射系数)
        _Specular("Specular",Color)=(1,1,1,1)  //高光反射中所需的specular值(高光反射系数)
        _Gloss("Gloss",Range(8.0,256))=20  //高光反射中所需的材质光泽度
            
        }
        SubShader
        {
    
            Pass{
                Tags{"LightMode"="ForwardBase"}
    
                CGPROGRAM
                //使用该指令能确保使用光照衰减等光照变量能被正确赋值
                #pragma multi_compile_fwdbase  
                #pragma vertex vert
                #pragma fragment frag
                #include "Lighting.cginc"
                //添加该内置文件以便于使用计算阴影用的宏
                #include "AutoLight.cginc"
    
                fixed4 _Diffuse;
                fixed4 _Specular;
                float _Gloss;
    
                struct a2v{
                    float4 vertex:POSITION;
                    float4 normal:NORMAL;
                };
    
                struct v2f{
                    float4 pos:SV_POSITION;
                    float3 worldNormal:TEXCOORD0;
                    float3 worldPos:TEXCOORD1;
                    //添加一个内置宏来声明一个用于对阴影纹理采样的坐标,括号中的参数需要是下一个可用的插值寄存器的索引值,比如上一个texcoord1,改插值寄存器索引值就为2
                    SHADOW_COORDS(2)
                };
    
                v2f vert(a2v v){
                    v2f o;
                    //得到裁剪空间下的顶点坐标
                    o.pos=UnityObjectToClipPos(v.vertex);
                    //得到世界坐标系下的模型法线和顶点坐标
                    o.worldNormal=mul(v.normal,(float3x3)unity_WorldToObject);
                    o.worldPos=mul(unity_ObjectToWorld,v.vertex).xyz;
                    //添加该宏用于在顶点着色器中计算上一步声明的阴影纹理坐标
                    TRANSFER_SHADOW(o);
                    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 viewDir=normalize(_WorldSpaceCameraPos-i.worldPos.xyz);
                    fixed3 halfDir=normalize(viewDir+worldLightDir);
                    fixed3 specular=_LightColor0.rgb*_Specular*pow(saturate(dot(worldNormal,halfDir)),_Gloss);
    
                    //计算阴影值
                    //fixed shadow=SHADOW_ATTENUATION(i);
                    
                    //计算光照衰减
                    //fixed atten=1.0;
    
                    //使用内置宏来计算光照衰减和阴影
                    UNITY_LIGHT_ATTENUATION(atten,i,i.worldPos);
    
                    //然后来计算最终产生的阴影
                    return fixed4(ambient+(diffuse+specular)*atten,1.0);
    
                }
                    
                ENDCG
            }
    
            Pass{
                Tags{"LightMode"="ForwardAdd"}
    
                Blend One One
    
                CGPROGRAM
                #pragma multi_compile_fwdadd_fullshadows
                
                #pragma vertex vert
                #pragma fragment frag
                #include "Lighting.cginc"
                #include "AutoLight.cginc"
    
                fixed4 _Diffuse;
                fixed4 _Specular;
                float _Gloss;
    
                
                struct a2v{
                    float4 vertex:POSITION;
                    float4 normal:NORMAL;
                };
    
                struct v2f{
                    float4 pos:SV_POSITION;
                    float3 worldNormal:TEXCOORD0;
                    float3 worldPos:TEXCOORD1;
                    //SHADOW_COORDS(2)
                };
    
                v2f vert(a2v v){
                    v2f o;
                    o.pos=UnityObjectToClipPos(v.vertex);
                    o.worldNormal=mul(v.normal,(float3x3)unity_WorldToObject);
                    o.worldPos=mul(unity_ObjectToWorld,v.vertex).xyz;
                    //TRANSFER_SHADOW(o);
                    return o;
                }
    
                fixed4 frag(v2f i):SV_Target{
                    
                    //fixed3 ambient=UNITY_LIGHTMODEL_AMBIENT.xyz;
    
                    fixed3 worldNormal=normalize(i.worldNormal);
                    //fixed3 worldLightDir=normalize(_WorldSpaceLightPos0.xyz);
                    #ifdef USING_DIRECTIONAL_LIGHT
                        fixed3 worldLightDir=normalize(_WorldSpaceLightPos0.xyz);
                    #else
                         fixed3 worldLightDir=normalize(_WorldSpaceLightPos0.xyz-i.worldPos.xyz);
                    #endif
    
                    fixed3 diffuse=_LightColor0.rgb*_Diffuse.rgb*saturate(dot(worldNormal,worldLightDir));
    
                    fixed3 viewDir=normalize(_WorldSpaceCameraPos-i.worldPos.xyz);
                    fixed3 halfDir=normalize(viewDir+worldLightDir);
    
                    fixed3 specular=_LightColor0.rgb*_Specular*pow(saturate(dot(worldNormal,halfDir)),_Gloss);
    
                   // fixed atten=1.0;
                    #ifdef USING_DIRECTIONAL_LIGHT
                        fixed atten=1.0;    
                    #else
                        float3 lightCoord=mul(unity_WorldToLight,float4(i.worldPos,1)).xyz;
                        fixed atten=tex2D(_LightTexture0,dot(lightCoord,lightCoord).rr).UNITY_ATTEN_CHANNEL;
                    #endif
    
                    //fixed shadow=SHADOW_ATTENUATION(i);
                    return fixed4((diffuse+specular)*atten,1.0);
    
                }
    
                ENDCG
            }
        }
        FallBack "Diffuse"
    }

    展开全文
  • 一、unityshader简单制作 1.顶点/片元着色器 基本结构 shader ”MyShader“ {//命名 Properties{ Name{"display name",PropertyType}=DefaultValue//属性名字:Name 显示名字:display name 类型:Property...
  • Unity Shader 基础教程

    万次阅读 多人点赞 2018-05-27 19:14:23
    在Github上看到一篇关于Unity-Shader的教程,感觉还不错,作者写的很好,很适合Unity-Shader的基础入门,我在这里翻译一下,分享给大家,英文水平很烂,大致能明白Unity-Shader是什么,渲染管线的工作流程,以及...
  • 故障效果越来越多的被应用于游戏开发,将事物发生故障的现象进行加工,形成了一种新的风格。本文实现了一个手电筒的故障效果,效果图如下,模型用的是Asset Store资源商店里的免费资源SciFi HandLight Free: ...
  • Unity Shader 颜色修改

    千次阅读 2021-01-07 14:29:04
    编写能够改变模型颜色值得Shader Shader "Unlit/ColorShader" { Properties { _Color("Color",Color) = (1,1,1,1) } SubShader { //渲染一次模型 Pass { //添加Cg/HLSL代码片段 CGPROGRAM //定义顶点...
  • unity shader写出的星云效果 是通过shadertoy 翻译出来的 !!很很好看 放代码=w= Shader "star" { Properties{ _color("TestColor",Color) = (0,0,0,0) _float("power",Float) = 1 _rotate("rotate",...
  • Unity ShaderGraph初始使用配置

    千次阅读 2021-11-16 10:11:53
    一般没使用URP管线渲染,或普通2/3D项目的Unity工程,第一次使用ShaderGraph,需要配置一番才能正常使用。下面记录下学习使用和配置设置的过程: 一、新建项目情况下使用ShaderGraph 最简单的方法是直接新建Hight-...
  • Unity Shader 自定义函数

    2021-01-07 16:48:09
    为了避免混乱,使代码结构清晰明朗,需要...Shader "Unlit/FunctionShader" { Properties { _Color("Color",Color) = (1,1,1,1) } SubShader { Pass { CGPROGRAM #pragma vertex vert #pragma fragment
  • Unity shader的坐标空间转换(图解)

    千次阅读 2021-11-06 11:17:39
    模型空间, 世界空间, 观察空间, 裁剪空间变化图解 ...模型空间到观察空间: UNITY_MATRIX_MV , mul(unity_MatrixV, unity_ObjectToWorld) 模型空间到裁剪空间的变化 就是所说的MVP变化 UNITY_MATRIX_MVP ...
  • Unity Shader 中各种Tag

    2021-01-31 08:52:19
    [Unity Shader 中各种Tag]转载自(https://www.cnblogs.com/wbaoqing/p/8979018.html) Unity shaderLab中,经常会看到 各种Tag(标签)。这里大致分为两类Tag,SubShader Tag 和 Pass Tag。· SubShader Tag Sub的这些...
  • Unity Shader(给Shader传递用户数据) 通过代码修改Shader中的属性 前面我有讲到可以定义各种类型的属性,然后在面板上可以进行更改。但是其实我们也可以在代码中进行修改。 Properties { _Color("I am Color", ...
  • unity实现纹理贴图很简单,首先在appdata结构体里声明uv语义,就可以获得当前顶点的uv坐标,对外部导入的模型来说。这个uv坐标是在3d模型软件例如maya中制作模型的人预先设定好的。我们只需要把他获取过来就行了。 ...
  • Unity Shader 数据精度使用情况 float half fixed Most mobile GPUs support both precision types, half being more efficient. So if you’re optimizing for mobiles it makes sense to use half as much as ...
  • unity shader 火焰效果

    2021-09-22 10:56:12
    Shader "ShaderMan/AABBCC" { Properties{ _MainTex ("MainTex", 2D) = "white" {} } SubShader { Tags { "RenderType" = "Transparent" "Queue" = "Transparent" } Pass { ZWrite Off Blend SrcAlpha...
  • 有时候我们需要传递控制修改多个信息变量等,此时需要用一...Shader "Unlit/StructShader" { Properties { _Color("Color",Color) = (1,1,1,1) } SubShader { Pass { CGPROGRAM #pragma vertex vert #pragm
  • Shader "Custom/PathNode" { Properties{ _Color("Color", Color) = (1,1,1,1) _MainTex("Albedo (RGB)", 2D) = "white" {} } SubShader{ Cull Off Pass { ZTest Greater } Pass { ZTest...
  • } 这样我们无论怎么移动摄影机,他的距离是永远不变的 我们来看看在UnityShader中是如何做到的吧 首先Unity里自带天空盒材质,只需要把图片贴上即可,同时要注意,天空盒的交界处我们是要过度的自然的,所以我们...
  • Unity Shader 热力图

    2020-12-21 19:47:31
    Unity Shader 热力图 分享一下自己写的unity热力图shader,感觉有帮助的话请点赞。 可调节热点位置,传热衰变和传热半径 。 也可叠加多个热点 Shader "Zxj/热力图" { Properties{ _CaiLiao("材料传热", Float...
  • 使用UnityShader实现Mask边框效果 实现原理------------------------------------------------------------------------------------ 把透明度小于阈值的像素舍弃, 缺点是锯齿很严重 透明度混合,需要开启透明队列...
  • 文章目录 前言 Unityshader相关的结构 什么是openGL和DirectX unity shader的分类 编写一个unity shader shader的基本结构 shader的property类型 在subshader中再次声明要用到的property 前言 本文所有内容来自 ...
  • Unity Shader 之 uv动画

    2020-12-18 18:46:16
    Unity 动画Unity Shader 内置时间变量引入时间变量名称类型描述_Timefloat4t是自该场景加载开始所经过的时间,4个分量分别是(t/20, t, 2t, 3t)_SinTimefloat4t是时间的正弦值,(t/8, t/4, t/2, t)_CosTimefloat4t是...
  • unity shader graph node

    2021-03-12 00:06:51
    artistic -> blend artistic -> mask
  • Unity_Shader基础篇_Unity Shader入门精要

    千次阅读 2018-03-14 18:20:30
    5-8初级篇 从最简单的shader开始,讲解Shader中基础的光照模型、纹理和透明效果等初级渲染效果。 9-16中极篇 讲解Unity中的渲染路径、如何计算光照衰减和阴影、如何使用高级纹理和动画等一系列内容。 17-22扩展篇 ...
  • Graph中用到了一个重要的节点,Fresnel Effect菲涅尔效果,在画面渲染中菲涅尔效果是一种很实用的技术手段,在Unity中则经常用它来实现边缘照明。 模型用到的依然是Asset Store资源商店中的免费模型Robot Kyle,...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 39,576
精华内容 15,830
关键字:

unityshader

友情链接: C Language.zip