- تم التحرير
(Solved-ish) A normal map skeleton shader for unity
- تم التحرير
I'm really excited to be playing around with spine and one thing I'd love to experiment with is using normal maps to give dynamic lighting to sprites, like in legend of dungeon.
I know next to nothing about shaders unfortunately so I'm posting here in hopes that someone more knowledgeable than me can add the functionality in
THAT IS AWESOME! Spine will rock if it support this feature!
Should be possible just like it's described at that page. Nothing about it is specific to using Spine. A Spine runtime just draws some images, so you just need to draw images in that fancy way. Sorry I don't have the bandwidth to lend a hand at the moment!
Yeah. Spine's system wouldn't have much to do with it. Just read up about shaders.
Specifically for Unity, Unity has its own language called ShaderLab which takes care of more typical shader logic, but it also supports the more standard shader language called Cg/HLSL which is more program-y and supports more arbitrary formulas and code. You'll need to be reasonably familiar with C though if you want to experiment that deep.
But Legend of Dungeon's shader looks pretty typical. You could probably just do it without being a super shader genius. The bigger task might be figuring out how to make your assets to make the shader work.
tl;dr Avoid built in shaders
Attempt 1:
I thought: what if I just used one of unity's built in shaders?
Result: Image removed due to the lack of support for HTTPS. | Show Anyway
That's the transparent cutout bumped diffuse shader.
- The individual parts are clearly visible and z fighting is occurs during runtime.
- Setting FlipX to true literally flips the mesh 180 degrees, making it invisible to the main camera but visible to a camera that is facing the opposite direction of the main camera.
- Lights have no effect on the mesh. I get an error saying: "Shader wants tangents, but the mesh Skeleton Mesh doesn't have them" I assume this refers to both lighting and normal map calculations. You get the error with every available shader including the legacy ones.
Next up: Tinker with the working skeleton shader
tl;dr Almost working, issues with z-fighting, flipping and transparency. Code at bottom of post
Attempt 2:
Research revealed that the reason I was getting an error was because tangents weren't being calculated for the mesh (mesh.tangents was equal to null). So, I set about looking for a way to do just that. This required me to change the SkeletonComponent.cs file.
Changes:
- @ Line 58
mesh.name = this.gameObject.name; // Instead of naming every mesh "Skeletal Mesh"
- @ Line 164 & 165
mesh.RecalculateNormals(); ComputeTangents(mesh);
ComputeTangents is a function I copy pasta'd from this here discussion.
The result: Image removed due to the lack of support for HTTPS. | Show Anyway
It responds to moving lights. Still has z fighting issues and it... becomes transparent when near a light source. Kind of like if it had see through skin?
Modified SkeletonComponent.cs file:
/*******************************************************************************
* Copyright (c) 2013, Esoteric Software
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
using System;
using System.IO;
using System.Collections.Generic;
using UnityEngine;
using Spine;
/** Renders a skeleton. Extend to apply animations, get bones and manipulate them, etc. */
[ExecuteInEditMode, RequireComponent(typeof(MeshFilter), typeof(MeshRenderer))]
public class SkeletonComponent : MonoBehaviour {
public SkeletonDataAsset skeletonDataAsset;
public Skeleton skeleton;
public String initialSkinName;
public float timeScale = 1;
private Mesh mesh;
private Vector3[] vertices;
private Color[] colors;
private Vector2[] uvs;
private int[] triangles;
private float[] vertexPositions = new float[8];
public virtual void Clear () {
GetComponent<MeshFilter>().mesh = null;
DestroyImmediate(mesh);
mesh = null;
renderer.sharedMaterial = null;
skeleton = null;
}
public virtual void Initialize () {
mesh = new Mesh();
GetComponent<MeshFilter>().mesh = mesh;
// Instead of naming every mesh "Skeletal Mesh"
mesh.name = this.gameObject.name;
mesh.hideFlags = HideFlags.HideAndDontSave;
vertices = new Vector3[0];
skeleton = new Skeleton(skeletonDataAsset.GetSkeletonData(false));
if (initialSkinName != null && initialSkinName.Length > 0) {
skeleton.SetSkin(initialSkinName);
skeleton.SetSlotsToSetupPose();
}
}
public virtual void UpdateSkeleton () {
skeleton.Update(Time.deltaTime * timeScale);
skeleton.UpdateWorldTransform();
}
public virtual void Update () {
// Clear fields if missing information to render.
if (skeletonDataAsset == null || skeletonDataAsset.GetSkeletonData(false) == null) {
Clear();
return;
}
// Initialize fields.
if (skeleton == null || skeleton.Data != skeletonDataAsset.GetSkeletonData(false))
Initialize();
UpdateSkeleton();
// Count quads.
int quadCount = 0;
List<Slot> drawOrder = skeleton.DrawOrder;
for (int i = 0, n = drawOrder.Count; i < n; i++) {
Slot slot = drawOrder[i];
Attachment attachment = slot.Attachment;
if (attachment is RegionAttachment)
quadCount++;
}
// Ensure mesh data is the right size.
if (quadCount > vertices.Length / 4) {
vertices = new Vector3[quadCount * 4];
colors = new Color[quadCount * 4];
uvs = new Vector2[quadCount * 4];
triangles = new int[quadCount * 6];
mesh.Clear();
for (int i = 0, n = quadCount; i < n; i++) {
int index = i * 6;
int vertexIndex = i * 4;
triangles[index] = vertexIndex;
triangles[index + 1] = vertexIndex + 2;
triangles[index + 2] = vertexIndex + 1;
triangles[index + 3] = vertexIndex + 2;
triangles[index + 4] = vertexIndex + 3;
triangles[index + 5] = vertexIndex + 1;
}
} else {
Vector3 zero = new Vector3(0, 0, 0);
for (int i = quadCount * 4, n = vertices.Length; i < n; i++)
vertices[i] = zero;
}
// Setup mesh.
float[] vertexPositions = this.vertexPositions;
int quadIndex = 0;
Color color = new Color();
for (int i = 0, n = drawOrder.Count; i < n; i++) {
Slot slot = drawOrder[i];
Attachment attachment = slot.Attachment;
if (attachment is RegionAttachment) {
RegionAttachment regionAttachment = (RegionAttachment)attachment;
regionAttachment.ComputeVertices(skeleton.X, skeleton.Y, slot.Bone, vertexPositions);
int vertexIndex = quadIndex * 4;
vertices[vertexIndex] = new Vector3(vertexPositions[RegionAttachment.X1], vertexPositions[RegionAttachment.Y1], 0);
vertices[vertexIndex + 1] = new Vector3(vertexPositions[RegionAttachment.X4], vertexPositions[RegionAttachment.Y4], 0);
vertices[vertexIndex + 2] = new Vector3(vertexPositions[RegionAttachment.X2], vertexPositions[RegionAttachment.Y2], 0);
vertices[vertexIndex + 3] = new Vector3(vertexPositions[RegionAttachment.X3], vertexPositions[RegionAttachment.Y3], 0);
color.a = skeleton.A * slot.A;
color.r = skeleton.R * slot.R * color.a;
color.g = skeleton.G * slot.G * color.a;
color.b = skeleton.B * slot.B * color.a;
colors[vertexIndex] = color;
colors[vertexIndex + 1] = color;
colors[vertexIndex + 2] = color;
colors[vertexIndex + 3] = color;
float[] regionUVs = regionAttachment.UVs;
uvs[vertexIndex] = new Vector2(regionUVs[RegionAttachment.X1], 1 - regionUVs[RegionAttachment.Y1]);
uvs[vertexIndex + 1] = new Vector2(regionUVs[RegionAttachment.X4], 1 - regionUVs[RegionAttachment.Y4]);
uvs[vertexIndex + 2] = new Vector2(regionUVs[RegionAttachment.X2], 1 - regionUVs[RegionAttachment.Y2]);
uvs[vertexIndex + 3] = new Vector2(regionUVs[RegionAttachment.X3], 1 - regionUVs[RegionAttachment.Y3]);
quadIndex++;
}
}
mesh.vertices = vertices;
mesh.colors = colors;
mesh.uv = uvs;
mesh.triangles = triangles;
mesh.RecalculateNormals();
ComputeTangents(mesh);
renderer.sharedMaterial = skeletonDataAsset.atlasAsset.material;
}
public virtual void OnEnable () {
Update();
}
public virtual void OnDisable () {
if (Application.isEditor)
Clear();
}
public virtual void Reset () {
Update();
}
void ComputeTangents(Mesh mesh)
{
int triangleCount = mesh.triangles.Length;
int vertexCount = mesh.vertices.Length;
Vector3[] tan1 = new Vector3[vertexCount];
Vector3[] tan2 = new Vector3[vertexCount];
Vector4[] tangents = new Vector4[vertexCount];
for(long a = 0; a < triangleCount; a+=3)
{
long i1 = mesh.triangles[a+0];
long i2 = mesh.triangles[a+1];
long i3 = mesh.triangles[a+2];
Vector3 v1 = mesh.vertices[i1];
Vector3 v2 = mesh.vertices[i2];
Vector3 v3 = mesh.vertices[i3];
Vector2 w1 = mesh.uv[i1];
Vector2 w2 = mesh.uv[i2];
Vector2 w3 = mesh.uv[i3];
float x1 = v2.x - v1.x;
float x2 = v3.x - v1.x;
float y1 = v2.y - v1.y;
float y2 = v3.y - v1.y;
float z1 = v2.z - v1.z;
float z2 = v3.z - v1.z;
float s1 = w2.x - w1.x;
float s2 = w3.x - w1.x;
float t1 = w2.y - w1.y;
float t2 = w3.y - w1.y;
float r = 1.0f / (s1 * t2 - s2 * t1);
Vector3 sdir = new Vector3((t2 * x1 - t1 * x2) * r, (t2 * y1 - t1 * y2) * r, (t2 * z1 - t1 * z2) * r);
Vector3 tdir = new Vector3((s1 * x2 - s2 * x1) * r, (s1 * y2 - s2 * y1) * r, (s1 * z2 - s2 * z1) * r);
tan1[i1] += sdir;
tan1[i2] += sdir;
tan1[i3] += sdir;
tan2[i1] += tdir;
tan2[i2] += tdir;
tan2[i3] += tdir;
}
for (long a = 0; a < vertexCount; ++a)
{
Vector3 n = mesh.normals[a];
Vector3 t = tan1[a];
Vector3 tmp = (t - n * Vector3.Dot(n, t)).normalized;
tangents[a] = new Vector4(tmp.x, tmp.y, tmp.z);
tangents[a].w = (Vector3.Dot(Vector3.Cross(n, t), tan2[a]) < 0.0f) ? -1.0f : 1.0f;
}
mesh.tangents = tangents;
}
}
tl;dr Success :whew:
Attempt 3:
Came across lots of awesome unity tutorials. It's 1 am right now so I skipped them and practiced some copy-paste heresy.
Le shader:
Shader "Spine/Normal Mapped Skeleton" {
Properties {
_MainTex ("Atlas Image", 2D) = "white" {}
_Bump ("Normal Map", 2D) = "bump" {}
}
SubShader {
Tags { "Queue" = "Transparent" }
LOD 200
Pass {
Tags { "LightMode"="ForwardBase" }
Cull Off
Lighting Off
ZWrite Off
Blend One OneMinusSrcAlpha
CGPROGRAM
#pragma exclude_renderers d3d11 xbox360
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
uniform float4 _LightColor0;
sampler2D _MainTex;
sampler2D _Bump;
float4 _MainTex_ST;
float4 _Bump_ST;
struct a2v
{
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 texcoord : TEXCOORD0;
float4 tangent : TANGENT;
};
struct v2f
{
float4 pos : POSITION;
float2 uv : TEXCOORD0;
float2 uv2 : TEXCOORD1;
float3 lightDirection;
};
v2f vert (a2v v)
{
v2f o;
TANGENT_SPACE_ROTATION;
o.lightDirection = mul(rotation, ObjSpaceLightDir(v.vertex));
o.pos = mul( UNITY_MATRIX_MVP, v.vertex);
o.uv = TRANSFORM_TEX (v.texcoord, _MainTex);
o.uv2 = TRANSFORM_TEX (v.texcoord, _Bump);
return o;
}
float4 frag(v2f i) : COLOR
{
float4 c = tex2D (_MainTex, i.uv);
float3 n = UnpackNormal(tex2D (_Bump, i.uv2));
float3 lightColor = UNITY_LIGHTMODEL_AMBIENT.xyz;
float lengthSq = dot(i.lightDirection, i.lightDirection);
float atten = 1.0 / (1.0 + lengthSq);
//Angle to the light
float diff = saturate (dot (n, normalize(i.lightDirection)));
lightColor += _LightColor0.rgb * (diff * atten);
c.rgb = lightColor * c.rgb * 2;
return c;
}
ENDCG
}
Pass {
Tags { "LightMode"="ForwardAdd" }
Cull Off
Lighting Off
ZWrite Off
Blend One OneMinusSrcAlpha
CGPROGRAM
#pragma exclude_renderers d3d11 xbox360
#pragma exclude_renderers xbox360 flash
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
uniform float4 _LightColor0;
sampler2D _MainTex;
sampler2D _Bump;
float4 _MainTex_ST;
float4 _Bump_ST;
struct a2v
{
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 texcoord : TEXCOORD0;
float4 tangent : TANGENT;
};
struct v2f
{
float4 pos : POSITION;
float2 uv : TEXCOORD0;
float2 uv2 : TEXCOORD1;
float3 lightDirection;
};
v2f vert (a2v v)
{
v2f o;
TANGENT_SPACE_ROTATION;
o.lightDirection = mul(rotation, ObjSpaceLightDir(v.vertex));
o.pos = mul( UNITY_MATRIX_MVP, v.vertex);
o.uv = TRANSFORM_TEX (v.texcoord, _MainTex);
o.uv2 = TRANSFORM_TEX (v.texcoord, _Bump);
return o;
}
float4 frag(v2f i) : COLOR
{
float4 c = tex2D (_MainTex, i.uv);
float3 n = UnpackNormal(tex2D (_Bump, i.uv2));
float3 lightColor = float3(0);
float lengthSq = dot(i.lightDirection, i.lightDirection);
float atten = 1.0 / (1.0 + lengthSq * unity_LightAtten[0].z);
//Angle to the light
float diff = saturate (dot (n, normalize(i.lightDirection)));
lightColor += _LightColor0.rgb * (diff * atten);
c.rgb = lightColor * c.rgb * 2;
return c;
}
ENDCG
}
}
FallBack "Diffuse"
}
Disclaimer: I'm not sure which parts (if any) of the shader can be cut out, nor how or why it works.
ps Had to make a small change again in the SkeletonComponent after RecalculateNormals():
// Make sure the normals are facing the right way if FlipX is set to true
if (skeleton.FlipX)
{
Vector3[] normals = mesh.normals;
for (int i=0;i<normals.Length;i++)
normals[i] = -normals[i];
mesh.normals = normals;
}
How to use:
- You need an atlas image like normal
- You need a normal map (generate how you please)
- Replace the code in SkeletonComponent.cs with:
/******************************************************************************* * Copyright (c) 2013, Esoteric Software * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ******************************************************************************/ using System; using System.IO; using System.Collections.Generic; using UnityEngine; using Spine; /** Renders a skeleton. Extend to apply animations, get bones and manipulate them, etc. */ [ExecuteInEditMode, RequireComponent(typeof(MeshFilter), typeof(MeshRenderer))] public class SkeletonComponent : MonoBehaviour { public SkeletonDataAsset skeletonDataAsset; public Skeleton skeleton; public String initialSkinName; public float timeScale = 1; private Mesh mesh; private Vector3[] vertices; private Color[] colors; private Vector2[] uvs; private int[] triangles; private float[] vertexPositions = new float[8]; public virtual void Clear () { GetComponent<MeshFilter>().mesh = null; DestroyImmediate(mesh); mesh = null; renderer.sharedMaterial = null; skeleton = null; } public virtual void Initialize () { mesh = new Mesh(); GetComponent<MeshFilter>().mesh = mesh; // Instead of naming every mesh "Skeletal Mesh" mesh.name = this.gameObject.name; mesh.hideFlags = HideFlags.HideAndDontSave; vertices = new Vector3[0]; skeleton = new Skeleton(skeletonDataAsset.GetSkeletonData(false)); if (initialSkinName != null && initialSkinName.Length > 0) { skeleton.SetSkin(initialSkinName); skeleton.SetSlotsToSetupPose(); } } public virtual void UpdateSkeleton () { skeleton.Update(Time.deltaTime * timeScale); skeleton.UpdateWorldTransform(); } public virtual void Update () { // Clear fields if missing information to render. if (skeletonDataAsset == null || skeletonDataAsset.GetSkeletonData(false) == null) { Clear(); return; } // Initialize fields. if (skeleton == null || skeleton.Data != skeletonDataAsset.GetSkeletonData(false)) Initialize(); UpdateSkeleton(); // Count quads. int quadCount = 0; List<Slot> drawOrder = skeleton.DrawOrder; for (int i = 0, n = drawOrder.Count; i < n; i++) { Slot slot = drawOrder[i]; Attachment attachment = slot.Attachment; if (attachment is RegionAttachment) quadCount++; } // Ensure mesh data is the right size. if (quadCount > vertices.Length / 4) { vertices = new Vector3[quadCount * 4]; colors = new Color[quadCount * 4]; uvs = new Vector2[quadCount * 4]; triangles = new int[quadCount * 6]; mesh.Clear(); for (int i = 0, n = quadCount; i < n; i++) { int index = i * 6; int vertexIndex = i * 4; triangles[index] = vertexIndex; triangles[index + 1] = vertexIndex + 2; triangles[index + 2] = vertexIndex + 1; triangles[index + 3] = vertexIndex + 2; triangles[index + 4] = vertexIndex + 3; triangles[index + 5] = vertexIndex + 1; } } else { Vector3 zero = new Vector3(0, 0, 0); for (int i = quadCount * 4, n = vertices.Length; i < n; i++) vertices[i] = zero; } // Setup mesh. float[] vertexPositions = this.vertexPositions; int quadIndex = 0; Color color = new Color(); for (int i = 0, n = drawOrder.Count; i < n; i++) { Slot slot = drawOrder[i]; Attachment attachment = slot.Attachment; if (attachment is RegionAttachment) { RegionAttachment regionAttachment = (RegionAttachment)attachment; regionAttachment.ComputeVertices(skeleton.X, skeleton.Y, slot.Bone, vertexPositions); int vertexIndex = quadIndex * 4; vertices[vertexIndex] = new Vector3(vertexPositions[RegionAttachment.X1], vertexPositions[RegionAttachment.Y1], 0); vertices[vertexIndex + 1] = new Vector3(vertexPositions[RegionAttachment.X4], vertexPositions[RegionAttachment.Y4], 0); vertices[vertexIndex + 2] = new Vector3(vertexPositions[RegionAttachment.X2], vertexPositions[RegionAttachment.Y2], 0); vertices[vertexIndex + 3] = new Vector3(vertexPositions[RegionAttachment.X3], vertexPositions[RegionAttachment.Y3], 0); color.a = skeleton.A * slot.A; color.r = skeleton.R * slot.R * color.a; color.g = skeleton.G * slot.G * color.a; color.b = skeleton.B * slot.B * color.a; colors[vertexIndex] = color; colors[vertexIndex + 1] = color; colors[vertexIndex + 2] = color; colors[vertexIndex + 3] = color; float[] regionUVs = regionAttachment.UVs; uvs[vertexIndex] = new Vector2(regionUVs[RegionAttachment.X1], 1 - regionUVs[RegionAttachment.Y1]); uvs[vertexIndex + 1] = new Vector2(regionUVs[RegionAttachment.X4], 1 - regionUVs[RegionAttachment.Y4]); uvs[vertexIndex + 2] = new Vector2(regionUVs[RegionAttachment.X2], 1 - regionUVs[RegionAttachment.Y2]); uvs[vertexIndex + 3] = new Vector2(regionUVs[RegionAttachment.X3], 1 - regionUVs[RegionAttachment.Y3]); quadIndex++; } } mesh.vertices = vertices; mesh.colors = colors; mesh.uv = uvs; mesh.triangles = triangles; mesh.RecalculateNormals(); // Make sure the normals are facing the right way if FlipX is set to true if (skeleton.FlipX) { Vector3[] normals = mesh.normals; for (int i=0;i<normals.Length;i++) normals[i] = -normals[i]; mesh.normals = normals; } ComputeTangents(mesh); renderer.sharedMaterial = skeletonDataAsset.atlasAsset.material; } public virtual void OnEnable () { Update(); } public virtual void OnDisable () { if (Application.isEditor) Clear(); } public virtual void Reset () { Update(); } void ComputeTangents(Mesh mesh) { int triangleCount = mesh.triangles.Length; int vertexCount = mesh.vertices.Length; Vector3[] tan1 = new Vector3[vertexCount]; Vector3[] tan2 = new Vector3[vertexCount]; Vector4[] tangents = new Vector4[vertexCount]; for(long a = 0; a < triangleCount; a+=3) { long i1 = mesh.triangles[a+0]; long i2 = mesh.triangles[a+1]; long i3 = mesh.triangles[a+2]; Vector3 v1 = mesh.vertices[i1]; Vector3 v2 = mesh.vertices[i2]; Vector3 v3 = mesh.vertices[i3]; Vector2 w1 = mesh.uv[i1]; Vector2 w2 = mesh.uv[i2]; Vector2 w3 = mesh.uv[i3]; float x1 = v2.x - v1.x; float x2 = v3.x - v1.x; float y1 = v2.y - v1.y; float y2 = v3.y - v1.y; float z1 = v2.z - v1.z; float z2 = v3.z - v1.z; float s1 = w2.x - w1.x; float s2 = w3.x - w1.x; float t1 = w2.y - w1.y; float t2 = w3.y - w1.y; float r = 1.0f / (s1 * t2 - s2 * t1); Vector3 sdir = new Vector3((t2 * x1 - t1 * x2) * r, (t2 * y1 - t1 * y2) * r, (t2 * z1 - t1 * z2) * r); Vector3 tdir = new Vector3((s1 * x2 - s2 * x1) * r, (s1 * y2 - s2 * y1) * r, (s1 * z2 - s2 * z1) * r); tan1[i1] += sdir; tan1[i2] += sdir; tan1[i3] += sdir; tan2[i1] += tdir; tan2[i2] += tdir; tan2[i3] += tdir; } for (long a = 0; a < vertexCount; ++a) { Vector3 n = mesh.normals[a]; Vector3 t = tan1[a]; Vector3 tmp = (t - n * Vector3.Dot(n, t)).normalized; tangents[a] = new Vector4(tmp.x, tmp.y, tmp.z); tangents[a].w = (Vector3.Dot(Vector3.Cross(n, t), tan2[a]) < 0.0f) ? -1.0f : 1.0f; } mesh.tangents = tangents; } }
- Create a new shader, replace the generated code with:
Use that shader on your sprite's materialShader "Spine/<WhatYouSavedTheShaderAs>" { Properties { _MainTex ("Atlas Image", 2D) = "white" {} _Bump ("Normal Map", 2D) = "bump" {} } SubShader { Tags { "Queue" = "Transparent" } LOD 200 Pass { Tags { "LightMode"="ForwardBase" } Cull Off Lighting Off ZWrite Off Blend One OneMinusSrcAlpha CGPROGRAM #pragma exclude_renderers d3d11 xbox360 #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" uniform float4 _LightColor0; sampler2D _MainTex; sampler2D _Bump; float4 _MainTex_ST; float4 _Bump_ST; struct a2v { float4 vertex : POSITION; float3 normal : NORMAL; float4 texcoord : TEXCOORD0; float4 tangent : TANGENT; }; struct v2f { float4 pos : POSITION; float2 uv : TEXCOORD0; float2 uv2 : TEXCOORD1; float3 lightDirection; }; v2f vert (a2v v) { v2f o; TANGENT_SPACE_ROTATION; o.lightDirection = mul(rotation, ObjSpaceLightDir(v.vertex)); o.pos = mul( UNITY_MATRIX_MVP, v.vertex); o.uv = TRANSFORM_TEX (v.texcoord, _MainTex); o.uv2 = TRANSFORM_TEX (v.texcoord, _Bump); return o; } float4 frag(v2f i) : COLOR { float4 c = tex2D (_MainTex, i.uv); float3 n = UnpackNormal(tex2D (_Bump, i.uv2)); float3 lightColor = UNITY_LIGHTMODEL_AMBIENT.xyz; float lengthSq = dot(i.lightDirection, i.lightDirection); float atten = 1.0 / (1.0 + lengthSq); //Angle to the light float diff = saturate (dot (n, normalize(i.lightDirection))); lightColor += _LightColor0.rgb * (diff * atten); c.rgb = lightColor * c.rgb * 2; return c; } ENDCG } Pass { Tags { "LightMode"="ForwardAdd" } Cull Off Lighting Off ZWrite Off Blend One OneMinusSrcAlpha CGPROGRAM #pragma exclude_renderers d3d11 xbox360 #pragma exclude_renderers xbox360 flash #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" uniform float4 _LightColor0; sampler2D _MainTex; sampler2D _Bump; float4 _MainTex_ST; float4 _Bump_ST; struct a2v { float4 vertex : POSITION; float3 normal : NORMAL; float4 texcoord : TEXCOORD0; float4 tangent : TANGENT; }; struct v2f { float4 pos : POSITION; float2 uv : TEXCOORD0; float2 uv2 : TEXCOORD1; float3 lightDirection; }; v2f vert (a2v v) { v2f o; TANGENT_SPACE_ROTATION; o.lightDirection = mul(rotation, ObjSpaceLightDir(v.vertex)); o.pos = mul( UNITY_MATRIX_MVP, v.vertex); o.uv = TRANSFORM_TEX (v.texcoord, _MainTex); o.uv2 = TRANSFORM_TEX (v.texcoord, _Bump); return o; } float4 frag(v2f i) : COLOR { float4 c = tex2D (_MainTex, i.uv); float3 n = UnpackNormal(tex2D (_Bump, i.uv2)); float3 lightColor = float3(0); float lengthSq = dot(i.lightDirection, i.lightDirection); float atten = 1.0 / (1.0 + lengthSq * unity_LightAtten[0].z); //Angle to the light float diff = saturate (dot (n, normalize(i.lightDirection))); lightColor += _LightColor0.rgb * (diff * atten); c.rgb = lightColor * c.rgb * 2; return c; } ENDCG } } FallBack "Diffuse" }
- Create a new shader, replace the generated code with:
Nice! Any chance for a video with the last shader?
Sure thing gonna put my awful art skillz to use yeaaaaaaaaah booooi
Does anyone have any recording software they recommend? (for windows)
In the meantime here's a webplayer build. Uses wasd/arrows to move.
Edit: Here's the "heightmap" I'm using: Image removed due to the lack of support for HTTPS. | Show Anyway
stray_train wroteDoes anyone have any recording software they recommend? (for windows)
In the meantime here's a webplayer build. Uses wasd/arrows to move.
Edit: Here's the "heightmap" I'm using:
Image removed due to the lack of support for HTTPS. | Show Anyway
use jing, I think it's way better than fraps
I think Nate was just curious to see it working. The webplayer is better than a video.
Yep, the web demo is very neat. Thanks for sharing! Tweeted!
- تم التحرير
.
Gave it a pop, but it's bugged if you flip any bones within the actual Spine editor, so it should check if the bone itself is flipped or not (for example some bones may animate to be flipped, ie a character looking left and right as part of the anim).
Really glad someone worked on this! Unfortunately I can't get the same results. I replaced the code in SkeletonComponent with the code above, then created a new shader and replaced the default added code with the shader code above. After that I used the new shader for the characters material and applied the heightmap to it.
Here are my results with the character facing both directions:
Image removed due to the lack of support for HTTPS. | Show Anyway
Did I miss a step? Or was the Unity runtime updated, causing this to no longer work?
I believe the runtime was updated. I've had a lot more practice with shaders and I'm going to try redo all this better (also with support for multiple lights), will hopefully have something ready by the end of the weekend