creating normals from alpha/heightmap inside a shader

http://www.polycount.com/forum/showthread.php?t=117185

 

I am making some custom terrain shaders with strumpy's editor and I want to be able to create normals based on my blend mask. Does anyone know how I can turn grayscale data into basic normal mapping info? I have seen someone do this in unreal but I can't remember where.

 

You probably want to get the difference of the values pixel-by-pixel using ddx, I think.

Off the top of my head... for CG/HLSL;

float heightmap = your height map value;
float3 normal;
normal.x = ddx(heightmap);
normal.y = ddy(heightmap);
normal.z = sqrt(1 - normal.x*normal.x - normal.y * normal.y); // Reconstruct z component to get a unit normal.

For OpenGL, I think the functions are dFdx and dFdy.

DirectX11 has ddx_fine and ddy_fine which I think give more accurate results.


That'll give you the normal in, ehr... screen space I think?

Otherwise you can sample the heightmap multiple times, offsetting it by a pixel and working out the difference from those values. That should get you it in whatever space the base normal is in.

Nicked frm here; http://www.gamedev.net/topic/594781-...mal-algorithm/

float me = tex2D(heightMapSampler,IN.tex).x;

float n = tex2D(heightMapSampler,float2(IN.tex.x,IN.tex.y+1.0/heightMapSizeY)).x;

float s = tex2D(heightMapSampler,float2(IN.tex.x,IN.tex.y-1.0/heightMapSizeY)).x;

float e = tex2D(heightMapSampler,float2(IN.tex.x+1.0/heightMapSizeX,IN.tex.y)).x;

float w = tex2D(heightMapSampler,float2(IN.tex.x-1.0/heightMapSizeX,IN.tex.y)).x;                



//find perpendicular vector to norm:        

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(norm,temp));

float3 perp2 = normalize(cross(norm,perp1));



//use the basis to move the normal in its own space by the offset        

float3 normalOffset = -bumpHeightScale*(((n-me)-(s-me))*perp1 + ((e-me)-(w-me))*perp2);

norm += normalOffset;

norm = normalize(norm);

 

Gave it a try out of curiosity.

Here's the multi-sample method in a stripped-down surface shader.

I had to change the handedness from the code above to match Unity (changing the sign of the operation when sampling the e and w values from + to - and vice versa).

 

Shader "Debug/Normal Map From Height" {

    Properties {

        _Color ("Main Color", Color) = (1,1,1,1)

        _MainTex ("Diffuse (RGB) Alpha (A)", 2D) = "white" {}

        _BumpMap ("Normal (Normal)", 2D) = "bump" {}

        _HeightMap ("Heightmap (R)", 2D) = "grey" {}

        _HeightmapStrength ("Heightmap Strength", Float) = 1.0

        _HeightmapDimX ("Heightmap Width", Float) = 2048

        _HeightmapDimY ("Heightmap Height", Float) = 2048

    }



    SubShader{

        Tags { "RenderType" = "Opaque" }



        CGPROGRAM



            #pragma surface surf NormalsHeight

            #pragma target 3.0



            struct Input

            {

                float2 uv_MainTex;

            };



            sampler2D _MainTex, _BumpMap, _HeightMap;

            float _HeightmapStrength, _HeightmapDimX, _HeightmapDimY;



            void surf (Input IN, inout SurfaceOutput o)

            {

                o.Albedo = fixed3(0.5);



                float3 normal = UnpackNormal(tex2D(_BumpMap, IN.uv_MainTex));



                float me = tex2D(_HeightMap,IN.uv_MainTex).x;

                float n = tex2D(_HeightMap,float2(IN.uv_MainTex.x,IN.uv_MainTex.y+1.0/_HeightmapDimY)).x;

                float s = tex2D(_HeightMap,float2(IN.uv_MainTex.x,IN.uv_MainTex.y-1.0/_HeightmapDimY)).x;

                float e = tex2D(_HeightMap,float2(IN.uv_MainTex.x-1.0/_HeightmapDimX,IN.uv_MainTex.y)).x;

                float w = tex2D(_HeightMap,float2(IN.uv_MainTex.x+1.0/_HeightmapDimX,IN.uv_MainTex.y)).x;



                float3 norm = normal;

                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(norm,temp));

                float3 perp2 = normalize(cross(norm,perp1));



                //use the basis to move the normal in its own space by the offset

                float3 normalOffset = -_HeightmapStrength * ( ( (n-me) - (s-me) ) * perp1 + ( ( e - me ) - ( w - me ) ) * perp2 );

                norm += normalOffset;

                norm = normalize(norm);



                o.Normal = norm;

            }



            inline fixed4 LightingNormalsHeight (SurfaceOutput s, fixed3 lightDir, fixed3 viewDir, fixed atten)

            {

                viewDir = normalize(viewDir);

                lightDir = normalize(lightDir);

                s.Normal = normalize(s.Normal);

                float NdotL = dot(s.Normal, lightDir);

                _LightColor0.rgb = _LightColor0.rgb;



                fixed4 c;

                c.rgb = float3(0.5) * saturate ( NdotL ) * _LightColor0.rgb * atten;

                c.a = 1.0;

                return c;

            }



        ENDCG

    }

    FallBack "VertexLit"

}

 

Derivative method works, but it's fucking ugly 'cause it's in screen space - really noisy.

ddx_fine might give better results in DX11, but it looks like crap in DX9.

你可能感兴趣的:(height)