Unity Shader 学习笔记(29) 表面着色器(Surface Shader)

44 篇文章 5 订阅
36 篇文章 5 订阅

Unity Shader 学习笔记(29) 表面着色器(Surface Shader)

参考书籍:《Unity Shader 入门精要》
官网API:Writing Surface Shaders
Unity Shader 学习笔记(3)Unity Shader模板、结构、形式
【浅墨Unity3D Shader编程】之六 暗黑城堡篇: 表面着色器(Surface Shader)的写法(一)
【浅墨Unity3D Shader编程】之七 静谧之秋篇: 表面着色器的写法(二)—— 自定义光照模式


结构

对顶点着色器和片元着色器的进一层封装。主要部分为两个结构体(Input、SurfaceOutput)和编译指令(#pragma surface)。

官网样例:

Shader "Example/Diffuse Bump" {
	Properties {
		  _MainTex ("Texture", 2D) = "white" {}
		  _BumpMap ("Bumpmap", 2D) = "bump" {}
	}
	SubShader {
		Tags { "RenderType" = "Opaque" }
		CGPROGRAM
		#pragma surface surf Lambert

		struct Input {
			float2 uv_MainTex;
			float2 uv_BumpMap;
	  	};

		sampler2D _MainTex;
		sampler2D _BumpMap;

		void surf (Input IN, inout SurfaceOutput o) {
			o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;
			o.Normal = UnpackNormal (tex2D (_BumpMap, IN.uv_BumpMap));
		}
	  ENDCG
	} 
	Fallback "Diffuse"
}

编译指令

指明表面着色器使用的表面函数和光照函数,并设置可选参数。格式如下:
#pragma surface surfaceFunction lightModel [optionalparams]

  • 表面函数(surfaceFunction)
    包含属性有反射率、光滑度、透明度等。使用输入结构体Input来设置各种表面属性。
  • [光照函数](file:///D:/Program/Unity/Editor/Data/Documentation/en/Manual/SL-SurfaceShaderLighting.html)
    使用表面函数的属性来模拟光照效果。Unity内置基于物理的光照模型Standard和StandardSpecular(在UnityPBSLighting.cginc中定义),非物理光照模型函数Lambert和BlinnPhong(在Lighting.cginc中定义)。

其他可选参数:

  • 自定义修改函数:
    • vertex:VertexFunction:顶点修改,实现一些顶点动画等。
    • finalcolor:ColorFunction:最终颜色修改,实现雾效等。
    • finalgbuffer:ColorFunction:延迟渲染修改,实现边缘检测等。
    • finalprepass:ColorFunction:prepass base路径修改?
  • 阴影:
    • addshadow:添加一个阴影投射Pass。一般FallBack通用的光照模式中可以找到,对于顶点动画、透明度等需要特殊处理。
    • fullforwardshadows:在前向渲染路径中支持所有光源类型的阴影。默认是只有平行光,添加这个参数就可以让点光或聚光灯有阴影渲染。
    • noshadow:取消所有阴影。
  • 透明度混合和测试:
    • alpha、alpha:auto:透明测试。
    • alpha:blend:透明混合。
    • alphatest:VariableName:VariableName用来剔除不满足的片元。
  • 光照:
    • noambient:不应用任何环境光照或光照探针(light probe)。
    • novertexlights:不应用逐顶点光照。
    • noforwardadd:去掉所有前向渲染的额外Pass,即支持逐像素平行光,其他光源用逐顶点或SH计算。
  • 控制代码生成:
    • exclude_path:deferred, exclude_path:forward, exclude_path:prepass :不需要为特定渲染路径生成代码。

两个结构体

Input结构体(数据来源)

包含主纹理和法线纹理的采样坐标uv_MainTexuv_BumpMap。采样纹理必须以“uv”作为前缀(或用"uv2"前缀表明为次纹理坐标集合)。

Unity会背后准备好数据,可以直接使用这些数据。
在这里插入图片描述

SurfaceOutput(表面属性)

存储表面属性的结构体,作为光照函数输入来计算光照。

// Lighting.cginc中定义
struct SurfaceOutput
{
    fixed3 Albedo;  // diffuse color
    fixed3 Normal;  // tangent space normal, if written
    fixed3 Emission;
    half Specular;  // specular power in 0..1 range,高光反射指数部分。
    fixed Gloss;    // specular intensity,高光反射强度系数。
    fixed Alpha;    // alpha for transparencies
};

// UnityPBSLighing.cginc中定义
struct SurfaceOutputStandard
{
    fixed3 Albedo;      // base (diffuse or specular) color
    fixed3 Normal;      // tangent space normal, if written
    half3 Emission;
    half Metallic;      // 0=non-metal, 1=metal
    half Smoothness;    // 0=rough, 1=smooth
    half Occlusion;     // occlusion (default 1)
    fixed Alpha;        // alpha for transparencies
};
struct SurfaceOutputStandardSpecular
{
    fixed3 Albedo;      // diffuse color
    fixed3 Specular;    // specular color
    fixed3 Normal;      // tangent space normal, if written
    half3 Emission;
    half Smoothness;    // 0=rough, 1=smooth
    half Occlusion;     // occlusion (default 1)
    fixed Alpha;        // alpha for transparencies
};

实例

在这里插入图片描述

Shader "Custom/Chapter 17/Normal Extrusion" {
	Properties {
		_ColorTint ("Color Tint", Color) = (1,1,1,1)
		_MainTex ("Base (RGB)", 2D) = "white" {}
		_BumpMap ("Normalmap", 2D) = "bump" {}
		_Amount ("Extrusion Amount", Range(-0.5, 0.5)) = 0.1
	}
	SubShader {
		Tags { "RenderType"="Opaque" }
		LOD 300
		
		CGPROGRAM
		
		// surf - 表面函数
		// CustomLambert - 光照模式(兰伯特漫反射)
		// vertex:myvert - 自定义顶点修改函数
		// finalcolor:mycolor - 自定义最后颜色修改函数
		// addshadow - 生成阴影捕获的Pass.因为修改了顶点位置,需要重新投射
		// exclude_path:deferred/exclude_path:prepass - 不要为延迟渲染路径生成相应的Pss
		// nometa - 不要生成元数据的Pass (that’s used by lightmapping & dynamic global illumination to extract surface information).
		#pragma surface surf CustomLambert vertex:myvert finalcolor:mycolor addshadow exclude_path:deferred exclude_path:prepass nometa
		#pragma target 3.0
		
		fixed4 _ColorTint;
		sampler2D _MainTex;
		sampler2D _BumpMap;
		half _Amount;
		
		struct Input {
			float2 uv_MainTex;
			float2 uv_BumpMap;
		};
		
		// 自定义顶点修改函数:把顶点延法线方向扩展。
		void myvert (inout appdata_full v) {
			v.vertex.xyz += v.normal * _Amount;
		}
		
		// 表面函数
		void surf (Input IN, inout SurfaceOutput o) {
			fixed4 tex = tex2D(_MainTex, IN.uv_MainTex);
			o.Albedo = tex.rgb;
			o.Alpha = tex.a;
			o.Normal = UnpackNormal(tex2D(_BumpMap, IN.uv_BumpMap));
		}
		
		// 自定义兰伯特光照
		half4 LightingCustomLambert (SurfaceOutput s, half3 lightDir, half atten) {
			half NdotL = dot(s.Normal, lightDir);
			half4 c;
			c.rgb = s.Albedo * _LightColor0.rgb * (NdotL * atten);
			c.a = s.Alpha;
			return c;
		}
		
		// 自定义最后颜色修改函数
		void mycolor (Input IN, SurfaceOutput o, inout fixed4 color) {
			color *= _ColorTint;
		}
		
		ENDCG
	}
	FallBack "Legacy Shaders/Diffuse"
}


表面着色器的渲染流水线

在这里插入图片描述

例如生成LightMode为ForwardBase的Pass流程:

  1. 将表面着色器中CGPROGRAMENDCG之间的代码复制过来。
  2. Unity根据上述代码生成结构体v2f_surf(顶点着色器的输出)。如果Input定义了一些变量但没有使用,生成的结构体也不会包含该变量。还会包含阴影纹理坐标、光照纹理坐标、逐顶点光照等。
  3. 生成顶点着色器。
      3.1. 如果定义了顶点修改函数,会先调用,或填充自定义Input结构体中的变量。Unity会分析该函数修改的数据,通过Input结构体把修改结果存储到v2f_surf相应变量。
      3.2. 计算v2f_surf中其他变量:顶点位置、纹理坐标、法线方向、逐顶点光照、光照纹理等。
      3.3. 把v2f_surf传递给片元着色器。
  4. 生成片元着色器。
      4.1. 将v2f_surf变量(纹理坐标、视角方向)填充到Input结构体。
      4.2. 调用自定义表面函数,填充SurfaceOutput结构体。
      4.3. 调用光照函数得到初始的颜色值。如果使用内置的Lambert或BlinnPhong光照函数,Unity还会计算动态全局光照,并添加到光照模型的计算。
      4.4. 进行其他颜色叠加。例如没有光照烘培,会添加逐顶点光照的影响。
      4.5. 调用最后的颜色修改函数。

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,您的第一个问题是关于Unity Shader学习笔记,具体是在表面着色器中控制顶点变换。那么我可以回答您的问题。 在Unity中,表面着色器是一种用于控制物体表面外观的程序。它们可以控制颜色、纹理和光照等方面。而在表面着色器中,我们可以使用顶点变换来控制物体表面的外观。 顶点变换是一个将物体的顶点坐标从局部空间转换为世界空间或者相机空间的过程。在表面着色器中,我们可以通过在CG程序中编写代码来实现顶点变换。以下是一个简单的例子: ```CG // 定义变换矩阵 float4x4 worldMatrix; float4x4 viewMatrix; float4x4 projectionMatrix; // 定义输入结构体 struct appdata { float4 vertex : POSITION; }; // 定义输出结构体 struct v2f { float4 vertex : SV_POSITION; }; // 顶点变换函数 v2f vert (appdata v) { v2f o; o.vertex = mul(mul(mul(v.vertex, worldMatrix), viewMatrix), projectionMatrix); return o; } ``` 在这个例子中,我们定义了三个变换矩阵:世界矩阵、视图矩阵和投影矩阵。然后定义了一个输入结构体appdata,其中包含了顶点的位置信息。输出结构体v2f则只包含了顶点的位置信息,用于传递给像素着色器。 在vert函数中,我们首先将顶点从局部空间转换到世界空间,然后再从世界空间转换到相机空间,最后再从相机空间转换到屏幕空间。这个过程中使用了mul函数来进行矩阵乘法运算。 通过这样的方式,我们可以在表面着色器中控制顶点变换,从而实现对物体表面外观的控制。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值