Surface Shader Height Map

Hey Guys,

I like to know the correct way to calculate/use a height map in a surface shader?

I’m making a Standard (metallic) shader with a heightmap texture. Here is what I currently have not sure if this is the right way to go about it.

Shader "Custom/PBR/Standard Height" {
    Properties {
        _Color ("Color", Color) = (1,1,1,1)
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
        _BumpMap("Normal Map", 2D) = "bump" {}
        _BumpScale ("Normal ", Float) = 1
            _HeightMap ("Height Map", 2D) = "white" {}
        _HeightMapScale ("Height", Float) = 1
        _Glossiness ("Smoothness", Range(0,1)) = 0.5
        _MetallicGlossMap ("Metallic", 2D) = "white" {}
        _Metallic ("Metallic", Range(0,1)) = 0.0
        _OcclusionMap("OcclusionMap", 2D) = "white" {}
        _OcclusionStrength("Occlusion Strength", Float) = 1

    }
    SubShader {
        Tags { "RenderType"="Opaque" }
        LOD 200
      
        CGPROGRAM
        // Physically based Standard lighting model, and enable shadows on all light types
        #pragma surface surf Standard fullforwardshadows vertex:vert

        // Use shader model 3.0 target, to get nicer looking lighting
        #pragma target 3.0
        #pragma glsl

        sampler2D _MainTex;
        sampler2D _OcclusionMap;
        sampler2D _BumpMap;
        sampler2D _MetallicGlossMap;
        sampler2D _HeightMap;

        struct Input {
            float2 uv_MainTex;
        };

        half _HeightMapScale;
        half _Glossiness;
        half _Metallic;
        half _BumpScale;
        half _OcclusionStrength;
        fixed4 _Color;

        void vert(inout appdata_full v,  out Input o) {
            UNITY_INITIALIZE_OUTPUT(Input, o);
            float4 heightMap = tex2Dlod(_HeightMap, float4(v.texcoord.xy,0,0));
            //fixed4 heightMap = _HeightMap;
            v.vertex.z += heightMap.b * _HeightMapScale;
        }

        void surf (Input IN, inout SurfaceOutputStandard o) {
            // Albedo comes from a texture tinted by color
            fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
            o.Albedo = c.rgb;
            // Metallic and smoothness come from slider variables
            fixed4 gloss = tex2D(_MetallicGlossMap, IN.uv_MainTex);
            o.Metallic = gloss.r * _Metallic;
            o.Smoothness = gloss.a * _Glossiness;

            o.Normal = UnpackScaleNormal(tex2D(_BumpMap, IN.uv_MainTex), _BumpScale);
            o.Occlusion = tex2D(_OcclusionMap, IN.uv_MainTex) * _OcclusionStrength;

            o.Alpha = c.a;
        }
        ENDCG
    }
    FallBack "Diffuse"
}

What effect are you expecting? There are many ways to use a heightmap with a surface shader, and the method you’re using is a perfectly valid option, but I get the impression it isn’t producing the result you want.

A height map is just description of data in a texture, it doesn’t really say anything about how you use it.

If you’re trying to replicate what the standard shader does with the heightmap, that’s parallax offset mapping, which uses a heightmap to distort UVs to give a very basic appeance of additional surface depth. If you’re looking to implement that in your surface shader see this post which includes an example of using ParallaxOffset, as well as a link to the more expensive, but better looking Parallax Occlusions Mapping.

2 Likes

This is not at all the correct way to do it for a couple of reasons:

1.) You’re doing it in the vertex program – Height maps are per pixel, not per vertex.

2.) You’re just blindly increasing the Z axis – Height maps operate on the normal of the current
surface.

Posting here since this page comes up as the topmost result whenever I google the issue, and because I finally figured it out.

And to clarify, this is assuming you mean “Height Map” as in what the Standard Shader calls a Height Map, example below – you can see the bricks stick out more as I drag the knob to the right…

Interestingly enough, even though Unity calls it a “Height Map” in the Standard Shader, the Shader language refers to it as a parallax map (there’s a hint in the Height Map documentation here: Unity - Manual: Heightmap ) – Discovering that makes it a bit easier to track down the proper code to make this work.

(So yeah, turns out ** @bgolus ** was on the right track – but saying “what you’re doing looks valid” was misleading for me… anyway, if you’re as thick as I can be, the step-by-step is below, give it a shot.)

First off Declare two Properties for your shader:

_HeightMap("Height Map", 2D) = "white" {}
_HeightPower("Height Power", Range(0,.125)) = 0

Add two globals to match:

sampler2D _HeightMap;
float _HeightPower;

to your “Input” struct, add two fields:

float2 uv_HeightMap;
float3 viewDir;

if your “surf” program, for the very first line add:

float2 texOffset = ParallaxOffset(tex2D(_HeightMap, IN.uv_HeightMap).r, _HeightPower, IN.viewDir);

then find all your other tex2D lookups and adjust them, like so:

fixed4 c = tex2D(_MainTex, IN.uv_MainTex + texOffset) * _Color;
o.Normal = UnpackNormal(tex2D(_BumpMap, IN.uv_BumpMap + texOffset));
//...
o.Albedo = c.rgb;
o.Alpha = c.a;

Crazily enough, all it’s doing is modifying the UVs based on the current view direction to simulate a height effect… it works really well though.

7 Likes