【问题标题】:Whats the logic behind creating a normal map from a texture?从纹理创建法线贴图背后的逻辑是什么?
【发布时间】:2012-05-18 12:38:19
【问题描述】:

我在谷歌上看过,但我唯一能找到的是一个关于如何使用 Photoshop 创建一个的教程。不感兴趣!我需要它背后的逻辑。 (而且我不需要如何“使用”凹凸贴图的逻辑,我想知道如何“制作”一个凹凸贴图!)

我正在编写自己的 HLSL 着色器,并且已经意识到两个像素之间存在某种渐变,这将显示其正常 - 因此可以根据灯光的位置进行照明。

我想实时进行,这样当纹理发生变化时,凹凸贴图也会发生变化。

谢谢

【问题讨论】:

  • 我不确定这是否可能。您实际上想要的是创建 2D 纹理的 3D 模型(法线贴图)。

标签: c# xna textures hlsl


【解决方案1】:

我意识到我参加这个聚会已经很晚了,但我最近在尝试为 3ds max 编写我自己的法线贴图生成器时也遇到了同样的情况。 C# 有庞大且不必要的库,但没有任何基于数学的简单解决方案。

所以我运行了转换背后的数学:Sobel 算子。这就是您希望在着色器脚本中使用的内容。

以下类是关于我见过的 C# 最简单的实现。它完全完成了它应该做的事情,并完全实现了所期望的:基于高度图、纹理甚至是您提供的以编程方式生成的程序的法线贴图。

正如您在代码中看到的,我已经实现了 if / else 来减轻边缘检测宽度和高度限制引发的异常。

它的作用:对每个像素/相邻像素的 HSB 亮度进行采样以确定输出色调/饱和度值的比例,这些值随后转换为 RGB 以进行 SetPixel 操作。

顺便说一句:您可以实现一个输入控件来缩放输出色相/饱和度值的强度,以缩放输出法线贴图将为您的几何体/照明提供的后续影响。

就是这样。不再需要处理那个已弃用的小窗口 PhotoShop 插件。天空是极限。

C# winforms 实现截图(源码/输出):

C# 类从源图像实现基于 Sobel 的法线贴图:

using System.Drawing;
using System.Windows.Forms;

namespace heightmap.Class
{
    class Normal
    {
        public void calculate(Bitmap image, PictureBox pic_normal)
        {
            Bitmap image = (Bitmap) Bitmap.FromFile(@"yourpath/yourimage.jpg");
            #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 + 1; y++)
            {
                for (int x = 0; x < h + 1; 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; // set as PictureBox image
        }
    }
}

【讨论】:

    【解决方案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 内在函数来生成法线,而无需重新采样源纹理。显然,您会在预传递步骤中执行此操作,然后读取生成的法线贴图,然后将其用作后续阶段的输入。

    无论如何,这对我来说已经足够快了。

    【讨论】:

      【解决方案3】:

      简短的回答是:没有办法可靠地执行此操作以产生良好的结果,因为无法区分由于凹凸而导致颜色/亮度变化的漫反射纹理与发生变化的漫反射纹理在颜色/亮度方面,因为此时表面实际上是不同的颜色/亮度。

      更长的答案:

      如果您假设表面实际上是恒定的颜色,那么颜色或亮度的任何变化都必须是由于凹凸造成的阴影效果。根据实际表面颜色计算每个像素的亮/暗程度;较亮的值表示“朝向”光源的表面部分,而较暗的值表示“远离”光源的表面部分。如果您还指定了光的来源方向,您可以计算纹理上每个点的表面法线,从而得到您计算的着色值。

      这是基本理论。当然,在现实中,表面几乎从不恒定的颜色,这就是为什么这种纯粹使用漫反射纹理作为输入的方法往往效果不佳的原因。我不确定像 CrazyBump 这样的东西是如何做到的,但我认为他们正在做的事情是对图像的局部部分而不是整个纹理进行平均颜色。

      通常,法线贴图是从“投影”到分辨率较低的几何体上的实际 3D 表面模型创建的。毕竟,法线贴图只是一种伪造高分辨率几何图形的技术。

      【讨论】:

        【解决方案4】:

        快速回答:这是不可能的。
        一个简单的通用(漫反射)纹理根本不包含此信息。我还没有完全了解 Photoshop 是如何做到的(曾经被艺术家使用过),但我认为他们只是简单地执行了类似“depth=r+g+b+a”之类的操作,它基本上返回了一个高度图/渐变。然后使用简单的边缘检测效果将高度图转换为法线贴图,得到切线空间法线贴图。

        请记住,在大多数情况下,您使用法线贴图来模拟高分辨率 3D 几何网格,因为它会填充顶点法线留下的空白点。如果您的场景严重依赖照明,这是不行的,但如果它是一个简单的定向光,这“可能”工作。 当然,这只是我的经验,你也可以从事一个完全不同类型的项目。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2014-05-06
          • 2017-10-17
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多