Using transformation matrix leads to type mismatch

Hi, I am trying to learn more about ShaderLab and HLSL in Unity, and I already figured out how to get uniform variables to work. As part of what I want to try next (specular reflections) I will need, among other things, the normal of the surface in world space. Since Unity provides no builtin matrix which can do a rotation-only transformation from object to world space, I deliver my own via a uniform variable. However, the compiler complains about a type mismatch when trying to assign the result, without any details given. Anyone has an idea?

Shader code:

Shader "Custom/CustomSpecular"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            // make fog work
//            #pragma multi_compile_fog
          
            #include "UnityCG.cginc"
          
            uniform float4x4 loc2world;
            uniform float4x4 rotateOnly;
            uniform float4 uniCol;

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
                float3 normal : NORMAL;
            };

            struct v2f
            {
                float4 vertex : SV_POSITION;
                float2 uv : TEXCOORD0;
                float3 normal : NORMAL;
//                UNITY_FOG_COORDS(1)
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
          
            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                o.normal = rotateOnly * float4(v.normal, 0); // ERROR: type mismatch (on d3d11)
              
//                UNITY_TRANSFER_FOG(o,o.vertex);
                return o;
            }
          
            fixed4 frag (v2f i) : SV_Target
            {
//                fixed4 col = tex2D(_MainTex, i.uv) * uniCol;
                fixed4 col = fixed4(i.normal, 1.0f);
//                UNITY_APPLY_FOG(i.fogCoord, col);
                return col;
            }
            ENDCG
        }
    }
}

C# MonoBehaviour code:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class SetUniforms : MonoBehaviour {

    private static int rotateOnlyID = -1;
    private static bool initialized = false;

    private MeshRenderer mr;

    void Awake () {
        if (!initialized) {
            rotateOnlyID = Shader.PropertyToID ("rotateOnly");
            initialized = true;
        }
        mr = GetComponent<MeshRenderer> ();
    }

    void OnRenderObject() {
        mr.material.SetMatrix (rotateOnlyID, Matrix4x4.TRS(Vector3.zero, transform.rotation, new Vector3(1, 1, 1)));
    }
}

It appears HLSL requires the use of mul(x,y) as opposed to using the asterisk operator, so that fixed it. Oops!

3 Likes

Because it’s not needed. Casting the 4x4 matrix to a 3x3 matrix results in a rotation (and yes, scale) transform. For most situations where you want to rotate a vector with out translation you’re dealing with unit length vectors, so you only need to normalize after transforming.

For normals you actually want to use the inverse transpose transform matrix, and Unity has a built in function with an optimized version of this (just using the inverse WorldToObject matrix to avoid calling the costly transpose function).

float3 worldNormal = UnityObjectToWorldNormal(v.normal)

1 Like

That’s very useful! Wish the docs would mention it. And in my defense, it has been a while since I’ve been knacking my knuckles over some matrix math.

Yeah, there’s a bunch of useful functions in the UnityCG.cginc file, with some odd exclusion, but most aren’t documented for some reason.