I am attempting to apply a gradient effect on a Unity3D(5.2) gui object but its as if one of the gradient color keys is being completely ignored. I have tried with both instantiating a new gradient field and declaring a gradient field public and edit its keys in the editor but yet the effects remain the same.
I'm beginning to think that I am not supposed to use Gradients in a BaseMeshEffect in the way I am using it. If only have 2 keys, the colors render properly. Where am I wrong?
Here is a code sample followed by a screen shot.
public class GradientUI : BaseMeshEffect
{
[SerializeField]
public Gradient Grad;
public override void ModifyMesh(VertexHelper vh)
{
if (!IsActive())
{
return;
}
List<UIVertex> vertexList = new List<UIVertex>();
vh.GetUIVertexStream(vertexList);
ModifyVertices(vertexList);
vh.Clear();
vh.AddUIVertexTriangleStream(vertexList);
}
void ModifyVertices(List<UIVertex> vertexList)
{
int count = vertexList.Count;
float bottomY = vertexList[0].position.y;
float topY = vertexList[0].position.y;
for (int i = 1; i < count; i++)
{
float y = vertexList[i].position.y;
if (y > topY)
{
topY = y;
}
else if (y < bottomY)
{
bottomY = y;
}
}
float uiElementHeight = topY - bottomY;
for (int i = 0; i < count; i++)
{
UIVertex uiVertex = vertexList[i];
float percentage = (uiVertex.position.y - bottomY) / uiElementHeight;
// Debug.Log(percentage);
Color col = Grad.Evaluate(percentage);
uiVertex.color = col;
vertexList[i] = uiVertex;
Debug.Log(uiVertex.position);
}
}
Screen shot
Your script is actually OK, no problem with it. The problem here is that UI elements simply don't have enough geometry for you to actually see the whole gradient.
Let me explain. In a nutshell, each UI element is actually a mesh made of several 3D triangles, each one rotated to face the camera exactly with its front so it looks 2D. You filter works by assigning a color value to each vertex of those triangles. The color values in the middle of triangles are interpolated based on the proximity to each of the colored vertices.
If you look at UI element in wireframe, you will see that its geometry is very simple. This is for example how a sliced image looks:
As you can see, all of its vertices are concentrated at the corners, and there are no vertices in the middle. So, lets assume your gradient is 2 keys WHITE=>RED. The upper vertices get value WHITE or close to WHITE, the bottom values get value RED or close to RED. This works OK for 2 keys.
Now assume you have 3 keys WHITE=>BLUE=>RED. The upper value is WHITE or close to WHITE, the bottom values get value RED or close to RED, the BLUE value is supposed to be somewhere in the middle, but there is no vertex in the middle, so it is not assigned to anything. So you still get WHITE to RED gradient.
Now, what you can do:
1) You can add some more geometry programmatically, for example by simply subdividing the whole mesh. This may help you: http://answers.unity3d.com/questions/259127/does-anyone-have-any-code-to-subdivide-a-mesh-and.html. Pay attention that in this case, the more keys your gradient has, the more subdivisions are required.
2) Use texture that looks like a gradient gradient.
Related
I am trying to create a basic tower defense game where I have set up my cell size to be 0.5 X and 0.5 Y (so that the player can place their towers more freely, think WC3).
This causes problems when I later in the game wants to check if a grid cell is occupied, because some cells will seem to be taken, but actually are not.
Here's an image to illustrate my problem:
The black square is rendered over 4 cells, but only 1 of the cells are occupied (the white square in the lower left corner of the black square).
Have anyone else faced this particular problem and knows how to solve this or are there any other solutions that you would like to recommend? :)
Thanks in advance!
Though I'm unsure how the unity Grid component works I have used the following approach in one of my grid based games. Note however that I implemented my own grid for this, which contain custom grid tiles. But maybe the same logic can be applied to the unity grid
This grid was just a simple grid made like this
for (int i = 0; i < terrainLength; i++)
{
for (int j = 0; j < terrainWidth; j++)
{
GameObject tile = Instantiate(gridTilePrefab, new Vector3(posX + i * gridCube.transform.localScale.x, posY + terrainHeight, posZ + j * gridCube.transform.localScale.z), Quaternion.identity);
tile.name = "grid[" + i + "," + j + "]";
tile.transform.parent = gridParent.transform;
}
}
These grid tiles would have a boolean isOccupied. That would be set to true if an object is placed on it, and false if not.
To check wether or not it was occupied I would simply cast a raycast up from the center of the tile and check for any collision while in the builder phase (No need to do these checks during play!) the implemented was a simple as this:
Class GridTile
{
public bool isOccupied {get; private set;}
public void BuildStageLoop()//this loops like an update while we're in building stage
{
if (Physics.Raycast(transform.position, Vector3.up * 2, out hit))
{
tileOccupied = true;
}
else
{
tileOccupied = false;
}
}
}
And on the placing object I would just check if every tile underneath it had isOccupied set to false. To check for the tiles underneath it I would do a boxRayCast downwards with the width and length of the object you're trying to place, and extending a bit underneath the object so it can collide with the grid tiles.
I'm writing a Compute Shader (in the unity environment, which uses DirectX11 DirectCompute) which I need to do a very simple task: check whether any pixel in the image has green == 1 and blue > 0.5. (For clarity - the green channel is a 2d "light" and the blue channel is that lights "trail" - I want to detect whenever the light crosses back over its trail.)
I'm as far as displaying the overlap (shown in white) for debugging purposes, but I have no idea how to do something as simple as return a value indicating whether the texture actually contains an overlap. My confusion stems from how threads work. I have a float buffer with room for a single float - I simply need a 1 or 0.
For clarification the following two images show a "before" and "after" - all I need is a single "true" value telling me that some white exists in the second image.
The compute shader is as follows:
#pragma kernel CSMain
Texture2D<float4> InputTexture;
RWTexture2D<float4> OutputTexture;
RWStructuredBuffer<float> FloatBuffer;
[numthreads(8,8,1)]
void CSMain(uint3 id : SV_DispatchThreadID)
{
// need to detect any region where g == 1 and blue > 0.5
float green = InputTexture[id.xy].g;
float blue = round(InputTexture[id.xy].b);
float overlap = round((green + blue) / 2.0);
OutputTexture[id.xy] = float4(overlap, overlap, overlap, 1);
// answer here?? Note that the output texture is only for debugging purposes
FloatBuffer[0] = ??
}
You have the option of using atomic operation and count the pixels. You run your compute shader with one thread per pixel, and if the pixel meet the criteria, increment your rwbuffer.
Something like this :
Texture2D<float4> InputTexture;
RWBuffer<uint> NotAFloatBuffer;
[numthreads(8,8,1)]
void CSMain(uint3 id : SV_DispatchThreadID {
// need to detect any region where g == 1 and blue > 0.5
float green = InputTexture[id.xy].g;
float blue = round(InputTexture[id.xy].b);
float overlap = round((green + blue) / 2.0);
if (overlap > 0)
InterlockedAdd(NotAFloatBuffer[0],1);
}
In your case, you can stop here but atomics have some little cost penalties and often they are optimized by grouping the call from one single thread in your group with prior reduction, but this is only in the most extreme cases, you do not have to worry about that.
So my goal is simple. I am trying to get my coordinate space set up, so that the origin is at the bottom left of the screen, and the top right coordinates are (screen.width, screen.height).
Also this is a COMPLETELY 2d engine, so no 3d stuff is needed. I just need those coordinates to work.
Right now I am trying to plot a couple points on the screen. Mostly at places like (0, 0), (width, height), (width / 2, height /2) etc so I can see if things are working right.
Unfortunately right now my efforts to get this going are in vain, and instead of having multiple points I have one in the dead center of the device (obviously they are all overlapping).
So here is my code what exactly am I doing wrong?
Vertex Shader
uniform vec4 color;
uniform float pointSize;
uniform mat4 orthoMatrix;
attribute vec3 position;
varying vec4 outColor;
varying vec3 center;
void main() {
center = position;
outColor = color;
gl_PointSize = pointSize;
gl_Position = vec4(position, 1) * orthoMatrix;
}
And here is how I make the matrix. I am using GLKit so it is theoretically making the orthographic matrix for me. However If you have a custom function you think would better do this then that is fine! I can use it too.
var width:Int32 = 0
var height:Int32 = 0
var matrix:[GLfloat] = []
func onload()
{
width = Int32(self.view.bounds.size.width)
height = Int32(self.view.bounds.size.height)
glViewport(0, 0, GLsizei(height), GLsizei(width))
matrix = glkitmatrixtoarray( GLKMatrix4MakeOrtho(0, GLfloat(width), 0, GLfloat(height), -1, 1))
}
func glkitmatrixtoarray(mat: GLKMatrix4) -> [GLfloat]
{
var buildme:[GLfloat] = []
buildme.append(mat.m.0)
buildme.append(mat.m.1)
buildme.append(mat.m.3)
buildme.append(mat.m.4)
buildme.append(mat.m.5)
buildme.append(mat.m.6)
buildme.append(mat.m.7)
buildme.append(mat.m.8)
buildme.append(mat.m.9)
buildme.append(mat.m.10)
buildme.append(mat.m.11)
buildme.append(mat.m.12)
buildme.append(mat.m.13)
buildme.append(mat.m.15)
return buildme
}
Passing it over to the shader
func draw()
{
//Setting up shader for use
let loc3 = glGetUniformLocation(program, "orthoMatrix")
if (loc3 != -1)
{
//glUniformMatrix4fv(loc3, 1, GLboolean(GL_TRUE), &matrix[0])
glUniformMatrix4fv(loc3, 1, GLboolean(GL_TRUE), &matrix[0])
}
//Passing points and extra data
}
Note: If you remove the multiplication with the matrix in the vertex shader the points show up, however obiously most of them are off screen because of how default OpenGL works.
Also: I have tried using this function rather then glKit's method. Same results. Perhaps I am not passing there might things into the matrix making function, or maybe im not getting it to the shader properly.
EDIT: I have thrown up the project file incase you want to see how everything goes.
OK I finally figured this out! What I did
1. I miscounted when turning the glkit matrix to an array.
2. When passing the matrix as a uniform you actually want the address of the whole array not just the beginning element.
3. GL_FALSE is not a proper argument when passing the matrix to the shader.
Thankyou reto matic
I am using Unity 2D and I am trying to achieve this simple effect.
I am making a Candy Crush Saga - like game.
I have a grid with all the items. In every level the grid field can have different shapes, created at runtime (a dark grey area in the middle of the background).
The background is a still image (blue sky and green hills). When the pieces (for example a blue pentagon) fall down from the top they must be hidden until they enter the grid field area (dark grey); so in practice the background image (sky and hills) is no more a background, but is a foreground with a hole that is represented by the grey area. The grey field is composed as well by tiles from a sprite sheet.
I have prepared a picture, but I cannot load it here unfortunately yet. How can I achieve this effect in Unity?
The most simple solution would be to create all the static levels graphics already with the hole, but I do not want to create them because it is a waste of time and also a waste of memory, I want to be able to create this effect at runtime.
I was thinking about creating a dynamic bitmap mask for the hole shape using a sprite sheet. Then using this bitmap mask as a material for example to be applied to the image in the foreground and make a hole.
click on your texture in the unity editor
change "texture type" from "texture" to "advanced"
check the "read/write enabled" checkbox
change "format" form "automatic compressed" to "RGBA 32 bit"
i attached this component to a raw image (you can attach it to something else, just change the "RawImage image" part)
this will create a hole with dimensions 100x100 at position 10,10 of the image, so make sure that your texture is at least 110x110 large.
using UnityEngine;
using UnityEngine.UI;
public class HoleInImage : MonoBehaviour
{
public RawImage image;
void Start()
{
// this will change the original:
Texture2D texture = image.texture as Texture2D;
// use this to change a copy (and not the original)
//Texture2D texture = Instantiate(image.mainTexture) as Texture2D;
//image.mainTexture = texture;
Color[] colors = new Color[100*100];
for( int i = 0; i < 100*100; ++i )
colors[i] = Color.clear;
texture.SetPixels( 10, 10, 100, 100, colors );
texture.Apply(false);
}
}
EDIT:
hole defined by one or more sprites:
do the same for these sprites: (advanced, read/write enabled, RGBA 32 bit)
for example: if sprite is white and the hole is defined with black:
for( int i = 0; i < 100*100; ++i )
colors[i] = Color.clear;
change to:
Texture2D holeTex; // set this in editor, it must have same dimensions (100x100 in my example)
Color[] hole = holeTex.GetPixels();
for( int i = 0; i < 100*100; ++i )
{
if( hole[i] == Color.black ) // where sprite is black, there will be a hole
colors[i] = Color.clear;
}
During the last few days i was coding a painting behavior for a game am working on, and am currently in a very advanced phase, i can say that i have 90% of the work done and working perfectly, now what i need to do is being able to draw with a "soft brush" cause for now it's like am painting with "pixel style" and that was totally expected cause that's what i wrote,
my current goal consist of using this solution :
import a brush texture, this image
create an array that contain all The alpha values of that texture
When drawing use the array elements in order to define the new pixels alpha
And this is my code to do that (it's not very long, there is too much comments)
//The main painting method
//theObject = the object to be painted
//tmpTexture = the object current texture
//targetTexture = the new texture
void paint (GameObject theObject, Texture2D tmpTexture, Texture2D targetTexture)
{
//x and y are 2 floats from another class
//they store the coordinates of the pixel
//that get hit by the RayCast
int x = (int)(coordinates.pixelPos.x);
int y = (int)(coordinates.pixelPos.y);
//iterate through a block of pixels that goes fro
//Y and X and go #brushHeight Pixels up
// and #brushWeight Pixels right
for (int tmpY = y; tmpY<y+brushHeight; tmpY++) {
for (int tmpX = x; tmpX<x+brushWidth; tmpX++) {
//check if the current pixel is different from the target pixel
if (tmpTexture.GetPixel (tmpX, tmpY) != targetTexture.GetPixel (tmpX, tmpY)) {
//create a temporary color from the target pixel at the given coordinates
Color tmpCol = targetTexture.GetPixel (tmpX, tmpY);
//change the alpha of that pixel based on the brush alpha
//myBrushAlpha is a 2 Dimensional array that contain
//the different Alpha values of the brush
//the substractions are to keep the index in range
if (myBrushAlpha [tmpY - y, tmpX - x].a > 0) {
tmpCol.a = myBrushAlpha [tmpY - y, tmpX - x].a;
}
//set the new pixel to the current texture
tmpTexture.SetPixel (tmpX, tmpY, tmpCol);
}
}
}
//Apply
tmpTexture.Apply ();
//change the object main texture
theObject.renderer.material.mainTexture = tmpTexture;
}
Now the fun (and bad) part is the code did exactly what i asked for, but there is something that i didn't think of and i couldn't solve after spend the whole night trying,
the thing is that by asking to draw anytime with the brush alpha i found myself create a very weird effect which is decreasing the alpha value of an "old" pixel, so i tried to fix that by adding an if statement that check if the current alpha of the pixel is less than the equivalent brush alpha pixel, if it is, then augment the alpha to be equal to the brush, and if the pixel alpha is bigger, then keep adding the brush alpha value to it in order to have that "soft brushing" effect, and in code it become this :
if (myBrushAlpha [tmpY - y, tmpX - x].a > tmpCol.a) {
tmpCol.a = myBrushAlpha [tmpY - y, tmpX - x].a;
} else {
tmpCol.a += myBrushAlpha [tmpY - y, tmpX - x].a;
}
But after i've done that, i got the "pixelized brush" effect back, am not sure but i think maybe it's because am making these conditions inside a for loop so everything is executed before the end of the current frame so i don't see the effect, could it be that ?
Am really lost here and hope that you can put me in the right direction,
Thank you very much and have a great day