【Unity Shader编程】之十六 基于MatCap实现适于移动平台的“次时代”车漆Shader

时间:2022-12-31 12:11:09

本系列文章由@浅墨_毛星云 出品,转载请注明出处。  
文章链接:http://blog.csdn.net/poem_qianmo/article/details/55803629
渲染
本文配图使用的Unity3D版本号: 5.5.0

这篇文章将基于MatCap的思想。在Unity中实现了具有高度真实感的MatCap车漆Shader。採用MatCap思想的Shader,用低廉的计算成本,就能够达到相似PBS很真实的渲染效果,可谓是在移动平台实现次时代渲染效果的一种优秀解决方式。

本文以车漆Shader为例,但MatCap思想能实现的,并不局限于车漆Shader。

本来准备给本文取名《一种基于MatCap的低计算成本、高真实感移动平台Shader解决方式》的,但这个名字太大了,遂改之。

先看一下终于的效果图。

【Unity Shader编程】之十六 基于MatCap实现适于移动平台的“次时代”车漆Shader

【Unity Shader编程】之十六 基于MatCap实现适于移动平台的“次时代”车漆Shader

【Unity Shader编程】之十六 基于MatCap实现适于移动平台的“次时代”车漆Shader

一、MatCap概述

Material Capture(材质捕获),通常被简称为MatCap。在Zbrush、Sculptris、Mudbox等3D软件中有比較多的使用。

MatCap Shader的基本思路是,使用某特定材质球的贴图,作为当前材质的视图空间环境贴图(view-space environment map),来实现具有均匀表面着色的反射材质物体的显示。考虑到物体的全部法线的投影的范围在x(-1,1),y(-1,1),构成了一个圆形,所以MatCap 贴图中存储光照信息的区域是一个圆形。

基于MatCap思想的Shader,能够无需提供不论什么光照,仅仅需提供一张或多张合适的MatCap贴图作为光照结果的“指导”就可以。

【Unity Shader编程】之十六 基于MatCap实现适于移动平台的“次时代”车漆Shader

【Unity Shader编程】之十六 基于MatCap实现适于移动平台的“次时代”车漆Shader

上图来自(http://digitalrune.github.io/DigitalRune-Documentation/html/9a8c8b37-b996-477a-aeab-5d92714be3ca.htm

不像一般的Shader。须要提供光照。须要在Shader代码中进行漫长的演算,基于MatCap思想的Shader相当于MatCap贴图就把光照结果应该是如何的标准答案告知Shader。我们仅仅用在试卷下写出答案。进行一些加工就可以。

须要注意,MatCap Shader有一定的局限性。由于从某种意义上来说。基于MatCap的Shader,就是某种固定光照条件下,从某个特定方向,特定角度的光照表现结果。

正是由于是选择的固定的MatCap贴图,得到相对固定的总体光照表现。若单单仅使用MatCap,就仅适用于摄像机不调整角度的情形,并不适合摄像机会频繁旋转,调节角度的情形。但我们能够在某些Shader中。用MatCap配合与光照交互的其它属性。如将MatCap结合一个作为光照反射的颜色指导的Reflection Cube Map,就有了与光照之间的交互表现。这样,就能够适当弥补MatCap太过单一总体光照表现的短板。

关于MatCap,《UnityShaders and Effects Cookbook》一书的Chapter 5: LightingModels中,The Lit Sphere lighting model一节也有一些涉及。

二、MatCap贴图的获取

须要使用基于MatCap Shader,合适的MatCap 贴图不可缺少。显而易见,MatCap贴图的获取,一般来说有两种方式。

1. 自己制作。对着3D软件中的材质球截图。

2. 从网络上获取。在网络上使用“matcap“等keyword搜索后获得。

这边提供几个能够获取MatCap贴图的网址:

[1] https://www.pinterest.com/evayali/matcap/

[2]https://www.google.com.hk/search?q=MatCap&newwindow=1&safe=strict&hl=zh-CN&biw=1575&bih=833&tbm=isch&tbo=u&source=univ&sa=X&ved=0ahUKEwju8JDTpZnSAhUGn5QKHawODTIQsAQIIg

[3]http://pixologic.com/zbrush/downloadcenter/library/#prettyPhoto

三、基于MatCap实现Physically Based Shading的思路简析

关于基于MatCap思想实现Physicallybased Shading,这篇文章(http://blog.csdn.net/ndsc_dw/article/details/50700201)提供了一定的思路,简单来说。就是用几张MatCap贴图来提供PBS须要的光滑度和金属度,来模拟出PBS的效果。

继续展开下去就脱离本文的主线了。有兴趣的朋友能够深入进行了解。

四、基于MatCap思想的车漆Shader实现

此车漆Shader,除了用到MatCap。主要还须要提供一个Reflection Cube Map作为反射的颜色指导。就能够适当弥补MatCap太过单一的总体光照表现的短板。实现很真实且高效的车漆Shader效果。

此Shader赋给Material后。Material在Inspector中的属性表现例如以下:

【Unity Shader编程】之十六 基于MatCap实现适于移动平台的“次时代”车漆Shader

当中的MatCap贴图为:

【Unity Shader编程】之十六 基于MatCap实现适于移动平台的“次时代”车漆Shader

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvcG9lbV9xaWFubW8=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" width="256" height="256" alt="" />

Shader源代码例如以下:

Shader "ShaderPrac/Car Paint Shader"
{
Properties
{
//主颜色
_MainColor("Main Color", Color) = (1.0, 1.0, 1.0, 1.0)
//细节颜色
_DetailColor("Detail Color", Color) = (1.0, 1.0, 1.0, 1.0)
//细节纹理
_DetailTex("Detail Textrue", 2D) = "white" {}
//细节纹理深度偏移
_DetailTexDepthOffset("Detail Textrue Depth Offset", Float) = 1.0
//漫反射颜色
_DiffuseColor("Diffuse Color", Color) = (0.0, 0.0, 0.0, 0.0)
//漫反射纹理
_DiffuseTex("Diffuse Textrue", 2D) = "white" {}
//Material Capture纹理
_MatCap("MatCap", 2D) = "white" {}
//反射颜色
_ReflectionColor("Reflection Color", Color) = (0.2, 0.2, 0.2, 1.0)
//反射立方体贴图
_ReflectionMap("Reflection Cube Map", Cube) = "" {}
//反射强度
_ReflectionStrength("Reflection Strength", Range(0.0, 1.0)) = 0.5
} SubShader
{
Tags
{
"Queue" = "Geometry"
"RenderType" = "Opaque"
} Pass
{
Blend Off
Cull Back
ZWrite On CGPROGRAM
#include "UnityCG.cginc"
#pragma fragment frag
#pragma vertex vert float4 _MainColor;
float4 _DetailColor;
sampler2D _DetailTex;
float4 _DetailTex_ST;
float _DetailTexDepthOffset;
float4 _DiffuseColor;
sampler2D _DiffuseTex;
float4 _DiffuseTex_ST;
sampler2D _MatCap;
float4 _ReflectionColor;
samplerCUBE _ReflectionMap;
float _ReflectionStrength; //顶点输入结构
struct VertexInput
{
float3 normal : NORMAL;
float4 position : POSITION;
float2 UVCoordsChannel1: TEXCOORD0;
}; //顶点输出(片元输入)结构
struct VertexToFragment
{
float3 detailUVCoordsAndDepth : TEXCOORD0;
float4 diffuseUVAndMatCapCoords : TEXCOORD1;
float4 position : SV_POSITION;
float3 worldSpaceReflectionVector : TEXCOORD2;
}; //------------------------------------------------------------
// 顶点着色器
//------------------------------------------------------------
VertexToFragment vert(VertexInput input)
{
VertexToFragment output; //漫反射UV坐标准备:存储于TEXCOORD1的前两个坐标xy。 output.diffuseUVAndMatCapCoords.xy = TRANSFORM_TEX(input.UVCoordsChannel1, _DiffuseTex); //MatCap坐标准备:将法线从模型空间转换到观察空间,存储于TEXCOORD1的后两个纹理坐标zw
output.diffuseUVAndMatCapCoords.z = dot(normalize(UNITY_MATRIX_IT_MV[0].xyz), normalize(input.normal));
output.diffuseUVAndMatCapCoords.w = dot(normalize(UNITY_MATRIX_IT_MV[1].xyz), normalize(input.normal));
//归一化的法线值区间[-1,1]转换到适用于纹理的区间[0,1]
output.diffuseUVAndMatCapCoords.zw = output.diffuseUVAndMatCapCoords.zw * 0.5 + 0.5; //坐标变换
output.position = mul(UNITY_MATRIX_MVP, input.position); //细节纹理准备准备UV,存储于TEXCOORD0的前两个坐标xy
output.detailUVCoordsAndDepth.xy = TRANSFORM_TEX(input.UVCoordsChannel1, _DetailTex); //深度信息准备,存储于TEXCOORD0的第三个坐标z
output.detailUVCoordsAndDepth.z = output.position.z; //世界空间位置
float3 worldSpacePosition = mul(unity_ObjectToWorld, input.position).xyz; //世界空间法线
float3 worldSpaceNormal = normalize(mul((float3x3)unity_ObjectToWorld, input.normal)); //世界空间反射向量
output.worldSpaceReflectionVector = reflect(worldSpacePosition - _WorldSpaceCameraPos.xyz, worldSpaceNormal); return output;
} //------------------------------------------------------------
// 片元着色器
//------------------------------------------------------------
float4 frag(VertexToFragment input) : COLOR
{
//镜面反射颜色
float3 reflectionColor = texCUBE(_ReflectionMap, input.worldSpaceReflectionVector).rgb * _ReflectionColor.rgb; //漫反射颜色
float4 diffuseColor = tex2D(_DiffuseTex, input.diffuseUVAndMatCapCoords.xy) * _DiffuseColor; //主颜色
float3 mainColor = lerp(lerp(_MainColor.rgb, diffuseColor.rgb, diffuseColor.a), reflectionColor, _ReflectionStrength); //细节纹理
float3 detailMask = tex2D(_DetailTex, input.detailUVCoordsAndDepth.xy).rgb; //细节颜色
float3 detailColor = lerp(_DetailColor.rgb, mainColor, detailMask); //细节颜色和主颜色进行插值。成为新的主颜色
mainColor = lerp(detailColor, mainColor, saturate(input.detailUVCoordsAndDepth.z * _DetailTexDepthOffset)); //从提供的MatCap纹理中,提取出相应光照信息
float3 matCapColor = tex2D(_MatCap, input.diffuseUVAndMatCapCoords.zw).rgb; //终于颜色
float4 finalColor=float4(mainColor * matCapColor * 2.0, _MainColor.a); return finalColor;
} ENDCG
}
} Fallback "VertexLit"
}

Shader凝视已经比較具体,以下对代码中或许会不太理解,须要注意的地方进行说明。

要使用MatCap贴图,主要是将法线从模型空间转换到视图空间,并切换到适合提取纹理UV的区域[0,1]。

(须要将法线从模型空间转换到视图空间。关于一些推导能够參考http://www.lighthouse3d.com/tutorials/glsl-12-tutorial/the-normal-matrix或者http://www.cnblogs.com/flytrace/p/3379816.html

Unity内置的矩阵UNITY_MATRIX_IT_MV,是UNITY_MATRIX_MV的逆转置矩阵。其作用正是将法线从模型空间转换到观察空间。于是顶点着色器vert中的这两句代码就很easy理解了:

//MatCap坐标准备:将法线从模型空间转换到观察空间,存储于TEXCOORD1的后两个纹理坐标zw
output.diffuseUVAndMatCapCoords.z =dot(normalize(UNITY_MATRIX_IT_MV[0].xyz), normalize(input.normal));
output.diffuseUVAndMatCapCoords.w= dot(normalize(UNITY_MATRIX_IT_MV[1].xyz), normalize(input.normal));

而得到的视图空间的法线,区域是[-1,1],要转换到提取纹理UV的区域[0,1],就须要乘以0.5并加上0.5,那么顶点着色器vert中接下来的的这句代码也就能够理解了:

//归一化的法线值区间[-1,1]转换到适用于纹理的区间[0,1]
output.diffuseUVAndMatCapCoords.zw= output.diffuseUVAndMatCapCoords.zw * 0.5 + 0.5;

稍后。在片元着色器frag中。用在顶点着色器vert中准备好的法线转换成的UV值,提取出MatCap的光照细节就可以:

//从提供的MatCap纹理中,提取出相应光照信息
float3matCapColor = tex2D(_MatCap, input.diffuseUVAndMatCapCoords.zw).rgb;

此Car Paint Shader其它实现。主要就是一些主要的Shader知识点的配合。如顶点坐标转换。反射,漫反射。细节纹理的计算,都是比較基础的内容,这里就不再赘述,直接看Shader源代码就可以。

最后看几张截图,然后就是相关Shader与MatCap资源的下载:

【Unity Shader编程】之十六 基于MatCap实现适于移动平台的“次时代”车漆Shader

【Unity Shader编程】之十六 基于MatCap实现适于移动平台的“次时代”车漆Shader

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvcG9lbV9xaWFubW8=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="" />

【Unity Shader编程】之十六 基于MatCap实现适于移动平台的“次时代”车漆Shader

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvcG9lbV9xaWFubW8=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="" />

【Unity Shader编程】之十六 基于MatCap实现适于移动平台的“次时代”车漆Shader

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvcG9lbV9xaWFubW8=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="" />

【Unity Shader编程】之十六 基于MatCap实现适于移动平台的“次时代”车漆Shader

【Unity Shader编程】之十六 基于MatCap实现适于移动平台的“次时代”车漆Shader

五、Shader源代码与MatCap资源下载

从Github下载:

【Github】Shader源代码与相关MatCap资源下载

參考与延伸

[1] http://wiki.unity3d.com/index.php/MatCap

[2] https://www.assetstore.unity3d.com/en/#!/content/8221

[3] http://blog.csdn.net/cubesky/article/details/38682975

[4] https://www.assetstore.unity3d.com/en/#!/content/76361

[5] http://www.cnblogs.com/flytrace/p/3379816.html

[6] http://www.cnblogs.com/flytrace/p/3395911.html

[7] http://digitalrune.github.io/DigitalRune-Documentation/html/9a8c8b37-b996-477a-aeab-5d92714be3ca.htm

[8] https://forum.unity3d.com/threads/_object2world-or-unity_matrix_it_mv.112446/

[9]http://www.lighthouse3d.com/tutorials/glsl-12-tutorial/the-normal-matrix

With Best Wishes.


【Unity Shader编程】之十六 基于MatCap实现适于移动平台的“次时代”车漆Shader的更多相关文章

  1. 【浅墨Unity3D Shader编程】之中的一个 夏威夷篇:游戏场景的创建 & 第一个Shader的书写

    本系列文章由@浅墨_毛星云 出品.转载请注明出处. 文章链接:http://blog.csdn.net/poem_qianmo/article/details/40723789 作者:毛星云(浅墨)  ...

  2. 学习ASP.NET Core Razor 编程系列十六——排序

    学习ASP.NET Core Razor 编程系列目录 学习ASP.NET Core Razor 编程系列一 学习ASP.NET Core Razor 编程系列二——添加一个实体 学习ASP.NET ...

  3. Unity 游戏框架搭建 (十六) v0.0.1 架构调整

    背景: 前段时间用Xamarin.OSX开发一些工具,遇到了两个问题. QFramework的大部分的类耦合了Unity的API,这样导致不能在其他CLR平台使用QFramework. QFramew ...

  4. java并发编程(十六)happen-before规则

    转载请注明出处:http://blog.csdn.net/ns_code/article/details/17348313 happen-before规则介绍 Java语言中有一个"先行发生 ...

  5. 并发编程(十六)——java7 深入并发包 ConcurrentHashMap 源码解析

    以前写过介绍HashMap的文章,文中提到过HashMap在put的时候,插入的元素超过了容量(由负载因子决定)的范围就会触发扩容操作,就是rehash,这个会重新将原数组的内容重新hash到新的扩容 ...

  6. C#编程(十六)----------匿名类型

    匿名类型 var和new关键字一起使用,可以创建匿名类型. 匿名类型提供了一种方便的方法,可用来将一组只读属性封装到单个对象中,而无需首先显式定义一个类型. 类型名由编译器生成,并且不能在源代码级使用 ...

  7. java并发编程(十六)----(线程池)java线程池的使用

    上节我们简单介绍了线程池,这次我们就来使用一下.Executors提供四种线程池,分别是:newCachedThreadPool,newFixedThreadPool ,newScheduledThr ...

  8. 【浅墨Unity3D Shader编程】之一 夏威夷篇:游戏场景的创建 & 第一个Shader的书写

    本系列文章由@浅墨_毛星云 出品,转载请注明出处. 文章链接:http://blog.csdn.net/poem_qianmo/article/details/40723789 作者:毛星云(浅墨)  ...

  9. 学习ASP.NET Core Razor 编程系列十九——分页

    学习ASP.NET Core Razor 编程系列目录 学习ASP.NET Core Razor 编程系列一 学习ASP.NET Core Razor 编程系列二——添加一个实体 学习ASP.NET ...

随机推荐

  1. SharpFileDB - a file database for small apps

    SharpFileDB - a file database for small apps 本文中文版在此处. I'm not an expert of database. Please feel fr ...

  2. RAS RC4 AES 加密 MD5

    这两者唯一的相同点是设计者中都包含了MIT的Ron Revist教授.RSA是公钥密码算法,优点:不用事先通过秘密信道传递密钥,可以用于数字签名.缺点:速度慢RC4是序列密码算法,优点:速度快,缺点: ...

  3. 【转】android程序编译过程

    现在很多人想对Android工程的编译和打包进行自动化,比如建立每日构建系统.自动生成发布文件等等.这些都需要我们对Android工程的编译和打包有一个深入的理解,至少要知道它的每一步都做了什么,需要 ...

  4. 【英语】Bingo口语笔记(67) - turn系列

  5. 使用jsonp跨域请求

    一.异步对象,不能实现跨域请求 在站点A中访问站点B的数据: 站点A代码: window.onload = function () { document.getElementById("bt ...

  6. 看精通SQL SERVER2008有感1

    SQLserver数据库中的其他数据库作用: Master:存储SQLserver所有的全局配置,也就是存储SQLserver所知道的关于自己的全部信息,包括自身的配置,和当前的状态,这些数据存储在系 ...

  7. 用js+cookie实现商城的购物车功能

    页面上的添加功能主要就是两个按钮 <input name="buy" type="image" alt="第一个商品" src=&qu ...

  8. java Callable创建线程

    package com.java.concurrent; import java.util.concurrent.Callable; import java.util.concurrent.Execu ...

  9. GitLab管理之 - Gitlab 用户管理

    1. 移除用户 (1) 使用管理员登陆Gitlab服务器 (2) 点击管理区域 (3) 点击Users. (4)点击[Block User] 2. 添加用户(1)用root 管理员登陆.(2)点击[管 ...

  10. &lbrack;leetcode&rsqb;984&period; 不含 AAA 或 BBB 的字符串

    给定两个整数 A 和 B,返回任意字符串 S,要求满足: S 的长度为 A + B,且正好包含 A 个 'a' 字母与 B 个 'b' 字母: 子串 'aaa' 没有出现在 S 中: 子串 'bbb' ...