public void FillTriangle(Vertex v1, Vertex v2, Vertex v3) { var verts = SortVertex(v1, v2, v3); if (MathUtility.IsEqual(verts[1].pos.y, verts[2].pos.y)) { FillBottomFlatTriangle(verts[0], verts[1], verts[2]); } else if (verts[0].pos.y == verts[1].pos.y) { FillTopFlatTriangle(verts[0], verts[1], verts[2]); } else //将三角面切割成平顶平底两个三角面绘制 { float t = (verts[1].pos.y - verts[0].pos.y) / (verts[2].pos.y - verts[0].pos.y); Vertex v4 = TransformUtility.LerpVertexInScreenSpace(verts[0], verts[2], t); v4.color = ColorUtility.Lerp(verts[0], verts[1], verts[2], v4); float y = verts[1].pos.y; float x = (int)(verts[0].pos.x + (((float)verts[1].pos.y - verts[0].pos.y) / ((float)verts[2].pos.y - verts[0].pos.y)) * (verts[2].pos.x - verts[0].pos.x)); v4.pos = new Vector(x, y, verts[1].pos.z); FillBottomFlatTriangle(verts[0], verts[1], v4); FillTopFlatTriangle(verts[1], v4, verts[2]); //测试用 //DrawTriangleLine(v1.pos, v2.pos, v3.pos, Color.Black); } }
//v1为最高点 public void FillBottomFlatTriangle(Vertex v1, Vertex v2, Vertex v3) { Vertex topVert = v1; Vertex leftBottomVert = v2.pos.x < v3.pos.x ? v2 : v3; Vertex rightBottomVert = v2.pos.x > v3.pos.x ? v2 : v3; Vector p1 = v1.pos; Vector p2 = leftBottomVert.pos; Vector p3 = rightBottomVert.pos; float invSlope1 = (p2.x - p1.x) / (p2.y - p1.y); float invSlope2 = (p3.x - p1.x) / (p3.y - p1.y); float onePerLength = 1 / (p2.y - p1.y); float curX1 = p1.x; float curX2 = p1.x; Color top = v1.color; Color leftBottom = leftBottomVert.color; Color rightBottom = rightBottomVert.color; //屏幕原点在左上,所以是Y++不是-- for (int scanLineY = (int)p1.y; scanLineY <= p2.y; scanLineY++) { float t = (p2.y - scanLineY) * onePerLength; Vertex right = TransformUtility.LerpVertexInScreenSpace(rightBottomVert, topVert, t); Vertex left = TransformUtility.LerpVertexInScreenSpace(leftBottomVert, topVert, t); if (Application.RenderType == RenderType.VertexColor) { DrawScanLine((int)curX1, (int)curX2, scanLineY, left.color, right.color); } else if (Application.RenderType == RenderType.Texture) { DrawScanLine(left, right); } //避免绘制非常窄的三角形时线段出界 if ((invSlope1 < 0 && curX1 + invSlope1 >= p2.x) || (invSlope1 > 0 && curX1 + invSlope1 <= p2.x)) { curX1 += invSlope1; } if ((invSlope2 < 0 && curX2 + invSlope2 >= p3.x) || (invSlope2 > 0 && curX2 + invSlope2 <= p3.x)) { curX2 += invSlope2; } } }
// v1为左上,v2为右上,v3为最低点 public void FillTopFlatTriangle(Vertex v1, Vertex v2, Vertex v3) { Vertex bottomVert = v3; Vertex leftTopVert = v1.pos.x < v2.pos.x ? v1 : v2; Vertex rightTopVert = v1.pos.x > v2.pos.x ? v1 : v2; Vector p1 = leftTopVert.pos; Vector p2 = rightTopVert.pos; Vector p3 = v3.pos; float invSlope1 = (p3.x - p1.x) / (p3.y - p1.y); float invSlope2 = (p3.x - p2.x) / (p3.y - p2.y); float onePerLength = 1 / (p3.y - p1.y); float curX1 = p3.x; float curX2 = p3.x; Color bottom = v3.color; Color leftTop = leftTopVert.color; Color rightTop = rightTopVert.color; for (int scanLineY = (int)p3.y; scanLineY >= p2.y; scanLineY--) { float t = (scanLineY - p1.y) * onePerLength; Vertex left = TransformUtility.LerpVertexInScreenSpace(leftTopVert, bottomVert, t); Vertex right = TransformUtility.LerpVertexInScreenSpace(rightTopVert, bottomVert, t); if (Application.RenderType == RenderType.VertexColor) { DrawScanLine((int)curX1, (int)curX2, scanLineY, left.color, right.color); } else if (Application.RenderType == RenderType.Texture) { DrawScanLine(left, right); } if ((invSlope1 > 0 && curX1 - invSlope1 >= p1.x) || (invSlope1 < 0 && curX1 - invSlope1 <= p1.x)) { curX1 -= invSlope1; } if ((invSlope2 < 0 && curX2 - invSlope2 <= p2.x) || (invSlope2 > 0 && curX2 - invSlope2 >= p2.x)) { curX2 -= invSlope2; } } }
public override void Render() { if (mesh == null) { return; } Vertex[] tempVerts = new Vertex[] { new Vertex(), new Vertex(), new Vertex() }; int length = mesh.Surfaces.Length; for (int i = 0; i < length; i++) { var surface = mesh.Surfaces[i]; //不把变换后的结果保存顶点中,否则下次渲染顶点位置就不对了 tempVerts[0].Copy(mesh.Vertices[surface.A]); tempVerts[1].Copy(mesh.Vertices[surface.B]); tempVerts[2].Copy(mesh.Vertices[surface.C]); Vector v1 = tempVerts[0].pos; Vector v2 = tempVerts[1].pos; Vector v3 = tempVerts[2].pos; //Obj -> World Vector wp1 = transform.ApplyObj2World(v1); Vector wp2 = transform.ApplyObj2World(v2); Vector wp3 = transform.ApplyObj2World(v3); //World -> view Vector vp1 = transform.ApplyWorldToView(wp1); Vector vp2 = transform.ApplyWorldToView(wp2); Vector vp3 = transform.ApplyWorldToView(wp3); //背面消隐 if (TransformUtility.IsBackCulling(vp1, vp2, vp3)) { continue; } //view -> 齐次裁剪空间 Vector cp1 = transform.ApplyViewToClip(vp1); Vector cp2 = transform.ApplyViewToClip(vp2); Vector cp3 = transform.ApplyViewToClip(vp3); // CVV裁剪 //if(...) //变换到齐次裁剪空间后,w值保存view空间下的z信息 //保存 1/z 用于后面求u'、v' tempVerts[0].onePerZ = 1 / cp1.w; tempVerts[1].onePerZ = 1 / cp2.w; tempVerts[2].onePerZ = 1 / cp3.w; tempVerts[0].u *= tempVerts[0].onePerZ; tempVerts[0].v *= tempVerts[0].onePerZ; tempVerts[1].u *= tempVerts[1].onePerZ; tempVerts[1].v *= tempVerts[1].onePerZ; tempVerts[2].u *= tempVerts[2].onePerZ; tempVerts[2].v *= tempVerts[2].onePerZ; //透视除法 齐次裁剪空间 -> NDC空间 cp1 /= cp1.w; cp2 /= cp2.w; cp3 /= cp3.w; //屏幕映射 NDC空间下 -> 屏幕空间 Vector sp1 = Screen.ComputeToScreenPos(cp1); Vector sp2 = Screen.ComputeToScreenPos(cp2); Vector sp3 = Screen.ComputeToScreenPos(cp3); tempVerts[0].pos = sp1; tempVerts[1].pos = sp2; tempVerts[2].pos = sp3; switch (Application.RenderType) { case RenderType.Wireframe: graphics.DrawTriangleLine(tempVerts[0].pos, tempVerts[1].pos, tempVerts[2].pos, Color.Red); break; case RenderType.Fill: graphics.FillTriangle(tempVerts[0].pos, tempVerts[1].pos, tempVerts[2].pos, Color.Red); break; case RenderType.VertexColor: graphics.FillTriangle(tempVerts[0], tempVerts[1], tempVerts[2]); break; case RenderType.Texture: graphics.FillTriangle(tempVerts[0], tempVerts[1], tempVerts[2]); break; } } }