void SubdivTriangle(VertexP3N3G3B3T2[] _triangle, uint _count, List <VertexP3N3G3B3T2> _vertices, List <uint> _indices) { if (_count == 0) { // Push triangle // _triangle[0].B.x = (float) _vertices.Count; // _triangle[1].B.x = (float) _vertices.Count+1; // _triangle[2].B.x = (float) _vertices.Count+2; _indices.Add((uint)_vertices.Count); _vertices.Add(_triangle[0]); _indices.Add((uint)_vertices.Count); _vertices.Add(_triangle[1]); _indices.Add((uint)_vertices.Count); _vertices.Add(_triangle[2]); return; } VertexP3N3G3B3T2[] insideTriangle = new VertexP3N3G3B3T2[3] { new VertexP3N3G3B3T2() { P = 0.5f * (_triangle[0].P + _triangle[1].P), N = 0.5f * (_triangle[0].N + _triangle[1].N), UV = 0.5f * (_triangle[0].UV + _triangle[1].UV), T = 0.5f * (_triangle[0].T + _triangle[1].T), B = 0.5f * (_triangle[0].B + _triangle[1].B) }, new VertexP3N3G3B3T2() { P = 0.5f * (_triangle[1].P + _triangle[2].P), N = 0.5f * (_triangle[1].N + _triangle[2].N), UV = 0.5f * (_triangle[1].UV + _triangle[2].UV), T = 0.5f * (_triangle[1].T + _triangle[2].T), B = 0.5f * (_triangle[1].B + _triangle[2].B) }, new VertexP3N3G3B3T2() { P = 0.5f * (_triangle[2].P + _triangle[0].P), N = 0.5f * (_triangle[2].N + _triangle[0].N), UV = 0.5f * (_triangle[2].UV + _triangle[0].UV), T = 0.5f * (_triangle[2].T + _triangle[0].T), B = 0.5f * (_triangle[2].B + _triangle[0].B) }, }; VertexP3N3G3B3T2[] temp = new VertexP3N3G3B3T2[3]; temp[0] = _triangle[0]; temp[1] = insideTriangle[0]; temp[2] = insideTriangle[2]; SubdivTriangle(temp, _count - 1, _vertices, _indices); temp[0] = insideTriangle[0]; temp[1] = _triangle[1]; temp[2] = insideTriangle[1]; SubdivTriangle(temp, _count - 1, _vertices, _indices); temp[0] = insideTriangle[0]; temp[1] = insideTriangle[1]; temp[2] = insideTriangle[2]; SubdivTriangle(temp, _count - 1, _vertices, _indices); temp[0] = insideTriangle[2]; temp[1] = insideTriangle[1]; temp[2] = _triangle[2]; SubdivTriangle(temp, _count - 1, _vertices, _indices); }
private void BuildPrimitives() { { VertexPt4[] Vertices = new VertexPt4[4]; Vertices[0] = new VertexPt4() { Pt = new float4(-1, +1, 0, 1) }; // Top-Left Vertices[1] = new VertexPt4() { Pt = new float4(-1, -1, 0, 1) }; // Bottom-Left Vertices[2] = new VertexPt4() { Pt = new float4(+1, +1, 0, 1) }; // Top-Right Vertices[3] = new VertexPt4() { Pt = new float4(+1, -1, 0, 1) }; // Bottom-Right ByteBuffer VerticesBuffer = VertexPt4.FromArray(Vertices); m_Prim_Quad = new Primitive(m_Device, Vertices.Length, VerticesBuffer, null, Primitive.TOPOLOGY.TRIANGLE_STRIP, VERTEX_FORMAT.Pt4); } { VertexP3N3G3B3T2[] Vertices = new VertexP3N3G3B3T2[4]; Vertices[0] = new VertexP3N3G3B3T2() { P = new float3(-1, +1, 0), N = new float3(0, 0, 1), T = new float3(1, 0, 0), B = new float3(0, 1, 0), UV = new float2(0, 0) }; // Top-Left Vertices[1] = new VertexP3N3G3B3T2() { P = new float3(-1, -1, 0), N = new float3(0, 0, 1), T = new float3(1, 0, 0), B = new float3(0, 1, 0), UV = new float2(0, 1) }; // Bottom-Left Vertices[2] = new VertexP3N3G3B3T2() { P = new float3(+1, +1, 0), N = new float3(0, 0, 1), T = new float3(1, 0, 0), B = new float3(0, 1, 0), UV = new float2(1, 0) }; // Top-Right Vertices[3] = new VertexP3N3G3B3T2() { P = new float3(+1, -1, 0), N = new float3(0, 0, 1), T = new float3(1, 0, 0), B = new float3(0, 1, 0), UV = new float2(1, 1) }; // Bottom-Right ByteBuffer VerticesBuffer = VertexP3N3G3B3T2.FromArray(Vertices); m_Prim_Rectangle = new Primitive(m_Device, Vertices.Length, VerticesBuffer, null, Primitive.TOPOLOGY.TRIANGLE_STRIP, VERTEX_FORMAT.P3N3G3B3T2); } { // Build the sphere const int W = 41; const int H = 22; VertexP3N3G3B3T2[] Vertices = new VertexP3N3G3B3T2[W * H]; for (int Y = 0; Y < H; Y++) { double Theta = Math.PI * Y / (H - 1); float CosTheta = (float)Math.Cos(Theta); float SinTheta = (float)Math.Sin(Theta); for (int X = 0; X < W; X++) { double Phi = 2.0 * Math.PI * X / (W - 1); float CosPhi = (float)Math.Cos(Phi); float SinPhi = (float)Math.Sin(Phi); float3 N = new float3(SinTheta * SinPhi, CosTheta, SinTheta * CosPhi); float3 T = new float3(CosPhi, 0.0f, -SinPhi); float3 B = N.Cross(T); Vertices[W * Y + X] = new VertexP3N3G3B3T2() { P = N, N = N, T = T, B = B, UV = new float2(2.0f * X / W, 1.0f * Y / H) }; } } ByteBuffer VerticesBuffer = VertexP3N3G3B3T2.FromArray(Vertices); uint[] Indices = new uint[(H - 1) * (2 * W + 2) - 2]; int IndexCount = 0; for (int Y = 0; Y < H - 1; Y++) { for (int X = 0; X < W; X++) { Indices[IndexCount++] = (uint)((Y + 0) * W + X); Indices[IndexCount++] = (uint)((Y + 1) * W + X); } if (Y < H - 2) { Indices[IndexCount++] = (uint)((Y + 1) * W - 1); Indices[IndexCount++] = (uint)((Y + 1) * W + 0); } } m_Prim_Sphere = new Primitive(m_Device, Vertices.Length, VerticesBuffer, Indices, Primitive.TOPOLOGY.TRIANGLE_STRIP, VERTEX_FORMAT.P3N3G3B3T2); } { // Build the cube float3[] Normals = new float3[6] { -float3.UnitX, float3.UnitX, -float3.UnitY, float3.UnitY, -float3.UnitZ, float3.UnitZ, }; float3[] Tangents = new float3[6] { float3.UnitZ, -float3.UnitZ, float3.UnitX, -float3.UnitX, -float3.UnitX, float3.UnitX, }; VertexP3N3G3B3T2[] Vertices = new VertexP3N3G3B3T2[6 * 4]; uint[] Indices = new uint[2 * 6 * 3]; for (int FaceIndex = 0; FaceIndex < 6; FaceIndex++) { float3 N = Normals[FaceIndex]; float3 T = Tangents[FaceIndex]; float3 B = N.Cross(T); Vertices[4 * FaceIndex + 0] = new VertexP3N3G3B3T2() { P = N - T + B, N = N, T = T, B = B, UV = new float2(0, 0) }; Vertices[4 * FaceIndex + 1] = new VertexP3N3G3B3T2() { P = N - T - B, N = N, T = T, B = B, UV = new float2(0, 1) }; Vertices[4 * FaceIndex + 2] = new VertexP3N3G3B3T2() { P = N + T - B, N = N, T = T, B = B, UV = new float2(1, 1) }; Vertices[4 * FaceIndex + 3] = new VertexP3N3G3B3T2() { P = N + T + B, N = N, T = T, B = B, UV = new float2(1, 0) }; Indices[2 * 3 * FaceIndex + 0] = (uint)(4 * FaceIndex + 0); Indices[2 * 3 * FaceIndex + 1] = (uint)(4 * FaceIndex + 1); Indices[2 * 3 * FaceIndex + 2] = (uint)(4 * FaceIndex + 2); Indices[2 * 3 * FaceIndex + 3] = (uint)(4 * FaceIndex + 0); Indices[2 * 3 * FaceIndex + 4] = (uint)(4 * FaceIndex + 2); Indices[2 * 3 * FaceIndex + 5] = (uint)(4 * FaceIndex + 3); } ByteBuffer VerticesBuffer = VertexP3N3G3B3T2.FromArray(Vertices); m_Prim_Cube = new Primitive(m_Device, Vertices.Length, VerticesBuffer, Indices, Primitive.TOPOLOGY.TRIANGLE_LIST, VERTEX_FORMAT.P3N3G3B3T2); } }
Primitive BuildTorus() { List <VertexP3N3G3B3T2> vertices = new List <VertexP3N3G3B3T2>(); List <uint> indices = new List <uint>(); // Build vertices VertexP3N3G3B3T2 V = new VertexP3N3G3B3T2(); for (uint i = 0; i < SUBDIVS0; i++) { float a0 = 2.0f * (float)Math.PI * i / SUBDIVS0; float3 X = new float3((float)Math.Cos(a0), (float)Math.Sin(a0), 0.0f); float3 Y = new float3(-X.y, X.x, 0.0f); float3 Z = X.Cross(Y); float3 C = RADIUS0 * X; // Center of little ring, around large ring for (uint j = 0; j < SUBDIVS1; j++) { float a1 = 2.0f * (float)Math.PI * j / SUBDIVS1; float3 lsN = new float3((float)Math.Cos(a1), (float)Math.Sin(a1), 0.0f); // float3 lsN2 = new float3( -lsN.y, lsN.x, 0.0f ); float3 N = lsN.x * X + lsN.y * Z; // float3 N2 = lsN2.x * X + lsN2.y * Z; V.P = C + RADIUS1 * N; V.N = N; V.T = Y; V.UV = new float2(4.0f * i / SUBDIVS0, 1.0f * j / SUBDIVS1); vertices.Add(V); } } // Build indices and curvature uint[,] neighborIndices = new uint[3, 3]; float3[] neighbors = new float3[8]; List <float2> curvatures = new List <float2>(); float2 minCurvature = new float2(float.MaxValue, float.MaxValue); float2 maxCurvature = new float2(-float.MaxValue, -float.MaxValue); float2 avgCurvature = float2.Zero; int count = 0; for (uint i = 0; i < SUBDIVS0; i++) { uint Pi = (i + SUBDIVS0 - 1) % SUBDIVS0; uint Ni = (i + 1) % SUBDIVS0; uint ringCurrent = SUBDIVS1 * i; uint ringPrevious = SUBDIVS1 * Pi; uint ringNext = SUBDIVS1 * Ni; for (uint j = 0; j < SUBDIVS1; j++) { uint Pj = (j + SUBDIVS1 - 1) % SUBDIVS1; uint Nj = (j + 1) % SUBDIVS1; neighborIndices[0, 0] = ringPrevious + Pj; neighborIndices[0, 1] = ringPrevious + j; neighborIndices[0, 2] = ringPrevious + Nj; neighborIndices[1, 0] = ringCurrent + Pj; neighborIndices[1, 1] = ringCurrent + j; neighborIndices[1, 2] = ringCurrent + Nj; neighborIndices[2, 0] = ringNext + Pj; neighborIndices[2, 1] = ringNext + j; neighborIndices[2, 2] = ringNext + Nj; // Build 2 triangles indices.Add(neighborIndices[1, 2]); indices.Add(neighborIndices[1, 1]); indices.Add(neighborIndices[2, 1]); indices.Add(neighborIndices[1, 2]); indices.Add(neighborIndices[2, 1]); indices.Add(neighborIndices[2, 2]); // Compute central vertex's curvature VertexP3N3G3B3T2 centerVertex = vertices[(int)neighborIndices[1, 1]]; neighbors[0] = vertices[(int)neighborIndices[0, 0]].P; neighbors[1] = vertices[(int)neighborIndices[1, 0]].P; neighbors[2] = vertices[(int)neighborIndices[2, 0]].P; neighbors[3] = vertices[(int)neighborIndices[0, 1]].P; // neighbors[] = vertices[(int) neighborIndices[1,1]].P; neighbors[4] = vertices[(int)neighborIndices[2, 1]].P; neighbors[5] = vertices[(int)neighborIndices[0, 2]].P; neighbors[6] = vertices[(int)neighborIndices[1, 2]].P; neighbors[7] = vertices[(int)neighborIndices[2, 2]].P; // Single curvature // float curvature = ComputeTangentSphereRadius( centerVertex.P, centerVertex.N, neighbors, false ); // centerVertex.B.x = curvature; // Double curvature float2 curvature = ComputeTangentCurvatureRadii(centerVertex.P, centerVertex.N, centerVertex.T, neighbors, false); centerVertex.B.x = curvature.x; centerVertex.B.y = curvature.y; vertices[(int)neighborIndices[1, 1]] = centerVertex; curvatures.Add(curvature); minCurvature.Min(curvature); maxCurvature.Max(curvature); avgCurvature += curvature; count++; } } avgCurvature /= count; labelMeshInfo.Text = "Curvature Avg. = " + avgCurvature + " - Min = " + minCurvature + " - Max = " + maxCurvature; return(new Primitive(m_device, (uint)vertices.Count, VertexP3N3G3B3T2.FromArray(vertices.ToArray()), indices.ToArray(), Primitive.TOPOLOGY.TRIANGLE_LIST, VERTEX_FORMAT.P3N3G3B3T2)); }
Primitive BuildSubdividedCube() { // Default example face where B is used to stored the triangle's center and float3 C0 = (new float3(-1.0f, 1.0f, 1.0f) + new float3(-1.0f, -1.0f, 1.0f) + new float3(1.0f, -1.0f, 1.0f)) / 3.0f; float3 C1 = (new float3(-1.0f, 1.0f, 1.0f) + new float3(1.0f, -1.0f, 1.0f) + new float3(1.0f, 1.0f, 1.0f)) / 3.0f; VertexP3N3G3B3T2[] defaultFace = new VertexP3N3G3B3T2[6] { // First triangle new VertexP3N3G3B3T2() { P = new float3(-1.0f, 1.0f, 1.0f), N = new float3(BEND * -1.0f, BEND * 1.0f, 1.0f).Normalized, T = new float3(0, 0, 1), B = C0, UV = new float2(0, 0) }, new VertexP3N3G3B3T2() { P = new float3(-1.0f, -1.0f, 1.0f), N = new float3(BEND * -1.0f, BEND * -1.0f, 1.0f).Normalized, T = new float3(0, 0, 1), B = C0, UV = new float2(0, 1) }, new VertexP3N3G3B3T2() { P = new float3(1.0f, -1.0f, 1.0f), N = new float3(BEND * 1.0f, BEND * -1.0f, 1.0f).Normalized, T = new float3(0, 0, 1), B = C0, UV = new float2(1, 1) }, // 2nd triangle new VertexP3N3G3B3T2() { P = new float3(-1.0f, 1.0f, 1.0f), N = new float3(BEND * -1.0f, BEND * 1.0f, 1.0f).Normalized, T = new float3(0, 0, 1), B = C1, UV = new float2(0, 0) }, new VertexP3N3G3B3T2() { P = new float3(1.0f, -1.0f, 1.0f), N = new float3(BEND * 1.0f, BEND * -1.0f, 1.0f).Normalized, T = new float3(0, 0, 1), B = C1, UV = new float2(1, 1) }, new VertexP3N3G3B3T2() { P = new float3(1.0f, 1.0f, 1.0f), N = new float3(BEND * 1.0f, BEND * 1.0f, 1.0f).Normalized, T = new float3(0, 0, 1), B = C1, UV = new float2(1, 0) }, }; float3[] faceNormals = new float3[6] { -float3.UnitX, float3.UnitX, -float3.UnitY, float3.UnitY, -float3.UnitZ, float3.UnitZ, }; float3[] faceTangents = new float3[6] { float3.UnitZ, -float3.UnitZ, float3.UnitX, float3.UnitX, -float3.UnitX, float3.UnitX, }; Func <float3, float3, float3, float3, float3> lambdaTransform = (float3 _P, float3 _T, float3 _B, float3 _N) => _P.x * _T + _P.y * _B + _P.z * _N; List <VertexP3N3G3B3T2> vertices = new List <VertexP3N3G3B3T2>(); List <uint> indices = new List <uint>(); const uint SUBVIVS_COUNT = 5; VertexP3N3G3B3T2[] temp = new VertexP3N3G3B3T2[3]; // for ( int faceIndex=3; faceIndex < 4; faceIndex++ ) { for (int faceIndex = 0; faceIndex < 6; faceIndex++) { float3 N = faceNormals[faceIndex]; float3 T = faceTangents[faceIndex]; float3 B = N.Cross(T); for (uint triIndex = 0; triIndex < 2; triIndex++) { for (int i = 0; i < 3; i++) { VertexP3N3G3B3T2 V = defaultFace[3 * triIndex + i]; temp[i].P = lambdaTransform(V.P, T, B, N); temp[i].N = lambdaTransform(V.N, T, B, N); temp[i].T = lambdaTransform(V.T, T, B, N); temp[i].B = lambdaTransform(V.B, T, B, N); temp[i].UV = V.UV; } SubdivTriangle(temp, SUBVIVS_COUNT, vertices, indices); } } return(new Primitive(m_device, (uint)vertices.Count, VertexP3N3G3B3T2.FromArray(vertices.ToArray()), indices.ToArray(), Primitive.TOPOLOGY.TRIANGLE_LIST, VERTEX_FORMAT.P3N3G3B3T2)); }
Primitive BuildCube() { // Default example face where B is used to stored the triangle's center and // float3 C0 = (new float3( -1.0f, 1.0f, 1.0f ) + new float3( -1.0f, -1.0f, 1.0f ) + new float3( 1.0f, -1.0f, 1.0f )) / 3.0f; // float3 C1 = (new float3( -1.0f, 1.0f, 1.0f ) + new float3( 1.0f, -1.0f, 1.0f ) + new float3( 1.0f, 1.0f, 1.0f )) / 3.0f; float R = (float)Math.Sqrt(3.0f) / BEND; float3 C0 = new float3(R, 0, 0); float3 C1 = new float3(R, 0, 0); VertexP3N3G3B3T2[] defaultFace = new VertexP3N3G3B3T2[6] { // First triangle new VertexP3N3G3B3T2() { P = new float3(-1.0f, 1.0f, 1.0f), N = new float3(BEND * -1.0f, BEND * 1.0f, 1.0f).Normalized, T = new float3(0, 0, 1), B = C0, UV = new float2(0, 0) }, new VertexP3N3G3B3T2() { P = new float3(-1.0f, -1.0f, 1.0f), N = new float3(BEND * -1.0f, BEND * -1.0f, 1.0f).Normalized, T = new float3(0, 0, 1), B = C0, UV = new float2(0, 1) }, new VertexP3N3G3B3T2() { P = new float3(1.0f, -1.0f, 1.0f), N = new float3(BEND * 1.0f, BEND * -1.0f, 1.0f).Normalized, T = new float3(0, 0, 1), B = C0, UV = new float2(1, 1) }, // 2nd triangle new VertexP3N3G3B3T2() { P = new float3(-1.0f, 1.0f, 1.0f), N = new float3(BEND * -1.0f, BEND * 1.0f, 1.0f).Normalized, T = new float3(0, 0, 1), B = C1, UV = new float2(0, 0) }, new VertexP3N3G3B3T2() { P = new float3(1.0f, -1.0f, 1.0f), N = new float3(BEND * 1.0f, BEND * -1.0f, 1.0f).Normalized, T = new float3(0, 0, 1), B = C1, UV = new float2(1, 1) }, new VertexP3N3G3B3T2() { P = new float3(1.0f, 1.0f, 1.0f), N = new float3(BEND * 1.0f, BEND * 1.0f, 1.0f).Normalized, T = new float3(0, 0, 1), B = C1, UV = new float2(1, 0) }, }; float3[] faceNormals = new float3[6] { -float3.UnitX, float3.UnitX, -float3.UnitY, float3.UnitY, -float3.UnitZ, float3.UnitZ, }; float3[] faceTangents = new float3[6] { float3.UnitZ, -float3.UnitZ, float3.UnitX, float3.UnitX, -float3.UnitX, float3.UnitX, }; Func <float3, float3, float3, float3, float3> lambdaTransform = (float3 _P, float3 _T, float3 _B, float3 _N) => _P.x * _T + _P.y * _B + _P.z * _N; VertexP3N3G3B3T2[] vertices = new VertexP3N3G3B3T2[6 * 2 * 3]; for (int faceIndex = 0; faceIndex < 6; faceIndex++) { float3 N = faceNormals[faceIndex]; float3 T = faceTangents[faceIndex]; float3 B = N.Cross(T); for (int i = 0; i < 6; i++) { VertexP3N3G3B3T2 V = defaultFace[i]; vertices[6 * faceIndex + i].P = lambdaTransform(V.P, T, B, N); vertices[6 * faceIndex + i].N = lambdaTransform(V.N, T, B, N); vertices[6 * faceIndex + i].T = lambdaTransform(V.T, T, B, N); // vertices[6*faceIndex+i].B = lambdaTransform( V.B, T, B, N ); vertices[6 * faceIndex + i].B = V.B; vertices[6 * faceIndex + i].UV = V.UV; } } uint[] indices = new uint[6 * 2 * 3] { // 0, 1, 2, 0, 2, 3, // 4, 5, 6, 4, 6, 7, // 8, 9, 10, 8, 10, 11, // 12, 13, 14, 12, 14, 15, // 16, 17, 18, 16, 18, 19, // 20, 21, 22, 20, 22, 23, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35 }; return(new Primitive(m_device, (uint)vertices.Length, VertexP3N3G3B3T2.FromArray(vertices), indices, Primitive.TOPOLOGY.TRIANGLE_LIST, VERTEX_FORMAT.P3N3G3B3T2)); }
/* // float Theta = 0.5 * _UV.x * PI; // float3 ToLight = float3( sin( Theta ), 0, cos( Theta ) ); // float3 ToView = float3( -sin( Theta ), 0, cos( Theta ) ); // // float Albedo = 0.0; // const int THETA_COUNT = 64; // * $alwaysOne; // warning X4008: floating point division by zero // const float dTheta = HALFPI / THETA_COUNT; // const float dPhi = PI / THETA_COUNT; // for ( int i=0; i < THETA_COUNT; i++ ) // { // Theta = HALFPI * (0.5 + i) / THETA_COUNT; // for ( int j=0; j < THETA_COUNT; j++ ) // { // float Phi = PI * j / THETA_COUNT; // // ToView = float3( sin( Theta ) * cos( Phi ), sin( Theta ) * sin( Phi ), cos( Theta ) ); // // float3 Half = normalize( ToLight + ToView ); // // // "True" and expensive evaluation of the Ward BRDF // float alpha = Roughness; // // float CosDelta = Half.z; // dot( Half, _wsNormal ); // float delta = acos( CosDelta ); // float CosThetaL = ToLight.z; // float SinThetaL = sqrt( 1.0 - CosThetaL*CosThetaL ); // float PhiL = atan2( ToLight.y, ToLight.x ); // float CosThetaV = ToView.z; // float SinThetaV = sqrt( 1.0 - CosThetaV*CosThetaV ); // float PhiV = atan2( ToView.y, ToView.x ); // // float BRDF = 1.0 / square(alpha) * exp( -square( tan( delta ) / alpha ) ) * 2.0 * (1.0 + CosThetaL*CosThetaV + SinThetaL*SinThetaV*cos( PhiV - PhiL )) / pow4( CosThetaL + CosThetaV ); // // Albedo += BRDF * cos( Theta ) * sin( Theta ) * dTheta * dPhi; // } // } // // Albedo *= 2.0; // Since we integrate on half a hemisphere... // Albedo *= INVPI; // Since we forgot that in the main loop // // ========================================================================== // arkDebugBRDFAlbedo // // Displays the true and theoretical BRDF albedos // // ========================================================================== // renderProg PostFX/Debug/WardBRDFAlbedo { newstyle hlsl_prefix { #include <ward> // Displays the BRDF albedo in 2 small screen insets showing how the albedo varies with roughness // The albedo is computed by integrating the BRDF over an entire hemisphere of view directions for a // single light direction that varies with U in [0,1] corresponding to Theta_Light in [0,PI/2]. // // The goal here is to demonstrate the albedo is correctly bounded (i.e. never > 1). // The especially important part is when the light is at grazing angles (i.e. U -> 1) // // Just call this function in the finalizer post-process, providing the screen UVs and the final color // void DEBUG_DisplayWardBRDFAlbedo( float2 _UV, inout float3 _Color ) { float Roughness = lerp( 0.01, 1.0, 0.5 * (1.0 + sin( $time.x )) ); // Roughness is varying with time here... if ( _UV.x < 0.2 && _UV.y > 0.8 ) { // This version integrates the "true" BRDF // The formula comes from eq. (15) from http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.169.9908&rep=rep1&type=pdf // _UV.x /= 0.2; _UV.y = (_UV.y - 0.8) / 0.2; float Theta = 0.5 * _UV.x * PI; float3 ToLight = float3( sin( Theta ), 0, cos( Theta ) ); float3 ToView = float3( -sin( Theta ), 0, cos( Theta ) ); float Albedo = 0.0; const int THETA_COUNT = 64; // * $alwaysOne; // warning X4008: floating point division by zero const float dTheta = HALFPI / THETA_COUNT; const float dPhi = PI / THETA_COUNT; for ( int i=0; i < THETA_COUNT; i++ ) { Theta = HALFPI * (0.5 + i) / THETA_COUNT; for ( int j=0; j < THETA_COUNT; j++ ) { float Phi = PI * j / THETA_COUNT; ToView = float3( sin( Theta ) * cos( Phi ), sin( Theta ) * sin( Phi ), cos( Theta ) ); float3 Half = normalize( ToLight + ToView ); // "True" and expensive evaluation of the Ward BRDF float alpha = Roughness; float CosDelta = Half.z; // dot( Half, _wsNormal ); float delta = acos( CosDelta ); float CosThetaL = ToLight.z; float SinThetaL = sqrt( 1.0 - CosThetaL*CosThetaL ); float PhiL = atan2( ToLight.y, ToLight.x ); float CosThetaV = ToView.z; float SinThetaV = sqrt( 1.0 - CosThetaV*CosThetaV ); float PhiV = atan2( ToView.y, ToView.x ); float BRDF = 1.0 / square(alpha) * exp( -square( tan( delta ) / alpha ) ) * 2.0 * (1.0 + CosThetaL*CosThetaV + SinThetaL*SinThetaV*cos( PhiV - PhiL )) / pow4( CosThetaL + CosThetaV ); Albedo += BRDF * cos( Theta ) * sin( Theta ) * dTheta * dPhi; } } Albedo *= 2.0; // Since we integrate on half a hemisphere... Albedo *= INVPI; // Since we forgot that in the main loop _Color = 1.0 - _UV.y < 0.8 * Albedo ? 1 : 0; if ( _UV.x < 0.01 ) _Color = Roughness; } else if ( _UV.x > 0.25 && _UV.x < 0.45 && _UV.y > 0.8 ) { // And this version uses our implementation of the Ward BRDF // The formula comes from eq. (23) of the same paper. // Both renderings should be equal and albedo should NEVER be > 1! // _UV.x = (_UV.x - 0.25) / 0.2; _UV.y = (_UV.y - 0.8) / 0.2; WardContext ctx; CreateWardContext( ctx, Roughness, 0.0, 0.0, float3( 1, 0, 0 ), float3( 0, 1, 0 ) ); float Theta = 0.5 * _UV.x * PI; float3 ToLight = float3( sin( Theta ), 0, cos( Theta ) ); float3 ToView = float3( -sin( Theta ), 0, cos( Theta ) ); float Albedo = 0.0; const int THETA_COUNT = 64; // * $alwaysOne; // Allows to prevent unrolling and lenghty shader compilation! const float dTheta = HALFPI / THETA_COUNT; const float dPhi = PI / THETA_COUNT; for ( int i=0; i < THETA_COUNT; i++ ) { Theta = HALFPI * (0.5 + i) / THETA_COUNT; for ( int j=0; j < THETA_COUNT; j++ ) { float Phi = PI * j / THETA_COUNT; ToView = float3( sin( Theta ) * cos( Phi ), sin( Theta ) * sin( Phi ), cos( Theta ) ); Albedo += ctx.ComputeWardTerm( float3( 0, 0, 1 ), ToLight, ToView ) * cos( Theta ) * sin( Theta ) * dTheta * dPhi; } } Albedo *= 2.0; // Since we integrate on half a hemisphere... Albedo *= INVPI; // Since we forgot that in the main loop _Color = (1.0 - _UV.y < 0.8 * Albedo ? 1 : 0) * float3( 1, 0, 0 ); if ( _UV.x < 0.01 ) _Color = Roughness; } } } } * / /* struct WardContext { float3 anisoTangentDivRoughness; float3 anisoBitangentDivRoughness; float specularNormalization; float isotropicRoughness; // Original normalized isotropic roughness // float diffuseRoughness; // ARKANE: bmayaux (2013-10-14) Disney diffuse roughness Fresnel term // Computes the Ward normal distribution // _wsNormal, surface normal // _wsToLight, world space light vector // _wsView, world space view vector // float ComputeWardTerm( float3 _wsNormal, float3 _wsToLight, float3 _wsView ) { float3 Half = _wsToLight + _wsView; // Half = normalize(Half); // not normalized on purpose, HdotH would be 1 if normalized, nonsense float HdotN = dot( Half, _wsNormal ); HdotN = max( 1e-4f, HdotN ); float invHdotN_2 = 1.0 / square( HdotN ); float invHdotN_4 = square( invHdotN_2 ); float HdotT = dot( Half, anisoTangentDivRoughness ); float HdotB = dot( Half, anisoBitangentDivRoughness ); float HdotH = dot( Half, Half ); float exponent = -invHdotN_2 * (square( HdotT ) + square( HdotB )); return specularNormalization * exp( exponent ) * HdotH * invHdotN_4; } }; void CreateWardContext( out WardContext wardContext, float _Roughness, float _Anisotropy, float _AnisotropyAngle, float3 _Tangent, float3 _BiTangent ) { // Tweak roughness so the user feels it's a linear parameter float Roughness = _Roughness * _Roughness; // Roughness squared seems to give a nice linear feel... // People at Disney seem to agree with me! (cf §5.4 http://blog.selfshadow.com/publications/s2012-shading-course/burley/s2012_pbs_disney_brdf_notes_v2.pdf) // Keep an average normalized roughness value for other people who require it (e.g. IBL) wardContext.isotropicRoughness = Roughness; // Build anisotropic roughness along tangent/bitangent float2 anisotropicRoughness = float2( Roughness, Roughness * saturate( 1.0 - _Anisotropy ) ); anisotropicRoughness = max( 0.01, anisotropicRoughness ); // Make sure we don't go below 0.01 otherwise specularity is unnatural for our poor lights (only IBL with many samples would solve that!) // Tangent/Ax, Bitangent/Ay float2 sinCosAnisotropy; sincos( _AnisotropyAngle, sinCosAnisotropy.x, sinCosAnisotropy.y ); float2 invRoughness = 1.0 / (1e-5 + anisotropicRoughness); wardContext.anisoTangentDivRoughness = (sinCosAnisotropy.y * _Tangent + sinCosAnisotropy.x * _BiTangent) * invRoughness.x; wardContext.anisoBitangentDivRoughness = (sinCosAnisotropy.y * _BiTangent - sinCosAnisotropy.x * _Tangent) * invRoughness.y; wardContext.specularNormalization = INVPI * invRoughness.x * invRoughness.y; // ARKANE: bmayaux (2014-02-05) Sheen is not tied to roughness anymore (for better or for worse?) so it doesn't need to be tied to Ward anymore either! // // ARKANE: bmayaux (2013-10-14) Disney diffuse roughness Fresnel term // // Diffuse roughness starts increasing for ward roughness > 0.35 and reaches 1 for ward roughness = 0.85 // // this to make sure only very diffuse and rough objects have a sheen... // // wardContext.diffuseRoughness = _Sheen * saturate( 2.0 * _Roughness - 0.7 ); // wardContext.diffuseRoughness = _Sheen; // Use direct sheen value, at the risk of making it strange on smooth materials... } */ #endregion #region Primitives private void BuildPrimitives() { { VertexPt4[] Vertices = new VertexPt4[4]; Vertices[0] = new VertexPt4() { Pt = new float4( -1, +1, 0, 1 ) }; // Top-Left Vertices[1] = new VertexPt4() { Pt = new float4( -1, -1, 0, 1 ) }; // Bottom-Left Vertices[2] = new VertexPt4() { Pt = new float4( +1, +1, 0, 1 ) }; // Top-Right Vertices[3] = new VertexPt4() { Pt = new float4( +1, -1, 0, 1 ) }; // Bottom-Right ByteBuffer VerticesBuffer = VertexPt4.FromArray( Vertices ); m_Prim_Quad = new Primitive( m_Device, Vertices.Length, VerticesBuffer, null, Primitive.TOPOLOGY.TRIANGLE_STRIP, VERTEX_FORMAT.Pt4 ); } { VertexP3N3G3B3T2[] Vertices = new VertexP3N3G3B3T2[4]; Vertices[0] = new VertexP3N3G3B3T2() { P = new float3( -1, +1, 0 ), N = new float3( 0, 0, 1 ), T = new float3( 1, 0, 0 ), B = new float3( 0, 1, 0 ), UV = new float2( 0, 0 ) }; // Top-Left Vertices[1] = new VertexP3N3G3B3T2() { P = new float3( -1, -1, 0 ), N = new float3( 0, 0, 1 ), T = new float3( 1, 0, 0 ), B = new float3( 0, 1, 0 ), UV = new float2( 0, 1 ) }; // Bottom-Left Vertices[2] = new VertexP3N3G3B3T2() { P = new float3( +1, +1, 0 ), N = new float3( 0, 0, 1 ), T = new float3( 1, 0, 0 ), B = new float3( 0, 1, 0 ), UV = new float2( 1, 0 ) }; // Top-Right Vertices[3] = new VertexP3N3G3B3T2() { P = new float3( +1, -1, 0 ), N = new float3( 0, 0, 1 ), T = new float3( 1, 0, 0 ), B = new float3( 0, 1, 0 ), UV = new float2( 1, 1 ) }; // Bottom-Right ByteBuffer VerticesBuffer = VertexP3N3G3B3T2.FromArray( Vertices ); m_Prim_Rectangle = new Primitive( m_Device, Vertices.Length, VerticesBuffer, null, Primitive.TOPOLOGY.TRIANGLE_STRIP, VERTEX_FORMAT.P3N3G3B3T2 ); } { // Build the sphere const int W = 41; const int H = 22; VertexP3N3G3B3T2[] Vertices = new VertexP3N3G3B3T2[W*H]; for ( int Y=0; Y < H; Y++ ) { double Theta = Math.PI * Y / (H-1); float CosTheta = (float) Math.Cos( Theta ); float SinTheta = (float) Math.Sin( Theta ); for ( int X=0; X < W; X++ ) { double Phi = 2.0 * Math.PI * X / (W-1); float CosPhi = (float) Math.Cos( Phi ); float SinPhi = (float) Math.Sin( Phi ); float3 N = new float3( SinTheta * SinPhi, CosTheta, SinTheta * CosPhi ); float3 T = new float3( CosPhi, 0.0f, -SinPhi ); float3 B = N.Cross( T ); Vertices[W*Y+X] = new VertexP3N3G3B3T2() { P = N, N = N, T = T, B = B, UV = new float2( 2.0f * X / W, 1.0f * Y / H ) }; } } ByteBuffer VerticesBuffer = VertexP3N3G3B3T2.FromArray( Vertices ); uint[] Indices = new uint[(H-1) * (2*W+2)-2]; int IndexCount = 0; for ( int Y=0; Y < H-1; Y++ ) { for ( int X=0; X < W; X++ ) { Indices[IndexCount++] = (uint) ((Y+0) * W + X); Indices[IndexCount++] = (uint) ((Y+1) * W + X); } if ( Y < H-2 ) { Indices[IndexCount++] = (uint) ((Y+1) * W - 1); Indices[IndexCount++] = (uint) ((Y+1) * W + 0); } } m_Prim_Sphere = new Primitive( m_Device, Vertices.Length, VerticesBuffer, Indices, Primitive.TOPOLOGY.TRIANGLE_STRIP, VERTEX_FORMAT.P3N3G3B3T2 ); } { // Build the cube float3[] Normals = new float3[6] { -float3.UnitX, float3.UnitX, -float3.UnitY, float3.UnitY, -float3.UnitZ, float3.UnitZ, }; float3[] Tangents = new float3[6] { float3.UnitZ, -float3.UnitZ, float3.UnitX, -float3.UnitX, -float3.UnitX, float3.UnitX, }; VertexP3N3G3B3T2[] Vertices = new VertexP3N3G3B3T2[6*4]; uint[] Indices = new uint[2*6*3]; for ( int FaceIndex=0; FaceIndex < 6; FaceIndex++ ) { float3 N = Normals[FaceIndex]; float3 T = Tangents[FaceIndex]; float3 B = N.Cross( T ); Vertices[4*FaceIndex+0] = new VertexP3N3G3B3T2() { P = N - T + B, N = N, T = T, B = B, UV = new float2( 0, 0 ) }; Vertices[4*FaceIndex+1] = new VertexP3N3G3B3T2() { P = N - T - B, N = N, T = T, B = B, UV = new float2( 0, 1 ) }; Vertices[4*FaceIndex+2] = new VertexP3N3G3B3T2() { P = N + T - B, N = N, T = T, B = B, UV = new float2( 1, 1 ) }; Vertices[4*FaceIndex+3] = new VertexP3N3G3B3T2() { P = N + T + B, N = N, T = T, B = B, UV = new float2( 1, 0 ) }; Indices[2*3*FaceIndex+0] = (uint) (4*FaceIndex+0); Indices[2*3*FaceIndex+1] = (uint) (4*FaceIndex+1); Indices[2*3*FaceIndex+2] = (uint) (4*FaceIndex+2); Indices[2*3*FaceIndex+3] = (uint) (4*FaceIndex+0); Indices[2*3*FaceIndex+4] = (uint) (4*FaceIndex+2); Indices[2*3*FaceIndex+5] = (uint) (4*FaceIndex+3); } ByteBuffer VerticesBuffer = VertexP3N3G3B3T2.FromArray( Vertices ); m_Prim_Cube = new Primitive( m_Device, Vertices.Length, VerticesBuffer, Indices, Primitive.TOPOLOGY.TRIANGLE_LIST, VERTEX_FORMAT.P3N3G3B3T2 ); } }