Matcap的原理和应用

一、概念和原理

2.1 什么是Matcap

什么是Matcap?Matcap实际上是Material Capture的缩写,即材质捕捉。实际上,这是一种离线渲染方案。类似光照烘焙,将光照或者其它更复杂环境下的渲染数据存储到一张2D贴图上, 再从这张2D贴图进行采样进行实时渲染。

Materials (MatCap)这篇文章对Matcap的定义是:
MatCap (Material Capture) shaders are complete materials, including lighting and reflections. They work by defining a color for every vertex normal direction relative to the camera.

2.2 如何理解Matcap

Matcap是一种在视线空间下使用单位法线采样单位球的离线渲染算法。

  • 为什么是视线空间?因为视线空间下,相机变化就可以看到不同的渲染结果。
  • 为什么使用法线去采样了?法线是描述表面朝向的向量,与渲染结果强相关,法线跟物体的曲率强相关等,因此这种算法经常用于 sculpting上。

2.3 Matcap的特点

Matcap的特点总结如下:

  • 使用视线空间下的法线向量采样2D贴图,作为光照和反射结果。
  • 在缺乏光照烘焙的环境下,可以一定程度上替代或者模拟光图。
  • 但是,Matcap代表的2D贴图不局限于光照信息,也可以理解为某种环境下的最终渲染结果。
  • 由于是离线方案,因此计算非常廉价,很适合低端机器或者特定场合下使用。

二、如何实现Matcap

2.1 如何获得Matcap贴图

按照定义,matcap贴图是一张2D贴图,内部包含一个单位球,表示光照信息。如何获得这样的贴图了?

  • 从网上的材质库下载
    比如,matcaps
  • 引擎预览材质球然后截图。
    请添加图片描述

如上图,可以把右边的预览结果紧贴着球体进行截图。
当然,如果严格按照定义,Matcap表示的是光照信息,不是所有材质预览的结果都可以当作Matcap贴图。

2.2 如何采样Matcap贴图

// -------------------------------
// Vertex
// -------------------------------
VertexNormalInputs normalInput = GetVertexNormalInputs(input.normalOS, input.tangentOS);
output.normalWS = normalInput.normalWS;

// -------------------------------
// Fragment
// -------------------------------
float3 viewNormal = mul((float3x3)GetWorldToViewMatrix(), normalWS);
float2 matCapUV = viewNormal.xy * 0.5 + 0.5;
half3 matcapColor = SAMPLE_TEXTURE2D(_Matcap, sampler_Matcap, matCapUV).rgb;

从上述glsl代码可以看出,需要把法线转换到视线空间,然后再将法线偏移到[0,1]的范围内,然后取xy分量作为uv,对matcap纹理进行采样。

三、Matcap的问题

3.1 边缘瑕疵

有时候使用Matcap渲染,模型上会出现一条线或者缝隙。可能的原因是采样到了贴图的边缘部分,而有些matcap贴图制作上不太好,边缘区域过大。
请添加图片描述

如上图所示:左边的matcap贴图就是一个非常不规范的matcap贴图,球没有紧贴边缘,而是出现大量空白部分,导致兔子的边缘出现大量的灰色边缘。
解决方式有两种,一种是强制采样内部的像素;另一种方式是修改采样算法,使得更合理避免出现边缘区域。

3.2 单点采样

对于平面来说,其法线朝着同一个方向的,因此会出现整个平面获得的matcap颜色都是同一个像素点,与正常的光照结果相差很大。我们希望的是,即使是一个平面,不同的像素点也是有不同的光照结果。

3.3 解决办法

3.1.1 缩放uv

第一种方式是对matcapUV进行缩放,比如缩小uv可以使得避免采样边缘区域。

float2 matCapUV = viewNormal.xy * 0.5 * _MatcapUVScale + 0.5;

这种方式可以简单的解决边缘瑕疵问题,但是无法解决单点采样。

3.1.2 使用视线空间下单位球的法线

请添加图片描述

如上图所示,在Matcap的定义中,我们处于视线空间内,视线方向始终是(0,0,1)。我们最终要使用的是单位球的N方向。假设反射方向是R,可以计算得到N是(Rx,Ry,Rz+1)。那么问题转化为求反射向量R。我们可以用视线空间的顶点和法线求得视线空间下的R,然后用视线空间的R去代替单位球上的反射向量R即可,即使两个方向向量不能等价,也可以得到相应正确的结果。
这种算法可以显著优化平面的单点采样问题。

#if _MATCAP_FIX_EDGE_FLAW
	float3 r = reflect(input.positionVS, viewNormal);
	r = normalize(r);
	float m = 2.82842712474619 * sqrt(r.z + 1.0); 
	float2 matCapUV = r.xy / m * _MatcapUVScale + 0.5;
#else
	float2 matCapUV = viewNormal.xy * 0.5 * _MatcapUVScale + 0.5;
#endif

请添加图片描述

从上图可以看出,对于平面来说,两种算法的效果差异非常明显。

四、Matcap与其它效果的结合

下面的测试均以如下Matcap贴图为例。
请添加图片描述

4.1 基础颜色

如果把Matcap当作光照的结果,那么可以额外提供基础颜色来控制最终结果。比如,提供基础颜色贴图和基础颜色,乘以到matcap上作为最终输出。
请添加图片描述

4.2 法线贴图

既然matcap需要用到法线,那么可以额外提供法线贴图去修改像素的法线。
请添加图片描述
从上图可以看出,法线对最终的渲染结果影响显著。

4.3 自发光

类似正常的光照计算,可以在matcap的结果之上,再叠加自发光。

4.4 模拟高光

matcap本身已经是光照计算的结果,因此理论上贴图内带有了漫反射、高光、反射的信息。但是,通常情况下,matcap主要包括的还是漫反射信息,或者说表现不出明显的高光信息。
有一种简单模拟高光的方式,提供一个高光阈值,使用matcap减去该颜色阈值,然后除以1-阈值。最终结果再用原matcap颜色相乘避免过曝。
请添加图片描述

4.5 Cubemap反射

同时,可以额外利用cubemap计算静态反射结果叠加到最终着色上。
请添加图片描述

4.6 模拟边缘光

利用dot(normalWS, viewDirWS)计算出边缘光的强度,再将边缘光颜色与强度相乘叠加到最终着色结果上即可。

4.7 模拟折射

折射一种扭曲的效果,因此我们可以通过扭曲matcap的采样位置和反射的采样位置来模拟折射。同时,可以乘以边缘光的强度来模拟菲尼尔效应,也就是边缘光强的地方折射更强。然后,利用这个扭曲强度去偏移matcap的uv和反射向量,即可在一定程度上模拟折射的效果。
请添加图片描述
如上图所示,边缘的红色是边缘光;同时,噪声贴图作为折射扭曲强度贴图让边缘光看起来比较细碎,用来模拟折射效果。

4.8 光照强度

同时,也可以计算出真实的光照强度,将光照强度乘以matcap颜色,让matcap的着色结果受到灯光影响。不过,这跟matcap的初衷不太一致。

五、参考资料

Materials (MatCap)
https://github.com/nidorx/matcaps
MatCap Shader 改进:解决平面渲染和环境反射问题

  • 24
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
以下是一个使用Three.js中Matcap材质的基本示例代码: ```javascript // 创建场景 var scene = new THREE.Scene(); // 创建相机 var camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); camera.position.z = 5; // 创建渲染器 var renderer = new THREE.WebGLRenderer(); renderer.setSize(window.innerWidth, window.innerHeight); document.body.appendChild(renderer.domElement); // 创建Matcap材质 var textureLoader = new THREE.TextureLoader(); var matcapTexture = textureLoader.load('path/to/matcap_texture.jpg'); var matcapMaterial = new THREE.MeshMatcapMaterial({matcap: matcapTexture}); // 创建立方体 var geometry = new THREE.BoxGeometry(1, 1, 1); var cube = new THREE.Mesh(geometry, matcapMaterial); scene.add(cube); // 渲染循环 function render() { requestAnimationFrame(render); cube.rotation.x += 0.01; cube.rotation.y += 0.01; renderer.render(scene, camera); } render(); ``` 在上面的代码中,首先我们创建了一个场景、相机和渲染器。然后,我们使用THREE.TextureLoader加载Matcap纹理,并使用THREE.MeshMatcapMaterial创建了一个Matcap材质。接着,我们创建了一个立方体,使用Matcap材质作为其材质,并将其添加到场景中。最后,我们创建了一个渲染循环,用于每帧更新立方体的旋转并渲染场景。 请注意,Matcap材质并不支持光照,因为它使用纹理来模拟光照效果。此外,Matcap材质通常用于创建具有卡通风格或艺术风格的渲染效果。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值