Simple cut shader: determine if fragment is inside another sphere?

I’m going over the cg series here.

In the first entry under the second chapter they throw this exercise to the reader:

f you are familiar with scripting in Unity, you could try this idea: write a script for an object that takes a reference to another sphere object and assigns (using renderer.sharedMaterial.SetMatrix()) the inverse model matrix (renderer.worldToLocalMatrix) of that sphere object to a float4x4 uniform parameter of the shader. In the shader, compute the position of the fragment in world coordinates and apply the inverse model matrix of the other sphere object to the fragment position. Now you have the position of the fragment in the local coordinate system of the other sphere object; here, it is easy to test whether the fragment is inside the sphere or not because in this coordinate system all Unity spheres are centered around the origin with radius 0.5. Discard the fragment if it is inside the other sphere object. The resulting script and shader can cut away points from the surface of any object with the help of a cutting sphere that can be manipulated interactively in the editor like any other sphere.

This is what I did:

using UnityEngine;

[ExecuteInEditMode]
public class CullMe : MonoBehaviour
{
    public Transform culler;

    void Update()
    {
        if (!culler)
            return;

        var renderer = GetComponent<Renderer>();
        if (!renderer)
            return;

        var material = renderer.sharedMaterial;
        material.SetMatrix("_CullerSpace", culler.worldToLocalMatrix);
    }
}

This is the shader I have:

Shader "CGShader5"
{
    SubShader
    {
        Pass
        {
            /*Cull Off*/

            CGPROGRAM

            #pragma vertex vert
            #pragma fragment frag

            uniform float4x4 _CullerSpace;

            struct vout
            {
                float4 pos : SV_POSITION;
                float4 worldpos : TEXCOORD0;
                float4 localpos : TEXCOORD1;
                float4 cullerpos : TEXCOORD2;
            };

            vout vert(float4 vertex : POSITION)
            {
                vout _out;
                _out.pos = mul(UNITY_MATRIX_MVP, vertex);
                _out.localpos = vertex;
                _out.worldpos = mul(_Object2World, vertex);
                _out.cullerpos = mul(_CullerSpace, _out.worldpos);
                return _out;
            }

            float4 frag(vout _in) : COLOR
            {
                /*return float4(_in.cullerpos.x, _in.cullerpos.y, 0, 1);*/

                if (_in.cullerpos.x > -0.5 && _in.cullerpos.x < 0.5 &&
                    _in.cullerpos.y > -0.5 && _in.cullerpos.y < 0.5 &&
                    _in.cullerpos.z > -0.5 && _in.cullerpos.z < 0.5)
                    discard;

                return float4(0, 1, 0, 1);
            }

            ENDCG
        }
    }
}

To use this:

  1. Create two spheres
  2. Give one of them a custom material with “CGShader5” as the material’s shader.
  3. Attach CullMe.cs to the previous sphere, and assign the other sphere as ‘culler’
  4. Move the two spheres close to each other

You should see something like this:

50831-whatyouget.png

What I expect however is:

50832-whatiexpect.png

That is, I want to discard the fragments in the left sphere that are intersecting/inside of the right sphere, only those fragments!

The check in the frag function is actually doing what it should, but it’s incorrect. See those non-intersecting frags that are discarded? well according to this check they should be discarded cause their ‘x’ local to the other sphere is less than 0.5 (contained within the radius of the other sphere)

I believe it can be solved just with this inverse model matrix without introducing any other uniform variables (like the position of the other sphere) but I’m just not seeing it…

What am I missing?

Any help is appreciated!

You did not test the length of the vector but it’s enclosing bounding box ^^. You have to handle the length like you would in Unity. There is a “length” function available in cg, so you don’t need to square, add and square-root it manually.

if (length(_in.cullerpos.xyz) < 0.5)
    discard;

edit
A square-root isn’t really a problem when calculated on the GPU, but you can get rid of it like this (just don’t forget to test against the squared radius):

if (dot(_in.cullerpos.xyz,_in.cullerpos.xyz) < 0.25)
    discard;

edit
As said in the comment below another problem was that we worked with float4 values. So “length” as well as “dot” will calculate x² + y² + z² + . Since w is 1.0 the caluclated length doesn’t match the 3d length. The solution is to convert it to a float3 value and discarding w before calculating the length. I changed the examples above

@vexe Hey could you please tell me how you got the texture to cover up the parts where the fragments were discarded? I’m trying to do something similar and any help would be really appreciated.

I did go through it. I’m actually just starting out with shader programming and I’m unable to grasp the use of that snippet without seeing the full shader code. I need to make this shader support Standard lighting model including all the channels as well(Even in the areas that are filled up after the fragments were discarded). If you could you please post the full shader code, it would be really helpful.