- Home /
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:
Create two spheres
Give one of them a custom material with "CGShader5" as the material's shader.
Attach CullMe.cs to the previous sphere, and assign the other sphere as 'culler'
Move the two spheres close to each other
You should see something like this:
What I expect however is:
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!
btw: I know your expected image is quickly drawn but what you would expect to see is the same as Unity already shows you with the mesh grid which is cut off at the seam where the spheres intersect.
When your culling sphere is visible you won't see any difference to a normal shader since the intersecting part is actually hidden due to z buffering. However if you disable the culling sphere's renderer or use a transparent material for it you should see the cut-out part.
I do realize that. But when I include the 'length' part it's not actually cutting anything :(
Hi I have a Problem, which bases on your Problem. I want multiple Spears not be inside each other. If I am using your shader and C# Code I would need a $$anonymous$$ind of Array of possitions, which will be checked in a loop inside the shader. Is this possible? (Full quesion here: http://answers.unity3d.com/questions/1185084/unity-overlapping-transparent-spheres.html#comment-1186541) Thank you for your time dude, even until now great work ;-)
Answer by Bunny83 · Jul 24, 2015 at 07:11 AM
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² + w². 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
So the equation your position has to fullfill is this:
(x² + y² + z² < 0.5²)
or
(x² + y² + z² < 0.25)
or
(sqrt(a.x² + a.y² + a.z²) < 0.5) // which is the same as: length(a) < 0.5
I did try length I should have mentioned it, it's not doing anything :(
if (length(_in.cullerpos) < 0.5)
discard;
or
if (length(_in.cullerpos) < 0.5 &&
_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;
not discarding the fragment, I can tell because I if I change the value I'm comparing against the legnth nothing happens.
I try to visualize the length:
return float4(length(_in.cullerpos), 0, 0, 1);
it's always bright red even if I move the sphere closer :(
:D i found the problem ^^. The problem is that we work with float4 values with a w component of 1 (since it's a position, not a direction). However that means that "length" does include the w component as well but we want the 3D euclidean length, not the 4D. So using this will work:
if (length(_in.cullerpos.xyz) < 0.5)
discard;
// or
if (dot(_in.cullerpos.xyz, _in.cullerpos.xyz) < 0.25)
discard;
edit
I first thought there has to be something wrong with the matrix so the position is way off. But i remembered your first picture. That was the prove that the position comes out right. So the problem has to be in checking the length ^^.
@Bunny83 in case you were wondering, I'm going after similar stuff as in here http://www.valvesoftware.com/publications/2010/gdc2010_vlachos_l4d2wounds.pdf - here's what I have so far:
Not perfect but getting there! :D
(Code)
Answer by AMAT_Sooraj · Aug 29, 2018 at 01:40 PM
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.
Hey @A$$anonymous$$AT_Sooraj, a bit late to the thread, but I'm also interested in the full shader or at least I want to fill the fragments that were discarded. Did you find a solution yet?