package com.alatus;
import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferInt;
public class Rect extends JFrame {
// 屏幕分辨率
public static int screen_W = 1024;
public static int screen_H = 682;
public static int half_screen_W = screen_W / 2;
public static int half_screen_H = screen_H / 2;
// 记载当前已经绚烂的帧数
public static int frameIndex;
public static int screenSize = screen_W * screen_H;
// 希望达到的每帧之间的间隔时间
public static int frameInterval = 33;
// 使用jPanel作为画板
public static JPanel panel;
// 使用一个int数组来存储屏幕像素数值
public static int[] screen;
// 图像缓冲区,提供了在内存中操作屏幕图像的方式
public static BufferedImage screenBuffer;
// 刷新率,计算刷新率所用的辅助参数
public static int framePerSecond;
public static long lastDraw;
public static double lastTime,thisTime;
// cpu睡眠时间
public static int sleepTime;
public static void main(String[] args) {
new Rect();
}
public Rect() {
// 弹出一个JPanel窗口, 设置窗口大小,并将它放置在屏幕中间
setTitle("三角形矢量计算");
panel = (JPanel) this.getContentPane();
panel.setPreferredSize(new Dimension(screen_W, screen_H));
panel.setMinimumSize(new Dimension(screen_W, screen_H));
panel.setLayout(null);
setResizable(false);
pack();
setVisible(true);
Dimension dimension = Toolkit.getDefaultToolkit().getScreenSize();
setLocation(dimension.width / 2 - this.getSize().width / 2, dimension.height / 2 - screen_H / 2);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// 使用TypeIntRGB来创建BufferedImage,然后把屏幕的像素数组指向BufferedImage中的DataBuffer
// 这样通过改变屏幕的像素数组中的数据就可以在屏幕上渲染出图象
screenBuffer = new BufferedImage(screen_W, screen_H, BufferedImage.TYPE_INT_RGB);
DataBuffer dest = screenBuffer.getRaster().getDataBuffer();
screen = ((DataBufferInt)dest).getData();
// 初始化查找表
LookupTable.init();
// 渲染
while(true){
// 把图像发送到显存,这是唯一用到显卡的地方
int r_skyBlue = 163, g_skyBlue = 216, b_skyBlue = 239;
int r_orange = 255, g_orange = 128, b_orange = 0;
// 渲染为天蓝色
screen[0] = (163 << 16) | (216 << 8) | b_skyBlue;//天蓝色
for (int i = 0; i < screenSize; i++) {
// arrayCopy是JAVA中为数不多的不适用JVM虚拟机而是直接使用系统资源计算的命令和方法,所以它的效率非常高
// 这里这一步等了超级久它才渲染出来,不知道为什么???
System.arraycopy(screen,0,screen,i,screenSize - i >= i ? i : screenSize - i);
}
// loop每次运行,帧数+1
frameIndex++;
// 计算当前的刷新率,并尽量让刷新率恒定不变
if(frameIndex%30==0){
double thisTime = System.currentTimeMillis();
framePerSecond = (int) (1000 / ((thisTime - lastTime)/30));
lastTime = thisTime;
}
sleepTime = 0;
while(System.currentTimeMillis() - lastDraw < frameInterval){
try{
Thread.sleep(1);
sleepTime++;
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
lastDraw = System.currentTimeMillis();
// 显示当前刷新率
Graphics2D e2 = (Graphics2D) screenBuffer.getGraphics();
e2.setColor(Color.BLACK);
e2.drawString("FPS:"+framePerSecond+"ThreadSleep:"+sleepTime,5,15);
panel.getGraphics().drawImage(screenBuffer, 0, 0, this);
}
}
}
package com.alatus;
public class Vector3D {
// 矢量在XYZ轴上的分量
public float x,y,z;
// 构造函数,传入矢量的三个分类
public Vector3D(float x,float y,float z){
this.x = x;
this.y = y;
this.z = z;
}
// 把XYZ轴上的分量设置为v
public void set(Vector3D v){
this.x = v.x;
this.y = v.y;
this.z = v.z;
}
// 矢量加
public void add(Vector3D v){
this.x += v.x;
this.y += v.y;
this.z += v.z;
}
// 矢量减
public void sub(Vector3D v){
this.x -= v.x;
this.y -= v.y;
this.z -= v.z;
}
// 矢量点之和,结果代表两个矢量之间的相似程度,用来判断多边形的亮度
public float dot(Vector3D v1,Vector3D v2){
return v1.x*v2.x+v1.y*v2.y+v1.z*v2.z;
}
// 矢量叉积,求一个与这两个矢量都垂直的矢量,求多边形的法矢量
public void cross(Vector3D v1,Vector3D v2){
x = v1.y*v2.z-v1.z*v2.y;
y = v1.z*v2.x-v1.x*v2.z;
z = v1.x*v2.y-v1.y*v2.x;
}
// 求矢量长度
public float getLength(){
return (float)Math.sqrt(x*x+y*y+z*z);
}
// 将矢量单位化
public void unit(){
float l = getLength();
x = x/l;
y = y/l;
z = z/l;
}
// 将矢量乘以一个标量
public void scale(float s){
x *= s;
y *= s;
z *= s;
}
// 绕Y轴旋转矢量,使其顺时针旋转指定角度
public void rotate_Y(int angle){
float sin = LookupTable.sin[angle];
float cos = LookupTable.cos[angle];
float old_X = x;
float old_Z = z;
x = old_X*cos-old_Z*sin;
z = old_X*sin+old_Z*cos;
}
// 绕X轴旋转矢量,使其顺时针旋转指定角度
public void rotate_X(int angle){
float sin = LookupTable.sin[angle];
float cos = LookupTable.cos[angle];
float old_Y = y;
float old_Z = z;
y = old_Y*cos-old_Z*sin;
z = old_Y*sin+old_Z*cos;
}
// 绕Z轴旋转矢量,使其顺时针旋转指定角度
public void rotate_Z(int angle){
float sin = LookupTable.sin[angle];
float cos = LookupTable.cos[angle];
float old_X = x;
float old_Y = y;
x = old_X*cos-old_Y*sin;
y = old_X*sin+old_Y*cos;
}
}
package com.alatus;
public class LookupTable {
public static float[] sin;
public static float[] cos;
public static void init(){
sin = new float[360];
cos = new float[360];
for (int i = 0; i < 360; i++) {
sin[i] = (float)Math.sin(Math.PI*i/100);
cos[i] = (float)Math.cos(Math.PI*i/100);
}
}
}
package com.alatus;
public class Rasterizer {
// 设置屏幕分辨率
public static final int screen_w = Rect.screen_W;
public static final int screen_h = Rect.screen_H;
public static int half_screen_w = Rect.half_screen_W;
public static int half_screen_h = Rect.half_screen_H;
// 屏幕的像素组
public static int[] screen = Rect.screen;
// 视角的原点到屏幕的距离(以像素为单位)这个值越大视角就越狭窄,常用的值为屏幕的一半
public static int screenDistance = screen_w/2;
// 用两个矢量数组来表示三角形的顶点坐标和变化后的顶点坐标
// 未经变换的三角形顶点
public static Vector3D[] triangleVertices;
// 变化后的三角形顶点坐标
public static Vector3D[] updatedVertices;
// 三角形的顶点个数,一般为3,但但三角形与视角z平面相切的时候有可能是4个
public static int vertexCount = 3;
// 三角形变换后的顶点投影在屏幕上的2D坐标
public static float[][] vertices2D = new float[4][2];
// 用于扫描三角形的两个数组,每行由两个值,分别表示描线的起点和终点的X坐标
public static int[] xleft = new int[screen_h],xRight = new int[screen_h];
// 三角形扫描线最高和最低的位置
public static int scanUpperPosition,scanLowerPosition;
// 三角形的颜色
public static int triangleColor;
// 三角形的s形
public static int renderType;
// 初始化光栅渲染器
public static void init() {
// 初始化三角形变换后的顶点
updatedVertices = new Vector3D[]{
new Vector3D(0,0,0),
new Vector3D(0,0,0),
new Vector3D(0,0,0),
new Vector3D(0,0,0),
};
}
// 光栅渲染器的入口
public static void rasterizer(){
// 变换三角形的顶点
transformVertices();
// 将三角形转为扫描线
scanTriangle();
// 给三角形像素着色
renderTriangle();
}
// 变换三角形的顶点
public static void transformVertices(){
// 当前演示是静态渲染的三角形,以此不做变换
updatedVertices[0].set(triangleVertices[0]);
updatedVertices[1].set(triangleVertices[1]);
updatedVertices[2].set(triangleVertices[2]);
// 用投影公式求出顶点在屏幕上的2D坐标
for (int i = 0; i < vertexCount; i++) {
vertices2D[i][0] = half_screen_w + updatedVertices[i].x*screenDistance/updatedVertices[i].z;
vertices2D[i][1] = half_screen_h - updatedVertices[i].y*screenDistance/updatedVertices[i].z;
}
}
// 将三角形转换为扫描线
public static void scanTriangle(){
}
}