非常详细易懂的法线贴图(NormalMapping)

⾮常详细易懂的法线贴图(NormalMapping)
翻译:⾮常详细易懂的法线贴图(Normal Mapping)
本⽂翻译⾃:
作者: Matt DesLauriers
译者: FreeBlues
这⼀系列依赖于最⼩规模的⽤于着⾊器和渲染⼯具的 API. 代码已经被移植到 . 这些概念是⾜够通⽤的, 它们能被应⽤于, , iOS, 或者其他⽀持GLSL的平台.
概述
本⽂聚焦于3D光照和法线贴图技术, 以及我们如何把它们应⽤到2D游戏中, ⽰范下图所⽰, 左边是纹理贴图, 右边实时应⽤了光照:
⼀旦你理解了光照的概念, 把它应⽤于任何设置都是⾮常直截了当的. 这⾥是⼀个Java4K⽰例中的法线贴
图的例⼦, 例如, 通过软件渲染:
效果跟这个和这个中展⽰的⼀样, 你还可以在看到效果, 其中包括⼀个可执⾏的⽰例.
介绍向量和法线
正如我们在之前的教程中讨论过的, ⼀个GLSL向量是⼀个浮点数的容器, 通常保存诸如位置(x,y,z)之类的值. 在数学中,向量意味着相当多的内容,以及⽤于表⽰长度(即⼤⼩)和⽅向. 如果你对向量很陌⽣并且想要学习关于它们更多⼀些的知识, 查看下⾯这些链接:
为了计算光照, 我们需要使⽤⽹格的"法线". ⼀个表⾯法线是⼀个垂直于切线平⾯的向量. 简单来说, 它是⼀个向量, 垂直于给定顶点处的⽹格.下⾯我们会看到⼀个⽹格, 每个顶点都有⼀条法线.
每个向量都指向外⾯, 遵循着⽹格的弯曲形状. 下⾯是另⼀个例⼦, 这次是⼀个简单的2D边沿视图:
法线贴图(Normal Mapping)是⼀个游戏编程技巧, 它允许我们渲染相同数⽬的多边形(例如低解析度的⽹格模型), 但是在计算光照时使⽤⾼解析度⽹格模型的法线. 这为我们带来更好的感受, 关于深度, 真实性和光滑度.
(图像来⾃于这个出⾊的博客⽂章)
⾼⾯数⽹格模型或者说精雕模型的法线被编码到⼀个纹理贴图(即法线图)中, 当我们渲染低⾯数⽹格模型时会从⽚段着⾊器中对它进⾏取样.结果如下:
译者注: 左侧是4百万个三⾓形的⾼模, 中间是500个三⾓形的低模, 右侧是在500个三⾓形的低模上使⽤法线贴图后的效果
对法线编码和解码
我们的表⾯法线是单位向量, 通常位于范围-1.0到1.0之间. 我们可以通过把法线范围转换为0.0到1.0之间来把法线向量(x, y, z)存储到⼀
个RGB纹理贴图中. 下⾯是伪码:
例如, ⼀个法线(-1, 0, 1)会被作为RGB编码为(0, 0.5, 1). x轴(左/右)被保存到红⾊通道, y轴(上/下)被保存到绿⾊通道, z轴(前/后)被保存到蓝⾊通道. 最终的法线图(normal map)看起来就是下⾯这个样⼦:
典型地, 我们使⽤程序来⽣成法线图, ⽽不是⼿动绘制.
理解法线图, 把每个通道独⽴出来查看会更清楚:
看着,绿⾊通道,我们看到更亮的部分(值更接近于1.0) 定义了法线指向上⽅的区域,⽽更暗的区域(值更接近为0.0) 定义了法线指向下⽅的区域. ⼤多数的法线图会是蓝⾊,因为Z轴(蓝⾊通道)通常指向我们(即值为1.0).
在我们游戏的⽚段着⾊器中, 我们可以把法线解码, 通过执⾏跟之前编码时相反的操作, 把颜⾊值展开为范围-1.0到1.0之间:
//sample the normal map
NormalMap = texture2D(NormalMapTex, TexCoord);
//convert to range -1.0 to 1.0
< = b * 2.0 - 1.0;
注意: 要记住不同的引擎和软件会使⽤不同的坐标系, 绿⾊通道可能需要翻转.
Lambertian 光照模型
在计算机图形学中, 我们有⼤量的算法,可以结合起来打造3D对象的不同渲染效果. 在这篇⽂章我们将专注于Lambert着⾊,没有任何反射(诸如"光泽"或"发光"). 其他的技术,像Phong, Cook-Torrance, 和Oren–Nayar, 可以⽤来产⽣不同的视觉效果(粗糙表⾯、有光泽的表⾯等等)。
我们整个光照模型看起来像这样:
N = )
L = )
Diffuse = LightColor * max(dot(N, L), 0.0)丙醇二酸
Ambient = AmbientColor * AmbientIntensity
Attenuation = 1.0 / (ConstantAtt + (LinearAtt * Distance) + (QuadraticAtt * Distance * Distance))
Intensity = Ambient + Diffuse * Attenuation
FinalColor = b * b
说实话,你不需要从数学⾓度理解为什么这个可以起作⽤,但如果你有兴趣, 可以阅读更多有关"N dot L"的内容, 在这⾥和这⾥.
⼀些关键的术语:
Normal-法线: 从法线图中解码得到的法线向量 XYZ.
LightDir-光线⽅向: 从物体表⾯到光源位置的向量, 我们将会简单解释.
Diffuse Color-漫射颜⾊: 纹理贴图的 RGB 颜⾊, 没有光.
Diffuse-漫射: 跟Lambertian反射相乘的光线颜⾊, 这是我们光照等式的主要部分.
Ambient-环境光: 处于阴影中的颜⾊和强度, 例如, ⼀个户外场景会有⼀个更亮的环境光强度, ⽐起⼀个暗淡灯光下的户内场景.
Attenuation-衰减: 这是光线的随距离⽽降低, 例如, 当我们远离点光源时强度/亮度的损失. 有多种⽅法来计算衰减--对于我们的⽬标⽽⾔, 我们将会使⽤. 这⾥⽤3个系数来计算衰减, 我们可以改变它们来影响光线衰减的视觉效果.
Intensity-强度: 我们阴影算法的强度--离1.0越近意味着有光, 离0.0越近意味着没有光.
下⾯的图有助于你对我们的光照模型有个直观的理解:
正如你所见, 感觉它是相当模块化的, 我们可以拿⾛那些不需要的部分, 就像衰减(attenuation) 或光线颜⾊(light colors).
现在, 让我们把它们应⽤到GLSL模型上. 注意我们只处理2D, 在3D中还有⼀些在这篇教程没有覆盖到(译者注:就是空间变换, 在3D场景下, 法线图中的法线所在的空间为正切空间, 光线所在的空间为世界空间, 需要统⼀到同⼀个空间计算才有意义). 我们将把模型分解为多个单独部分,每⼀个都建⽴在下⾯的基础上.
Java 例程
你可以在这⾥看到. 它是相对直截了当的, 并不会介绍过多的在在前⾯的课程中还没有讨论过的内容. 我们将使⽤以下两种纹理贴图︰
我们的⽰例根据⿏标位置(归⼀化到分辨率)调整, 根据⿏标滚轮(点击则重置光线的Z值)调整LightPos.z(深度). 在特定的坐标系中, 就像LibGDX, 你可能需要翻转Y值.
注意, 我们的例⼦使⽤了如下这些常量, 你可以调整它们来获得不同的视觉效果:
public static final float DEFAULT_LIGHT_Z = 0.075f;
...
//Light RGB and intensity (alpha)
public static final Vector4f LIGHT_COLOR = new Vector4f(1f, 0.8f, 0.6f, 1f);
药事管理
//Ambient RGB and intensity (alpha)
public static final Vector4f AMBIENT_COLOR = new Vector4f(0.6f, 0.6f, 1f, 0.2f);
//Attenuation coefficients for light falloff
public static final Vector3f FALLOFF = new Vector3f(.4f, 3f, 20f);
下⾯是我们的渲染代码, 就像⼀样, 我们会在渲染时使⽤多重纹理:
...
//update light position, normalized to screen resolution float x = X() / (Width();
float y = Y() / (Height();拉伸模量
LIGHT_POS.x = x;
LIGHT_POS.y = y;
//send a Vector4f to GLSL
shader.setUniformf("LightPos", LIGHT_POS);
//bind normal map to texture unit 1
glActiveTexture(GL_TEXTURE1);
rockNormals.bind();
//bind diffuse color to texture unit 0
glActiveTexture(GL_TEXTURE0);
rock.bind();
/
/draw the texture unit 0 with our shader effect applied batch.draw(rock, 50, 50);
阴影贴图的结果:
下⾯对光线使⽤了更低的Z值:黄埔船厂
⽚段着⾊器
这⾥是我们完整的⽚段着⾊器
//attributes from vertex shader
varying vec4 vColor;
varying vec2 vTexCoord;
//our texture samplers
uniform sampler2D u_texture;  //diffuse map
uniform sampler2D u_normals;  //normal map
//values used for
uniform vec2 Resolution;      //resolution of screen
2012年诺贝尔奖获得者uniform vec3 LightPos;        //light position, normalized
uniform vec4 LightColor;      //light RGBA -- alpha is intensity
uniform vec4 AmbientColor;    //ambient RGBA -- alpha is intensity
uniform vec3 Falloff;        //attenuation coefficients
void main() {
//RGBA of our diffuse color
vec4 DiffuseColor = texture2D(u_texture, vTexCoord);
//RGB of our normal map
vec3 NormalMap = texture2D(u_normals, vTexCoord).rgb;
//The delta position of light
vec3 LightDir = - ( / ), LightPos.z);
//Correct for aspect ratio
LightDir.x *= Resolution.x / Resolution.y;
//Determine distance (used for attenuation) BEFORE we normalize our LightDir    float D = length(LightDir);
NORWAL
//normalize our vectors
vec3 N = normalize(NormalMap * 2.0 - 1.0);
vec3 L = normalize(LightDir);
//Pre-multiply light color with intensity
//Then perform "N dot L" to determine our diffuse term
vec3 Diffuse = (b * LightColor.a) * max(dot(N, L), 0.0);
//pre-multiply ambient color with intensity
vec3 Ambient = b * AmbientColor.a;
//calculate attenuation
float Attenuation = 1.0 / ( Falloff.x + (Falloff.y*D) + (Falloff.z*D*D) );
//the calculation which brings it all together
vec3 Intensity = Ambient + Diffuse * Attenuation;
vec3 FinalColor = b * Intensity;
gl_FragColor = vColor * vec4(FinalColor, DiffuseColor.a);
}
GLSL 分解
现在, 把它分解. ⾸先, 我们从两个纹理贴图中取样:
//RGBA of our diffuse color
vec4 DiffuseColor = texture2D(u_texture, vTexCoord);
//RGB of our normal map
vec3 NormalMap = texture2D(u_normals, vTexCoord).rgb;

本文发布于:2024-09-20 13:23:38,感谢您对本站的认可!

本文链接:https://www.17tex.com/xueshu/705068.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

上一篇:normally用法
标签:法线   模型   贴图   光照   向量   空间   光线
留言与评论(共有 0 条评论)
   
验证码:
Copyright ©2019-2024 Comsenz Inc.Powered by © 易纺专利技术学习网 豫ICP备2022007602号 豫公网安备41160202000603 站长QQ:729038198 关于我们 投诉建议