一、逆向工程
Sketchup 逆向工程(一)破解.skp文件数据结构
Sketchup 逆向工程(二)分析三维模型数据结构
Sketchup 逆向工程(三)软件逆向工程从何处入手
Sketchup 逆向工程(四)破解的乐趣 钩子 外挂 代码注入
二、OpenGL渲染模型
Python+OpenGL绘制3D模型(一)Python 和 PyQt环境搭建
Python+OpenGL绘制3D模型(二)程序框架PyQt5
Python+OpenGL绘制3D模型(三)程序框架PyQt6
Python+OpenGL绘制3D模型(四)绘制线段
Python+OpenGL绘制3D模型(五)绘制三角型
Python+OpenGL绘制3D模型(六)材质文件载入和贴图映射
Python+OpenGL 杂谈(一)
三、成果
疫情期间关在家里实在没事干,破解了Sketchup,成功做出可以读取并显示.skp文件的程序SuViewer
Sketchup作为目前设计院最为流行的设计软件(非工程制图软件),深受设计师的喜爱,软件小巧,而功能强大,有不少为之开发的插件应运而生,不过呢,关于底层数据结构和工作原理相关的文章少之又少,本文意在填补一下这方面的空缺,通过逆向软件分析,展示软件内部奥秘。本文用到的工具:IDA Pro,Immunity Debugger,Visual Studio (逆向工程三件套)数据结构属于知识产权的核心机密:
运行效果
绘制面的最小单元是三角形,如果你在想,为什么不用OpenGL的多边形来绘制。很好的问题,根据实践,所有的3D软件,都是把多边形转成三角型来绘制的,而且转换的过程所用到的Tessellation算法非常耗时,要把3D空间的模型转到2D空间才能计算。绘制多边形,你要考虑所有顶点是不是在一个平面上,多边形是不是凸多边形,线是否有交叉,中间是否有开孔,等等。。
所以3D绘图,还是老老实实用三角面吧
def draw_tirangle(p1, p2, p3, gl):
gl.glBegin(gl.GL_TRIANGLES)
gl.glVertex3d(p1.x(), p1.y(), p1.z())
gl.glVertex3d(p2.x(), p2.y(), p2.z())
gl.glVertex3d(p3.x(), p3.y(), p3.z())
gl.glEnd()
一个4边型是由2个三角型组成的,所以绘制1个面需要完整的调用2次三角形绘制
def draw_single_face(p1, p2, p3, p4, gl):
gl.glBegin(gl.GL_TRIANGLES)
gl.glVertex3d(p1.x(), p1.y(), p1.z())
gl.glVertex3d(p2.x(), p2.y(), p2.z())
gl.glVertex3d(p3.x(), p3.y(), p3.z())
gl.glEnd()
gl.glBegin(gl.GL_TRIANGLES)
gl.glVertex3d(p1.x(), p1.y(), p1.z())
gl.glVertex3d(p2.x(), p2.y(), p2.z())
gl.glVertex3d(p3.x(), p3.y(), p3.z())
gl.glEnd()
一个完整的立方体,由6个面组成,我们现在来把它们全部画出来
def draw_box_faces(gl):
p1 = QVector3D(-1, -1, 0 )
p2 = QVector3D(+1, -1, 0 )
p3 = QVector3D(+1, +1, 0 )
p4 = QVector3D(-1, +1, 0 )
p5 = QVector3D(-1, -1, 2 )
p6 = QVector3D(+1, -1, 2 )
p7 = QVector3D(+1, +1, 2 )
p8 = QVector3D(-1, +1, 2 )
draw_single_face(p1, p2, p3, p4, gl)
draw_single_face(p5, p6, p7, p8, gl)
draw_single_face(p1, p2, p6, p5, gl)
draw_single_face(p3, p4, p8, p7, gl)
draw_single_face(p2, p3, p7, p6, gl)
draw_single_face(p4, p1, p5, p8, gl)
现在的程序,是没有开启明暗处理和灯光的,所以绘制的所有面都是一个颜色的,画出来的东西是一片混沌,区分不出不同的面,现在我们用单色面+线框的方式来绘制,我们把盒子的所有线断都绘制出来
def draw_box_lines(gl):
p1 = QVector3D(-1, -1, 0 )
p2 = QVector3D(+1, -1, 0 )
p3 = QVector3D(+1, +1, 0 )
p4 = QVector3D(-1, +1, 0 )
p5 = QVector3D(-1, -1, 2 )
p6 = QVector3D(+1, -1, 2 )
p7 = QVector3D(+1, +1, 2 )
p8 = QVector3D(-1, +1, 2 )
# 一个立方体有12条边
draw_single_line( p1, p2, gl )
draw_single_line( p2, p3, gl )
draw_single_line( p3, p4, gl )
draw_single_line( p4, p1, gl )
draw_single_line( p5, p6, gl )
draw_single_line( p6, p7, gl )
draw_single_line( p7, p8, gl )
draw_single_line( p8, p5, gl )
draw_single_line( p1, p5, gl )
draw_single_line( p2, p6, gl )
draw_single_line( p3, p7, gl )
draw_single_line( p4, p8, gl )
def draw_single_line(p1, p2, gl):
gl.glBegin(gl.GL_LINES)
gl.glVertex3d(p1.x(), p1.y(), p1.z())
gl.glVertex3d(p2.x(), p2.y(), p2.z())
gl.glEnd()
运行后,我们得到一个方形的盒子,很漂亮,用这么少的代码,就可以达到如此的效果
我们已经绘制出模型和轮廓了,但是有个很明显的问题,模型的轮廓有时候是虚线,有时候是实线,闪来闪去,看着很不舒服
这是3D编辑软件都会遇到的一个问题,因为3D软件需要显示模型的线框,但是画线和画面在遇到同一个像素时,就会出现Z-Buffer的值一样,那么这个像素最后是画线时的颜色还是画面时的颜色是个不确定的值。这在3D领域有个专有名词叫做z-fighting,就是说2个面靠的很近很近的时候,这2个面就会来回闪硕的现象。
解决这个问题有个技巧,叫做z-buff bias,也叫做z-buff偏移,具体的办法是在绘制面的时候算出的z-buff值稍微增加一个很小的单位,那么在画线的时候,这个地方的Z值就肯定比画线的Z值小那么一点,就能够保证线能正常显示,不会和面发生z-buff冲突
from PyQt5.QtGui import QVector3D
############
# draw
# 绘图主入口
############
def draw(gl):
# Python+OpenGL绘制3D模型(四) 绘制线段
#draw_rect(gl)
# Python+OpenGL绘制3D模型(五) 绘制三角面
draw_filled_cube(gl)
############
# draw_filed_cube
# 绘制填充的方形
############
def draw_filled_cube(gl):
# 设置z-buff偏移
gl.glEnable(gl.GL_POLYGON_OFFSET_FILL)
gl.glPolygonOffset(1, 1)
# 绘制填充面
gl.glColor3f(0.9, 0.83, 0.6)
draw_box_faces(gl)
# 关闭z-buff偏移
gl.glDisable(gl.GL_POLYGON_OFFSET_FILL)
# 绘制线框
gl.glColor3f(0.0, 0.0, 0.0)
draw_box_lines(gl)
############
# draw_box_faces
# 画面 - 中间的填充部分
############
def draw_box_faces(gl):
p1 = QVector3D(-1, -1, 0 )
p2 = QVector3D(+1, -1, 0 )
p3 = QVector3D(+1, +1, 0 )
p4 = QVector3D(-1, +1, 0 )
p5 = QVector3D(-1, -1, 2 )
p6 = QVector3D(+1, -1, 2 )
p7 = QVector3D(+1, +1, 2 )
p8 = QVector3D(-1, +1, 2 )
draw_single_face(p1, p2, p3, p4, gl)
draw_single_face(p5, p6, p7, p8, gl)
draw_single_face(p1, p2, p6, p5, gl)
draw_single_face(p3, p4, p8, p7, gl)
draw_single_face(p2, p3, p7, p6, gl)
draw_single_face(p4, p1, p5, p8, gl)
def draw_single_face(p1, p2, p3, p4, gl):
gl.glBegin(gl.GL_TRIANGLES)
gl.glVertex3d(p1.x(), p1.y(), p1.z())
gl.glVertex3d(p2.x(), p2.y(), p2.z())
gl.glVertex3d(p3.x(), p3.y(), p3.z())
gl.glEnd()
gl.glBegin(gl.GL_TRIANGLES)
gl.glVertex3d(p3.x(), p3.y(), p3.z())
gl.glVertex3d(p4.x(), p4.y(), p4.z())
gl.glVertex3d(p1.x(), p1.y(), p1.z())
gl.glEnd()
############
# draw_box_lines
# 画面 外侧的线
############
def draw_box_lines(gl):
p1 = QVector3D(-1, -1, 0 )
p2 = QVector3D(+1, -1, 0 )
p3 = QVector3D(+1, +1, 0 )
p4 = QVector3D(-1, +1, 0 )
p5 = QVector3D(-1, -1, 2 )
p6 = QVector3D(+1, -1, 2 )
p7 = QVector3D(+1, +1, 2 )
p8 = QVector3D(-1, +1, 2 )
# 一个立方体有12条边
draw_single_line( p1, p2, gl )
draw_single_line( p2, p3, gl )
draw_single_line( p3, p4, gl )
draw_single_line( p4, p1, gl )
draw_single_line( p5, p6, gl )
draw_single_line( p6, p7, gl )
draw_single_line( p7, p8, gl )
draw_single_line( p8, p5, gl )
draw_single_line( p1, p5, gl )
draw_single_line( p2, p6, gl )
draw_single_line( p3, p7, gl )
draw_single_line( p4, p8, gl )
def draw_single_line(p1, p2, gl):
gl.glBegin(gl.GL_LINES)
gl.glVertex3d(p1.x(), p1.y(), p1.z())
gl.glVertex3d(p2.x(), p2.y(), p2.z())
gl.glEnd()
############
# draw_rect
# 绘制线段组成的方形
############
def draw_rect(gl):
gl.glColor3f(1.0, 0.0, 0.0)
gl.glBegin(gl.GL_LINES)
gl.glVertex3d(-1, 0, 0 )
gl.glVertex3d(+1, 0, 0 )
gl.glEnd()
gl.glBegin(gl.GL_LINES)
gl.glVertex3d(-1, 0, 2 )
gl.glVertex3d(+1, 0, 2 )
gl.glEnd()
gl.glBegin(gl.GL_LINES)
gl.glVertex3d(-1, 0, 0 )
gl.glVertex3d(-1, 0, 2 )
gl.glEnd()
gl.glBegin(gl.GL_LINES)
gl.glVertex3d(+1, 0, 0 )
gl.glVertex3d(+1, 0, 2 )
gl.glEnd()
目标是一个完善的Viewer,能够显示Sketchup的.skp文件中的3D模型
Corona渲染器照片级渲染效果