Unity Shader - What is MatCap - 什么是 MatCap - ShaderLab 中实现

141 篇文章 33 订阅
7 篇文章 1 订阅


声明

下面部分资源是通过抓帧得到的资源,仅用于学习,也不公开

如果有 qin quan,可以提醒一下,马上删除


环境

Unity : 5.X 以上
Pipeline : BRP

(修改到 URP 也就非常简单)


什么是 MatCap?

MatCaps

Matcap stands for “material capture” and is an image that is used as an image texture to fake a whole material including lighting and reflections in 3D applications. The most common use is while sculpting objects but it can be used in games for objects that should always be lit or to achieve special effects such as x-ray, ghost or radar.

The image consist of a simple sphere that is mapped to the object’s normals in relation to the camera. As the camera moves around the object, the reflections and highlights move around your object much like the object where moving and not the camera. In other words, if your object were a sphere, no matter how you looked at it, it would look like the matcap sphere (shading, colors , reflections etc. would always be displayed in the same place). But when a matcap is applied to non-spherical shapes the material responds as if it were made of a complex material.

Matcaps is nice while sculpting. It do work with rendering, to an extent, as the material do not interact with the scenes lightning and reflection of other objects in the scene is best used for proof of concepts. It can be, and is, used to achieve special effects in games. It’s a good time saver when you do not have the time to set up complex lighting or materials.


Matcap 代表“材料捕捉”,是一种图像,用作图像纹理来伪造整个材料,包括 3D 应用程序中的照明和反射。最常见的用途是在雕刻物体时,但它可用于游戏中应始终点亮的物体或实现特殊效果,例如 X 射线、鬼影或雷达。

该图像由一个简单的球体组成,该球体映射到相对于相机的对象法线。当相机围绕对象移动时,反射和高光围绕您的对象移动,就像移动的对象而不是相机一样。换句话说,如果你的对象是一个球体,无论你怎么看它,它看起来都像 matcap 球体(阴影、颜色、反射等总是显示在同一个地方)。但是,当将 matcap 应用于非球形形状时,材料的反应就好像它是由复杂材料制成的。

Matcaps 在雕刻时很好用。在一定程度上,它确实适用于渲染,因为材质不会与场景中的灯光交互,并且场景中其他对象的反射最适合用于概念、效果验证。它可以并且用于在游戏中实现特殊效果。当您没有时间设置复杂的照明或材质时,这是一个很好的节省时间的方法。


总结一句话,就是:MatCap 是早期为了方便查看在 Sculpt(雕刻模型时的) 一些光影(灯光、反射)效果而制作的简单的 shader


因为效果好,见效快,提升建模效果预览,然后一直流传开来,在各个建模阶段的模型材质预览

直到后面,甚至部分游戏中的实时渲染的情况,也制作用上了,如:角色选择界面,为了给 角色补一些反射效果时,可以使用到


看个效果

在这里插入图片描述

别看这个效果以为是 PBR,其实 shader 超级简单

在 鹅C,早期(2017发布的SDSXS)项目 中也看到有使用到 MatCap
在这里插入图片描述


Unity ShaderLab 实现 MatCap

下面代码是在 这篇: MatCap — Render & Art pipeline optimization for mobile devices 上实现的

// jave.lin : 参考:https://medium.com/playkids-tech-blog/matcap-render-art-pipeline-optimization-for-mobile-devices-4e1a520b9f1a
// 简单到不能再简单了
// 原理是:将 法线转换到 ViewSpace,然后将 ViewSpace 下的法线 [-1~1] 转为 [0~1]
Shader "MatCap1"
{
	Properties
	{
		_MainTex ("Diffuse (RGB)", 2D) = "white" {}
		_MatCap  ("MatCap (RGB)", 2D) = "gray" {}
	}

	Subshader
	{
		Tags { "Queue"="Geometry" "RenderType"="Opaque" }

		Pass
		{
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			#include "UnityCG.cginc"
			
			uniform sampler2D _MainTex;
			uniform sampler2D _MatCap;
			uniform float4 _MainTex_ST;
			uniform float4 _MatCap_ST;
			
			struct appdata 
			{
			    float4 vertex    : POSITION;
			    float4 texcoord0 : TEXCOORD0;
			    float3 normal    : NORMAL;
			};

			struct v2f
			{
			    float4 pos	  : SV_POSITION;
			    float2 uv 	  : TEXCOORD0;
			    float2 uvCap	  : TEXCOORD1;
			};
	
			v2f vert (appdata v)
			{
				v2f o;
				o.pos = UnityObjectToClipPos(v.vertex);
				o.uv  = TRANSFORM_TEX(v.texcoord0, _MainTex);

				float3 worldNormal = normalize( unity_WorldToObject[0].xyz * v.normal.x + 
						                        unity_WorldToObject[1].xyz * v.normal.y + 
						                        unity_WorldToObject[2].xyz * v.normal.z);

				worldNormal = mul((float3x3)UNITY_MATRIX_V, worldNormal);
				o.uvCap.xy = worldNormal.xy * 0.5 + 0.5;

				return o;
			}
	
			float4 frag (v2f i) : COLOR
			{
				float4 albedo = tex2D(_MainTex, i.uv);
				float4 MatCap = tex2D(_MatCap, i.uvCap);
        		return (albedo + (MatCap * 2) - 1); // jave.lin : 这里就不应该 albedo + matcap,会过曝
			}
			ENDCG
		}
	}
}

然后自己撸一个 - 只有 Reflect

// jave.lin : 参考:https://medium.com/playkids-tech-blog/matcap-render-art-pipeline-optimization-for-mobile-devices-4e1a520b9f1a
// 简单到不能再简单了
// 原理是:将 法线转换到 ViewSpace,然后将 ViewSpace 下的法线 [-1~1] 转为 [0~1]
// 然后将  0~1 作为正面半球法线的 MatCapTex 纹理的采样坐标即可
Shader "MatCap2"
{
	Properties
	{
		_MainTex ("MainTex (RGB)", 2D) = "white" {}
		_MatCapTex("MatCap Reflect Tex (RGB)", 2D) = "gray" {}
		_ReflectIntensity ("Reflection Intensity", Range(0, 1)) = 0.5
	}

	Subshader
	{
		Tags { "Queue"="Geometry" "RenderType"="Opaque" }

		Pass
		{
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			#include "UnityCG.cginc"
			
			struct a2v {
			  float4 positionOS : POSITION;
			  float2 uv : TEXCOORD0;
			  float3 normal : NORMAL;
			};

			struct v2f {
			  float4 positionCS : SV_POSITION;
			  float2 uv : TEXCOORD0;
			  float2 uvMC : TEXCOORD1;
			};

			sampler2D _MainTex;
			sampler2D _MatCapTex;
			float4 _MatCap_TexelSize;
			fixed _ReflectIntensity;

			v2f vert(a2v v)
			{
			  v2f o = (v2f)0;
			  o.positionCS = UnityObjectToClipPos(v.positionOS);
			  o.uv = v.uv;

			  float aspect = _ScreenParams.x / _ScreenParams.y;

			  float3 worldNormal = UnityObjectToWorldNormal(v.normal); // jave.lin : obj to world space
			  worldNormal = mul((float3x3)UNITY_MATRIX_V, worldNormal); // jave.lin : world to view space
			  o.uvMC = worldNormal.xy * 0.5 + 0.5; // jave.lin : [-1~1] to [0~1], 正对镜头的法线是 0.5, 0.5
			  // jave.lin : 另外,这里值得注意的是,因为 uvMC 是在 ViewSpace 下的
			  // 而 ViewSpace 下的变换是正交的
			  // 所以需要将 Camera.project type 修改为:othrographic 才能正常预览此效果
			  // 如果使用 perspective 透视的话,离相机透视中心越远的像素,将会出现边缘采样问题
			  return o;
			}

			float4 frag(v2f i) : SV_TARGET
			{
			  float4 albedo = tex2D(_MainTex, i.uv);

#ifdef _TEST_ON
			  // jave.lin : 测试采样控制在圆形内
			  float2 UVVec = i.uvMC - 0.5;
			  float2 normalUV = normalize(UVVec);
			  float lenUV = length(UVVec);
			  float2 matcapUV = 0.5 + lenUV * normalUV;
			  float4 matcap = tex2D(_MatCapTex, matcapUV);
#else
			  // jave.lin : 发现根本不用控制在原型内
			  // 因为 uvMC 的值本身就是在 圆形内
			  // 因为 镜头永远只能看到正面,也就是法线 xyz 方向都是正面的
			  // 那么 xyz 中的 xy 也是正面方向上的分量,所以不可能会有超出正面半球的情况
			  // 而且 _MatCapTex 本身是提供与 正面半球的采样纹理
			  float4 matcap = tex2D(_MatCapTex, i.uvMC);
#endif
			  return lerp(albedo, matcap, _ReflectIntensity); // jave.lin : 这里使用 lerp 混合就好了
			}

			ENDCG
		}
	}
}

再撸一个 - Reflect, Normal, BaseCol - 后续可以按需求,自己添加

// jave.lin : 参考:https://medium.com/playkids-tech-blog/matcap-render-art-pipeline-optimization-for-mobile-devices-4e1a520b9f1a
// 简单到不能再简单了
// 原理是:将 法线转换到 ViewSpace,然后将 ViewSpace 下的法线 [-1~1] 转为 [0~1]
// 然后将  0~1 作为正面半球法线的 MatCapTex 纹理的采样坐标即可
Shader "MatCap3"
{
	Properties
	{
		_MainTex ("MainTex (RGB)", 2D) = "white" {}
		_MatCapTex("MatCap Reflect Tex (RGB)", 2D) = "gray" {}
		_ReflectIntensity ("Reflection Intensity", Range(0, 1)) = 0.5
		[KeywordEnum(REFLECT, NORMAL, BASECOL)] _MC("Mode", float) = 0
	}

	Subshader
	{
		Tags { "Queue"="Geometry" "RenderType"="Opaque" }

		Pass
		{
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			#pragma multi_compile _ _MC_REFLECT _MC_NORMAL _MC_BASECOL
			#include "UnityCG.cginc"
			
			struct a2v {
			  float4 positionOS : POSITION;
			  float2 uv : TEXCOORD0;
			  float3 normal : NORMAL;
			};

			struct v2f {
			  float4 positionCS : SV_POSITION;
			  float2 uv : TEXCOORD0;
			  float2 uvMC : TEXCOORD1;
			};

			sampler2D _MainTex;
			sampler2D _MatCapTex;
			float4 _MatCap_TexelSize;
			fixed _ReflectIntensity;

			v2f vert_reflect(a2v v)
			{
				v2f o = (v2f)0;
				o.positionCS = UnityObjectToClipPos(v.positionOS);
				o.uv = v.uv;

				float aspect = _ScreenParams.x / _ScreenParams.y;

				float3 worldNormal = UnityObjectToWorldNormal(v.normal); // jave.lin : obj to world space
				worldNormal = mul((float3x3)UNITY_MATRIX_V, worldNormal); // jave.lin : world to view space
				o.uvMC = worldNormal.xy * 0.5 + 0.5; // jave.lin : [-1~1] to [0~1], 正对镜头的法线是 0.5, 0.5
				// jave.lin : 另外,这里值得注意的是,因为 uvMC 是在 ViewSpace 下的
				// 而 ViewSpace 下的变换是正交的
				// 所以需要将 Camera.project type 修改为:othrographic 才能正常预览此效果
				// 如果使用 perspective 透视的话,离相机透视中心越远的像素,将会出现边缘采样问题
				return o;
			}

			v2f vert_normal(a2v v)
			{
				v2f o = (v2f)0;
				o.positionCS = UnityObjectToClipPos(v.positionOS);
				float3 worldNormal = UnityObjectToWorldNormal(v.normal); // jave.lin : obj to world space
				worldNormal = mul((float3x3)UNITY_MATRIX_V, worldNormal); // jave.lin : world to view space
				o.uvMC = worldNormal.xy * 0.5 + 0.5; // jave.lin : [-1~1] to [0~1]
				return o;
			}

			v2f vert_basecol(a2v v)
			{
				v2f o = (v2f)0;
				o.positionCS = UnityObjectToClipPos(v.positionOS);
				o.uv = v.uv;
				return o;
			}

			v2f vert(a2v v)
			{
#ifdef _MC_REFLECT
			  return vert_reflect(v);
#elif defined(_MC_NORMAL)
			  return vert_normal(v);
#else
			  return vert_basecol(v);
#endif
			}

			float4 frag_reflect(v2f i)
			{
			  float4 albedo = tex2D(_MainTex, i.uv);

#ifdef _TEST_ON
			  // jave.lin : 测试采样控制在圆形内
			  float2 UVVec = i.uvMC - 0.5;
			  float2 normalUV = normalize(UVVec);
			  float lenUV = length(UVVec);
			  float2 matcapUV = 0.5 + lenUV * normalUV;
			  float4 matcap = tex2D(_MatCapTex, matcapUV);
#else
			  // jave.lin : 发现根本不用控制在原型内
			  // 因为 uvMC 的值本身就是在 圆形内
			  // 因为 镜头永远只能看到正面,也就是法线 xyz 方向都是正面的
			  // 那么 xyz 中的 xy 也是正面方向上的分量,所以不可能会有超出正面半球的情况
			  // 而且 _MatCapTex 本身是提供与 正面半球的采样纹理
			  float4 matcap = tex2D(_MatCapTex, i.uvMC);
#endif
			  return lerp(albedo, matcap, _ReflectIntensity); // jave.lin : 这里使用 lerp 混合就好了
			}

			float4 frag_normal(v2f i)
			{
			  return float4(i.uvMC, 0, 1);
			}

			float4 frag_basecol(v2f i)
			{
			  return tex2D(_MainTex, i.uv);
			}

			float4 frag(v2f i) : SV_TARGET
			{
#ifdef _MC_REFLECT
			  return frag_reflect(i);
#elif defined(_MC_NORMAL)
			  return frag_normal(i);
#else
			  return frag_basecol(i);
#endif
			}

			ENDCG
		}
	}
}

使用了法线贴图的纹理 的情况

如果使用了 法线贴图的情况,那么只能将 matcap uv 放在 fragment shader 中计算,如下伪代码:

思路是:先要有 TBN,然后将 normal map 中的 normalTS 采样出来,然后 T2W 计算出 normalWS,再将 normalWS.xy * 0.5 + 0.5 之后的结果用于采样 mapcatTex

    struct VS_INPUT
    {
        float4 vertex : POSITION; 
        float4 uvMC : TEXCOORD0;
        float3 normalOS : NORMAL;
        float4 tangentOS : TANGENT;
    };

    struct VS_OUTPUT
    {
        float4 pos : SV_Position;
        float4 uvMC : TEXCOORD0;
		...
        half3 normalWS : TEXCOORD3;
		half3 bitangentWS : TEXCOORD4;
		half3 tangentWS : TEXCOORD5;
		...
    };
    ...
	sampler2D __NormalTex;
	sampler2D __MatCapTex;
    ...
    VS_OUTPUT vert(VS_INPUT v)
    {
    	...
		half3 normalWS = UnityObjectToWorldNormal(v.normalOS);
		// half3 tangentWS = mul((float3x3)unity_ObjectToWorld, v.tangentOS.xyz);
		half3 tangentWS = UnityObjectToWorldDir(v.tangentOS.xyz);
		// (float3 bitangentWS = ((normalWS.yzx * tangentWS.zxy) - (normalWS.zxy * tangentWS.yzx)));
		half3 bitangentWS = cross(normalWS, tangentWS) * v.tangentOS.w;
        // jave.lin : 具体 TBN 可以参考:UnityCG.cginc 中的 TANGENT_SPACE_ROTATION 宏定义
        
		...

        VS_OUTPUT output = (VS_OUTPUT)0;
        output.pos = UnityObjectToClipPos(v.vertex);
        output.normalWS = normalWS;
        output.bitangentWS = bitangentWS;
        output.tangentWS = tangentWS;

        return output;
    }

    fixed4 frag(VS_OUTPUT input) : SV_TARGET
    {
    	...
        half3 normalWS = normalize(input.normalWS);
        half3 bitangentWS = normalize(input.bitangentWS);
        half3 tangentWS = normalize(input.tangentWS);

        half3x3 TBN = half3x3(tangentWS, bitangentWS, normalWS);
		// jave.lin : sample & decode normalTS
        half3 N_from_map_WS = ((tex2D(__NormalTex, input.uv.xy).xyz * 2.0) - 1.0);
        // jave.lin : TS 2 WS
        // N_from_map_WS = normalize(mul(N_from_map_WS, TBN)); // jave.lin : T2W,精度要求不是非常高的,normalize 也可以节省
        N_from_map_WS = mul(N_from_map_WS, TBN); // jave.lin : T2W,已经省去 normalize
		// jave.lin : WS 2 VS
        half3 N_from_map_VS = mul(N_from_map_WS, (float3x3)UNITY_MATRIX_V);
        // jave.lin : remap : [-1~1] to [0, 1]
        half2 uv_matcap = N_from_map_VS.xy * 0.5 + 0.5;
        // jave.lin : 采样 matcap
        float3 mapcat = tex2D(__MatCapTex, uv_matcap).rgb;
        ...
    }

使用到的纹理 & 材质球

纹理
在这里插入图片描述
材质
在这里插入图片描述


预览 MatCap 需要在 相机投影

一般为了更好的预览 MatCap 的效果,都会在 正交 投影下进行,透视也不是不可以,只不过效果没那么好

如下,透视,下的瑕疵:
在这里插入图片描述

正交就很完美
在这里插入图片描述


Project

backup(不公开) :


References

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Unity Shader是一种用于在Unity引擎创建和控制图形渲染效果的编程语言。通过使用Unity Shader,开发人员可以自定义游戏各种物体的外观和行为,从而实现更加逼真和出色的视觉效果。 而热图(Heatmap)是一种用于显示某个区域内物体热度分布的视觉化工具。在游戏开发,热图通常用于统计和分析玩家在游戏的行为和偏好,以便开发人员可以根据这些数据进行游戏优化和改进。 为了创建一个热图效果,我们可以使用Unity Shader实现。首先,我们需要将游戏各个物体按照玩家与其的互动情况和频率进行区分,不同的行为和频率可以对应不同的颜色或者纹理。接着,我们可以在Shader根据这些信息来着色和渲染物体,以展示物体的热度分布。 在Shader,我们可以通过为物体添加一张热图纹理,并使用该纹理来表示物体的热度值。热图纹理可以是一张灰度图,不同的灰度值对应不同的热度。然后,我们可以使用纹理坐标和采样操作来获取每个像素对应的热度值,并根据这些值来着色和渲染物体。 除了使用纹理来表示热度分布,我们还可以使用其他的技术和效果来增强热图的可视化效果。例如,我们可以使用颜色渐变和透明度来形成平滑的过渡效果,以更好地显示物体的热度变化。我们还可以添加动画效果,使热图效果更加生动和有趣。 总结而言,Unity Shader可以用于创建热图效果,通过着色和渲染来展示物体的热度分布。这样的热图可以帮助开发人员分析游戏玩家的行为和偏好,从而优化和改进游戏的设计和内容。这些热图效果能够增强游戏的可视化效果,并提供有价值的数据供开发人员参考。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值