// DDA 画线算法 private void DrawLine(Vertex v1, Vertex v2, Vector4 point0, Vector4 point1, Scene scene) { int x0 = (int)point0.X; int y0 = (int)point0.Y; int x1 = (int)point1.X; int y1 = (int)point1.Y; float z1 = point0.Z; float z2 = point1.Z; int dx = x1 - x0; int dy = y1 - y0; int steps = Math.Max(Math.Abs(dx), Math.Abs(dy)); if (steps == 0) { return; } float xInc = (float)dx / (float)steps; float yInc = (float)dy / (float)steps; float x = x0; float y = y0; //顶点位置和法线方向与 光线方向的点积 // 顶点的实际颜色 = 光线方向 点积 法线方向 * 光的颜色* 本身颜色 float nDotL1 = DirectionLight.ComputeNDotL(v1.Position, v1.Normal); float nDotL2 = DirectionLight.ComputeNDotL(v2.Position, v2.Normal); for (int i = 1; i <= steps; i++) { float ratio = (float)i / (float)steps; Color4 vertexColor = new Color4(0, 0, 0); Color4 lightColor = new Color4(0, 0, 0); if (DirectionLight.IsEnable) { Color4 c1 = DirectionLight.GetFinalLightColor(nDotL1); Color4 c2 = DirectionLight.GetFinalLightColor(nDotL2); lightColor = MathUtil.ColorInterp(c1, c2, ratio); } vertexColor = MathUtil.ColorInterp(v1.Color, v2.Color, ratio); float z = MathUtil.Interp(z1, z2, ratio); if (float.IsNaN(z)) { //Console.WriteLine("IsNaN"); return; } DrawPoint(new Vector4((int)x, (int)y, z, 0), vertexColor + lightColor); x += xInc; y += yInc; } }
public void DrawDDALine(Vertex v1, Vertex v2) { //int a, b, c, d; double m = v2.ScreenSpacePosition.X - v1.ScreenSpacePosition.X; double n = v2.ScreenSpacePosition.Y - v1.ScreenSpacePosition.Y; Vector4 point1 = v1.ScreenSpacePosition; Vector4 point2 = v2.ScreenSpacePosition; //顶点位置和法线方向与 光线方向的点积 // 顶点的实际颜色 = 光线方向 点积 法线方向 * 光的颜色* 本身颜色 float nDotL1 = DirectionLight.ComputeNDotL(v1.Position, v1.Normal); float nDotL2 = DirectionLight.ComputeNDotL(v2.Position, v2.Normal); float z1 = point1.Z; float z2 = point2.Z; double px = point1.X; double py = point1.Y; double steps = Math.Max(Math.Abs(m), Math.Abs(n)); double xt = m / steps; double yt = n / steps; for (int i = 0; i < steps; i++) { Color4 vertexColor = new Color4(0, 0, 0); Color4 lightColor = new Color4(0, 0, 0); float ratio = (float)(i / steps); if (DirectionLight.IsEnable) { Color4 c1 = DirectionLight.GetFinalLightColor(nDotL1); Color4 c2 = DirectionLight.GetFinalLightColor(nDotL2); lightColor = MathUtil.ColorInterp(c1, c2, ratio); } vertexColor = MathUtil.ColorInterp(v1.Color, v2.Color, ratio); float z = MathUtil.Interp(z1, z2, ratio); //PutPixel((int)x, (int)(y + 0.5), color); DrawPoint(new Vector4((int)px, (int)(py + 0.5), z, 0), vertexColor + lightColor); px += xt; py += yt; } }
/* gouraud 着色 * * Ia = (ya - y2)*I1/(y1-y2) + (y1-ya)*I2/(y1 -y2) * */ public static void GouraudColor(Scene scene, Device device, float x, float y, float z, VertexTriangle vt, VertexTriangle orivt) { Color4 final = new Color4(); Vertex A = vt.Vertices[0]; Vertex B = vt.Vertices[1]; Vertex C = vt.Vertices[2]; float nDotLA1V1 = DirectionLight.ComputeNDotL(A.nowPos, A.nowNormal); A.lightColor = A.Color * DirectionLight.GetFinalLightColor(nDotLA1V1); float nDotLA1V2 = DirectionLight.ComputeNDotL(B.nowPos, B.nowNormal); B.lightColor = B.Color * DirectionLight.GetFinalLightColor(nDotLA1V2); float nDotLA2V1 = DirectionLight.ComputeNDotL(C.nowPos, C.nowNormal); C.lightColor = C.Color * DirectionLight.GetFinalLightColor(nDotLA2V1); float ay = A.ScreenSpacePosition.Y; float by = B.ScreenSpacePosition.Y; float cy = C.ScreenSpacePosition.Y; float ax = A.ScreenSpacePosition.X; float bx = B.ScreenSpacePosition.X; float cx = C.ScreenSpacePosition.X; bool isIn = vt.CalculateWeight(new Vector4(x, y, 0, 0)); if (isIn) { final = vt.GetInterColor(); device.DrawPoint(new Vector4(x, y, z, 0), final); } else { } }
public static void GouraudColor(Scene scene, Device device, int i, Edge a1, Edge a2, VertexTriangle orivt, VertexTriangle vt, bool isworld) { Color4 final = new Color4(); /* * 记过排序后的边传入连续两条边 相交的情况只有两种钝角三角形 和锐角三角形 * x 从小到大 * * /\ /\ * / \ / \ * / \/ \_________ * x1 x3 x4 * 1. x左右分开 * 2. x相同 以斜率排序 * */ /* 双线性插值算法 * * */ Vector4 screenA1V1 = a1.v1.ScreenSpacePosition; Vector4 screenA1V2 = a1.v2.ScreenSpacePosition; Vector4 screenA2V1 = a2.v1.ScreenSpacePosition; Vector4 screenA2V2 = a2.v2.ScreenSpacePosition; // 双线性插值系数 // 第一次插值算出交点的颜色系数 float r1 = ((float)i - screenA1V1.Y) / (float)(screenA1V2.Y - screenA1V1.Y); float r2 = ((float)i - screenA2V1.Y) / (float)(screenA2V2.Y - screenA2V1.Y); r1 = MathUtil.Clamp01(r1); r2 = MathUtil.Clamp01(r2); // 像素点深度插值 float z1 = MathUtil.Interp(screenA1V1.Z, screenA1V2.Z, r1); float z2 = MathUtil.Interp(screenA2V1.Z, screenA2V2.Z, r2); //float z3 = 0; float r3 = MathUtil.Clamp01(((float)x - a1.x) / (a2.x - a1.x)); //float r3 = (float)(x - Math.Floor(a1.x)) / (a2.x - a1.x); float z = MathUtil.Interp(z1, z2, r3); //float z = vt.GetInterValue(z1, z2, z3); float nDotL1 = 0, nDotL2 = 0; switch (scene.renderState) { case Scene.RenderState.WireFrame: { } break; case Scene.RenderState.GouraduShading: { } break; case Scene.RenderState.TextureMapping: { orivt.CalWeight(new Vector4(x, i, 0, 0)); Vector4 uv = orivt.GetInterUV(); if (isworld) { //final = device.Tex2D(uv.X, uv.Y, scene.worldMap.texture); } else { if (DirectionLight.IsEnable) { float nDotLA1V1 = DirectionLight.ComputeNDotL(a1.v1.nowPos, a1.v1.nowNormal); float nDotLA1V2 = DirectionLight.ComputeNDotL(a1.v2.nowPos, a1.v2.nowNormal); float nDotLA2V1 = DirectionLight.ComputeNDotL(a2.v1.nowPos, a2.v1.nowNormal); float nDotLA2V2 = DirectionLight.ComputeNDotL(a2.v2.nowPos, a2.v2.nowNormal); nDotL1 = MathUtil.Interp(nDotLA1V1, nDotLA1V2, r1); nDotL2 = MathUtil.Interp(nDotLA2V1, nDotLA2V2, r2); float nDotL = MathUtil.Interp(nDotL1, nDotL2, r3); //Console.WriteLine(nDotL1+" "+ nDotL2+" " + nDotL); final = device.Tex2D(uv.X, uv.Y, scene.mesh.texture); final = final * DirectionLight.GetFinalLightColor(nDotL); } else { final = device.Tex2D(uv.X, uv.Y, scene.mesh.texture); } } } break; } device.DrawPoint(new Vector4(x, i, z, 0), final); }
/* 边表法填充 * 优点 扫描效率高,结构清晰 * 缺点 光栅化时三角形斜边在水平情况下差值不均匀造成颜色区块 * vt是裁剪后的三角形, * orivt是原始三角形,目的是在纹理映射的时候计算权重 * */ public void ProcessScanLine(VertexTriangle vt, VertexTriangle oriVt, Scene scene, bool isworld) { int yMin = this.height; int yMax = 0; Vertex A = vt.Vertices[0]; Vertex B = vt.Vertices[1]; Vertex C = vt.Vertices[2]; /* 扫描线原理 * * 1. 求扫描线与多边形的交点 * 2. 对所求得的交点按 x 从小到大排序 * 3. 将交点两两配对,并填充每一区段 * */ // ET AEL 扫描算法见 72 页 4.2.2 /* 1. 找到三角形 y 的最大值和最小值 * 并将三角形的边以顶点坐标X的值从左往右(x 从小到大)填充 * 在边表 ET[] 中对应三角形的Ymin处 */ Vertex[] vertices = vt.Vertices; for (int i = 0; i < vertices.Length; i++) { for (int j = i + 1; j < vertices.Length; j++) { Vector4 screen1 = vertices[i].ScreenSpacePosition; Vector4 screen2 = vertices[j].ScreenSpacePosition; if ((int)screen1.Y != (int)screen2.Y) { if (screen1.Y > yMax) { yMax = (int)screen1.Y; } if (screen2.Y > yMax) { yMax = (int)screen2.Y; } if (screen1.Y < yMin) { yMin = (int)screen1.Y; } if (screen2.Y < yMin) { yMin = (int)screen2.Y; } if (yMax > this.height) { yMax = this.height; } if (yMin < 0) { yMin = 0; } int x1 = (int)screen1.X; int y1 = (int)screen1.Y; int x2 = (int)screen2.X; int y2 = (int)screen2.Y; int ymin = y1 > y2 ? y2 : y1; if (ymin < 0) { ymin = 0; } int ymax = y1 > y2 ? y1 : y2; if (ymax > this.height) { ymax = this.height; } // 保存的边的上端点x坐标 float x = y1 > y2 ? x2 : x1; //边的斜率的倒数 float dx = (float)(x1 - x2) * 1.0f / (float)(y1 - y2); Edge e = new Edge(); e.yMax = ymax; // 保存x的目的是为了插入时保证边的顺序是按照x从小到大插入 // 在AEL中表示当前扫描线与边的交点x坐标,初值(在ET中的值)为边的下端点的x坐标 e.x = x; // 斜率是保证在插入时,如果 x相同,以斜率大小比较 e.deltaX = dx; // 保存边的上端点 e.v1 = y1 > y2 ? vertices[j] : vertices[i]; // 保存边的下端点 e.v2 = y1 > y2 ? vertices[i] : vertices[j]; EdgeTable.InsertEdge(ref ET[ymin].nextEdge, e); } } } /* 2. 置空活动边表 AEL * AEL 存储的是 ET表在 扫描线 y = i 有交点的三角形的边 */ AEL = new Edge(); oriVt.PreCalWeight(); for (int i = yMin; i < yMax; i++) { // 将边表中在 y = i 的边 插入活动边表AEL中,并删除边表里的边 EdgeTable.SearchTable(i, ET, AEL); if (AEL.nextEdge == null) { continue; } // 获取连续的两条边 Edge a1 = (Edge)AEL.nextEdge.Clone(); Edge a2 = (Edge)AEL.nextEdge.nextEdge.Clone(); // 从ymin开始扫描填充 /* * 记过排序后的边传入连续两条边 相交的情况只有两种钝角三角形 和锐角三角形 * x 从小到大 * * /\ /\ * / \ / \ * / \/ \_________ * x1 x3 x4 * 1. x左右分开 * 2. x相同 以斜率排序 * */ /* 双线性插值算法 * 斜边一次差值找出交点 * 水平扫描差值找出当前点 */ Vector4 screenA1V1 = a1.v1.ScreenSpacePosition; Vector4 screenA1V2 = a1.v2.ScreenSpacePosition; Vector4 screenA2V1 = a2.v1.ScreenSpacePosition; Vector4 screenA2V2 = a2.v2.ScreenSpacePosition; // 第一次插值算出交点的颜色系数 float r1 = ((float)i - screenA1V1.Y) / (float)(screenA1V2.Y - screenA1V1.Y); float r2 = ((float)i - screenA2V1.Y) / (float)(screenA2V2.Y - screenA2V1.Y); r1 = MathUtil.Clamp01(r1); r2 = MathUtil.Clamp01(r2); // 像素点深度插值 float z1 = MathUtil.Interp(screenA1V1.Z, screenA1V2.Z, r1); float z2 = MathUtil.Interp(screenA2V1.Z, screenA2V2.Z, r2); float nDotLA1V1 = 0, nDotLA1V2 = 0, nDotLA2V1 = 0, nDotLA2V2 = 0, nDotL1 = 0, nDotL2 = 0; if (DirectionLight.IsEnable) { nDotLA1V1 = DirectionLight.ComputeNDotL(a1.v1.nowPos, a1.v1.nowNormal); nDotLA1V2 = DirectionLight.ComputeNDotL(a1.v2.nowPos, a1.v2.nowNormal); nDotLA2V1 = DirectionLight.ComputeNDotL(a2.v1.nowPos, a2.v1.nowNormal); nDotLA2V2 = DirectionLight.ComputeNDotL(a2.v2.nowPos, a2.v2.nowNormal); // 双线性插值系数 nDotL1 = MathUtil.Interp(nDotLA1V1, nDotLA1V2, r1); nDotL2 = MathUtil.Interp(nDotLA2V1, nDotLA2V2, r2); } // 横向填充 while (a1 != null && a2 != null) { for (int x = (int)AEL.nextEdge.x; x < (int)AEL.nextEdge.nextEdge.x; x++) { float r3 = MathUtil.Clamp01(((float)x - a1.x) / (a2.x - a1.x)); float z = MathUtil.Interp(z1, z2, r3); switch (scene.renderState) { case Scene.RenderState.WireFrame: { } break; case Scene.RenderState.GouraduShading: { //Padding.x = x; //Padding.GouraudColor(scene,device,x,i,z,vt,oriVt); } break; case Scene.RenderState.TextureMapping: { oriVt.CalWeight(new Vector4(x, i, 0, 0)); Vector4 uv = oriVt.GetInterUV(); if (isworld) { final = device.Tex2D(uv.X, uv.Y, scene.worldMap.texture); } else { if (DirectionLight.IsEnable) { float nDotL = MathUtil.Interp(nDotL1, nDotL2, r3); final = device.Tex2D(uv.X, uv.Y, scene.mesh.texture); final = final * DirectionLight.GetFinalLightColor(nDotL); } else { final = device.Tex2D(uv.X, uv.Y, scene.mesh.texture); } } } break; } device.DrawPoint(new Vector4(x, i, z, 0), final); } if (a2.nextEdge != null) { a1 = (Edge)a2.nextEdge.Clone(); a2 = (Edge)a1.nextEdge.Clone(); } else { break; } } // 删除y=yMax-1的边 , 边的特性是上开下闭, 所以 当扫描到y = yamx后,ymax以前的边和扫描线不会再有交点 // 所以要删除,避免盲目求交 // 同时算出 x 的下一个坐标 并保存在 AEL的第一个边中 p.nextEdge.x += p.nextEdge.deltaX; EdgeTable.DeleteEdge(AEL, i); } }
/* 重心坐标填充 * 填充效果明显,但基于精度问题,会有漏点 * 切效率低,不建议使用 */ public void StartScanLine(VertexTriangle vt, VertexTriangle oriVt, Scene scene) { float yMin = this.height; float yMax = 0; Vertex[] vv = GetList(vt.Vertices); Vertex A = vv[0]; Vertex B = vv[1]; Vertex C = vv[2]; float nDotLA1V1 = DirectionLight.ComputeNDotL(A.nowPos, A.nowNormal); A.lightColor = A.Color * DirectionLight.GetFinalLightColor(nDotLA1V1); float nDotLA1V2 = DirectionLight.ComputeNDotL(B.nowPos, B.nowNormal); B.lightColor = B.Color * DirectionLight.GetFinalLightColor(nDotLA1V2); float nDotLA2V1 = DirectionLight.ComputeNDotL(C.nowPos, C.nowNormal); C.lightColor = C.Color * DirectionLight.GetFinalLightColor(nDotLA2V1); float ay = A.ScreenSpacePosition.Y; float by = B.ScreenSpacePosition.Y; float cy = C.ScreenSpacePosition.Y; float ax = A.ScreenSpacePosition.X; float bx = B.ScreenSpacePosition.X; float cx = C.ScreenSpacePosition.X; float az = A.ScreenSpacePosition.Z; float bz = B.ScreenSpacePosition.Z; float cz = C.ScreenSpacePosition.Z; yMin = Math.Min(Math.Min(ay, by), Math.Min(by, cy)); yMax = Math.Max(Math.Max(ay, by), Math.Max(by, cy)); float xMin = (int)Math.Min(Math.Min(ax, bx), Math.Min(bx, cx)); float xMax = (int)Math.Max(Math.Max(ax, bx), Math.Max(bx, cx)); vt.Preproccess(); float dtx = 0; float dty = 0; if (yMax - yMin > xMax - xMin) { dtx = (xMax - xMin) / (yMax - yMin); dty = 1; } else { dtx = 1; dty = (yMax - yMin) / (xMax - xMin); } for (int y = (int)yMin; y < yMax; y++) { for (int x = (int)xMin; x < xMax; x++) { float z = vt.GetInterValue(az, bz, cz); Padding.GouraudColor(scene, device, x, y, z, vt, oriVt); } } }