- Home /
Can I unpack normal / tangent / uv information in the vertex function in a surface shader?
In the following code, I try to use indexes passed in as uv.x and uv.y to look up the tangent, normal, and actual uv for the vertex. I set up a mesh with a appropriate data (a cube) but the result is that the cube is always drawn black (the worldNormal always ends up as zero).
I've tried sending in a mesh with no normals, and no tangents, and I've also tried sending in a mesh with dummy normals and tangents. Neither seems to work.
Is there some way to make this sort of thing work? Is there some general way to pass in custom data, and use it to generate the actual vertex data?
I've included the shader and C# script code I use to build the mesh.
Here's the shader. Take a look at the vertex_unpacker function.
Shader "Custom/CubeShader" {
Properties {
_MainTex ("Base (RGB)", 2D) = "white" {}
}
SubShader {
Tags { "RenderType"="Opaque" }
CGPROGRAM
// Upgrade NOTE: excluded shader from DX11, Xbox360, OpenGL ES 2.0 because it uses unsized arrays
#pragma exclude_renderers gles flash
//d3d11 xbox360
#pragma surface surf Lambert vertex:vertex_unpacker
sampler2D _MainTex;
float3 _normals[6] =
{
float3(1,0,0),
float3(-1,0,0),
float3(0,0,1),
float3(0,0,-1),
float3(0,1,0),
float3(0,-1,0)
};
float4 _tangents[6] =
{
float4(0,0,1,1),
float4(0,0,-1,1),
float4(-1,0,0,1),
float4(1,0,0,1),
float4(0,0,1,1),
float4(0,0,-1,1)
};
float4 _uvs[4] =
{
float4(0,0,0,0),
float4(1,0,0,0),
float4(0,1,0,0),
float4(1,1,0,0)
};
struct Input {
float3 worldNormal;
};
void vertex_unpacker(inout appdata_full i) {
int face = i.texcoord.x;
int vx = i.texcoord.y;
i.normal = _normals[face];
i.tangent = _tangents[face];
i.texcoord = _uvs[vx];
}
void surf (Input IN, inout SurfaceOutput o) {
o.Albedo = IN.worldNormal.xyz;
o.Alpha = 1;
}
ENDCG
}
FallBack "Diffuse"
}
And here's the script that builds the cube:
using UnityEngine;
using System.Collections;
public class CubeBuilder : MonoBehaviour {
// Use this for initialization
enum Faces
{
POSX,
NEGX,
POSZ,
NEGZ,
POSY,
NEGY
};
enum C
{
RUF,
RUB,
LUF,
LUB,
RLF,
RLB,
LLF,
LLB,
};
void Start ()
{
MeshFilter filter = GetComponent<MeshFilter>();
if (filter != null)
{
Mesh mesh = new Mesh();
filter.sharedMesh = mesh;
Vector3[] cubeCorners = new Vector3[8];
Vector3 v3 = Vector3.zero;
for (int i = 0; i < 8; i++)
{
v3.x = (i & 2) == 0 ? 1 : -1;
v3.y = (i & 4) == 0 ? 1 : -1;
v3.z = (i & 1) == 0 ? 1 : -1;
cubeCorners[i] = v3;
}
Vector3[] faceVertexes = new Vector3[24]
{
cubeCorners[(int)C.RUB],
cubeCorners[(int)C.RUF],
cubeCorners[(int)C.RLB],
cubeCorners[(int)C.RLF],
cubeCorners[(int)C.LUF],
cubeCorners[(int)C.LUB],
cubeCorners[(int)C.LLF],
cubeCorners[(int)C.LLB],
cubeCorners[(int)C.RUF],
cubeCorners[(int)C.LUF],
cubeCorners[(int)C.RLF],
cubeCorners[(int)C.LLF],
cubeCorners[(int)C.LUB],
cubeCorners[(int)C.RUB],
cubeCorners[(int)C.LLB],
cubeCorners[(int)C.RLB],
cubeCorners[(int)C.LUF],
cubeCorners[(int)C.RUF],
cubeCorners[(int)C.LUB],
cubeCorners[(int)C.RUB],
cubeCorners[(int)C.RLF],
cubeCorners[(int)C.LLF],
cubeCorners[(int)C.RLB],
cubeCorners[(int)C.LLB],
};
Vector2[] faceUVs = new Vector2[24];
for(int face = 0; face < 6; face++)
for(int i = 0; i < 4; i++)
{
faceUVs[face*4+i] = new Vector2(face,i);
}
int[] indexes = new int[36];
for (int i = 0; i < 6; i++)
{
int j = i*6;
int vxcount = i*4;
indexes[j++] = vxcount;
indexes[j++] = vxcount+1;
indexes[j++] = vxcount+2;
indexes[j++] = vxcount+2;
indexes[j++] = vxcount+1;
indexes[j++] = vxcount+3;
}
mesh.vertices = faceVertexes;
mesh.uv = faceUVs;
mesh.triangles = indexes;
}
}
// Update is called once per frame
void Update () {
}
}
Answer by Tyrathect · May 09, 2013 at 03:15 PM
Thanks for looking at this.
worldNormal gets filled in automatically. If you replace line 51 of the shader with i.normal = float3(1,0,0), you get appropriate output. It has to work this way because some of the data you get in the fragment shader relies on the tangent space calculation which you can't set explicitly. Some things have to be set automatically.
I've verified that the indexes are passed in correctly by outputting them directly as color, so the only thing left is that the tables aren't getting filled in. There must be another way to declare this type of constant data.
UPDATE:
I think this boils down to not being able to set the data in the _normals/_tangents/_uvs arrays.
The data doesn't appear to be available when the program is run, and as far as I can tell there's no way to set an array from a script.
This seems like a serious oversight. I'll see if I can find some more information.
UPDATE 2
Using the workaround posted here I got it to work, but I'm guessing that I'm doing an extra API call for each member of the array now, which is sad.
It sure would be easier to have a real array type, but everything else worked just fine (and I didn't need to use dummy normals or tangents).