我看过google,但我唯一能找到的就是如何使用photoshop创建一个教程。不感兴趣!我需要它背后的逻辑。 (我不需要如何'使用'凹凸贴图的逻辑,我想知道如何'制作'一个!)
我正在编写自己的HLSL着色器,并且已经意识到两个像素之间存在某种渐变,这将显示其正常 - 因此光的位置可以随意点亮。
我想实时做这个,这样当纹理改变时,凹凸贴图也会这样做。
由于
答案 0 :(得分:2)
用于读取高度或深度图的采样器。
/// same data as HeightMap, but in a format that the pixel shader can read
/// the pixel shader dynamically generates the surface normals from this.
extern Texture2D HeightMap;
sampler2D HeightSampler = sampler_state
{
Texture=(HeightMap);
AddressU=CLAMP;
AddressV=CLAMP;
Filter=LINEAR;
};
请注意,我的输入贴图是512x512单组件灰度纹理。从中计算法线非常简单:
#define HALF2 ((float2)0.5)
#define GET_HEIGHT(heightSampler,texCoord) (tex2D(heightSampler,texCoord+HALF2))
///calculate a normal for the given location from the height map
/// basically, this calculates the X- and Z- surface derivatives and returns their
/// cross product. Note that this assumes the heightmap is a 512 pixel square for no particular
/// reason other than that my test map is 512x512.
float3 GetNormal(sampler2D heightSampler, float2 texCoord)
{
/// normalized size of one texel. this would be 1/1024.0 if using 1024x1024 bitmap.
float texelSize=1/512.0;
float n = GET_HEIGHT(heightSampler,texCoord+float2(0,-texelSize));
float s = GET_HEIGHT(heightSampler,texCoord+float2(0,texelSize));
float e = GET_HEIGHT(heightSampler,texCoord+float2(-texelSize,0));
float w = GET_HEIGHT(heightSampler,texCoord+float2(texelSize,0));
float3 ew = normalize(float3(2*texelSize,e-w,0));
float3 ns = normalize(float3(0,s-n,2*texelSize));
float3 result = cross(ew,ns);
return result;
}
和一个像素着色器来调用它:
#define LIGHT_POSITION (float3(0,2,0))
float4 SolidPS(float3 worldPosition : NORMAL0, float2 texCoord : TEXCOORD0) : COLOR0
{
/// calculate a normal from the height map
float3 normal = GetNormal(HeightSampler,texCoord);
/// return it as a color. (Since the normal components can range from -1 to +1, this
/// will probably return a lot of "black" pixels if rendered as-is to screen.
return float3(normal,1);
}
LIGHT_POSITION
可以(并且可能应该)从您的主机代码输入,虽然我在这里作弊并使用常量。
请注意,此方法每个法线需要4次纹理查找,而不是计算一次以获取颜色。这对你来说可能不是问题(取决于你正在做的其他事情)。如果这对性能影响太大,您可以在纹理更改时调用它,渲染到目标,然后将结果捕获为法线贴图。
另一种方法是将带有高度图的屏幕对齐四边形绘制到渲染目标,并使用ddx
/ ddy
HLSL内在函数生成法线,而无需重新采样源纹理。显然,你会在预通过步骤中执行此操作,将生成的法线贴图读回,然后将其用作后期阶段的输入。
无论如何,这对我来说已经足够快了。
答案 1 :(得分:0)
快速回答:这是不可能的。
简单的通用(漫反射)纹理根本不包含此信息。我没有看到Photoshop究竟是如何做到的(看过它曾经被艺术家使用过),但我认为它们只是做了类似'depth = r + g + b + a'的东西,它基本上返回了一个高度图/渐变。然后使用简单的边缘检测效果将高度图转换为法线贴图,以获得切线空间法线贴图。
请记住,在大多数情况下,您使用法线贴图来模拟高分辨率3D几何网格,因为它填充了空白点顶点法线留下。如果您的场景严重依赖于照明,这是不可取的,但如果它是一个简单的定向灯,这可能会起作用。 当然,这只是我的经验,你也可能正在开发一个完全不同类型的项目。
答案 2 :(得分:0)
简短的回答是:没有办法可靠地做到这一点会产生良好的效果,因为没有办法区分由于凹凸而导致颜色/亮度发生变化的漫反射纹理与具有变化的漫反射纹理之间的区别在颜色/亮度上,因为表面实际上是不同的颜色/亮度。
更长的答案:
如果你假设表面实际上是一个恒定的颜色,那么颜色或亮度的任何变化都必须归因于凹凸造成的阴影效果。计算每个像素与实际表面颜色相比有多亮/暗;较亮的值表示面向“朝向”光源的表面部分,而较暗的值表示表面“远离”光源的部分。如果还指定了光源的方向,则可以计算纹理上每个点的曲面法线,使其产生您计算的着色值。
这是基本理论。当然,实际上,表面几乎永远不会是恒定的颜色,这就是为什么这种纯粹使用漫反射纹理作为输入的方法往往不能很好地工作。我不确定像CrazyBump这样的东西是怎么做的,但我认为他们正在做的事情就是在图像的局部部分而不是整个纹理上平均颜色。
通常,法线贴图是从表面的实际3D模型创建的,这些模型被“投影”到较低分辨率的几何体上。毕竟,法线贴图只是伪造高分辨率几何体的一种技术。
答案 3 :(得分:0)
我意识到我要晚一点参加这次聚会,但是最近我也遇到了同样的情况,试图为3ds max编写自己的法线贴图生成器。 C#有大量且不必要的库,但没有一个简单的基于数学的解决方案。
因此,我进行了转换背后的数学运算:Sobel运算符。这就是您要在着色器脚本中使用的内容。
以下类是关于C#的最简单实现。它完全可以完成预期的工作,并且可以完全实现所需的结果:基于高度图,纹理甚至您提供的程序生成的过程的法线贴图。
您可以在代码中看到,我实现了if / else来减轻边缘检测宽度和高度限制引发的异常。
作用:对每个像素/相邻像素的HSB亮度进行采样,以确定输出色相/饱和度值的比例,然后将其转换为RGB以进行SetPixel操作。
顺便说一句:您可以实现输入控件来缩放输出色相/饱和度值的强度,以缩放输出法线贴图将为您的几何图形/照明提供的后续影响。
就是这样。无需再处理那些已淘汰的,窗口很小的PhotoShop插件。天空是极限。
using System.Drawing;
using System.Windows.Forms;
namespace heightmap.Class
{
class Normal
{
public void calculate(Bitmap image, PictureBox pic_normal)
{
#region Global Variables
int w = image.Width - 1;
int h = image.Height - 1;
float sample_l;
float sample_r;
float sample_u;
float sample_d;
float x_vector;
float y_vector;
Bitmap normal = new Bitmap(image.Width, image.Height);
#endregion
for (int y = 0; y < w; y++)
{
for (int x = 0; x < h; x++)
{
if (x > 0) { sample_l = image.GetPixel(x - 1, y).GetBrightness(); }
else { sample_l = image.GetPixel(x, y).GetBrightness(); }
if (x < w) { sample_r = image.GetPixel(x + 1, y).GetBrightness(); }
else { sample_r = image.GetPixel(x, y).GetBrightness(); }
if (y > 1) { sample_u = image.GetPixel(x, y - 1).GetBrightness(); }
else { sample_u = image.GetPixel(x, y).GetBrightness(); }
if (y < h) { sample_d = image.GetPixel(x, y + 1).GetBrightness(); }
else { sample_d = image.GetPixel(x, y).GetBrightness(); }
x_vector = (((sample_l - sample_r) + 1) * .5f) * 255;
y_vector = (((sample_u - sample_d) + 1) * .5f) * 255;
Color col = Color.FromArgb(255, (int)x_vector, (int)y_vector, 255);
normal.SetPixel(x, y, col);
}
}
pic_normal.Image = normal;
}
}
}