2
\$\begingroup\$

All the batched objects have the same rotation. To simulate the effect of directional light I need to determine the object sides actual (world) directions. Normally I would use unity_ObjectToWorld & unity_ObjectToWorld for that, which is not possible when the objects are batched.

Is it possible to pass the object rotation to the shader in one action (since it's the same for all of them) without breaking the batching?


This is a simplified shader example (here I use colors instead of light/shadow effect and only 2 sides instead of 4):

Shader "Test/UnlitTest"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _TopColor ("TopColor", Color) = (1, 0, 0, 1)
        _BottomColor ("_BottomColor", Color) = (0, 0, 1, 1)
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        ZTest Off
        ZWrite Off
        Lighting Off
        Cull Off
        Fog { Mode Off }

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"

            sampler2D _MainTex;
            float4 _MainTex_ST;
            float4 _MainTex_TexelSize;
            fixed4 _TopColor, _BottomColor;            

            struct vertData
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;    
            };

            struct fragData
            {
                float2 uv : TEXCOORD0;
                float2 uvW : TEXCOORD1;
                float4 vertex : SV_POSITION;
            };

            fragData vert (vertData v)
            {
                fragData f;
                f.vertex = UnityObjectToClipPos(v.vertex);
                f.uv = TRANSFORM_TEX(v.uv, _MainTex);
                f.uvW = mul(unity_ObjectToWorld, f.uv);
                return f;
            }

            fixed isTransparentPixel(fixed2 uv)
            {
                return step(tex2D(_MainTex, uv).a, 0.0);
            }

            fixed isEdgePixel(fixed2 uv)
            {
                uv = mul(unity_WorldToObject, uv); // back to object space
                return isTransparentPixel(uv);
            }

            fixed4 frag (fragData f) : SV_Target
            {
                fixed isTransparent = isTransparentPixel(f.uv);
                clip(-isTransparent);
                
                fixed4 c = tex2D(_MainTex, f.uv);
                float2 borderSize = float2(3, 3); // border size in tex pixels
                float dy = borderSize.y * _MainTex_TexelSize.y;
                fixed3 borderColor = fixed3(0, 0, 0);
                float borderColorsCount = 0;
                float2 uvW = f.uvW; // UV in the world space

                // Top edge:
                fixed isEdge = isEdgePixel(uvW + float2(0, dy));
                borderColor += isEdge * _TopColor.rgb;
                borderColorsCount += isEdge;

                // Bottom edge:
                isEdge = isEdgePixel(uvW + float2(0, -dy));
                borderColor += isEdge * _BottomColor.rgb;
                borderColorsCount += isEdge;

                fixed noBorderColor = step(length(borderColor), 0);
                fixed hasBorderColor = 1 - noBorderColor;
                c.rgb = c.rgb * noBorderColor + hasBorderColor * borderColor / max(borderColorsCount, 1);
                return c;
            }
            ENDCG
        }
    }
}

It gets a semi-transparent image as a texture. The image is completely transparent at the edges and contains an opaque shape in the center.

enter image description here

The shader colors the top edges of the non-transparent shape in red and the bottom ones in yellow. It uses unity_WorldToObject & unity_ObjectToWorld to check the edges facing directions in the world space.

enter image description here enter image description here

\$\endgroup\$
1
  • \$\begingroup\$ Comments are not for extended discussion; this conversation has been moved to chat. \$\endgroup\$
    – DMGregory
    Commented Feb 9, 2021 at 13:32

1 Answer 1

0
\$\begingroup\$

Here is an edited version of the code of the resolved question:

C#

using UnityEngine;

[DisallowMultipleComponent]
public class UnlitTest : MonoBehaviour
{
#pragma warning disable CS0649

    [SerializeField]
    private MeshFilter _meshFilter;

#pragma warning restore CS0649

    private void OnValidate()
    {
        SetNormals();
    }

    private void SetNormals()
    {
        Mesh mesh = _meshFilter.sharedMesh;
        Vector3[] normals = new Vector3[mesh.vertexCount];

        for (int i = 0; i < mesh.vertexCount; i++)
        {
            normals[i] = Vector3.up;
        }

        mesh.normals = normals;
    }
}

Shader

Shader "Test/UnlitTest"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _TopColor ("TopColor", Color) = (1, 0, 0, 1)
        _BottomColor ("_BottomColor", Color) = (0, 0, 1, 1)
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        ZTest Off
        ZWrite Off
        Lighting On
        Cull Off
        Fog { Mode Off }

        Blend SrcAlpha OneMinusSrcAlpha

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"

            sampler2D _MainTex;
            float4 _MainTex_ST;
            float4 _MainTex_TexelSize;
            fixed4 _TopColor, _BottomColor;            

            struct vertData
            {
                float4 vertex : POSITION;
                float2 texcoord : TEXCOORD0;
                float3 normal : NORMAL;
            };

            struct fragData
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
                float3 up : NORMAL;
            };

            fragData vert (vertData v)
            {
                fragData f;
                f.vertex = UnityObjectToClipPos(v.vertex);
                f.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
                f.up = mul(unity_ObjectToWorld, v.normal);
                return f;
            }

            fixed isTransparentPixel(fixed2 uv)
            {
                return step(tex2D(_MainTex, uv).a, 0.0);
            }

            fixed isEdgePixel(fixed2 uv)
            {
                return isTransparentPixel(uv);
            }

            fixed4 frag (fragData f) : SV_Target
            {
                float2 borderSize = float2(3, 3); // border size in tex pixels
                float dx = borderSize.x * _MainTex_TexelSize.x;
                float dy = borderSize.y * _MainTex_TexelSize.y;
                fixed3 borderColor = fixed3(0, 0, 0);
                float borderColorsCount = 0;
                fixed2 uv = f.uv;

                // Top edge:
                float2 dTop = float2(-f.up.x * dx, f.up.y * dy);
                fixed isEdge = isEdgePixel(uv + dTop);
                borderColor += isEdge * _TopColor;
                borderColorsCount += isEdge;

                // Bottom edge:
                float2 dDown = dTop * -1;
                isEdge = isEdgePixel(uv + dDown);
                borderColor += isEdge * _BottomColor;
                borderColorsCount += isEdge;

                fixed noBorderColor = step(length(borderColor), 0);
                fixed hasBorderColor = 1 - noBorderColor;
                fixed4 c = tex2D(_MainTex, uv);
                c.rgb = c.rgb * noBorderColor + hasBorderColor * borderColor / max(borderColorsCount, 1);
                return c;
            }
            ENDCG
        }
    }
}
\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .