在探索通用编程语言时,常常会有这样的疑问:“这种语言是否支持硬件加速图形库,比如OpenGL或DirectX?这样就可以利用它来制作游戏或3D应用程序了!”对图形编程有着浓厚的兴趣。当看到谷歌的Go语言时,首先想到的就是寻找一个OpenGL包,幸运的是在一个GitHub仓库中找到了一个完美的包。接下来,使用Go语言和OpenGL创建了一个简单的项目,展示了一个带有基本光照的立方体。就像之前关于Go语言的文章中展示的如何使用纯Win32 API创建窗口一样,也在这个项目中使用了纯Win32 API来创建窗口。这有助于理解基本概念。所以,想为什么不与大家分享呢?让开始吧!
OpenGL包可以在GitHub网站上找到:。该包提供了OpenGL的多个版本。还有其他一些Go绑定,例如osmesa、GLFW 3、OpenCL等。还需要w32包来使用Windows API。如果还没有这个包,请从以下链接下载:。
在本文中,不会过多描述窗口创建代码,因为在之前关于Go语言的文章中已经做过了。如果还没有阅读过,建议先阅读那篇文章再继续。这里是链接:。
首先,导入程序所需的所有包。将使用OpenGLAPI的2.1版本。还需要导入unsafe包以获取任何结构的大小。导入从import关键字开始:
import (
"runtime"
"syscall"
"unsafe"
"go-libs/w32"
"go-libs/gl/v2.1/gl"
)
在main函数中,这是任何Go程序的入口点,开始创建主窗口,然后初始化OpenGL渲染上下文。函数以Go的func关键字开始:
func main() {
以下是一个布尔变量,用于确定OpenGL是否已初始化。因此,在程序执行的最开始,将其初始值设置为false:
var GlInitialized = false
以下代码用于创建主窗口。这个窗口实际上是绘图表面,使用OpenGL渲染上下文在这里绘制3D场景。Go语言让可以轻松地创建并分配变量:
hInstance := w32.GetModuleHandle("")
lpszClassName := syscall.StringToUTF16Ptr("GoOpenGL!--Class")
让创建并填充一个WNDCLASSEX结构,用适当的值:
var wcex w32.WNDCLASSEX
wcex.Size = uint32(unsafe.Sizeof(wcex))
wcex.Style = w32.CS_HREDRAW | w32.CS_VREDRAW
wcex.WndProc = syscall.NewCallback(WndProc)
wcex.ClsExtra = 0
wcex.WndExtra = 0
wcex.Instance = hInstance
wcex.Icon = w32.LoadIcon(hInstance, w32.IDI_APPLICATION)
wcex.Cursor = w32.LoadCursor(0, w32.IDC_ARROW)
wcex.Background = w32.COLOR_WINDOW+11
wcex.MenuName = nil
wcex.ClassName = lpszClassName
wcex.IconSm = w32.LoadIcon(hInstance, w32.IDI_APPLICATION)
w32.RegisterClassEx(&wcex)
接下来,创建窗口:
hWnd := w32.CreateWindowEx(0, lpszClassName, syscall.StringToUTF16Ptr("Go OpenGL!"), w32.WS_OVERLAPPEDWINDOW|w32.WS_VISIBLE, w32.CW_USEDEFAULT, w32.CW_USEDEFAULT, 400, 400, 0, 0, hInstance, nil)
为了设置绘图表面的像素格式并创建GL上下文,首先需要获取主窗口的设备上下文:
hDC := w32.GetDC(hWnd)
现在,初始化一个像素格式描述符结构,用适当的值,这让描述绘图表面:
var pfd w32.PIXELFORMATDESCRIPTOR
pfd.Size = uint16(unsafe.Sizeof(pfd))
pfd.Version = 1
pfd.DwFlags = w32.PFD_DRAW_TO_WINDOW | w32.PFD_SUPPORT_OPENGL | w32.PFD_DOUBLEBUFFER
pfd.IPixelType = w32.PFD_TYPE_RGBA
pfd.ColorBits = 32
以下ChoosePixelFormat函数将尝试将窗口的设备上下文支持的适当像素格式与给定的像素格式描述相匹配。如果成功,它返回适当的像素格式索引;否则,返回零:
pf := w32.ChoosePixelFormat(hDC, &pfd)
现在将窗口的设备上下文的像素格式设置为ChoosePixelFormat返回的像素格式:
w32.SetPixelFormat(hDC, pf, &pfd)
现在,通过调用w32包的WglCreateContext函数创建OpenGL渲染上下文:
hRC := w32.WglCreateContext(hDC)
w32.WglMakeCurrent(hDC, hRC)
别忘了初始化gl包,否则任何OpenGL函数都不会工作,可能会导致程序崩溃:
if err := gl.Init(); err != nil {
panic(err)
}
GlInitialized = true
还在代码中启用了一些光照,以获得更好和更美观的视觉效果。如果没有光照,3D场景看起来会很平坦。OpenGL有Enable和Disable函数,用于启用/禁用任何特定功能或功能。所以使用gl包的Enable函数来启用OpenGL光照:
gl.Enable(gl.LIGHTING)
同时设置灯光的环境光、漫反射和位置属性。可能会想要为了实验目的调整这些属性或参数。在固定管线中,OpenGL支持多达8个灯光,它们被索引为LIGHT0、LIGHT1和LIGHT2等。但3D场景只需要一个灯光。所以使用第一个,即LIGHT0。让创建参数变量,并为它们分配适当的值:
ambient := []float32{0.5, 0.5, 0.5, 1}
diffuse := []float32{1, 1, 1, 1}
lightPosition := []float32{-5, 5, 10, 0}
现在告诉OpenGL这些参数是为LIGHT0:
gl.Lightfv(gl.LIGHT0, gl.AMBIENT, &ambient[0])
gl.Lightfv(gl.LIGHT0, gl.DIFFUSE, &diffuse[0])
gl.Lightfv(gl.LIGHT0, gl.POSITION, &lightPosition[0])
还得手动启用灯光才能使用它。所以让启用LIGHT0:
gl.Enable(gl.LIGHT0)
现在,所有的初始化都完成了。是时候开始绘制了……
在应用程序的主循环中,绘制场景,然后交换前后缓冲区,以在窗口中显示渲染的图像:
for {
drawScene()
w32.SwapBuffers(hDC)
}
当主循环结束时,清理内存:
w32.WglMakeCurrent(w32.HDC(0), w32.HGLRC(0))
w32.ReleaseDC(hWnd, hDC)
w32.WglDeleteContext(hRC)
w32.DestroyWindow(hWnd)
}
drawScene函数用灰色清除窗口,设置投影和模型视图矩阵,然后绘制一个简单的立方体:
func drawScene() {
gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
gl.ClearColor(0.5, 0.5, 0.5, 0.0)
gl.ClearDepth(1)
gl.MatrixMode(gl.PROJECTION)
gl.LoadIdentity()
gl.Frustum(-1, 1, -1, 1, 1.0, 10.0)
gl.MatrixMode(gl.MODELVIEW)
gl.LoadIdentity()
gl.Translatef(0, 0, -3.0)
gl.Rotatef(40.0, 1, 0, 0)
gl.Rotatef(40.0, 0, 1, 0)
drawCube()
}
还得处理WM_SIZE消息,以便根据主窗口的大小调整OpenGL视口:
func WndProc(hWnd w32.HWND, msg uint32, wParam, lParam uintptr) (uintptr) {
switch msg {
case w32.WM_SIZE:
if GlInitialized == true {
rc := w32.GetClientRect(hWnd)
gl.Viewport(0, 0, rc.Right, rc.Bottom)
}
break
}
}
最后,使用以下命令编译程序。这些命令可以/应该放在一个批处理(.bat)文件中,以便于快捷构建:
go build -ldflags "-H windowsgui" main.go
现在,运行'main.exe'文件来执行程序,并查看'Go OpenGL!'请求。