I am trying to implement fur in Unity with the Shells technique. The Fins technique is purposely left out because I want this to run on low end mobiles (mostly Android devices) and that requires OpenGL ES 3.0 and above while Shells technique only requires OpenGL ES 2.0.
There is an example on the Shell technique based on XNA and I made an attempt to port that into Unity but it failed to work. Here is the article with the XNA project.
The XNA shader:
float4x4 World;
float4x4 View;
float4x4 Projection;
float CurrentLayer; //value between 0 and 1
float MaxHairLength; //maximum hair length
texture FurTexture;
sampler FurSampler = sampler_state
{
Texture = (FurTexture);
MinFilter = Point;
MagFilter = Point;
MipFilter = Point;
AddressU = Wrap;
AddressV = Wrap;
};
struct VertexShaderInput
{
float3 Position : POSITION0;
float3 Normal : NORMAL0;
float2 TexCoord : TEXCOORD0;
};
struct VertexShaderOutput
{
float4 Position : POSITION0;
float2 TexCoord : TEXCOORD0;
};
VertexShaderOutput FurVertexShader(VertexShaderInput input)
{
VertexShaderOutput output;
float3 pos;
pos = input.Position + input.Normal * MaxHairLength * CurrentLayer;
float4 worldPosition = mul(float4(pos,1), World);
float4 viewPosition = mul(worldPosition, View);
output.Position = mul(viewPosition, Projection);
output.TexCoord = input.TexCoord;
return output;
}
float4 FurPixelShader(VertexShaderOutput input) : COLOR0
{
return tex2D(FurSampler, input.TexCoord);
}
technique Fur
{
pass Pass1
{
AlphaBlendEnable = true;
SrcBlend = SRCALPHA;
DestBlend = INVSRCALPHA;
CullMode = None;
VertexShader = compile vs_2_0 FurVertexShader();
PixelShader = compile ps_2_0 FurPixelShader();
}
}
The XNA C# script that controls the shader:
/// <summary>
/// This is the main type for your game
/// </summary>
public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
public Game1()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
}
//simple camera for use in the game
Camera camera;
//texture containing fur data
Texture2D furTexture;
//effect for fur shaders
Effect furEffect;
//number of layers of fur
int nrOfLayers = 60;
//total length of the hair
float maxHairLength = 2.0f;
//density of hair
float density = 0.2f;
Texture2D furColorTexture;
//movement vectors
Vector3 gravity = new Vector3(0, -1.0f, 0);
Vector3 forceDirection = Vector3.Zero;
//final displacement for hair
Vector3 displacement;
/// <summary>
/// Allows the game to perform any initialization it needs to before starting to run.
/// This is where it can query for any required services and load any non-graphic
/// related content. Calling base.Initialize will enumerate through any components
/// and initialize them as well.
/// </summary>
protected override void Initialize()
{
// TODO: Add your initialization logic here
camera = new Camera(this);
Components.Add(camera);
base.Initialize();
}
/// <summary>
/// LoadContent will be called once per game and is the place to load
/// all of your content.
/// </summary>
protected override void LoadContent()
{
// Create a new SpriteBatch, which can be used to draw textures.
spriteBatch = new SpriteBatch(GraphicsDevice);
//generate the geometry
GenerateGeometry();
//load the effect
furEffect = Content.Load<Effect>("FurEffect");
//create the texture
furTexture = new Texture2D(GraphicsDevice,
256, 256, 1,
TextureUsage.None,
SurfaceFormat.Color);
//fill the texture
FillFurTexture(furTexture, density);
furColorTexture = Content.Load<Texture2D>("bigtiger");
}
/// <summary>
/// UnloadContent will be called once per game and is the place to unload
/// all content.
/// </summary>
protected override void UnloadContent()
{
// TODO: Unload any non ContentManager content here
}
/// <summary>
/// Allows the game to run logic such as updating the world,
/// checking for collisions, gathering input, and playing audio.
/// </summary>
/// <param name="gameTime">Provides a snapshot of timing values.</param>
protected override void Update(GameTime gameTime)
{
// Allows the game to exit
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
this.Exit();
// TODO: Add your update logic here
base.Update(gameTime);
}
/// <summary>
/// This is called when the game should draw itself.
/// </summary>
/// <param name="gameTime">Provides a snapshot of timing values.</param>
protected override void Draw(GameTime gameTime)
{
forceDirection.X = (float)Math.Sin(gameTime.TotalGameTime.TotalSeconds) * 0.5f;
displacement = gravity + forceDirection;
furEffect.Parameters["Displacement"].SetValue(displacement);
graphics.GraphicsDevice.Clear(Color.CornflowerBlue);
furEffect.Parameters["World"].SetValue(Matrix.CreateTranslation(0, -10, 0));
furEffect.Parameters["View"].SetValue(camera.View);
furEffect.Parameters["Projection"].SetValue(camera.Projection);
furEffect.Parameters["MaxHairLength"].SetValue(maxHairLength);
furEffect.Parameters["FurTexture"].SetValue(furTexture);
furEffect.Parameters["Texture"].SetValue(furColorTexture);
furEffect.Begin();
for (int i = 0; i < nrOfLayers; i++)
{
furEffect.Parameters["CurrentLayer"].SetValue((float)i / nrOfLayers);
furEffect.CommitChanges();
furEffect.CurrentTechnique.Passes[0].Begin();
DrawGeometry();
furEffect.CurrentTechnique.Passes[0].End();
}
furEffect.End();
base.Draw(gameTime);
}
/// <summary>
/// This functions prepares a texture to be used for fur rendering
/// </summary>
/// <param name="furTexture">This will contain the final texture</param>
/// <param name="density">Hair density in [0..1] range </param>
private void FillFurTexture(Texture2D furTexture, float density)
{
//read the width and height of the texture
int width = furTexture.Width;
int height = furTexture.Height;
int totalPixels = width * height;
//an array to hold our pixels
Color[] colors;
colors = new Color[totalPixels];
//random number generator
Random rand = new Random();
//initialize all pixels to transparent black
for (int i = 0; i < totalPixels; i++)
colors[i] = Color.TransparentBlack;
//compute the number of opaque pixels = nr of hair strands
int nrStrands = (int)(density * totalPixels);
//compute the number of strands that stop at each layer
int strandsPerLayer = nrStrands / nrOfLayers;
//fill texture with opaque pixels
for (int i = 0; i < nrStrands; i++)
{
int x, y;
//random position on the texture
x = rand.Next(height);
y = rand.Next(width);
//compute max layer
int max_layer = i / strandsPerLayer;
//normalize into [0..1] range
float max_layer_n = (float)max_layer / (float)nrOfLayers;
//put color (which has an alpha value of 255, i.e. opaque)
//max_layer_n needs to be multiplied by 255 to achieve a color in [0..255] range
colors[x * width + y] = new Color((byte)(max_layer_n * 255), 0, 0, 255);
}
//set the pixels on the texture.
furTexture.SetData<Color>(colors);
}
VertexPositionNormalTexture[] vertices;
private void GenerateGeometry()
{
vertices = new VertexPositionNormalTexture[6];
vertices[0] = new VertexPositionNormalTexture(
new Vector3(-10, 0, 0),
-Vector3.UnitZ,
new Vector2(0, 0));
vertices[1] = new VertexPositionNormalTexture(
new Vector3(10, 20, 0),
-Vector3.UnitZ,
new Vector2(1, 1));
vertices[2] = new VertexPositionNormalTexture(
new Vector3(-10, 20, 0),
-Vector3.UnitZ,
new Vector2(0, 1));
vertices[3] = vertices[0];
vertices[4] = new VertexPositionNormalTexture(
new Vector3(10, 0, 0),
-Vector3.UnitZ,
new Vector2(1, 0));
vertices[5] = vertices[1];
}
private void DrawGeometry()
{
using (VertexDeclaration vdecl = new VertexDeclaration(
GraphicsDevice,
VertexPositionNormalTexture.VertexElements))
{
GraphicsDevice.VertexDeclaration = vdecl;
GraphicsDevice.DrawUserPrimitives<VertexPositionNormalTexture>(PrimitiveType.TriangleList, vertices, 0, 2);
}
}
}
I carefully ported the both the shader and the control script line by line to Unity.
The Ported Unity shader:
Shader "Programmer/Fur Shader"
{
Properties
{
_MainTex("Texture", 2D) = "white" {}
//_TintColor("Tint Color", Color) = (1,1,1,1)
}
SubShader
{
Tags{ "Queue" = "Transparent" "RenderType" = "Transparent" }
LOD 100
Blend SrcAlpha One
Blend DstAlpha OneMinusSrcAlpha
ZWrite Off
Cull Off
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
// make fog work
//#pragma multi_compile_fog
#include "UnityCG.cginc"
//In
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
//Out
struct v2f
{
float2 uv : TEXCOORD0;
UNITY_FOG_COORDS(1)
float4 vertex : SV_POSITION;
};
struct VertexShaderInput
{
float3 Position : POSITION0;
float3 Normal : NORMAL0;
float2 TexCoord : TEXCOORD0;
};
struct VertexShaderOutput
{
float4 Position : POSITION0;
float2 TexCoord : TEXCOORD0;
};
sampler2D _MainTex;
float4 _MainTex_ST;
//Test variable/delete after
float4 _TintColor;
//The variables
float4x4 World;
float4x4 View;
float4x4 Projection;
float CurrentLayer; //value between 0 and 1
float MaxHairLength; //maximum hair length
VertexShaderOutput vert(VertexShaderInput input)
{
VertexShaderOutput output;
float3 pos;
pos = input.Position + input.Normal * MaxHairLength * CurrentLayer;
float4 worldPosition = mul(float4(pos, 1), World);
float4 viewPosition = mul(worldPosition, View);
output.Position = mul(viewPosition, Projection);
output.TexCoord = input.TexCoord;
return output;
}
float4 frag(VertexShaderOutput i) : COLOR0
{
return tex2D(_MainTex, i.TexCoord);
}
ENDCG
}
}
}
The ported Unity C# script that controls the shader:
public class Game1 : MonoBehaviour
{
public Material material;
public Vector3 pos = new Vector3(0f, 0.98f, -9.54f);
//simple camera for use in the game
private new Camera camera;
//texture containing fur data
public Texture2D furTexture;
//effect for fur shaders
//Effect furEffect;
//number of layers of fur
public int nrOfLayers = 40;
//total length of the hair
public float maxHairLength = 2.0f;
//density of hair
public float density = 0.2f;
//[Space(20)]
//public Vector3 dirWorldVal = new Vector3(0, -10, 0);
void Start()
{
Initialize();
GenerateGeometry();
}
public void Update()
{
Draw();
}
void Initialize()
{
//Initialize the camera
camera = Camera.main;
//create the texture
furTexture = new Texture2D(256, 256, TextureFormat.ARGB32, false);
furTexture.wrapModeU = TextureWrapMode.Repeat;
furTexture.wrapModeV = TextureWrapMode.Repeat;
furTexture.filterMode = FilterMode.Point;
//fill the texture
FillFurTexture(furTexture, density);
/*XNA's SurfaceFormat.Color is ARGB.
//https://gamedev.stackexchange.com/a/6442/98839*/
if (material.mainTexture != null)
{
material.mainTexture.wrapModeU = TextureWrapMode.Repeat;
material.mainTexture.wrapModeV = TextureWrapMode.Repeat;
material.mainTexture.filterMode = FilterMode.Point;
}
}
bool firstDraw = true;
protected void Draw()
{
camera.backgroundColor = CornflowerBlue();
Matrix4x4 worldValue = Matrix4x4.Translate(pos);
Matrix4x4 viewValue = camera.projectionMatrix;
// viewValue = camera.worldToCameraMatrix;
Matrix4x4 projectionValue = camera.projectionMatrix;
material.SetMatrix("World", worldValue);
material.SetMatrix("View", viewValue);
material.SetMatrix("Projection", projectionValue); //Causes object to disappear
material.SetFloat("MaxHairLength", maxHairLength);
if (firstDraw)
material.SetTexture("_MainTex", furTexture);
//furEffect.Begin();
for (int i = 0; i < nrOfLayers; i++)
{
material.SetFloat("CurrentLayer", (float)i / nrOfLayers);
DrawGeometry();
}
if (firstDraw)
{
material.mainTexture.wrapModeU = TextureWrapMode.Repeat;
material.mainTexture.wrapModeV = TextureWrapMode.Repeat;
material.mainTexture.filterMode = FilterMode.Point;
}
if (firstDraw)
firstDraw = false;
}
void DrawGeometry()
{
Quaternion rotation = Quaternion.Euler(0, 180, 0);
Graphics.DrawMesh(verticesMesh, pos, rotation, material, 0, camera);
}
private VertexPositionNormalTexture[] verticesPText;
public Mesh verticesMesh;
private void GenerateGeometry()
{
verticesPText = new VertexPositionNormalTexture[6];
verticesPText[0] = new VertexPositionNormalTexture(new Vector3(-10, 0, 0),
-UnitZ(),
new Vector2(0, 0));
verticesPText[1] = new VertexPositionNormalTexture(new Vector3(10, 20, 0),
-UnitZ(),
new Vector2(1, 1));
verticesPText[2] = new VertexPositionNormalTexture(new Vector3(-10, 20, 0),
-UnitZ(),
new Vector2(0, 1));
verticesPText[3] = verticesPText[0];
verticesPText[4] = new VertexPositionNormalTexture(new Vector3(10, 0, 0),
-UnitZ(),
new Vector2(1, 0));
verticesPText[5] = verticesPText[1];
verticesMesh = VertexPositionNormalTextureToUnityMesh(verticesPText);
}
Mesh VertexPositionNormalTextureToUnityMesh(VertexPositionNormalTexture[] vpnt)
{
Vector3[] vertices = new Vector3[vpnt.Length];
Vector3[] normals = new Vector3[vpnt.Length];
Vector2[] uvs = new Vector2[vpnt.Length];
int[] triangles = new int[vpnt.Length];
//Copy variables to create a mesh
for (int i = 0; i < vpnt.Length; i++)
{
vertices[i] = vpnt[i].Position;
normals[i] = vpnt[i].Normal;
uvs[i] = vpnt[i].TextureCoordinate;
triangles[i] = i;
}
Mesh mesh = new Mesh();
mesh.vertices = vertices;
mesh.normals = normals;
mesh.uv = uvs;
mesh.triangles = triangles;
return mesh;
}
private void FillFurTexture(Texture2D furTexture, float density)
{
//read the width and height of the texture
int width = furTexture.width;
int height = furTexture.height;
int totalPixels = width * height;
//an array to hold our pixels
Color32[] colors = new Color32[totalPixels];
//random number generator
System.Random rand = new System.Random();
//initialize all pixels to transparent black
for (int i = 0; i < totalPixels; i++)
colors[i] = TransparentBlack();
//compute the number of opaque pixels = nr of hair strands
int nrStrands = (int)(density * totalPixels);
//fill texture with opaque pixels
for (int i = 0; i < nrStrands; i++)
{
int x, y;
//random position on the texture
x = rand.Next(height);
y = rand.Next(width);
//put color (which has an alpha value of 255, i.e. opaque)
colors[x * width + y] = Gold();
}
//set the pixels on the texture.
furTexture.SetPixels32(colors);
// actually apply all SetPixels, don't recalculate mip levels
furTexture.Apply();
}
Color32 TransparentBlack()
{
//http://www.monogame.net/documentation/?page=P_Microsoft_Xna_Framework_Color_TransparentBlack
Color32 color = new Color32(0, 0, 0, 0);
return color;
}
Color32 Gold()
{
//http://www.monogame.net/documentation/?page=P_Microsoft_Xna_Framework_Color_Gold
Color32 color = new Color32(255, 215, 0, 255);
return color;
}
Color32 CornflowerBlue()
{
//http://www.monogame.net/documentation/?page=P_Microsoft_Xna_Framework_Color_CornflowerBlue
Color32 color = new Color32(100, 149, 237, 255);
return color;
}
public static Vector3 UnitZ()
{
return new Vector3(0f, 0f, 1f);
}
}
The ported VertexPositionNormalTexture struct for Unity
public struct VertexPositionNormalTexture
{
public Vector3 Position;
public Vector3 Normal;
public Vector2 TextureCoordinate;
//public static readonly VertexDeclaration VertexDeclaration;
public VertexPositionNormalTexture(Vector3 position, Vector3 normal, Vector2 textureCoordinate)
{
this.Position = position;
this.Normal = normal;
this.TextureCoordinate = textureCoordinate;
}
public override int GetHashCode()
{
// TODO: FIc gethashcode
return 0;
}
public override string ToString()
{
return string.Format("{{Position:{0} Normal:{1} TextureCoordinate:{2}}}", new object[] { this.Position, this.Normal, this.TextureCoordinate });
}
public static bool operator ==(VertexPositionNormalTexture left, VertexPositionNormalTexture right)
{
return (((left.Position == right.Position) && (left.Normal == right.Normal)) && (left.TextureCoordinate == right.TextureCoordinate));
}
public static bool operator !=(VertexPositionNormalTexture left, VertexPositionNormalTexture right)
{
return !(left == right);
}
public override bool Equals(object obj)
{
if (obj == null)
{
return false;
}
if (obj.GetType() != base.GetType())
{
return false;
}
return (this == ((VertexPositionNormalTexture)obj));
}
}
The ported Unity work is not working properly. No shells and the output image is flat.
This is the expected result in XNA (Works fine):
But this is what I see in Unity (no shells):
The final image supposed to look like the image below but I can't go on with the porting work since the basic implementation is not working properly in Unity.
My script public variable settings:
Why is the the ported Unity result flat? Did I miss anything?
EDIT:
Leo mentioned about possible backface problem because Unity uses the left-handed coordinate system while XNA uses the right-handed coordinate system.
I flipped the UnitZ() value and also tried reversing the mesh vertices but there was nothing on the screen. This is not likely the issue.
Unity is doing a batch optimization on the material. You can see this in the frame debugger. Each DrawGeometry call is using the same value for CurrentLayer. You need to use a propertyblock for each call to DrawMesh.
Setting a new material causes some flickering.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
namespace foo {
public class FurBehavior : MonoBehaviour
{
public Material material;
public Vector3 pos = new Vector3(0f, 0.98f, -9.54f);
//simple camera for use in the game
private new Camera camera;
//texture containing fur data
public Texture2D furTexture;
//effect for fur shaders
//Effect furEffect;
//number of layers of fur
public int nrOfLayers = 40;
//total length of the hair
public float maxHairLength = 2.0f;
//density of hair
public float density = 0.2f;
//[Space(20)]
//public Vector3 dirWorldVal = new Vector3(0, -10, 0);
void Start()
{
this.transform.position = new Vector3(0f, 0.98f, -9.54f);
this.transform.rotation = Quaternion.Euler(0, 180, 0);
Initialize();
GenerateGeometry();
}
public void Update()
{
Draw();
}
void Initialize()
{
//Initialize the camera
camera = Camera.main;
//create the texture
furTexture = new Texture2D(256, 256, TextureFormat.ARGB32, false);
furTexture.wrapModeU = TextureWrapMode.Repeat;
furTexture.wrapModeV = TextureWrapMode.Repeat;
//furTexture.filterMode = FilterMode.Point;
//fill the texture
FillFurTexture(furTexture, density);
/*XNA's SurfaceFormat.Color is ARGB.
//https://gamedev.stackexchange.com/a/6442/98839*/
if (material.mainTexture != null)
{
material.mainTexture.wrapModeU = TextureWrapMode.Repeat;
material.mainTexture.wrapModeV = TextureWrapMode.Repeat;
// material.mainTexture.filterMode = FilterMode.Point;
}
}
bool firstDraw = true;
protected void Draw()
{
var pos = this.transform.position;
camera.backgroundColor = CornflowerBlue();
Matrix4x4 worldValue = Matrix4x4.Translate(pos);
Matrix4x4 viewValue = camera.projectionMatrix;
// viewValue = camera.worldToCameraMatrix;
Matrix4x4 projectionValue = camera.projectionMatrix;
material.SetMatrix("World", worldValue);
material.SetMatrix("View", viewValue);
material.SetMatrix("Projection", projectionValue); //Causes object to disappear
material.SetFloat("MaxHairLength", maxHairLength);
//if (firstDraw)
material.SetTexture("_MainTex", furTexture);
//furEffect.Begin();
for (int i = 0; i < nrOfLayers; i++)
{
var propertyBlock = new MaterialPropertyBlock();
var layer = (float)i / (float)nrOfLayers;
propertyBlock.SetFloat("CurrentLayer", layer);
propertyBlock.SetFloat("MaxHairLength", maxHairLength);
propertyBlock.SetColor("_TintColor", new Color(layer, layer, layer, layer));
DrawGeometry(propertyBlock);
}
if (firstDraw)
{
material.mainTexture.wrapModeU = TextureWrapMode.Repeat;
material.mainTexture.wrapModeV = TextureWrapMode.Repeat;
material.mainTexture.filterMode = FilterMode.Point;
}
if (firstDraw)
firstDraw = false;
}
void DrawGeometry(MaterialPropertyBlock props)
{
var rot = Quaternion.Euler(0, 180, 0);
Graphics.DrawMesh(verticesMesh, pos, rot, material, 0, camera, 0, props);
}
private VertexPositionNormalTexture[] verticesPText;
public Mesh verticesMesh;
private void GenerateGeometry()
{
var UnitZ = new Vector3(0, 0, 1);
var verticesPText = new VertexPositionNormalTexture[6];
verticesPText[5] = new VertexPositionNormalTexture(new Vector3(-10, 0, 0),
-UnitZ,
new Vector2(0, 0));
verticesPText[4] = new VertexPositionNormalTexture(new Vector3(10, 20, 0),
-UnitZ,
new Vector2(1, 1));
verticesPText[3] = new VertexPositionNormalTexture(new Vector3(-10, 20, 0),
-UnitZ,
new Vector2(0, 1));
verticesPText[2] = verticesPText[5];
verticesPText[1] = new VertexPositionNormalTexture(new Vector3(10, 0, 0),
-UnitZ,
new Vector2(1, 0));
verticesPText[0] = verticesPText[4];
}
Mesh VertexPositionNormalTextureToUnityMesh(VertexPositionNormalTexture[] vpnt)
{
Vector3[] vertices = new Vector3[vpnt.Length];
Vector3[] normals = new Vector3[vpnt.Length];
Vector2[] uvs = new Vector2[vpnt.Length];
int[] triangles = new int[vpnt.Length];
//Copy variables to create a mesh
for (int i = 0; i < vpnt.Length; i++)
{
vertices[i] = vpnt[i].Position;
normals[i] = vpnt[i].Normal;
uvs[i] = vpnt[i].TextureCoordinate;
triangles[i] = i;
}
Mesh mesh = new Mesh();
mesh.vertices = vertices;
mesh.normals = normals;
mesh.uv = uvs;
mesh.MarkDynamic();
mesh.triangles = triangles;
mesh.UploadMeshData(false);
return mesh;
}
private void FillFurTexture(Texture2D furTexture, float density)
{
//read the width and height of the texture
int width = furTexture.width;
int height = furTexture.height;
int totalPixels = width * height;
//an array to hold our pixels
Color32[] colors = new Color32[totalPixels];
//random number generator
System.Random rand = new System.Random();
//initialize all pixels to transparent black
for (int i = 0; i < totalPixels; i++)
colors[i] = TransparentBlack();
//compute the number of opaque pixels = nr of hair strands
int nrStrands = (int)(density * totalPixels);
//fill texture with opaque pixels
for (int i = 0; i < nrStrands; i++)
{
int x, y;
//random position on the texture
x = rand.Next(height);
y = rand.Next(width);
//put color (which has an alpha value of 255, i.e. opaque)
// colors[x * width + y] = new Color32((byte)255, (byte)x, (byte)y, (byte)255);
colors[x * width + y] = Gold();
}
//set the pixels on the texture.
furTexture.SetPixels32(colors);
// actually apply all SetPixels, don't recalculate mip levels
furTexture.Apply();
}
Color32 TransparentBlack()
{
//http://www.monogame.net/documentation/?page=P_Microsoft_Xna_Framework_Color_TransparentBlack
Color32 color = new Color32(0, 0, 0, 0);
return color;
}
Color32 Gold()
{
//http://www.monogame.net/documentation/?page=P_Microsoft_Xna_Framework_Color_Gold
Color32 color = new Color32(255, 215, 0, 255);
return color;
}
Color32 CornflowerBlue()
{
//http://www.monogame.net/documentation/?page=P_Microsoft_Xna_Framework_Color_CornflowerBlue
Color32 color = new Color32(100, 149, 237, 255);
return color;
}
public static Vector3 UnitZ()
{
return new Vector3(0f, 0f, 1f);
}
}
}
I also modified the shader to visualize the shells.
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with
'UnityObjectToClipPos(*)'
Shader "Programmer/Fur Shader"
{
Properties
{
_MainTex("Texture", 2D) = "white" {}
_TintColor("Tint Color", Color) = (1,1,1,1)
}
SubShader
{
Tags{ "Queue" = "Transparent" "RenderType" = "Transparent" }
LOD 100
//Blend SrcAlpha One
//Blend DstAlpha OneMinusSrcAlpha
Blend SrcAlpha OneMinusSrcAlpha
ZWrite Off
Cull Off
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
// make fog work
//#pragma multi_compile_fog
#include "UnityCG.cginc"
//In
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
//Out
struct v2f
{
float2 uv : TEXCOORD0;
UNITY_FOG_COORDS(1)
float4 vertex : SV_POSITION;
};
struct VertexShaderInput
{
float3 Position : POSITION0;
float3 Normal : NORMAL0;
float2 TexCoord : TEXCOORD0;
};
struct VertexShaderOutput
{
float4 Position : POSITION0;
float2 TexCoord : TEXCOORD0;
float4 Tint: COLOR1;
};
sampler2D _MainTex;
float4 _MainTex_ST;
//Test variable/delete after
float4 _TintColor;
//The variables
float4x4 World;
float4x4 View;
float4x4 Projection;
float CurrentLayer; //value between 0 and 1
float MaxHairLength; //maximum hair length
VertexShaderOutput vert(VertexShaderInput input)
{
VertexShaderOutput output;
float3 pos;
pos = input.Position + input.Normal * MaxHairLength * CurrentLayer;
//float4 worldPosition = mul(float4(pos, 1), World);
//float4 viewPosition = mul(worldPosition, View);
output.Position = UnityObjectToClipPos(pos);
output.TexCoord = input.TexCoord;
output.Tint = float4(CurrentLayer, CurrentLayer, 0, 1);
return output;
}
float4 frag(VertexShaderOutput i) : COLOR0
{
float4 t = tex2D(_MainTex, i.TexCoord) * i.Tint;
return t;//float4(t, i.x, i.y, 1);
}
ENDCG
}
}
}
Here's what it looks like after messing with the parameters and moving the camera a bit.
Related
Hello I'm using c# with monogame and rendering models with hardware instancing I extract a models vertices, normal, textureCoordinate and color. When I render the model using its Texture and textureCoordinate with HLSL the model looks fine. But when I render the model only by its Color it comes up all broken and wrong.
The left model is correct this is the same 3d model but is loaded as a single 3d model without hardware instancing. This is how the colors should look I used blender to manually set these colors.
The right model is using hardware instancing, the shape looks correct but the colors are wrong.
This is how I initialize the custom vertex decleration:
public struct VertexPositionNormalTextureColor
{
public Vector3 Position;
public Vector3 Normal;
public Vector2 TextureCoordinate;
public Vector4 Color;
public VertexPositionNormalTextureColor(Vector3 _Position, Vector3 _Normal, Vector2 _TextureCoordinate, Vector4 _Color)
{
Position = _Position;
Normal = _Normal;
TextureCoordinate = _TextureCoordinate;
Color = _Color;
}
static public VertexDeclaration VertexDeclaration { get { return MyVertexDeclaration; } }
static readonly VertexDeclaration MyVertexDeclaration = new VertexDeclaration(new VertexElement[]
{
new VertexElement( 0, VertexElementFormat.Vector3, VertexElementUsage.Position, 0 ),
new VertexElement(sizeof(float) * 3, VertexElementFormat.Vector3, VertexElementUsage.Normal, 0 ),
new VertexElement(sizeof(float) * 6, VertexElementFormat.Vector2, VertexElementUsage.TextureCoordinate, 0 ),
new VertexElement(sizeof(float) * 8, VertexElementFormat.Vector4, VertexElementUsage.Color, 0 )
});
}
This is how I gather the models vertices, normal, textureCoordinate and color:
List<Vector3> vertices = new List<Vector3>();
List<Vector3> normal = new List<Vector3>();
List<Vector2> texCoord = new List<Vector2>();
List<Vector4> color = new List<Vector4>();
bones = new Matrix[myModel.Bones.Count];
myModel.CopyAbsoluteBoneTransformsTo(bones);
foreach (ModelMesh mm in myModel.Meshes)
{
foreach (ModelMeshPart mmp in mm.MeshParts)
{
VertexPositionNormalTextureColor[] vertexData = new
VertexPositionNormalTextureColor[mmp.NumVertices];
mmp.VertexBuffer.GetData(mmp.VertexOffset * mmp.VertexBuffer.VertexDeclaration.VertexStride,
vertexData, 0, mmp.NumVertices, mmp.VertexBuffer.VertexDeclaration.VertexStride);
for (int i = 0; i != vertexData.Length; i++)
{
vertices.Add(vertexData[i].Position);
normal.Add(vertexData[i].Normal);
texCoord.Add(vertexData[i].TextureCoordinate);
color.Add(vertexData[i].Color);
}
}
}
This is how I set the models vertices, normal, textureCoordinate, color to the vertex buffer:
jvertices = new List<VertexPositionNormalTextureColor>(vertices.Count);
for(int i = 0; i < vertices.Count(); i++)
{
jvertices.Add(new VertexPositionNormalTextureColor(vertices[i], normal[i], texCoord[i], color[i]));
}
geometryBuffer = new VertexBuffer(device, VertexPositionNormalTextureColor.VertexDeclaration, vertices.Count(), BufferUsage.WriteOnly);
geometryBuffer.SetData(jvertices.ToArray());
The code for the HLSL is as below:
struct VertexShaderInput
{
float3 inPositionOS : SV_Position;
float3 NormalOS : NORMAL0;
float2 inTexCoords : TEXCOORD0;
float4 inColor : COLOR0;
};
struct VertexShaderOutput
{
float4 PositionCS : SV_Position; //clip space
float4 PositionWS : POSITIONWS; //world space
float3 NormalWS : NORMAL0;
float2 inTexCoords : TEXCOORD0;
float4 inColor : COLOR0;
};
VertexShaderOutput InstancingVS(VertexShaderInput input, float4x4 instanceTransform : TEXCOORD2)
{
VertexShaderOutput output;
float4x4 instance = transpose(instanceTransform);
output.PositionWS = mul(float4(input.inPositionOS.xyz, 1.0f), instance);
output.PositionCS = mul(output.PositionWS, ViewProjection);
output.NormalWS = normalize(mul(input.NormalOS, (float3x3)instance));
output.inTexCoords = input.inTexCoords;
output.inColor = input.inColor;
return output;
}
float4 InstancingPS(VertexShaderOutput input) : COLOR0
{
float4 color = input.inColor;
if (color.a < 0.75f) { clip(-1); return color; }
else color.a = 1;
return color;
}
If anyone notices any issue that might be the reason why Color wont work on HLSL but a texture works perfectly fine.
Edit:
I changed the order of the variables in the struct setting the vertex declaration. It turns out it changed the result I looked into what mmp.VertexBuffer.GetData() actually had stored from the model and for some reason it doesn't have Color.
static readonly VertexDeclaration MyVertexDeclaration = new VertexDeclaration(new VertexElement[]
{
new VertexElement(0, VertexElementFormat.Vector3, VertexElementUsage.Position, 0 ),
new VertexElement(12, VertexElementFormat.Vector3, VertexElementUsage.Normal, 0 ),
new VertexElement(24, VertexElementFormat.Vector2, VertexElementUsage.TextureCoordinate, 0 ),
new VertexElement(32, VertexElementFormat.Vector3, VertexElementUsage.Tangent, 0 ),
new VertexElement(44, VertexElementFormat.Vector3, VertexElementUsage.Binormal, 0 )
});
I followed the offset and order for these variables in the vertex declaration struct and it seems to be working. How do I make mmp.VertexBuffer.GetData() also gather the Color from the model, Thankyou.
I want to set each three vertex of a triangle from a mesh red, blue and green.
As seen in the first part this tutorial which is for another language. This is the code they are using to set red, green and blue to each vertext in the each triangle from a mesh:
function set_wireframe_colors(m)
local cc = {}
for i = 1, m.size/3 do
table.insert(cc, color(255,0,0))
table.insert(cc, color(0,255,0))
table.insert(cc, color(0,0,255))
end
m.colors = cc
end
and this is what the output looks like with a simple vertex color shader:
I tried to recreate the-same thing in Unity with C# but I am struggling with the first part of this tutorial.
Here is my code:
void Start()
{
Mesh mesh = GetComponent<MeshFilter>().mesh;
Vector3[] vertices = mesh.vertices;
//Create new colors array where the colors will be created.
Color32[] colors = new Color32[vertices.Length];
for (int i = 0; i < vertices.Length; i += 3)
{
colors[i] = new Color32(255, 0, 0, 255);
colors[i + 1] = new Color32(0, 255, 0, 255);
colors[i + 2] = new Color32(0, 0, 255, 255);
}
//assign the array of colors to the Mesh.
mesh.colors32 = colors;
}
but this is the output I get from Unity with a simple vertex color shader:
If you look closely you will see that each vertex in my cube does not have an rgb color assigned to it like the cube from my first screenshot. It looks very close though.
What's wrong with the code? Why does each vertex not have rgb color like the image from my first screenshot.
Shader:
This problem likely has nothing to do with the shader but here the simple color shader in Unity:
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
float4 color : COLOR;
};
struct v2f
{
float2 uv : TEXCOORD0;
UNITY_FOG_COORDS(1)
float4 vertex : SV_POSITION;
float4 color : COLOR;
};
sampler2D _MainTex;
float4 _MainTex_ST;
v2f vert(appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.color = v.color;
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
UNITY_TRANSFER_FOG(o,o.vertex);
return o;
}
float4 frag(v2f i) : SV_Target
{
return i.color;
}
The mesh he is using has a 3 separate vertices for each triangle (6 vertices per quad). In a unity cube each face is a quad with 4 vertices and the 2 triangles on each face share 2 of the vertices.
On the left is a quad that has 4 vertices and the mesh.triangles array would be 0 1 2 1 0 3, and on the right is a quad with 6 vertices with mesh.triangles = 0 1 2 3 4 5 (vertex order matters for back-face culling. In my shader I have Cull set to off).
So as you can see in the image for this shader you can use meshes made out of 4 vertices quads as long as you are careful and make sure that each triangle has one of each color in each vertex.
As I said in the comments you can split your mesh so that you have 3 unique vertices for each triangle.
void Start () {
Mesh mesh = GetComponent<MeshFilter>().mesh;
SplitMesh(mesh);
SetColors(mesh);
}
void SplitMesh(Mesh mesh)
{
int[] triangles = mesh.triangles;
Vector3[] verts = mesh.vertices;
Vector3[] normals = mesh.normals;
Vector2[] uvs = mesh.uv;
Vector3[] newVerts;
Vector3[] newNormals;
Vector2[] newUvs;
int n = triangles.Length;
newVerts = new Vector3[n];
newNormals = new Vector3[n];
newUvs = new Vector2[n];
for(int i = 0; i < n; i++)
{
newVerts[i] = verts[triangles[i]];
newNormals[i] = normals[triangles[i]];
if (uvs.Length > 0)
{
newUvs[i] = uvs[triangles[i]];
}
triangles[i] = i;
}
mesh.vertices = newVerts;
mesh.normals = newNormals;
mesh.uv = newUvs;
mesh.triangles = triangles;
}
void SetColors(Mesh mesh)
{
Color[] colors = new Color[mesh.vertexCount];
for (int i = 0; i < colors.Length; i+=3)
{
colors[i] = Color.red;
colors[i + 1] = Color.green;
colors[i + 2] = Color.blue;
}
mesh.colors = colors;
}
Not a very elegant solution, but this works for me.
Mesh newMesh = newObject.GetComponent<MeshFilter>().mesh = mesh;
Vector3[] vertices = newMesh.vertices;
Vector3[] normals = newMesh.normals;
int[] triangles = newMesh.triangles;
var newVertices = new Vector3[triangles.Length];
var newNormals = new Vector3[triangles.Length];
var newTriangles = new int[triangles.Length];
for (var i = 0; i < triangles.Length; i++)
{
newVertices[i] = vertices[triangles[i]];
newNormals[i] = normals[triangles[i]];
newTriangles[i] = i;
}
newMesh.vertices = newVertices;
newMesh.normals = newNormals;
newMesh.triangles = newTriangles;
Color[] colors = new Color[newVertices.Length];
for (int i = 0; i < newVertices.Length-5; i++)
{
float r = UnityEngine.Random.value;
float g = UnityEngine.Random.value;
float b = UnityEngine.Random.value;
if (i % 6 == 0)
{
colors[i] = new Color(r, g, b, 1f);
colors[i + 1] = new Color(r, g, b, 1f);
colors[i + 2] = new Color(r, g, b, 1f);
r = UnityEngine.Random.value;
g = UnityEngine.Random.value;
b = UnityEngine.Random.value;
colors[i + 3] = new Color(r, g, b, 1f);
colors[i + 4] = new Color(r, g, b, 1f);
colors[i + 5] = new Color(r, g, b, 1f);
}
}
newMesh.colors = colors;
I have a Shape class that has 4 points (Vector2). This way, the shape can be manipulated for whatever purpose. I'm wanting to be able to fill this shape with a color of choice, but am not sure where to begin. I found references to something called "VertexPositionColorTexture" but soon realized it was for 3D space only. My program is 2D only and I am at a loss.
If someone has suggestions to get to where I am trying, I will appreciate the help.
public class CShape
{
public Vector2 PointA { get; set; }
public Vector2 PointB { get; set; }
public Vector2 PointC { get; set; }
public Vector2 PointD { get; set; }
public Color Color { get; set; }
public float GetWidth()
{
Vector2 Left, Right;
Left = PointA;
if (PointB.X < Left.X) { Left = PointB; }
if (PointC.X < Left.X) { Left = PointC; }
if (PointD.X < Left.X) { Left = PointD; }
Right = PointA;
if (PointB.X > Right.X) { Right = PointB; }
if (PointC.X > Right.X) { Right = PointC; }
if (PointD.X > Right.X) { Right = PointD; }
return (Left.X - Right.X);
}
public float GetHeight()
{
Vector2 Top, Bottom;
Top = PointA;
if (PointB.Y < Top.Y) { Top = PointB; }
if (PointC.Y < Top.Y) { Top = PointC; }
if (PointD.Y < Top.Y) { Top = PointD; }
Bottom = PointA;
if (PointB.Y > Bottom.Y) { Bottom = PointB; }
if (PointC.Y > Bottom.Y) { Bottom = PointC; }
if (PointD.Y > Bottom.Y) { Bottom = PointD; }
return (Top.Y - Bottom.Y);
}
public CShape(Vector2 Location, int Width, int Height, Color Color)
{
PointA = Location;
PointB = new Vector2(PointA.X + Width, PointA.Y);
PointC = new Vector2(PointA.X, PointA.Y + Height);
PointD = new Vector2(PointA.X + Width, PointA.Y + Height);
this.Color = Color;
}
public void Move(Vector2 Velocity)
{
PointA = new Vector2(PointA.X + Velocity.X, PointA.Y + Velocity.Y);
PointB = new Vector2(PointB.X + Velocity.X, PointB.Y + Velocity.Y);
PointC = new Vector2(PointC.X + Velocity.X, PointC.Y + Velocity.Y);
PointD = new Vector2(PointD.X + Velocity.X, PointD.Y + Velocity.Y);
}
public void Draw(SpriteBatch sb)
{
sb.Begin();
SpriteBatchEx.DrawLine(sb, PointA, PointB, Color, 3);
SpriteBatchEx.DrawLine(sb, PointB, PointD, Color, 3);
SpriteBatchEx.DrawLine(sb, PointD, PointC, Color, 3);
SpriteBatchEx.DrawLine(sb, PointC, PointA, Color, 3);
sb.End();
}
}
A common approach is simply to draw in 3D but using a 2D projection, it's really simple once setup and there are benefits in doing so (TODO find out more on that topic, there are plenty tutorials out there) !
So here's a simple code that draws squares anywhere and you can tint them:
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
namespace Game1
{
public class Game1 : Game
{
private BasicEffect _basicEffect;
private GraphicsDeviceManager _graphics;
public Game1()
{
_graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
}
protected override void Initialize()
{
_basicEffect = new BasicEffect(GraphicsDevice) {VertexColorEnabled = true};
base.Initialize();
}
protected override void Update(GameTime gameTime)
{
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed ||
Keyboard.GetState().IsKeyDown(Keys.Escape))
Exit();
base.Update(gameTime);
}
private void DrawSquare(BasicEffect basicEffect, Vector2 position, Vector2 size, Color tint)
{
// square made out of 2 triangles
var colors = new[]
{
new VertexPositionColor(new Vector3(-0.5f, -0.5f, 0.0f), Color.White),
new VertexPositionColor(new Vector3(+0.5f, -0.5f, 0.0f), Color.White),
new VertexPositionColor(new Vector3(+0.5f, +0.5f, 0.0f), Color.White),
new VertexPositionColor(new Vector3(-0.5f, -0.5f, 0.0f), Color.White),
new VertexPositionColor(new Vector3(+0.5f, +0.5f, 0.0f), Color.White),
new VertexPositionColor(new Vector3(-0.5f, +0.5f, 0.0f), Color.White)
};
basicEffect.World = // NOTE: the correct order for matrices is SRT (scale, rotate, translate)
Matrix.CreateTranslation(0.5f, 0.5f, 0.0f)* // offset by half pixel to get pixel perfect rendering
Matrix.CreateScale(size.X, size.Y, 1.0f)* // set size
Matrix.CreateTranslation(position.X, position.Y, 0.0f)* // set position
Matrix.CreateOrthographicOffCenter // 2d projection
(
0.0f,
GraphicsDevice.Viewport.Width, // NOTE : here not an X-coordinate (i.e. width - 1)
GraphicsDevice.Viewport.Height,
0.0f,
0.0f,
1.0f
);
// tint it however you like
basicEffect.DiffuseColor = tint.ToVector3();
var passes = _basicEffect.CurrentTechnique.Passes;
foreach (var pass in passes)
{
pass.Apply();
GraphicsDevice.DrawUserPrimitives(PrimitiveType.TriangleList, colors, 0, colors.Length/3);
}
}
/// <summary>
/// This is called when the game should draw itself.
/// </summary>
/// <param name="gameTime">Provides a snapshot of timing values.</param>
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
DrawSquare(_basicEffect, new Vector2(10, 10), new Vector2(100, 100), Color.Red);
DrawSquare(_basicEffect, new Vector2(200, 200), new Vector2(50, 50), Color.Green);
base.Draw(gameTime);
}
}
}
You can still use the good'ol xblig sample :
http://xbox.create.msdn.com/en-US/education/catalog/sample/primitives
I am currently developing a class for my XNA game whose rendering the lights on the image. At the time, i have made the source to draw my lightmap, however, the FPS is very low in my source. I know that it is brutally reduced upon looping through each pixel, however, I do not know any other way to get & set each pixel on my Texture in XNA but using the "For" statement?
Current Source:
public struct Light
{
public int Range;
public int Intensity;
public Color LightColor;
public Vector2 LightLocation;
public Light(int _Range, int _Intensity, Color _LightColor, Vector2 _LightLocation)
{
Range = _Range;
Intensity = _Intensity;
LightLocation = _LightLocation;
LightColor = _LightColor;
}
}
public class RenderClass
{
[System.Runtime.InteropServices.DllImport("User32.dll")]
public static extern bool MessageBox(IntPtr h, string S, string C, int a);
public static Texture2D RenderImage(Light[] LightLocations, Texture2D ScreenImage, Viewport v, bool ShadowBack = false)
{
Texture2D[] Images = new Texture2D[LightLocations.Count()];
int curCount = 0;
/*LOOP THROUGHT EACH LIGHT*/
foreach (Light LightLocation in LightLocations)
{
/*VARIABLES*/
Color LightColor = LightLocation.LightColor;
int Range = LightLocation.Range;
int Intensity = LightLocation.Intensity;
/*GET COLORS*/
int Width = v.Width;
int Height = v.Height;
Color[] Data = new Color[Width * Height];
ScreenImage.GetData<Color>(Data);
/*VARIABLES TO SET COLOR*/
Color[] SetColorData = new Color[Width * Height];
/*CIRCEL*/
int Radius = 15 / 2; // Define range to middle [Radius]
int Area = (int)Math.PI * (Radius * Radius);
for (int X = 0; X < Width; X++)
{
for (int Y = 0; Y < Height; Y++)
{
int Destination = X + Y * Width;
#region Light
/*GET COLOR*/
Color nColor = Data[Destination];
/*CREATE NEW COLOR*/
Vector2 MiddlePos = new Vector2(LightLocation.LightLocation.X + Radius, LightLocation.LightLocation.Y + Radius);
Vector2 CurrentLocation = new Vector2(X, Y);
float Distance;
Distance = Vector2.Distance(MiddlePos, CurrentLocation);
Distance *= 100;
Distance /= MathHelper.Clamp(Range, 0, 100);
Vector3 newColors = nColor.ToVector3();
nColor = new Color(
newColors.X,
newColors.Y,
newColors.Z,
Distance / 100);
/*SET COLOR*/
SetColorData[Destination] = nColor; // Add to array
#endregion
#region Shadow
#endregion
}
}
ScreenImage.SetData<Color>(SetColorData);
Images[curCount] = ScreenImage;
curCount++;
}
return Images[0]; // Temporarily returning the first image of the array.
}
}
As you can see, this is a slow and bad method. So I was wondering, is there a better way to get & set each pixel?
Thanks in advance, dotTutorials! =)
I think that job would be best done in a pixel shader.
You could create an Effect file that operates over one light at a time.XNA uses DX9 so you'll be limited to 128 constant registers, which I think you can use to squeeze up to three lights.
So you set your lightmap as a render target, loop through all the lights, set the constant data on your effect, render a render-target-sized quad and in your pixel shader compute your lighting equation.
In essence something like that:
// In LoadContent
RenderTarget2D lightmapRT = new RenderTarget2D(graphics.GraphicsDevice,
128,
128,
false, //No mip-mapping
SurfaceFormat.Color,
DepthFormat.Depth24);
// We now render to the lightmap in Render method
graphics.GraphicsDevice.SetRenderTarget(lightmapRT);
// Lightmap is black by default
graphics.GraphicsDevice.Clear(Color.Black);
// Use the sprite batch to draw quads with custom shader
spriteBatch.Begin(0, BlendState.Opaque, null, null, null, lightmapFx);
foreach (var light in lights)
{
// Pass the light parameters to the shader
lightmapFx.Parameters["Viewport"].SetValue(new Vector2(GraphicsDevice.Viewport.Width, GraphicsDevice.Viewport.Height));
lightmapFx.Parameters["Range"].SetValue(light.Range);
lightmapFx.Parameters["Intensity"].SetValue(light.Intensity);
lightmapFx.Parameters["LightColor"].SetValue(light.LightColor);
lightmapFx.Parameters["LightLocation"].SetValue(light.LightLocation);
// Render quad
spriteBatch.Draw(...);
}
spriteBatch.End();
And the FX file would look something like that:
float Range;
float Intensity;
float3 LightColor;
float2 LightLocation;
float2 Viewport;
struct VStoPS
{
float4 Position : POSITION0;
float2 TexCoord : TEXCOORD0;
};
VStoPS VS(in float4 color : COLOR0,
in float2 texCoord : TEXCOORD0,
in float4 position : POSITION0)
{
VStoPS vsout = (VStoPS)0;
// Half pixel offset for correct texel centering.
vsout.Position.xy -= 0.5;
// Viewport adjustment.
vsout.Position.xy = position.xy / Viewport;
vsout.Position.xy *= float2(2, -2);
vsout.Position.xy -= float2(1, -1);
// Pass texcoords as is
vsout.TexCoord = texCoord;
return vsout;
}
float4 PS(VStoPS psin)
{
// Do calculations here
// Here I just set it to white
return float4(1.0f, 1.0f, 1.0f, 1.0f);
}
technique Main
{
pass p0
{
VertexShader = compile vs_3_0 VS();
PixelShader = compile ps_3_0 PS();
}
}
Note that this non-tested code and probably full of errors. I leave it up to you to figure out what needs to go in the pixel shader.
I have this basic 3d application and trying to write my own toon shader, but even if I remove the tooning part it still still just stays plain darkblue when I give a red color to it.
shader code :
struct VertexShaderInput
{
float4 Position : POSITION0;
float3 Normal : NORMAL0;
float4 Color : COLOR0;
};
struct VertexShaderOutput
{
float4 Position : POSITION0;
float LightAmount : TEXCOORD1;
float4 Color : COLOR0;
};
VertexShaderOutput VertexShaderFunction(VertexShaderInput input)
{
VertexShaderOutput output;
float4 worldPosition = mul(input.Position, World);
float4 viewPosition = mul(worldPosition, View);
output.Position = mul(viewPosition, Projection);
output.Color = input.Color;
float3 worldNormal = mul(input.Normal, World);
output.LightAmount = dot(worldNormal, LightDirection);
// TODO: add your vertex shader code here.
return output;
}
float4 PixelShaderFunction(VertexShaderOutput input) : COLOR0
{
float4 color = input.Color;
float light;
if (input.LightAmount > ToonThresholds[0])
light = ToonBrightnessLevels[0];
else if (input.LightAmount > ToonThresholds[1])
light = ToonBrightnessLevels[1];
else
light = ToonBrightnessLevels[2];
color.rgb *= light;
return color;
}
Custom vertex format :
public struct VertexPositionNormalColored
{
public Vector3 Position;
public Color Color;
public Vector3 Normal;
public static int SizeInBytes = 7 * 4;
public static VertexElement[] VertexElements = new VertexElement[]
{
new VertexElement(0,VertexElementFormat.Vector3,VertexElementUsage.Position,0),
new VertexElement(12,VertexElementFormat.Vector3,VertexElementUsage.Normal,0),
new VertexElement(24,VertexElementFormat.Color,VertexElementUsage.Color,0)
};
}
Init of the triangle I am trying to draw :
testVetices = new VertexPositionNormalColored[3];
testVetices[0].Position = new Vector3(-0.5f, -0.5f, 0f);
testVetices[0].Color = Color.Red;
testVetices[0].Normal = new Vector3(0, 0, 1);
testVetices[1].Position = new Vector3(0, 0.5f, 0f);
testVetices[1].Color = Color.Red;
testVetices[1].Normal = new Vector3(0, 0, 1);
testVetices[2].Position = new Vector3(0.5f, -0.5f, 0f);
testVetices[2].Color = Color.Red;
testVetices[2].Normal = new Vector3(0, 0, 1);
In C#, struct fields are ordered sequentially in memory. But the order of fields in your structure does not match what you have set in VertexElements.
It should be:
public struct VertexPositionNormalColored
{
public Vector3 Position;
public Vector3 Normal;
public Color Color; // oh look I've moved!
public static int SizeInBytes = 7 * 4;
public static VertexElement[] VertexElements = new VertexElement[]
{
new VertexElement(0,VertexElementFormat.Vector3,VertexElementUsage.Position,0),
new VertexElement(12,VertexElementFormat.Vector3,VertexElementUsage.Normal,0),
new VertexElement(24,VertexElementFormat.Color,VertexElementUsage.Color,0)
};
}
(Not sure if that's the only problem in your code, but that's what stuck out.)
If you are using XNA 4.0, you might also want to have a read of this blog post.