置换贴图 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_normal
float3 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 时,虽然都是纵向,但是方向相反。
置换贴图 Displacement Mapping的更多相关文章
- 【ZZ】 DShader之位移贴图(Displacement Mapping)
http://www.myexception.cn/other/1397638.html DShader之位移贴图(Displacement Mapping) www.MyException.Cn ...
- 【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) 上实现 视 ...
- Tessellation (曲面细分) Displacement Mapping (贴图置换)
DirectX 11 Tessellation (曲面细分)-什么是 Tessellation (曲面细分) ? 它为什么可以起到如此关键的数据? 随着近期人们对 DirectX 11 的议论纷纷,你 ...
- Displacement Mapping
[Displacement Mapping] Displacement Mapping(移位贴图). Normal maps的另一个应用是displacement mapping,在这个应用中,细节的 ...
- 翻译:非常详细易懂的法线贴图(Normal Mapping)
翻译:非常详细易懂的法线贴图(Normal Mapping) 本文翻译自: Shaders » Lesson 6: Normal Mapping 作者: Matt DesLauriers 译者: Fr ...
- 3DShader之法线贴图(normal mapping)
凹凸贴图(bump mapping)实现的技术有几种,normal mapping属于其中的一种,这里实现在物体的坐标系空间中实现的,国际惯例,上图先: 好了讲下原理 可以根据高度图生成法线量图,生成 ...
- 《The Cg Tutorial》阅读笔记——凹凸贴图 Bump Mapping
本文为大便一箩筐的原创内容,转载请注明出处,谢谢:http://www.cnblogs.com/dbylk/p/5018103.html 凹凸贴图 Bump Mapping 一.简介 凹凸贴图用于在不 ...
随机推荐
- SSH_框架整合7--整个项目CODE
一 架构 1Action类 2 配置文件 3 View页面 二 Code 1 src (1)com.atguigu.ssh.actions >EmployeeAction.java packa ...
- 基于.net开发chrome核心浏览器
本文转载自:http://www.cnblogs.com/liulun/archive/2013/04/20/3031502.html 一: 上一篇的链接: 基于.net开发chrome核心浏览器[一 ...
- [Freescale]E9学习笔记-LTIB总结
转自:http://blog.csdn.net/wl_haanel/article/details/6231353 写在前面 符号'##'后面语句均为注释 需要做的操作 ...
- [git/svn]Git和SVN差异
转自:http://blog.csdn.net/huacuilaifa/article/details/19124635 在参加百度的开源项目时接触到Git,后来又陆续在微博上看到很多宣扬Git为程序 ...
- Env:VIM配置
注:文章来自于http://www.cnblogs.com/ma6174/archive/2011/12/10/2283393.html 花了很长时间整理的,感觉用起来很方便,共享一下. 我的vim配 ...
- 【转】CSS浏览器兼容性与解析问题终极归纳
1.怪异模式问题:漏写DTD声明,Firefox仍然会按照标准模式来解析网页,但在IE中会触发怪异模式.为避免怪异模式给我们带来不必要的麻烦,最好养成书写DTD声明的好习惯. 2.IE6双边距问题:在 ...
- CSS实现背景透明,文字不透明(各浏览器兼容)
/*CSS*/.waps{ background:url(07158.bmp) no-repeat top center fixed; width:1004px; text-align:center; ...
- 4. 对list进行sort
一. sort命令 sort命令可以对list排序 sort命令把字段转先换为double类型在进行比较 sort排序list 127.0.0.1:6379> lrange list2 0 -1 ...
- Builder模式(建造者模式)
在设计模式中对Builder模式的定义是用于构建复杂对象的一种模式,所构建的对象往往需要多步初始化或赋值才能完成.那么,在实际的开发过程中,我们哪些地方适合用到Builder模式呢?其中使用Build ...
- C# 通过委托控制进度条以及多线程更新控件
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; usin ...





