当今用于游戏和多媒体的3D渲染引擎在数学和编程的复杂性上足以令大多数人望而生畏,从编程接口的OpenGL再到逼真到令人叹为观止的UE5(虚幻五)引擎,后者单单引擎本身(不含调试)的大小就达到了将近40g(当然UE5不光只有渲染的功能),其中带来的全新的核心的Nanite虚拟微多边形几何技术和Lumen动态全局光照技术更是及其复杂。
对于非渲染引擎相关工作的开发者来说,可能认为即使构建最简单的3D程序也非常困难,但事实上并非如此,本篇文章将通过简单的200多行的纯 Java代码,去实践正交投影、简单三角形光栅化、z缓冲(深度缓冲区)和平面着色等基本的3D渲染技术,然后在下一片文章中,将着重介绍光线追踪的知识。
当然本篇文章最终实现的“3D渲染引擎”非常简单,没有做任何的算法优化,而且仅使用到了CPU,实际性能远不如OpenGl。不过其目的是用于去帮我们了解真正的现代引擎是如何发挥它们的黑魔法,以便更好的上手使用它们。
三角函数、矩阵运算、向量运算、法向量。
如果你尚未学习或者忘记了以上的知识也不用担心,本篇文章中会结合例子对上述知识进行简单的解释,同时也不必太过纠结这些数学知识,会用即可,毕竟连卡神也会“what the fuck?”。
当然如果熟悉上述知识,阅读起来会更加轻松。
我们将会绘制一个四面体,因为它是最简单的3D图形~
用于展示图形的界面
- public static void main(String[] args) {
- JFrame frame = new JFrame();
- Container pane = frame.getContentPane();
- pane.setLayout(new BorderLayout());
-
- // panel to display render results
- JPanel renderPanel = new JPanel() {
- public void paintComponent(Graphics g) {
- Graphics2D g2 = (Graphics2D) g;
- g2.setColor(Color.BLACK);
- g2.fillRect(0, 0, getWidth(), getHeight());
-
- // rendering magic will happen here
- }
- };
- pane.add(renderPanel, BorderLayout.CENTER);
-
- frame.setSize(600, 600);
- frame.setVisible(true);
- }
现在让我们添加一些3D世界的基本的模型类——顶点和三角形。Vertex 只是一个简单的结构来存储我们的三个坐标(X、Y 和 Z),而三角形将三个顶点绑定在一起并存储它的颜色。
- // X 坐标表示左右方向的移动
- // Y 表示屏幕上的上下移动
- // Z 表示深度(因此 Z 轴垂直于您的屏幕)。正 Z 表示“朝向观察者”。
- class Vertex {
- double x;
- double y;
- double z;
- Vertex(double x, double y, double z) {
- this.x = x;
- this.y = y;
- this.z = z;
- }
- }
-
- class Triangle {
- Vertex v1;
- Vertex v2;
- Vertex v3;
- Color color;
- Triangle(Vertex v1, Vertex v2, Vertex v3, Color color) {
- this.v1 = v1;
- this.v2 = v2;
- this.v3 = v3;
- this.color = color;
- }
- }
那么为什么要使用三角形来描述3D世界呢?
a.三角形是最简单的多边形,少于3个顶点就不能成为一个表面
b.三角形必然是平坦的
c.三角形经多种转换之后,仍然是三角形,这对于仿射转换和透视转换也成立。最坏的情况下,从三角形的边去看,三角形会退化为线段。在其它角度观察,仍能维持是三角形
d.它可以很好地用叉积判断一个点是不是在三角形内部(三角形的内外定义特别清晰)
e.几乎所有商用图形加速硬件都是为三角形光栅化而设计的
非常简单,就是四个三角形合并而成(先将它们放入列表)。同时为了区分它们,赋予不同的颜色。
- List tris = new ArrayList<>();
- tris.add(new Triangle(new Vertex(100, 100, 100),
- new Vertex(-100, -100, 100),
- new Vertex(-100, 100, -100),
- Color.WHITE));
- tris.add(new Triangle(new Vertex(100, 100, 100),
- new Vertex(-100, -100, 100),
- new Vertex(100, -100, -100),
- Color.RED));
- tris.add(new Triangle(new Vertex(-100, 100, -100),
- new Vertex(100, -100, -100),
- new Vertex(100, 100, 100),
- Color.GREEN));
- tris.add(new Triangle(new Vertex(-100, 100, -100),
- new Vertex(100, -100, -100),
- new Vertex(-100, -100, 100),
- Color.BLUE));
现在将它们放置到我们之前的界面中,不过先只展示框线。因为是正交投影,所以非常简单,忽略z轴绘制连线即可。
框线仅是用于目前直观的看到四面体,最终渲染的时候不会用到此2dAPI
- // 生成的形状以原点 (0, 0, 0) 为中心,稍后我们将围绕该点进行旋转。
- g2.translate(getWidth() / 2, getHeight() / 2);
- g2.setColor(Color.WHITE);
- for (Triangle t : tris) {
- Path2D path = new Path2D.Do