在 Google 的 Go 中使用 OpenGL






4.57/5 (9投票s)
本文将向您展示如何在 Google 的 Go 语言中使用 OpenGL 图形库。
引言
当我看到一种新的编程语言时,一个问题通常会浮现在我的脑海中;特别是当我看到一种通用编程语言时。“这种语言支持任何硬件加速图形库吗……比如 OpenGL 或 DirectX?这样我就可以用它来制作游戏或 3D 应用程序了!” 我不知道别人怎么想,但我真的很喜欢图形编程。所以当我看到 Google 的 Go 语言时,我也是这么想的,首先寻找了一个 OpenGL 包,并在 GitHub 网站上找到了一个完美的。然后我用 Go 语言的 OpenGL 做了一个简单的项目,它显示了一个带有基本光照的立方体。就像我之前关于 Go 语言的文章展示了使用纯 Win32 API 创建一个简单窗口一样,在这个项目中我也使用了纯 Win32 API 来创建窗口。我认为这有助于理解基本概念。所以我想为什么不分享给人们呢?让我们开始吧!
下载必要的 Go 包
OpenGL 包可以在 GitHub 网站上找到 - https://github.com/go-gl/.
该包提供了各种版本的 OpenGL。还有其他几个 Go 绑定,如 osmesa、GLFW 3、OpenCL 等。
我们还需要 w32 包来使用 Windows API。如果您还没有,请从以下链接下载:
Using the Code
在本文中,我不会详细描述窗口创建代码,因为我在之前关于 Go 语言的文章“在 Go 语言中创建窗口”中已经讲过了。所以如果您还没有读过,我建议您先读一下,然后再继续。这是链接:
首先,我们导入程序所需的所有必要包。我们将使用 OpenGL API 的 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
作为其初始值。
GlInitialized = false
下面的代码用于创建主窗口。这个窗口实际上是我们的绘图表面,我们使用 OpenGL 渲染上下文在此绘制 3D 场景。Go 语言允许我们通过使用 :=
轻松地创建和分配变量。
hInstance := w32.GetModuleHandle("")
我们需要调用 syscall
包的 StringToUTF16Ptr
函数,将 Go 的 string
文字转换为 UTF-16 string
。基本上,这里的 string
文字是我们的窗口类名。
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, MakeIntResource(w32.IDI_APPLICATION))
wcex.Cursor = w32.LoadCursor(0, MakeIntResource(w32.IDC_ARROW))
wcex.Background = w32.COLOR_WINDOW + 11
wcex.MenuName = nil
wcex.ClassName = lpszClassName
wcex.IconSm = w32.LoadIcon(hInstance, MakeIntResource(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
我们将为绘图表面使用 32 位颜色。
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
。
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 {
// Render our scene scene
drawScene()
SwapBuffers
函数在当前设备上下文引用的窗口的像素格式包含后缓冲区时,会交换前后缓冲区。因此,如果我们不交换前后缓冲区,窗口中将不会显示任何内容。
w32.SwapBuffers(hDC)
}
主循环结束时,我们清理内存。
w32.WglMakeCurrent(w32.HDC(0), w32.HGLRC(0))
w32.ReleaseDC(hWnd, hDC)
w32.WglDeleteContext(hRC)
同时删除我们创建的主窗口。
w32.DestroyWindow(hWnd)
}
drawScene
函数使用灰色清除窗口,设置投影和模型视图矩阵,然后绘制一个简单的立方体。
func drawScene() {
// Clear the drawing surface with gray color
gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
gl.ClearColor(0.5, 0.5, 0.5, 0.0)
gl.ClearDepth(1)
// Setup projection matrix
gl.MatrixMode(gl.PROJECTION)
gl.LoadIdentity()
gl.Frustum(-1, 1, -1, 1, 1.0, 10.0)
// Setup model-view matrix
gl.MatrixMode(gl.MODELVIEW)
gl.LoadIdentity()
让我们稍微旋转一下视角,以获得 3D 的外观和感觉。
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:
确保在调用 CreateWindowEx
函数创建窗口后,我们在使用 OpenGL 之前已经初始化了它(因为 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!’。
致读者的请求
如果您对 Go 语言的新项目有任何想法,可以帮助人们更轻松、更有效地学习这门语言,请通过评论告知我。如果您对改进本文有任何建议,也请随时告诉我。
结论
Go 是一种非常优秀的通用编程语言。关于它有很多东西需要学习,我们应该明智地使用它以从中获得应有的益处。最后,我希望我的文章能够帮助到初学者。