置换贴图 Displacement Mapping
视差贴图和法线贴图都是使用特定的手段来达到欺骗视觉的目的,让人以为物体的表面是凹凸起伏的。而置换贴图却是真的将模型的顶点进行偏移,在原本的平面上创造出凹凸的效果。既然是对顶点进行偏移,那么就需要模型有足够多的顶点数量,否则达不到比较好的效果。为了达到足以置换的顶点数量,一般会使用 Tessellation 技术来增加低模的面数。查阅了相关资料,Tessellation 是 DirectX11 才有的技术,OpenGL 要到 4.0 才能使用。对于 Tessellation 知道的并不多,再加上移动平台根本无法使用,就没有去看这部分的内容,这里也不会涉及到。其实目前来看 Tessellation 技术并不是非用不可,很多情况下还是光照和贴图起到更大的作用。
看下要实现的效果,是一个转动的地球。地球的表面并不是平坦的,每一块陆地都是有凸起的,并且可以看到凸起的陆地上山脉和沟壑。球模型本身是 1200 面。
置换前效果 | 置换后效果 | 置换后效果 |
---|---|---|
![]() |
![]() |
![]() |
首先是在顶点阶段采样高度纹理,将采样到的颜色作为高度数据偏移顶点坐标(在顶点阶段采样纹理是sm3.0才支持的,我记得看到过什么资料上说 sm3.0 还是 2.0 是 DirectX 的叫法,和 OpenGL 没关系,不知道对不对。编译选项需要加上 #pragma glsl,让编译器把 cg 代码直接编译成 glsl,而不是中间代码,否则会报错)。可以看到如下效果。
// vertex shader
o.wnormal = mul (i.normal, (float3x3)_World2Object);
float dispValue = tex2D (_DisplacementMap, i.texcoord.xy).r * _DisplacementAmount;
o.wpos = mul(_Object2World, i.vertex);
o.wpos.xyz += o.wnormal * dispValue;
o.pos = mul(UNITY_MATRIX_VP, o.wpos);
地形表面已经有了凸起效果了。由于这里没有任何的光照计算,所以整体效果特别的平,没有体积感。到此为止还是很简单的,下面就是要加入光照计算。光照计算最重要的就是法线了,使用法线和光线进行计算来实现明暗效果,表现出物体的体积感。但是现在的问题是,由于模型表面已经不是平坦的了,而是凹凸不平的,如果还使用原来的法线信息肯定会得到错误的效果。
模型顶点的法线 | 置换顶点后错误的法线 | 置换顶点后正确的法线 |
---|---|---|
![]() |
![]() |
![]() |
只要能够计算出如右图所示的法线,再计算光照就不是什么难事了。我特意查看了下 Unity 文档中关于 Displacement Mapping 的代码,并在 Unity 编辑器中测试了下,光照效果是不正确的,因为当偏移顶点坐标的时候,并没有对法线进行调整。
从图中可以看到,左边的凸起是模型做出来的(可以看到网格),右边凸起是使用置换贴图实现的(看不到网格,使用的 Unity 文档中的代码实现)。问题就在于右边的光照是错误的,凸起的背光面并没有变暗,这正是因为偏移了顶点坐标后没有重新计算法线造成的。至于如何能够比较精确的计算出新的法线可以看这篇文章,作者对置换贴图进行了多次采样,通过计算高度差的方式实现的,感觉类似于 Unity 编辑器将灰度图转换成法线图的功能。然而我们这里使用另一种近似计算的方式,这种方式得到的结果并不是很精确,在模型边缘可能会出现瑕疵,但是计算量要小得多。我在设备上测试过,是可以得到比较好的效果。如果使用置换贴图后,光照效果的错误是可以忽略的,就不用调整法线了(和 Unity 一样,并且我看到很多类似的资料里也都没有讲到调整法线)。
下面就来说说如何来近似模拟计算调整后的法线。这里使用到的两个 cg 函数是 ddx 和 ddy。首先要说明的是 ddx 和 ddy 是在屏幕空间中的两个函数。如果写了这样的代码 ddx(x)
,得到的结果就是下一个像素(一般当前像素的右边一个像素)的 ddx(x)
括号中的 x
的值,减去当前像素 ddx(x)
括号中的 x
的值。也就是两个像素的 x
值相差多少。ddy同理,只是描述的是纵向,而不是横向。在旋转的地球这个例子中,如果下一个像素并不是地球模型,而是其他模型,那就可能出现上文所说的边缘瑕疵,如果你不是太在意,一般不会注意到。之所以这两个函数能够做到这点,是因为 GPU 中 fragment 是并行的。原理说明白了就看下代码。
float4 frag(v2f i)
{// 水平方向uv的增量float2 uv_dx = ddx(i.uv.xy);// 垂直方向uv的增量float2 uv_dy = ddy(i.uv.xy);// 当前置换距离float height = tex2D(_DisplacementMap, i.uv.xy).r;// 水平方向下一个像素点的置换距离float height_h = tex2D(_DisplacementMap, i.uv.xy + uv_dx).r;// 垂直方向下一个像素点的置换距离float height_v = tex2D(_DisplacementMap, i.uv.xy + uv_dy).r;// 水平方向置换增量float t_h = (height_h - height) * _DisplacementScale;// 垂直方向置换增量float t_v = (height_v - height) * _DisplacementScale;// 水平方向顶点坐标增量,作为一个假的tangent来使用float3 fake_tangent = ddx(i.wpos);// 水平方向顶点坐标增量,作为一个假的bintangent来使用float3 fake_bintangent = ddx(i.wpos);// 到这里为止,其实已经可以用 fake_tangent 差乘 fake_bintangent 来得到 fake_normal 了// 但是会发现 fake_normal 并不平滑,用来计算光照会出现硬边// 解决办法是使用从 vertex shader 传过来的 normal 来进行纠正,顶点上的 normal 是平滑的// corss 部分就是用平滑的 normal 来重新计算新的 fake_tangent// 后面加法是使用置换增量来对其进行扰动float3 fake_tangent_new = cross(fake_bintangent, i.wnormal) + i.wnormal * t_h;// 同理float3 fake_bintangent_new = cross(i.wnormal, fake_tangent) + i.wnormal * t_v;// 最终得到平滑的 fake_normalfloat3 fake_normal = cross(fake_bintangent_new, fake_tangent_new);fake_normal = normalize(fake_normal);// 这里就可以使用这个 fake_normal 参与光照计算了
}
至此,所有的要点都说明了。最后我又在设备上进行了测试,发现当图形接口是 OpenglES 3.0 的时候显示效果是正确的,但是 Metal 就不正确了。最终发现 Metal 中 ddy 和 OpenglES 3.0 中的 ddy 返回值不同,第一反应是 Metal 在实现上应该不会出问题,因为两个图形接口在 ddx 的返回结果是一致的。我把最后一步求平滑的 fake_normal 的差乘两边交换了一下,变成 float3 fake_normal = cross(fake_tangent_new, fake_bintangent_new)
,于是效果就正确了。这说明两个图形接口在处理 ddy 时,虽然都是纵向,但是方向相反。
goto blog
转载于:https://www.cnblogs.com/jim-game-dev/p/5476336.html
置换贴图 Displacement Mapping相关推荐
- 置换贴图(Displacement map),凹凸贴图(Bump map)与法线贴图(Normal map)的区别
英文原文地址<Difference between Displacement , Bump and Normap Maps> By Pluralsight on August 14, 20 ...
- 【ZZ】 移位贴图 Displacement Mapping
http://blog.csdn.net/huazai434/article/details/5650629 说明:该技术需要VS3.0的支持!!! 一,移位贴图类似于地形渲染.不过由于移位纹理可以做 ...
- 3DShader之移位贴图(Displacement Mapping)
我们知道法线贴图是只是改了物体的法线属性,用来计算光照,但是并没有改变物体本身的网格.但是移位贴图就不一样了,它会移动物体的顶点.我用移位贴图做了个海洋,好了,上了图再讲: 注意看海的边缘的顶点,已经 ...
- 在 OpenGL ES 2.0 上实现视差贴图(Parallax Mapping)
在 OpenGL ES 2.0 上实现视差贴图(Parallax Mapping) 视差贴图 最近一直在研究如何在我的 iPad 2(只支持 OpenGL ES 2.0, 不支持 3.0) 上实现 视 ...
- 【3D建模制作技巧分享】Zbrush中凹凸贴图、法线贴图和置换贴图的区别
我们现在的银幕早就充斥着电脑生成的虚拟物体,这些虚拟物体是电脑中生成的物体,但这些物体比较现实中的物体没有那么多细致的表面.因而需要用平面映射的方式加入更多的细节.这些用做映射的图片被称作贴图.贴图有 ...
- 教学|Zbrush中凹凸贴图、法线贴图和置换贴图的区别
我们现在的银幕早就充斥着电脑生成的虚拟物体,这些虚拟物体是电脑中生成的物体,但这些物体比较现实中的物体没有那么多细致的表面.因而需要用平面映射的方式加入更多的细节.这些用做映射的图片被称作贴图.贴图有 ...
- 「建模学习」Zbrush中凹凸贴图、法线贴图和置换贴图的区别
ZBrush中的三种修正虚拟三维物体表面形状的贴图分别是bump,normal和displacement,就是我们常说的凹凸贴图.法线贴图和置换贴图.这三个术语在现在的电影工业技术中也是很重要的概念. ...
- OpenGL 视差贴图 Parallax Mapping
OpenGL 视差贴图 Parallax Mapping 视差贴图 Parallax Mapping简介 视差贴图 陡峭视差映射 视差遮蔽映射 视差贴图 Parallax Mapping简介 视差贴图 ...
- 灰度图,法线贴图,置换贴图和位移贴图
查看全文 http://www.taodudu.cc/news/show-5684567.html 相关文章: OC置换可以混合.相乘.相加吗?多个置换贴图叠加 unity-shader-曲面细分与置 ...
最新文章
- 系统检测到您正在使用网页抓取工具_【安全】58反抓取简介
- 本地日志数据实时接入到hadoop集群的数据接入方案
- 获取不到时间_一月自考成绩查询时间一出,看了文章这次稳过
- Primefaces,Spring 4 with JPA(Hibernate 4 / EclipseLink)示例教程
- php和c语言那个竞争大,【后端开发】php和c语言哪个难
- manjaro配置zsh和oh-my-zsh
- 笔记本cpu排名_2020年双十一哪一款笔记本电脑值得买?高性价比笔记本电脑推荐(10月更新)...
- 【工大笔记】ACL20 如何使用选择机制提升自注意力网路能力?
- php在html中if,html里的if注释怎么使用
- CPC客户端报错 error
- Debian上如何打开关闭端口
- zuc算法代码详解_ZUC祖冲之序列密码算法
- Kmeans聚类算法应用实战
- 交换机ftp将文件传到服务器,如何用FTP实现交换机间配置文件复制?
- ffmpeg:制作gif / 提取视频帧为图片
- 计算机网络概念基础——分组交换
- Win10多用户远程桌面软件RDP Wrapper Library下载安装教程和解决Win10 1809(OS build17763)not supported问题
- 技术前沿与经典文章8:智能家居发展白皮书——全屋智能解决方案
- 支持向量机_6:Duality
- TF:jupyter notebook中plt.grid设置后不显示网格以及“TypeError: ‘bool‘ object is not callable”错误
热门文章
- ADG主备延迟时间的查询方法
- LIMS实验室管理系统的应用领域
- IntelliJ IDEA 下载安装及配置使用超详细图文教程
- php 数组转为字符,php 数组字符串如何转换为字符串
- 百度NLP面试记录(实习offered)
- 怎么样看待上海车展?
- 《Mastering the game of Go with deep neural networks and tree search》
- Unable to determine the device handle for GPU 0000:02:00.0: GPU is lost. Reboot the sys
- pytorch visualizer 深度神经网络可视化工具
- 我的世界java版怎么进不去存档_Java版进不去存档 #Bugjump#