diff --git a/cmd/main.go b/cmd/main.go index adb00b3..0b598c6 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -64,6 +64,7 @@ func main() { winClock = time.Now() game.Update(win, deltaTime) + gl.ClearColor(0.6, 0.8, 1.0, 1) gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT) game.Render(win) diff --git a/internal/asset/shader.go b/internal/asset/shader.go index 2795cd8..ed4e8bf 100644 --- a/internal/asset/shader.go +++ b/internal/asset/shader.go @@ -14,6 +14,12 @@ var WorldShaderShadowmapFrag string //go:embed shader/world.shadowmap.vert var WorldShaderShadowmapVert string +//go:embed shader/world.geometry.frag +var WorldShaderGeometryFrag string + +//go:embed shader/world.geometry.vert +var WorldShaderGeometryVert string + //go:embed shader/framewire.frag var FramewireShaderFrag string diff --git a/internal/asset/shader/world.frag b/internal/asset/shader/world.frag index d26965d..3c36d34 100644 --- a/internal/asset/shader/world.frag +++ b/internal/asset/shader/world.frag @@ -1,16 +1,23 @@ #version 330 -uniform sampler2D tex; uniform sampler2D shadowmap; +uniform mat4 lightspace; uniform vec3 viewPos; uniform vec3 sun; uniform vec4 fogColor; +// G-Buffers +uniform sampler2D gPos; +uniform sampler2D gNorm; +uniform sampler2D gColor; +// Fragment information from G-Buffers +vec4 fragPos; +vec4 fragPosLightspace; +float fragPosLightspaceZ; +vec3 fragNormal; +vec4 fragColor; -in vec4 fragPos; -in vec4 fragPosLightspace; -in vec2 fragTexCoord; -in vec3 fragNormal; +in vec2 fragPosScreen; out vec4 outputColor; @@ -28,19 +35,30 @@ float lightSunShadow(); void lightPoint(int i); +void loadGBuffer() { + fragNormal = texture(gNorm, fragPosScreen).xyz; + if (fragNormal == vec3(0.0f, 0.0f, 0.0f)) + discard; + + vec4 fragGPos = texture(gPos, fragPosScreen); + fragPos = vec4(fragGPos.xyz + viewPos, 1.0f); + fragPosLightspaceZ = fragGPos.w; + fragColor = texture(gColor, fragPosScreen); + + fragPosLightspace = lightspace * fragPos; +} + + void main() { - texpixel = texture(tex, fragTexCoord); - if (texpixel.a < 1e-3) - discard; - texpixel.rgb = pow(texpixel.rgb, vec3(gamma)); + loadGBuffer(); light = ambient; lightSun(); - color += vec4(texpixel.rgb * light, 0.0f); - color.a = texpixel.a; + color += vec4(fragColor.rgb * light, 0.0f); + color.a = fragColor.a; color.rgb = pow(color.rgb, vec3(1.0/gamma)); float z = gl_FragCoord.z / gl_FragCoord.w; @@ -66,22 +84,23 @@ void lightSun() { float lightSunShadow() { /* Shadow */ - float bias = max(0.005 * (1.0 - dot(fragNormal, sun)), 0.0005); + float bias = max(0.0013 * (1.0 - dot(fragNormal, sun)), 0.0001); vec3 projCoords = fragPosLightspace.xyz / fragPosLightspace.w; projCoords = projCoords*0.5 + 0.5; - //float closestDepth = texture(shadowmap, projCoords.xy).r; - float currentDepth = projCoords.z; + float closestDepth = texture(shadowmap, projCoords.xy).r; + //float currentDepth = projCoords.z; + float currentDepth = fragPosLightspaceZ; float shadow = 0; if (currentDepth > 1.0f || currentDepth < 0.0f) return 1.0f; - vec2 texelSize = 1.0 / textureSize(shadowmap, 0); - for (int x=-1; x<=1; ++x) - for (int y=-1; y<=1; ++y) { + //vec2 texelSize = clamp((currentDepth+bias-closestDepth)*100.0f, 0.05f, 1.5f) / textureSize(shadowmap, 0); + vec2 texelSize = 0.4 / textureSize(shadowmap, 0); + for (int x=-4; x<=4; ++x) + for (int y=-4; y<=4; ++y) { float pcfDepth = texture(shadowmap, projCoords.xy + vec2(x,y)*texelSize).r; shadow += currentDepth-bias < pcfDepth ? 1.0f : 0.0f; } - shadow /= 9.0f; - return shadow; + return min(shadow/81.0f, 1.0f); } diff --git a/internal/asset/shader/world.geometry.frag b/internal/asset/shader/world.geometry.frag new file mode 100644 index 0000000..28f3bb5 --- /dev/null +++ b/internal/asset/shader/world.geometry.frag @@ -0,0 +1,26 @@ +#version 330 + +uniform sampler2D tex; + +in vec3 fragPosWorld; +in float fragPosLightspaceZ; +in vec2 fragTexCoord; +in vec3 fragNormal; +in float fragLight; + +layout (location = 0) out vec4 outputPosition; +layout (location = 1) out vec4 outputNormal; +layout (location = 2) out vec4 outputColor; + +const float gamma = 2.2; + + +void main() { + + outputPosition.xyz = fragPosWorld; + outputPosition.w = fragPosLightspaceZ; + outputNormal.xyz = fragNormal; + outputColor = texture(tex, fragTexCoord); + outputColor = vec4(pow(outputColor.rgb, vec3(gamma)), outputColor.a); +} + diff --git a/internal/asset/shader/world.geometry.vert b/internal/asset/shader/world.geometry.vert new file mode 100644 index 0000000..353a062 --- /dev/null +++ b/internal/asset/shader/world.geometry.vert @@ -0,0 +1,36 @@ +#version 330 + +uniform mat4 projection; +uniform mat4 view; +uniform mat4 model; +uniform mat4 lightspace; +uniform vec3 viewPos; + + +layout (location = 0) in vec3 vert; +layout (location = 1) in vec3 normal; +layout (location = 2) in vec2 vertTexCoord; +layout (location = 3) in float light; + +out vec3 fragPosWorld; +out float fragPosLightspaceZ; +out vec2 fragTexCoord; +out vec3 fragNormal; +out float fragLight; + +void main() { + + fragTexCoord = vertTexCoord; + fragNormal = normalize(normal); + fragLight = light; + + gl_Position = projection * view * model * vec4(vert, 1); + + vec4 pos4 = model * vec4(vert, 1); + fragPosWorld = pos4.xyz / pos4.w; + vec4 fragPosLightspace = lightspace * vec4(fragPosWorld, 1.0f); + fragPosLightspaceZ = fragPosLightspace.z / fragPosLightspace.w *0.5f + 0.5f; + + fragPosWorld -= viewPos; +} + diff --git a/internal/asset/shader/world.vert b/internal/asset/shader/world.vert index 1b40525..fd8b422 100644 --- a/internal/asset/shader/world.vert +++ b/internal/asset/shader/world.vert @@ -1,28 +1,12 @@ #version 330 -uniform mat4 projection; -uniform mat4 view; -uniform mat4 model; -uniform mat4 lightspace; +layout (location = 0) in vec2 vert; +layout (location = 1) in vec2 texCoord; - -layout (location = 0) in vec3 vert; -layout (location = 1) in vec3 normal; -layout (location = 2) in vec2 vertTexCoord; -layout (location = 3) in float light; - -out vec4 fragPos; -out vec4 fragPosLightspace; -out vec2 fragTexCoord; -out vec3 fragNormal; +out vec2 fragPosScreen; void main() { - - fragTexCoord = vertTexCoord; - fragNormal = normalize(normal); - - gl_Position = projection * view * model * vec4(vert, 1); - fragPos = model * vec4(vert, 1); - fragPosLightspace = lightspace * fragPos; + gl_Position = vec4(vert, 0.0f, 1); + fragPosScreen = texCoord; } diff --git a/internal/game/logic.go b/internal/game/logic.go index 42d6031..0cfb264 100644 --- a/internal/game/logic.go +++ b/internal/game/logic.go @@ -72,8 +72,8 @@ func (g *Game) Init(win *glfw.Window) { var seed int64 = time.Now().Unix() gensync := make(chan struct{}) gensynccnt := 0 - for i := -8; i <= 8; i++ { - for j := -8; j <= 8; j++ { + for i := -4; i <= 4; i++ { + for j := -4; j <= 4; j++ { c := &world.Chunk{} g.world.SetChunk(i, j, c) go func() { diff --git a/internal/render/render_world.go b/internal/render/render_world.go index 7844858..d587cc6 100644 --- a/internal/render/render_world.go +++ b/internal/render/render_world.go @@ -1,8 +1,6 @@ package render import ( - "unsafe" - "edgaru089.ml/go/gl01/internal/asset" "edgaru089.ml/go/gl01/internal/io" "edgaru089.ml/go/gl01/internal/util/itype" @@ -13,17 +11,30 @@ import ( ) var ( - ShadowmapSize = itype.Vec2i{3072, 3072} // Size of the shadow mapping + ShadowmapSize = itype.Vec2i{6144, 6144} // Size of the shadow mapping ) // WorldRenderer holds texture/shader resource and viewport // information for world rendering. type WorldRenderer struct { - shader *Shader - texture *Texture + lastDisplaySize itype.Vec2i - depthmapFBO, depthmap uint32 - depthmapShader *Shader + depthmap struct { + fbo, tex uint32 // Framebuffer Object and Texture. + shader *Shader // Shader. + } + + gbuffer struct { + fbo uint32 // The Framebuffer object. + + // Textures. Position/Lightspace Depth; Normal/Specular Intensity; Diffuse Color. + pos, norm, color uint32 + depth uint32 + shader *Shader // Geometry pass shaders. + } + + shader *Shader // Deffered lighting pass shaders + texture *Texture // World texture atlas } // The default WorldRenderer. @@ -36,8 +47,11 @@ func (r *WorldRenderer) Init(w *world.World) (err error) { if err != nil { return err } - - r.depthmapShader, err = NewShader(asset.WorldShaderShadowmapVert, asset.WorldShaderShadowmapFrag) + r.depthmap.shader, err = NewShader(asset.WorldShaderShadowmapVert, asset.WorldShaderShadowmapFrag) + if err != nil { + return err + } + r.gbuffer.shader, err = NewShader(asset.WorldShaderGeometryVert, asset.WorldShaderGeometryFrag) if err != nil { return err } @@ -45,27 +59,17 @@ func (r *WorldRenderer) Init(w *world.World) (err error) { asset.InitWorldTextureAtlas() r.texture = NewTextureRGBA(asset.WorldTextureAtlas.Image) r.texture.GenerateMipMap() - r.shader.SetUniformTexture("tex", r.texture) + r.gbuffer.shader.SetUniformTexture("tex", r.texture) - r.shader.SetUniformMat4("model", mgl32.Ident4()) - r.depthmapShader.SetUniformMat4("model", mgl32.Ident4()) + r.depthmap.shader.SetUniformMat4("model", mgl32.Ident4()) + r.gbuffer.shader.SetUniformMat4("model", mgl32.Ident4()) // and view and projection uniforms not yet set gl.BindFragDataLocation(r.shader.Handle(), 0, gl.Str("outputColor\x00")) - // Set the pointers for attributions - vertAttrib := r.shader.GetAttribLocation("vert") - gl.VertexAttribPointer(vertAttrib, 3, gl.FLOAT, false, int32(unsafe.Sizeof(world.Vertex{})), gl.PtrOffset(int(unsafe.Offsetof(world.Vertex{}.World)))) - normalAttrib := r.shader.GetAttribLocation("normal") - gl.VertexAttribPointer(normalAttrib, 3, gl.FLOAT, false, int32(unsafe.Sizeof(world.Vertex{})), gl.PtrOffset(int(unsafe.Offsetof(world.Vertex{}.Normal)))) - texCoordAttrib := r.shader.GetAttribLocation("vertTexCoord") - gl.VertexAttribPointer(texCoordAttrib, 2, gl.FLOAT, false, int32(unsafe.Sizeof(world.Vertex{})), gl.PtrOffset(int(unsafe.Offsetof(world.Vertex{}.Texture)))) - lightAttrib := r.shader.GetAttribLocation("light") - gl.VertexAttribPointer(lightAttrib, 1, gl.FLOAT, false, int32(unsafe.Sizeof(world.Vertex{})), gl.PtrOffset(int(unsafe.Offsetof(world.Vertex{}.Light)))) - // generate the depthmap and depthmap FBO - gl.GenFramebuffers(1, &r.depthmapFBO) - gl.GenTextures(1, &r.depthmap) - gl.BindTexture(gl.TEXTURE_2D, r.depthmap) + gl.GenFramebuffers(1, &r.depthmap.fbo) + gl.GenTextures(1, &r.depthmap.tex) + gl.BindTexture(gl.TEXTURE_2D, r.depthmap.tex) gl.TexImage2D(gl.TEXTURE_2D, 0, gl.DEPTH_COMPONENT, int32(ShadowmapSize[0]), int32(ShadowmapSize[1]), 0, gl.DEPTH_COMPONENT, gl.FLOAT, nil) gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST) gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST) @@ -74,56 +78,129 @@ func (r *WorldRenderer) Init(w *world.World) (err error) { borderColor := []float32{1, 1, 1, 1} gl.TexParameterfv(gl.TEXTURE_2D, gl.TEXTURE_BORDER_COLOR, &borderColor[0]) // attach depth texture as FBO's depth buffer - gl.BindFramebuffer(gl.FRAMEBUFFER, r.depthmapFBO) - gl.FramebufferTexture2D(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.TEXTURE_2D, r.depthmap, 0) + gl.BindFramebuffer(gl.FRAMEBUFFER, r.depthmap.fbo) + gl.FramebufferTexture2D(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.TEXTURE_2D, r.depthmap.tex, 0) gl.DrawBuffer(gl.NONE) gl.ReadBuffer(gl.NONE) + // attach the shadowmap to the shader + r.shader.SetUniformTextureHandle("shadowmap", r.depthmap.tex) + + // generate G-buffer and friends + gl.GenFramebuffers(1, &r.gbuffer.fbo) + gl.BindFramebuffer(gl.FRAMEBUFFER, r.gbuffer.fbo) + // position + gl.GenTextures(1, &r.gbuffer.pos) + gl.BindTexture(gl.TEXTURE_2D, r.gbuffer.pos) + gl.TexImage2D(gl.TEXTURE_2D, 0, gl.RGBA32F, int32(io.DisplaySize[0]), int32(io.DisplaySize[1]), 0, gl.RGBA, gl.FLOAT, nil) + gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST) + gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST) + gl.FramebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, r.gbuffer.pos, 0) + // normal + gl.GenTextures(1, &r.gbuffer.norm) + gl.BindTexture(gl.TEXTURE_2D, r.gbuffer.norm) + gl.TexImage2D(gl.TEXTURE_2D, 0, gl.RGBA16F, int32(io.DisplaySize[0]), int32(io.DisplaySize[1]), 0, gl.RGBA, gl.FLOAT, nil) + gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST) + gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST) + gl.FramebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT1, gl.TEXTURE_2D, r.gbuffer.norm, 0) + // diffuse color + gl.GenTextures(1, &r.gbuffer.color) + gl.BindTexture(gl.TEXTURE_2D, r.gbuffer.color) + gl.TexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, int32(io.DisplaySize[0]), int32(io.DisplaySize[1]), 0, gl.RGBA, gl.UNSIGNED_BYTE, nil) + gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST) + gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST) + gl.FramebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT2, gl.TEXTURE_2D, r.gbuffer.color, 0) + // depth + gl.GenRenderbuffers(1, &r.gbuffer.depth) + gl.BindRenderbuffer(gl.RENDERBUFFER, r.gbuffer.depth) + gl.RenderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, int32(io.DisplaySize[0]), int32(io.DisplaySize[1])) + gl.FramebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, r.gbuffer.depth) + // tell OpenGL which color attachments we'll use (of this framebuffer) + attachments := [...]uint32{gl.COLOR_ATTACHMENT0, gl.COLOR_ATTACHMENT1, gl.COLOR_ATTACHMENT2} + gl.DrawBuffers(int32(len(attachments)), &attachments[0]) + // attach the textures + r.shader.SetUniformTextureHandle("gPos", r.gbuffer.pos) + r.shader.SetUniformTextureHandle("gNorm", r.gbuffer.norm) + r.shader.SetUniformTextureHandle("gColor", r.gbuffer.color) + gl.BindFramebuffer(gl.FRAMEBUFFER, 0) + r.lastDisplaySize = io.DisplaySize - r.shader.SetUniformTextureHandle("shadowmap", r.depthmap) - - gl.Enable(gl.CULL_FACE) - gl.Enable(gl.DEPTH_TEST) - gl.DepthFunc(gl.LESS) + initScreenQuad() return nil } +// ResizeDisplay resizes the size of the internal buffers dependent on the window size. +// It is called automatically most of the time. +func (r *WorldRenderer) ResizeDisplay(newSize itype.Vec2i) { +} + var sun = [3]float32{0.2, 0.4, 0.3} func (r *WorldRenderer) Render(world *world.World, view *View) { + + // re-generate the G-buffers if the display size changed + if r.lastDisplaySize != io.DisplaySize { + gl.BindTexture(gl.TEXTURE_2D, r.gbuffer.pos) + gl.TexImage2D(gl.TEXTURE_2D, 0, gl.RGBA16F, int32(io.DisplaySize[0]), int32(io.DisplaySize[1]), 0, gl.RGBA, gl.FLOAT, nil) + gl.BindTexture(gl.TEXTURE_2D, r.gbuffer.norm) + gl.TexImage2D(gl.TEXTURE_2D, 0, gl.RGBA16F, int32(io.DisplaySize[0]), int32(io.DisplaySize[1]), 0, gl.RGBA, gl.FLOAT, nil) + gl.BindTexture(gl.TEXTURE_2D, r.gbuffer.color) + gl.TexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, int32(io.DisplaySize[0]), int32(io.DisplaySize[1]), 0, gl.RGBA, gl.UNSIGNED_BYTE, nil) + gl.BindRenderbuffer(gl.RENDERBUFFER, r.gbuffer.norm) + gl.RenderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, int32(io.DisplaySize[0]), int32(io.DisplaySize[1])) + r.lastDisplaySize = io.DisplaySize + } + imgui.SliderFloat3("Sun", &sun, -1, 1) normalSun := itype.Vec3f(sun).Normalize() + gl.Enable(gl.CULL_FACE) + gl.Enable(gl.DEPTH_TEST) + gl.DepthFunc(gl.LESS) + // 1. Render to depth map gl.Viewport(0, 0, int32(ShadowmapSize[0]), int32(ShadowmapSize[1])) - gl.BindFramebuffer(gl.FRAMEBUFFER, r.depthmapFBO) + gl.BindFramebuffer(gl.FRAMEBUFFER, r.depthmap.fbo) gl.Clear(gl.DEPTH_BUFFER_BIT) - lightPos := view.EyePos.Add(normalSun.Multiply(20)) + lightPos := view.EyePos.Add(normalSun.Multiply(50)) lightView := mgl32.LookAt(lightPos[0], lightPos[1], lightPos[2], view.EyePos[0], view.EyePos[1], view.EyePos[2], 0, 1, 0) - lightProjection := mgl32.Ortho(-50, 50, -50, 50, 1, 50) + lightProjection := mgl32.Ortho(-50, 50, -50, 50, 1, 100) lightspace := lightProjection.Mul4(lightView) - r.depthmapShader.UseProgram() - r.depthmapShader.SetUniformMat4("lightspace", lightspace) + r.depthmap.shader.UseProgram() + r.depthmap.shader.SetUniformMat4("lightspace", lightspace) + + world.Render() + + // 2. Geometry pass, render to G-buffer + gl.Viewport(0, 0, int32(r.lastDisplaySize[0]), int32(r.lastDisplaySize[1])) + gl.BindFramebuffer(gl.FRAMEBUFFER, r.gbuffer.fbo) + gl.ClearColor(0, 0, 0, 1) + gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT) + gl.Disable(gl.BLEND) + + r.gbuffer.shader.UseProgram() + r.gbuffer.shader.BindTextures() + r.gbuffer.shader.SetUniformMat4("lightspace", lightspace) + r.gbuffer.shader.SetUniformMat4("view", view.View()) + r.gbuffer.shader.SetUniformMat4("projection", view.Perspective()) + r.gbuffer.shader.SetUniformVec3f("viewPos", view.EyePos) world.Render() gl.BindFramebuffer(gl.FRAMEBUFFER, 0) - // 2. Render the scene - gl.Viewport(0, 0, int32(io.DisplaySize[0]), int32(io.DisplaySize[1])) + // 3. Render the actual output with deferred lighting gl.Clear(gl.DEPTH_BUFFER_BIT) r.shader.UseProgram() r.shader.BindTextures() r.shader.SetUniformMat4("lightspace", lightspace) - r.shader.SetUniformMat4("view", view.View()) - r.shader.SetUniformMat4("projection", view.Perspective()) r.shader.SetUniformVec3f("viewPos", view.EyePos) r.shader.SetUniformVec4f("fogColor", itype.Vec4f{0.6, 0.8, 1.0, 1.0}) r.shader.SetUniformVec3f("sun", normalSun) - world.Render() + r.drawScreenQuad() } diff --git a/internal/render/render_world_helper.go b/internal/render/render_world_helper.go new file mode 100644 index 0000000..db190c7 --- /dev/null +++ b/internal/render/render_world_helper.go @@ -0,0 +1,46 @@ +package render + +import ( + "unsafe" + + "github.com/go-gl/gl/all-core/gl" +) + +var ( + screenQuadVerts = []float32{ + -1, -1, + 1, -1, + 1, 1, + -1, -1, + 1, 1, + -1, 1, + } + screenQuadVAO uint32 + screenQuadVBO uint32 +) + +func initScreenQuad() { + gl.GenBuffers(1, &screenQuadVBO) + gl.BindBuffer(gl.ARRAY_BUFFER, screenQuadVBO) + gl.BufferData(gl.ARRAY_BUFFER, int(unsafe.Sizeof(float32(0)))*len(screenQuadVerts), gl.Ptr(screenQuadVerts), gl.STATIC_DRAW) + + gl.GenVertexArrays(1, &screenQuadVAO) + gl.BindVertexArray(screenQuadVAO) + + gl.VertexAttribPointer(0, 2, gl.FLOAT, false, int32(2*unsafe.Sizeof(float32(0))), gl.PtrOffset(0)) + gl.EnableVertexAttribArray(0) +} + +// drawScreenQuad draws a Quad covering the entire screen. +// +// Attribute: vert: [ -1.0 --> 1.0] +func (r *WorldRenderer) drawScreenQuad() { + if screenQuadVAO == 0 { + initScreenQuad() + } + + gl.Disable(gl.DEPTH_TEST) + gl.Disable(gl.CULL_FACE) + gl.BindVertexArray(screenQuadVAO) + gl.DrawArrays(gl.TRIANGLES, 0, 6) +}