I'm trying to write a fragment shader that will give a different color depending on the position. For this purpose, I wrote a script that returns the color from given vector3 and I want to call this function inside a shader. Is it possible at all?
My code:
using System.Collections.Generic;
using UnityEngine;
public class CustomLight : MonoBehaviour
{
public static List<CustomLight> lights = new List<CustomLight>();
[Min(0)]
public float intensity = 1;
public Color color = Color.white;
[Min(0)]
public float radius = 4;
[Range(0, 1)]
public float innerRadius = 0;
public Color GetLight(Vector3 point)
{
if (intensity <= 0 || radius <= 0) return Color.clear;
float value = 0;
float distanceSqr = (point - transform.position).sqrMagnitude;
if (distanceSqr >= radius * radius) return Color.clear;
if (innerRadius == 1) value = 1;
else
{
if (distanceSqr <= radius * radius * innerRadius * innerRadius) value = 1;
else value = Mathf.InverseLerp(radius, radius * innerRadius, Mathf.Sqrt(distanceSqr));
}
return color * intensity * value;
}
private void OnEnable()
{
if (!lights.Contains(this)) lights.Add(this);
}
private void OnDisable()
{
lights.Remove(this);
}
}
I haven't written any shader yet, because I don't even know where to start. I need the sum of results from all scripts on the scene, then multiply it by the color of the shader.
I apologize for poor English
C# functions run on the CPU while shaders run on the GPU, as such you can't call c# functions from a shader.
You can however access variables passed to the shaders through the materials via Material.SetX methods, which is likely the closest to what your trying to achieve.
Related
I have two files (NewComputeShader.compute and ShaderRun.cs). ShaderRun.cs runs shader and draws it's texture on a camera (script is camera's component)
On start, unity draws one white pixel in a bottom-left corner.
(Twidth = 256, Theight = 256, Agentsnum = 10)
NewComputeShader.compute:
// Each #kernel tells which function to compile; you can have many kernels
#pragma kernel CSUpdate
// Create a RenderTexture with enableRandomWrite flag and set it
// with cs.SetTexture
RWTexture2D<float4> Result;
uint width = 256;
uint height = 256;
int numAgents = 10;
float moveSpeed = 100;
uint PI = 3.1415926535;
float DeltaTime = 1;
uint hash(uint state) {
state ^= 2747636419u;
state *= 2654435769u;
state ^= state >> 16;
state *= 2654435769u;
state ^= state >> 16;
state *= 2654435769u;
return state;
}
uint scaleToRange01(uint state) {
state /= 4294967295.0;
return state;
}
struct Agent {
float2 position;
float angle;
};
RWStructuredBuffer<Agent> agents;
[numthreads(8,8,1)]
void CSUpdate(uint3 id : SV_DispatchThreadID)
{
//if (id.x >= numAgents) { return; }
Agent agent = agents[id.x];
uint random = hash(agent.position.y * width + agent.position.x + hash(id.x));
float2 direction = float2(cos(agent.angle), sin(agent.angle));
float2 newPos = agent.position + direction * moveSpeed * DeltaTime;
if (newPos.x < 0 || newPos.x >= width || newPos.y < 0 || newPos.y >= height) {
newPos.x = min(width - 0.01, max(0, newPos.x));
newPos.y = min(height - 0.01, max(0, newPos.y));
agents[id.x].angle = scaleToRange01(random) * 2 * PI;
}
agents[id.x].position = newPos;
Result[int2(newPos.x, newPos.y)] = float4(1,1,1,1);
}
ShaderRun.cs:
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ShaderRun : MonoBehaviour
{
public ComputeShader computeShader;
public RenderTexture renderTexture;
public int twidth;
public int theight;
public int agentsnum;
ComputeBuffer agentsBuffer;
struct MyAgent
{
public Vector2 position;
public float angle;
};
// Start is called before the first frame update
void Start()
{
renderTexture = new RenderTexture(twidth, theight, 24);
renderTexture.enableRandomWrite = true;
renderTexture.Create();
computeShader.SetTexture(0, "Result", renderTexture);
agentsBuffer = new ComputeBuffer(agentsnum, sizeof(float)*3); //make new compute buffer with specified size, and specified "stride" //stride is like the size of each element, in your case it would be 3 floats, since Vector3 is 3 floats.
ResetAgents();
computeShader.SetBuffer(0, "agents", agentsBuffer); //Linking the compute shader and cs shader buffers
computeShader.Dispatch(0, renderTexture.width / 8, renderTexture.height / 8, 1);
}
void OnRenderImage(RenderTexture src, RenderTexture dest)
{
Graphics.Blit(renderTexture, dest);
}
private void ResetAgents()
{
MyAgent[] aArray = new MyAgent[agentsnum];
for (int i=0; i<agentsnum; i++)
{
MyAgent a = new MyAgent();
a.position = new Vector2(128, 128);
a.angle = 2 * (float)Math.PI * (i / agentsnum);
aArray[i] = a;
}
agentsBuffer.SetData(aArray);
ComputeStepFrame();
}
private void ComputeStepFrame()
{
computeShader.SetFloat("DeltaTime", Time.deltaTime);
int kernelHandle = computeShader.FindKernel("CSUpdate");
computeShader.SetBuffer(kernelHandle, "agents", agentsBuffer);
computeShader.Dispatch(0, renderTexture.width / 8, renderTexture.height / 8, 1);
}
// Update is called once per frame
void Update()
{
ComputeStepFrame();
}
}
Also this is an attempt of recreating this code: https://www.youtube.com/watch?v=X-iSQQgOd1A&t=730s (part: Side-tracked by Slime). Result must be like on a first demonstration of agents in video.
Edit: I really recommend to check this video. It is very good!
I'm doing the same. To start the scaleToRange01 function should probably return a float. As for location you might want to look at the C# side, how are you initializing agents and getting that data into the buffer? Need to create a similar struct in C# then assign it something like below.
int totalSize = (sizeof(float) * 2) + (sizeof(float));
agentBuffer = new ComputeBuffer(agents.Length, totalSize);
agentBuffer.SetData(agents);
computeShader.SetBuffer(0, "agents", agentBuffer);
I am also attempting to recreate this. The problem is Sebastian leaves out his c# code and some of his HLSL so it's hard to put together the pieces that aren't there. I worked nonstop all day yesterday and finally got it to perform demonstration 2. The most difficult thing for me is getting the threading correctly and having the GPU compute all the items I need it to. I am dreading starting the trail dissipation and trail sense but honestly, it felt great getting to demo 2 and it's what is pushing me to keep at it. Everything is very touchy with this project and it is not for the casual programmer. (Also learn a bit about HLSL if you haven't.) Another thing is I don't use his random angle generator I just created my own. I know this doesn't help but just know other people are attempting to struggle through this also. Sebastian is a genius.
Found this question after so much time. But theme is still interesting. Maybe my answer will help passersby here later.
BTW look at this video.
Slime is a game life form now!
The problem with the code from original question is ambiguity with what are you going to process with kernel.
[numthreads(8,8,1)]
void CSUpdate(uint3 id : SV_DispatchThreadID)
{
//if (id.x >= numAgents) { return; }
Agent agent = agents[id.x];
//etc...
}
In this kernel you are intended to process 1D array of agents. You need to dispatch this code like this:
shader.Dispatch(kernelUpdateAgents,
Mathf.CeilToInt(numAgents / (float) xKernelThreadGroupSize),
1,
1);
And of course you need to correct kernel TGS:
[numthreads(8,1,1)]
void CSUpdate(uint3 id : SV_DispatchThreadID)
For 2D texture you need to keep kernel like
[numthreads(8,8,1)]
void ProcessTexture(uint3 id : SV_DispatchThreadID)
{
//some work
}
And only then it is okay to dispatch it with second dimension:
shader.Dispatch(kernelProcessTexture,
Mathf.CeilToInt(TextureWidth / (float) xKernelThreadGroupSize),
Mathf.CeilToInt(TextureHeight / (float) yKernelThreadGroupSize),
1);
P.S. And there is a link to github repo under video.
I'm working on implementing Context Steering movement for the enemy AI in my unity project and running into problems when it comes to determining the interest of each angle.
I have two classes: ContextMap and DirectionContext.
DirectionContext is a data storage class. It contains an angle, a Vector3 that relates that angle into a direction to move in, and a float ranging from 0 to 1 depending on how interesting that angle is to move in.
ContextMap contains a list of DirectionContext depending on the "detail" of the contextmap. So if I set the detail to 4, it will generate 4 DirectionContexts, and if I set the detail to 8, it will generate 8 DirectionContexts. The angles go from -180, to +180.
What I want to do is write a function that determins the angle between the enemy gameobject and the player gameobject, then compare it to each DirectionContext in the context map. I want it to set the interest float for each DirectionContext depending on the similarity of the angle between the objects and the DirectionContexts angle. I want it to set interest to 1 if the directions are identical, and to set interest to 0 if the difference between the angles is a high as they can be (360.)
The issue I'm having, is my scripts aren't setting the interest for the angles correctly. If the Player is directly above the enemy, it works as expected, but if the player is to the diagonals or sides it starts working opposite.
Here's a picture of what's happening: The black object is the enemy, the red is the human. The lines are longer depending on how high the interest is.
Link to the image
As you can see in the image, if the directions are up/down then it works fine, but things get weird when it goes diagonal or to the sides.
Here's what I've come up with in code:
public class DirectionContext
{
[SerializeField] private Vector3 direction;
[SerializeField] private float interest;
[SerializeField] private float angle;
[SerializeField] private bool isExcluded = false;
public Vector3 GetDirection() { return direction; }
public float GetInterest() { return interest; }
public void SetInterest(float value) { interest = value; }
public void CheckInterestAndSet(float newInterest)
{
if (newInterest > interest)
interest = newInterest;
}
public float GetAngle() { return angle; }
public DirectionContext(float angle)
{
this.angle = angle;
this.direction = new Vector3(Mathf.Sin(angle * Mathf.Deg2Rad), Mathf.Cos(angle * Mathf.Deg2Rad), 0).normalized;
}
}
Context map:
public class ContextMap
{
[SerializeField] private List<DirectionContext> contextMap = new List<DirectionContext>();
public List<DirectionContext> GetContext()
{
return contextMap;
}
public void GenerateMap(int detail)
{
contextMap.Clear();
for (int i = 0; i < detail; i++)
{
float angle = (360 / detail * i) - 180;
contextMap.Add(new DirectionContext(angle));
}
}
public void UpdateContext(int index, float interest)
{
contextMap[index].CheckInterestAndSet(interest);
}
public void DebugDrawMap(Vector3 position, Color color)
{
foreach (DirectionContext direction in contextMap)
{
Debug.DrawLine(position, (position + direction.GetDirection()).normalized * direction.GetInterest());
}
}
public void Clear()
{
foreach (DirectionContext dir in contextMap)
dir.Clear();
}
}
Seek: (I left out SteeringContext as it's a super simple class. It just contains variables that may be interesting. The two used in seek are parent and targetDestination. TargetDestination is the players location, and parent is a reference to the enemy.
public class Seek : SteeringBehavior
{
public override ContextMap AddContext(ContextMap contextMap, SteeringContext steeringContext)
{
float angleToTarget = AngleBetween(Vector3.up, steeringContext.targetDestination - steeringContext.parent.transform.position);
//Debug.Log(angleToTarget);
for (int i = 0; i < contextMap.GetContext().Count; i++)
{
float delta = Mathf.Abs(Mathf.Max(angleToTarget, contextMap.GetContext()[i].GetAngle()) - Mathf.Min(angleToTarget, contextMap.GetContext()[i].GetAngle()));
if (delta > 180)
{
delta = 360f - delta;
}
float interest = 1f - delta / 180f;
contextMap.UpdateContext(i, interest);
}
return contextMap;
}
private float AngleBetween(Vector3 vector1, Vector3 vector2)
{
float sin = vector1.x * vector2.y - vector2.x * vector1.y;
float cos = vector1.x * vector2.x + vector1.y * vector2.y;
return Mathf.Atan2(sin, cos) * (180) / Mathf.PI;
}
}
And I make the call to Seek using this function:
private void PopulateInterestMap()
{
interestMap.Clear();
foreach (SteeringBehavior behavior in interestBehaviors)
{
interestMap = behavior.AddContext(interestMap, steeringContext);
}
}
Thank you for any help!
I solved it. I'm going to answer my own question as there's no source code online that really goes into Context Steering behaviors, so maybe the code posted here will save somebody else the trouble I went through.
Now that I have it up and working, anybody messing around with steering behaviors I highly recommend looking into Context Steering (If you google there's some good blog posts and papers about it's benefits.) It totally fixed the jitter issues with my steering behaviors, and fixed all of the issues of blended behaviors cancelling themselves out in oddball situations.
The problem wasn't in determining the weight, but the part of DirectionContext where I convert the angle into a direction vector.
this.direction = new Vector3(Mathf.Sin(angle * Mathf.Deg2Rad), Mathf.Cos(angle * Mathf.Deg2Rad), 0).normalized;
Should have been
this.direction = new Vector3(Mathf.Cos(angle * Mathf.Deg2Rad), Mathf.Sin(angle * Mathf.Deg2Rad), 0);
The normalized isn't what ruined it, it was Cos vs Sin. Normalized was just unneccessary.
My project is make a line between a target and a AGV.
apologize my english skill. My english is not good.
here are my code how it works.
find positions of target and AGV
get a simple linear equation by use of AGV's position x,y and target's position x,y
place dots on the simple linear equation which I just found in step 2.
when my dots meet a tag "wall" do something something... (not yet made)
so here is my problem:
my C# 'MapFile.cs' Script is installed on EmptyObject 'Map'.
When scene start, dots begin to place on the linear equation gradually AGV to target.
but i want stop placing dots when the last dot meet the 'wall' objects.
so tried to use function
void onTriggerEnter(Collider other)
but as you see, 'MapFile.cs' script is installed on EmptyObject 'Map'...
I have no idea how to my dots interaction with the walls.
here is my code
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using MLAgents;
public class MapFile : Agent
{
public Transform targetPosition;
public Transform AGVposition;
public GameObject dot;
public float w, b;
public float functionTarget = 0f;
public float[] data = new float[2];
void Awake()
{
Initializing();
}
public void Initializing()
{
data[0] = AGVposition.position.x;
data[1] = AGVposition.position.y;
}
public void drawline()
{
if(data[0] < 20f && data[0] > -20f)
{
while(true)
{
data[1] = FindYValue(data[0]);
Instantiate(dot, new Vector3( data[0] , 0f , data[1] ) , Quaternion.identity);
if(targetPosition.position.x > 0 )
{
data[0] += 0.07f;
if(data[0] > 20f) break;
}
else
{
data[0] -= 0.07f;
if(data[0] < -20f)break;
}
}
}
}
void Update()
{
drawline();
}
public float FindYValue(float a)
{
float result;
float x1 = AGVposition.position.x;
float y1 = AGVposition.position.z;
float x2 = targetPosition.position.x;
float y2 = targetPosition.position.z;
result = ( (y2 - y1) / (x2 - x1) ) * (a - x1) + y1;
return result;
}
}
First off, sorry it this isn't written very well, I've spend hours debugging this and I'm very stressed. I'm trying to make a moving platform in unity that can move between way-points, I don't want to have to have tons of gameobjects in the world taking up valuable processing power though so I'm trying to use something I can just add to the script through the editor.
The only problem is that it seems to be doing this at an incredible speed:
Black = The Camera View, Blue = The platform and where it should be going based on waypoints, Red = What it is currently doing.
I've spend hours trying to find a fix but I have no idea why it's doing this.
My Script on the Platform:
public Vector3[] localWaypoints;
Vector3[] globalWaypoints;
public float speed;
public bool cyclic;
public float waitTime;
[Range(0, 2)]
public float easeAmount;
int fromWaypointIndex;
float percentBetweenWaypoints;
float nextMoveTime;
void Start()
{
globalWaypoints = new Vector3[localWaypoints.Length];
for (int i = 0; i < localWaypoints.Length; i++)
{
globalWaypoints[i] = localWaypoints[i] + transform.position;
}
}
void Update()
{
Vector3 velocity = CalculatePlatformMovement();
transform.Translate(velocity);
}
float Ease(float x)
{
float a = easeAmount + 1;
return Mathf.Pow(x, a) / (Mathf.Pow(x, a) + Mathf.Pow(1 - x, a));
}
Vector3 CalculatePlatformMovement()
{
if (Time.time < nextMoveTime)
{
return Vector3.zero;
}
fromWaypointIndex %= globalWaypoints.Length;
int toWaypointIndex = (fromWaypointIndex + 1) % globalWaypoints.Length;
float distanceBetweenWaypoints = Vector3.Distance(globalWaypoints[fromWaypointIndex], globalWaypoints[toWaypointIndex]);
percentBetweenWaypoints += Time.deltaTime * speed / distanceBetweenWaypoints;
percentBetweenWaypoints = Mathf.Clamp01(percentBetweenWaypoints);
float easedPercentBetweenWaypoints = Ease(percentBetweenWaypoints);
Vector3 newPos = Vector3.Lerp(globalWaypoints[fromWaypointIndex], globalWaypoints[toWaypointIndex], easedPercentBetweenWaypoints);
if (percentBetweenWaypoints >= 1)
{
percentBetweenWaypoints = 0;
fromWaypointIndex++;
if (!cyclic)
{
if (fromWaypointIndex >= globalWaypoints.Length - 1)
{
fromWaypointIndex = 0;
System.Array.Reverse(globalWaypoints);
}
}
nextMoveTime = Time.time + waitTime;
}
return newPos - transform.position;
}
struct PassengerMovement
{
public Transform transform;
public Vector3 velocity;
public bool standingOnPlatform;
public bool moveBeforePlatform;
public PassengerMovement(Transform _transform, Vector3 _velocity, bool _standingOnPlatform, bool _moveBeforePlatform)
{
transform = _transform;
velocity = _velocity;
standingOnPlatform = _standingOnPlatform;
moveBeforePlatform = _moveBeforePlatform;
}
}
void OnDrawGizmos()
{
if (localWaypoints != null)
{
Gizmos.color = Color.red;
float size = .3f;
for (int i = 0; i < localWaypoints.Length; i++)
{
Vector3 globalWaypointPos = (Application.isPlaying) ? globalWaypoints[i] : localWaypoints[i] + transform.position;
Gizmos.DrawLine(globalWaypointPos - Vector3.up * size, globalWaypointPos + Vector3.up * size);
Gizmos.DrawLine(globalWaypointPos - Vector3.left * size, globalWaypointPos + Vector3.left * size);
}
}
}
UPDATE: Upon further testing I found that if the first object in my localWaypoint array is set to 0,0,0 and my 2nd object is set to 1,0,0 then the platform will spiral to the right, making sure to hit the waypoints as it's spiraling, and then spiraling out into nowhere like in the image above. But if I set my first object to 0,0,0 and my second object to -1,0,0 then the object will act the same way as before, but will spiral to the left as displayed in this image. (The second image has also bee updated to display how the platfrom makes sure to hit both waypoints before is spirals out into nowhere).
I've also noticed that if I set both waypoints to 0,0,0 then the platform stays still, these 2 things prove that it has somthing to do with the way the waypoints are being handled and not some other script or parent object interfering.
Using the updated numbers ([0,0,0], [1,0,0]) works in my test app. However, if I put a rotation on the object's Y axis, then I see behavior like you are seeing. In Update, if you change:
transform.Translate(velocity);
to
transform.Translate(velocity, Space.World);
You should see your desired behavior. Note that "transform.Translate(velocity)" is the same as "transform.Translate(velocity, Space.Self)". Your translation is being rotated.
If you are curious, take a look at this for more information on how the values in the transform are applied:
https://gamedev.stackexchange.com/questions/138358/what-is-the-transformation-order-when-using-the-transform-class
So I have been working on a piece of code that generates a perlin noise texture and applies it to a plane in order to create waves. But I can't get it to set the heightmap texture of the material. I have included material.EnableKeyword("_PARALLAXMAP"); but it does nothing. I have tried this with the normal map as well, without results. Here's the full code.
using UnityEngine;
using System.Collections;
public class NoiseGenerator : MonoBehaviour {
private Texture2D noiseTex;
private float x = 0.0F;
private float y = 0.0F;
public int scale = 10;
private Color[] pixels;
public float speed;
public float move = 0.0F;
void Start () {
Renderer render = GetComponent<Renderer>();
noiseTex = new Texture2D(scale,scale);
render.material = new Material(Shader.Find("Standard"));
render.material.EnableKeyword("_PARALLAXMAP");
render.material.SetTexture("_PARALLAXMAP", noiseTex);
pixels = new Color[noiseTex.width * noiseTex.height];
}
void Update () {
float y = 0.0F;
while (y < noiseTex.height)
{
float x = 0.0F;
while (x < noiseTex.width)
{
float xCoord = move + x / noiseTex.width * scale;
float yCoord = move + y / noiseTex.height * scale;
float sample = Mathf.PerlinNoise(xCoord, yCoord);
pixels[Mathf.RoundToInt(y) * noiseTex.width + Mathf.RoundToInt(x)] = new Color(sample, sample, sample);
x++;
}
y++;
}
noiseTex.SetPixels(pixels);
noiseTex.Apply();
move = move + speed;
}
}
You need to include a Material that use this Parallax variant to notify Unity about you need this.
This can be used in the scene or include in the resource folder.
If not Unity omit this on build how unused.
Just use
ur_material.SetFloat("_Parallax",[value])