Unity shader - 如何根据纹理贴图生成法线贴图

Posted

技术标签:

【中文标题】Unity shader - 如何根据纹理贴图生成法线贴图【英文标题】:Unity shader - How to generate normal map based on texture map 【发布时间】:2021-12-02 16:02:33 【问题描述】:

我目前被困在我正在编写的着色器中。 我正在尝试创建一个雨水着色器。 我设置了 3 个模拟下雨的粒子系统,以及一个 相机来看看这个模拟。相机视图是我 用作纹理。在我的着色器中,我现在正在尝试制作法线贴图 从那个纹理贴图,但我不知道该怎么做。

Shader "Unlit/Rain"

    Properties
    
        _MainTex("Albedo (RGB)", 2D) = "white" 
        _Color("Color", Color) = (1,1,1,1)
        _NormalIntensity("NormalIntensity",Float) = 1
      
    
        SubShader
        
            
            Tags  "RenderType" = "Opaque" 
            LOD 100

            Pass
            
                CGPROGRAM
                #pragma vertex vert
                #pragma fragment frag

                #include "UnityCG.cginc"
                #include "Lighting.cginc"
                #include "AutoLight.cginc"

                struct VertexInput 
                    float4 vertex : POSITION;
                    float2 uv : TEXCOORD0;
                    float4 normal : NORMAL;
                    float3 tangent : TANGENT;

                ;

                struct VertexOutput 
                    float2 uv : TEXCOORD0;
                    float4 vertex : SV_POSITION;
                    float2 uv1 : TEXCOORD1;
                    float4 normals : NORMAL;

                    float3 tangentSpaceLight: TANGENT;
                   
                ;

                sampler2D _MainTex;
                float4 _MainTex_ST;
                half4 _Color;
                float _NormalIntensity;

                VertexOutput vert(VertexInput v) 

                    VertexOutput o;
                    o.normals = v.normal;
                    o.uv1 = v.uv;
                    o.vertex = UnityObjectToClipPos( v.vertex );
                   // o.uv = TRANSFORM_TEX( v.uv, _MainTex ); // used for texture

                    return o;
                



                float4 frag(VertexOutput i) : COLOR

                    float4 col2 = tex2D(_MainTex, i.uv1); 
                    return col2 * i.normals * 5;
                
                ENDCG
            
        

This is what the camera sees. I set the TargetTexture for this camera to be a texture I created.

In my shader I then put that texture as an albedo property

所以我现在想做的是找到该纹理的法线来创建凹凸贴图。

【问题讨论】:

“基于”纹理贴图是什么意思?如果纹理贴图是一个渲染,那和法线有什么关系? @Ruzihm 我在下面发布了一个答案:) 下面的答案应该是这篇文章的答案,下次请edit the question 包括澄清信息。 :) 好吧,对不起。我是新手 :) 你有这个问题的答案吗? :) 你可以试试interpreting it as a height map。没有像高度图这样的东西,没有真正令人满意的方法来根据它的外观来猜测它的形状。特别是以适合着色器的快速方式。如果着色器位于粒子系统本身而不是渲染纹理上,则可以让粒子系统创建着色器可以使用的法线贴图,因为粒子系统知道粒子速度、液滴大小等信息。这可能会更容易. 【参考方案1】:

看起来你的“TargetTexture”正在给你一个高度图。这是我发现的关于how to turn a height map into a normal map 的帖子。我已经将您最初拥有的代码与该论坛帖子的核心代码混合在一起,并将法线输出为颜色,以便您可以测试并查看其工作原理:

Shader "Unlit/HeightToNormal"

    Properties
    
        _MainTex("Albedo (RGB)", 2D) = "white" 
        _Color("Color", Color) = (1,1,1,1)
        _NormalIntensity("NormalIntensity",Float) = 1
        _HeightMapSizeX("HeightMapSizeX",Float) = 1024
        _HeightMapSizeY("HeightMapSizeY",Float) = 1024
    
    SubShader
    

        Tags  "RenderType" = "Opaque" 
        LOD 100

        Pass
        
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"
            #include "Lighting.cginc"
            #include "AutoLight.cginc"

            struct VertexInput 
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
                float4 normal : NORMAL;
                float3 tangent : TANGENT;

            ;

            struct VertexOutput 
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
                float2 uv1 : TEXCOORD1;
                float4 normals : NORMAL;

                //float3 tangentSpaceLight: TANGENT;
            ;

            sampler2D _MainTex;
            float4 _MainTex_ST;
            half4 _Color;
            float _NormalIntensity;
            float _HeightMapSizeX;
            float _HeightMapSizeY;

            VertexOutput vert(VertexInput v) 

                VertexOutput o;
                o.uv = TRANSFORM_TEX( v.uv, _MainTex ); // used for texture
                o.uv1 = v.uv;
                o.normals = v.normal;
                o.vertex = UnityObjectToClipPos(v.vertex);

                 return o;
             

            float4 frag(VertexOutput i) : COLOR
            
                float me = tex2D(_MainTex,i.uv1).x;
                float n = tex2D(_MainTex,float2(i.uv1.x, i.uv1.y + 1.0 / _HeightMapSizeY)).x;
                float s = tex2D(_MainTex,float2(i.uv1.x, i.uv1.y - 1.0 / _HeightMapSizeY)).x;
                float e = tex2D(_MainTex,float2(i.uv1.x + 1.0 / _HeightMapSizeX,i.uv1.y)).x;
                float w = tex2D(_MainTex,float2(i.uv1.x - 1.0 / _HeightMapSizeX,i.uv1.y)).x;

                // defining starting normal as color has some interesting effects, generally makes this more flexible
                float3 norm = _Color;
                float3 temp = norm; //a temporary vector that is not parallel to norm
                if (norm.x == 1)
                temp.y += 0.5;
                else
                temp.x += 0.5;

                //form a basis with norm being one of the axes:
                float3 perp1 = normalize(cross(i.normals,temp));
                float3 perp2 = normalize(cross(i.normals,perp1));

                //use the basis to move the normal i its own space by the offset
                float3 normalOffset = -_NormalIntensity * (((n - me) - (s - me)) * perp1 + ((e - me) - (w - me)) * perp2);
                norm += normalOffset;
                norm = normalize(norm);

                // it's also interesting to output temp, perp1, and perp1, or combinations of the float samples.
                return float4(norm, 1);
            
            ENDCG
         
    

要从高度贴图生成法线贴图,您尝试使用高度贴图中的定向变化率来得出一个法线向量,该向量可以使用 3 个浮点值(或颜色通道,如果它是一个图像)。您可以对您所在的图像点进行采样,然后在四个基本方向上离该点一小步。使用叉积来保证正交性,您可以定义一个基。使用图像上的定向步骤,您可以缩放两个基向量并将它们加在一起以找到“法线偏移”,这是近似高度图上值的定向变化的 3D 表示。基本上这是你的常态。

你可以看到我玩正常强度here 和“正常颜色”here 的效果。当这看起来适合您的用例时,您可以尝试将法线用作法线而不是彩色输出。

可能仍需要对值进行一些调整。祝你好运!

【讨论】:

我认为这和它所能得到的一样好,除非asker 可以让粒子生成器输出正常数据。很好的答案 非常感谢 :D 我会尝试实现它,看看它是否有效 :) @NSJacob 是我从相机接收到的“heightMapSampler”纹理吗?在那种情况下,“heightMapSizeY”和“heightMapSizeX”是什么?我也不完全理解你链接到的帖子,因为大部分着色器都不包含在那里:(你能帮忙吗? 是的,在本例中,'heightMapSampler' 将是您从相机接收到的纹理。 UV 是从 0 到 1 的浮点数,所以像 IN.tex.y + 1.0/heightMapSizeY 这样的东西意味着“在给定 heightMapSizeX by heightMapSizeY 大小的纹理的情况下,在 y 轴上有效地采样一个像素的值。” 在编辑器中试了一下,做了一个小着色器玩具来玩。它将高度图图像转换为普通图像,这基本上是我理解的问题的核心。

以上是关于Unity shader - 如何根据纹理贴图生成法线贴图的主要内容,如果未能解决你的问题,请参考以下文章

[Unity] Shader(着色器)之纹理贴图

Unity Shaders学习笔记之通过修改UV坐标实现纹理贴图的滚动

Unity Shaders and Effects Cookbook (2-4) 压缩和混合纹理贴图:使用灰度图存储插值信息

Unity Shader Graph中的Worldspace 2d纹理置换

贴图纹理材质的区别是什么? 还有shader

Unity Shader 知识点总结