-
Notifications
You must be signed in to change notification settings - Fork 0
/
rendering.go
393 lines (322 loc) · 14.9 KB
/
rendering.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
package main
import (
"fmt"
"log"
"strconv"
"github.com/go-gl/gl/v4.1-core/gl"
"github.com/go-gl/mathgl/mgl32"
)
const (
TEX_UNIT_BASECOLOR = 0
TEX_UNIT_METALLICROUGHNESS = 1
TEX_UNIT_DEPTH = 2
TEX_UNIT_VOLUMETRIC = 3
TEX_UNIT_SHADOW_MAP_BASE = 4
MAX_LIGHTS = 8 //sync this value in shaders
)
type RenderEngine struct {
width, height int32
hdrProg Shader
volProg Shader
postprProg Shader
dirLightProg Shader
screenQuad MeshGPUBufferIDs
query [4]uint32
volFB FrameBuffer
hdrFB FrameBuffer
}
type FrameBuffer struct {
fboID uint32
colorBufferID uint32
zBufferID uint32
}
func (scn *Scene) initRendering(width int32, height int32) RenderEngine {
hdrProg := MakeShaders("./shaders/hdr_vert.glsl", "./shaders/hdr_frag.glsl")
volProg := MakeShaders("./shaders/vol_vert.glsl", "./shaders/vol_frag.glsl")
postprProg := MakeShaders("./shaders/postpr_vert.glsl", "./shaders/postpr_frag.glsl")
dirLightProg := MakeShaders("./shaders/lightDir_vert.glsl", "./shaders/lightDir_frag.glsl")
//BTW, arrays on GPU turn out to be a nasty area. Simply declaring arrays of MAX_LIGHTS
//and then sending only the needed type of light num < MAX_LIGHTS breaks things as the
//unused arrays or array elements interfere in some hard to predict/debug ways even
//when the shader needs for instance just the first 0-th element of a single cube texture
//array.
//The solution is to fill all the arrays completely with pregenerated texture units and
//then save them into the light structs. The lights can then later be also removed during
//the rendering in a safer way.
gl.UseProgram(hdrProg.ID)
hdrProg.SetInt("albedoMap", TEX_UNIT_BASECOLOR)
hdrProg.SetInt("metalRoughMap", TEX_UNIT_METALLICROUGHNESS)
//for it, lht := range scn.lights {
//It's not completely clear if sending the same texture unit number into cube and 2d
//texture samples is a good idea, but it works.
for it := 0; it < MAX_LIGHTS; it++ {
textureUnit := it + TEX_UNIT_SHADOW_MAP_BASE
hdrProg.SetInt("dirlights["+strconv.Itoa(it)+"].txrUnit", int32(textureUnit))
if it < len(scn.lights) {
scn.lights[it].txrUnit = uint32(textureUnit) //save for the use in the render loop
}
}
gl.UseProgram(volProg.ID)
volProg.SetInt("shadowMap", TEX_UNIT_DEPTH)
for it := 0; it < MAX_LIGHTS; it++ {
textureUnit := it + TEX_UNIT_SHADOW_MAP_BASE
volProg.SetInt("dirlights["+strconv.Itoa(it)+"].txrUnit", int32(textureUnit))
}
gl.UseProgram(postprProg.ID)
postprProg.SetInt("hdrTexture", TEX_UNIT_BASECOLOR)
postprProg.SetInt("volTexture", TEX_UNIT_VOLUMETRIC)
quadVertices := [][3]float32{{-1.0, 1.0, 0.0}, {-1.0, -1.0, 0.0}, {1.0, 1.0, 0.0}, {1.0, -1.0, 0.0}}
quadUvs := [][2]float32{{0.0, 1.0}, {0.0, 0.0}, {1.0, 1.0}, {1.0, 0.0}}
quadIndices := []uint32{0, 1, 2, 1, 3, 2}
screenQuad := uploadMeshToGPU(MeshGeometry{
vertArray: quadVertices,
uvArray: quadUvs,
normArray: [][3]float32{},
indArray: quadIndices})
gl.Enable(gl.CULL_FACE)
gl.Enable(gl.DEPTH_TEST)
//gl.DepthFunc(gl.LESS)
var query [4]uint32
gl.GenQueries(4, &query[0])
volFB, err := makeFrameBuffer(width, height)
check(err)
hdrFB, err := makeFrameBuffer(width, height)
check(err)
return RenderEngine{
width: width,
height: height,
hdrProg: hdrProg,
volProg: volProg,
postprProg: postprProg,
dirLightProg: dirLightProg,
screenQuad: screenQuad,
query: query,
volFB: volFB,
hdrFB: hdrFB}
}
func makeFrameBuffer(width int32, height int32) (FrameBuffer, error) {
var fboID uint32
gl.GenFramebuffers(1, &fboID)
var colorBufferID uint32
gl.GenTextures(1, &colorBufferID)
gl.BindTexture(gl.TEXTURE_2D, colorBufferID)
gl.TexImage2D(gl.TEXTURE_2D, 0, gl.RGBA16F, width, height, 0, gl.RGBA, gl.FLOAT, nil)
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR)
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
var zBufferID uint32
gl.GenTextures(1, &zBufferID)
gl.BindTexture(gl.TEXTURE_2D, zBufferID)
gl.TexImage2D(gl.TEXTURE_2D, 0, gl.DEPTH_COMPONENT, width, height, 0, gl.DEPTH_COMPONENT, 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.TexParameterf(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
gl.TexParameterf(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)
// Attach buffers to FBO
gl.BindFramebuffer(gl.FRAMEBUFFER, fboID)
gl.FramebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, colorBufferID, 0)
gl.FramebufferTexture2D(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.TEXTURE_2D, zBufferID, 0)
if gl.CheckFramebufferStatus(gl.FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE {
return FrameBuffer{}, fmt.Errorf("incomplete frame buffer: %v", gl.CheckFramebufferStatus(gl.FRAMEBUFFER))
}
// Bind hdr framebuffer
gl.BindFramebuffer(gl.FRAMEBUFFER, 0)
return FrameBuffer{fboID: fboID, colorBufferID: colorBufferID, zBufferID: zBufferID}, nil
}
func resizeFrameBuffer(fb FrameBuffer, width int32, height int32) {
gl.BindTexture(gl.TEXTURE_2D, fb.colorBufferID)
gl.TexImage2D(gl.TEXTURE_2D, 0, gl.RGBA16F, width, height, 0, gl.RGBA, gl.FLOAT, nil)
gl.BindTexture(gl.TEXTURE_2D, fb.zBufferID)
gl.TexImage2D(gl.TEXTURE_2D, 0, gl.DEPTH_COMPONENT, width, height, 0, gl.DEPTH_COMPONENT, gl.UNSIGNED_BYTE, nil)
}
func resizeRendering(reng RenderEngine) {
resizeFrameBuffer(reng.volFB, reng.width, reng.height)
resizeFrameBuffer(reng.hdrFB, reng.width, reng.height)
gl.Viewport(0, 0, reng.width, reng.height)
}
func (scn *Scene) mainRendering(rengine RenderEngine, cam Camera) [5]float64 {
var timeOpenGLms [5]float64
// Update shadow maps, they will be drawn for each light into its corresponding lht.FBOID
//framebuffer with depth texture attachment created with initShadowMap() in scene.go.
//TD: Think about framebuffer objects spread in: (i) scn.lights Light struct and (ii) rengine RenderEngine struct.
//This is alright, but shows how low level graphics API concepts spread into different things.
//Scene and RenderEngine has no clear borders as to what belongs where, e.g. this is not a rigorous math question:
//Do shadow map framebuffer ids belong to the light struct in the scene, or RenderEngine's shadow map stage, which
//is not clearly delineated in RenderEngine struct BTW.
//----------------------------------------------------------------------------------------------------------------
gl.BeginQuery(gl.TIME_ELAPSED, rengine.query[0])
gl.Enable(gl.DEPTH_TEST)
for it, lht := range scn.lights {
gl.BindFramebuffer(gl.FRAMEBUFFER, lht.FBOID)
gl.Viewport(0, 0, lht.shMapWidth, lht.shMapHeight)
gl.Clear(gl.DEPTH_BUFFER_BIT)
var proj mgl32.Mat4
zNear := float32(0.1) //should these be different per light, adaptive?
zFar := float32(100.0)
/* This setup is wrong, but pretty/intense with light
// and lits up Sponza in ways that would never happen in reality as its sky dome is too narrow.
// Light leaks from the wall because the light reference frame does not
//capture the whole Sponza scene, what is not rendered in the depth buffer
// cannot cast a shadow.
// Try this with lht.dir set to mgl32.Vec3{1, -0.5, -0.5} in scene.go
proj = mgl32.Ortho(-20.0, 20.0, -15.0, 15.0, zNear, zFar)
//If dir and up are aligned LookAtV will output NaN matrices!!!
pos := mgl32.Vec3{-4.0, 2.0, 5.0}
up := mgl32.Vec3{1.0, 0.0, 1.0}
*/
proj = mgl32.Ortho(-30.0, 30.0, -30.0, 30.0, zNear, zFar)
//If dir and up are aligned LookAtV will output NaN matrices!!!
pos := mgl32.Vec3{1, -0.5, -0.5}.Add(lht.dir.Normalize().Mul(-50.0))
up := Z_AXIS
projView := proj.Mul4(mgl32.LookAtV(pos, pos.Add(lht.dir), up))
//fmt.Printf("Lights projView:\n")
//fmt.Printf("%v", projView)
gl.UseProgram(rengine.dirLightProg.ID)
rengine.dirLightProg.SetMat4("projView", projView)
for _, instance := range scn.drawables {
drawMeshVertOnly(instance, rengine.dirLightProg)
}
scn.lights[it].projView = projView
}
//(0, 0) arguments would change for "multi-viewport" settings, "the lower left corner of the viewport rectangle, in pixels".
//Framebuffer texture resolution is different for shadow map stages and hdr/vol passes.
//hdr/vol pass resolution reacts to glfw window resizes, but shadowmap resolutions are fixed when setting up lights in scene.go.
gl.Viewport(0, 0, rengine.width, rengine.height)
gl.EndQuery(gl.TIME_ELAPSED)
// Draw scene to the HDR frame buffer
//---------------------------------------------------------------------------------------------
gl.BeginQuery(gl.TIME_ELAPSED, rengine.query[1])
gl.BindFramebuffer(gl.FRAMEBUFFER, rengine.hdrFB.fboID)
//gl.BindFramebuffer(gl.FRAMEBUFFER, 0) //Screen
bgColor := mgl32.Vec3{0.0, 0.0, 1.0}
gl.ClearColor(bgColor[0], bgColor[1], bgColor[2], 1.0)
gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
gl.UseProgram(rengine.hdrProg.ID)
rengine.hdrProg.SetVec3("camPos", cam.pos)
rengine.hdrProg.SetInt("numDirLights", int32(len(scn.lights)))
//Activate and bind depth textures, quite a ceremony, but they get into a .glsl file via texture ints.
//These ints are set in initRendering and are esentially global texture ids/variables.
//In a GLSL program they become sampler2D/samplerCube, find a concrete variable in .glsl first, and
//then search for its texture associations in higher level programs.
//TD: need to better manage those ints to remove max lights bounds and such, but this exists due to
//a static nature of GPU shaders, probably a hassle to use an array with a variable number of structs.
for it, lht := range scn.lights {
gl.ActiveTexture(gl.TEXTURE0 + lht.txrUnit)
gl.BindTexture(gl.TEXTURE_2D, lht.txrID)
str := "dirlights[" + strconv.Itoa(it) + "]"
lht.setLightParams(rengine.hdrProg, str)
}
//glCheckError()
//return
for _, instance := range scn.drawables {
rengine.hdrProg.SetMat4("projViewModel", cam.projView.Mul4(*(instance.model)))
rengine.hdrProg.SetMat4("model", *(instance.model))
//TD if there is no texture file upload material.diffuseColor to the shaders instead
//the field and value is set there in the scene just in case, but not used for now.
//Meshes without all the proper textures are simply not loaded at the moment.
gl.ActiveTexture(gl.TEXTURE0 + TEX_UNIT_BASECOLOR)
gl.BindTexture(gl.TEXTURE_2D, instance.baseColorTextureID)
gl.ActiveTexture(gl.TEXTURE0 + TEX_UNIT_METALLICROUGHNESS)
gl.BindTexture(gl.TEXTURE_2D, instance.metallicRoughnessTextureID)
drawMesh(*(instance.mesh), rengine.hdrProg.ID)
}
gl.EndQuery(gl.TIME_ELAPSED)
// Draw vol pass
//---------------------------------------------------------------------------------------------
gl.BeginQuery(gl.TIME_ELAPSED, rengine.query[2])
gl.Disable(gl.DEPTH_TEST)
gl.BindFramebuffer(gl.FRAMEBUFFER, rengine.volFB.fboID)
gl.UseProgram(rengine.volProg.ID)
//gl.ActiveTexture(gl.TEXTURE0 + TEX_UNIT_BASECOLOR)
//gl.BindTexture(gl.TEXTURE_2D, rengine.hdrFB.colorBufferID)
gl.ActiveTexture(gl.TEXTURE0 + TEX_UNIT_DEPTH)
gl.BindTexture(gl.TEXTURE_2D, rengine.hdrFB.zBufferID)
rengine.volProg.SetInt("numDirLights", int32(len(scn.lights)))
//Activate and bind depth textures
for it, lht := range scn.lights {
gl.ActiveTexture(gl.TEXTURE0 + lht.txrUnit)
gl.BindTexture(gl.TEXTURE_2D, lht.txrID)
str := "dirlights[" + strconv.Itoa(it) + "]"
lht.setLightParams(rengine.volProg, str)
}
rengine.volProg.SetMat4("invProjView", cam.invProjView)
rengine.volProg.SetVec3("camPos", cam.pos)
rengine.volProg.SetFloat("screenWidth", float32(rengine.width))
rengine.volProg.SetFloat("screenHeight", float32(rengine.height))
rengine.volProg.SetInt("volumetricAlgo", 1) //0 for visibility accumulation experiments
rengine.volProg.SetFloat("scatteringZFar", float32(100.0))
rengine.volProg.SetInt("scatteringSamples", 64)
drawMesh(rengine.screenQuad, rengine.volProg.ID)
gl.EndQuery(gl.TIME_ELAPSED)
// Draw resulting frame buffer to screen with gamma correction and tone mapping
//----------------------------------------------------------------------------------------------
gl.BeginQuery(gl.TIME_ELAPSED, rengine.query[3])
gl.BindFramebuffer(gl.FRAMEBUFFER, 0) //Screen
gl.UseProgram(rengine.postprProg.ID)
//fmt.Printf("ns=%v, interleave=%v, shader=%v\n", 24, 3, "science")
gl.ActiveTexture(gl.TEXTURE0 + TEX_UNIT_BASECOLOR)
gl.BindTexture(gl.TEXTURE_2D, rengine.hdrFB.colorBufferID)
gl.ActiveTexture(gl.TEXTURE0 + TEX_UNIT_VOLUMETRIC)
gl.BindTexture(gl.TEXTURE_2D, rengine.volFB.colorBufferID)
rengine.postprProg.SetInt("hdrVolMixType", 0)
rengine.postprProg.SetFloat("clampPower", 0.8) //adjustable only when hdrVolMixType !=0
rengine.postprProg.SetFloat("gamma", 2.2)
rengine.postprProg.SetFloat("exposure", 5.0)
drawMesh(rengine.screenQuad, rengine.postprProg.ID)
gl.EndQuery(gl.TIME_ELAPSED)
var elapsedTime uint64
totalms := float64(0.0)
for i := 0; i < 4; i++ {
gl.GetQueryObjectui64v(rengine.query[i], gl.QUERY_RESULT, &elapsedTime)
timeOpenGLms[i] = float64(elapsedTime) / 1000000.0
totalms += timeOpenGLms[i]
}
timeOpenGLms[4] = totalms
return timeOpenGLms
}
func drawMesh(msh MeshGPUBufferIDs, shdrProgID uint32) {
gl.BindVertexArray(msh.vaoID)
loc := uint32(gl.GetAttribLocation(shdrProgID, gl.Str("inPosition\x00")))
if loc >= 0 {
gl.BindBuffer(gl.ARRAY_BUFFER, msh.vertexBufferID)
gl.VertexAttribPointer(loc, 3, gl.FLOAT, false, 0, gl.PtrOffset(0))
gl.EnableVertexAttribArray(loc)
} else {
log.Fatalln("drawInstance could not find uniform: inPosition")
}
if msh.normalBufferID > 0 && (msh.normalBufferID != gl.INVALID_VALUE) {
loc = uint32(gl.GetAttribLocation(shdrProgID, gl.Str("inNormal\x00")))
if loc >= 0 {
gl.BindBuffer(gl.ARRAY_BUFFER, msh.normalBufferID)
gl.VertexAttribPointer(loc, 3, gl.FLOAT, false, 0, gl.PtrOffset(0))
gl.EnableVertexAttribArray(loc)
} else {
log.Fatalln("drawInstance could not find uniform: inNormal")
}
}
if msh.uvBufferID > 0 && (msh.uvBufferID != gl.INVALID_VALUE) {
loc = uint32(gl.GetAttribLocation(shdrProgID, gl.Str("inTexCoord\x00")))
if loc >= 0 {
gl.BindBuffer(gl.ARRAY_BUFFER, msh.uvBufferID)
gl.VertexAttribPointer(loc, 2, gl.FLOAT, false, 0, gl.PtrOffset(0))
gl.EnableVertexAttribArray(loc)
} else {
log.Fatalln("drawInstance could not find uniform: inTexCoord")
}
}
gl.DrawElements(gl.TRIANGLES, msh.lenIndices, gl.UNSIGNED_INT, gl.PtrOffset(0))
}
func drawMeshVertOnly(instance DrawableInstance, shdrProg Shader) {
shdrProg.SetMat4("model", *(instance.model))
gl.BindVertexArray((*(instance.mesh)).vaoID)
gl.BindBuffer(gl.ARRAY_BUFFER, (*(instance.mesh)).vertexBufferID)
//Vert attr loc 0:
gl.VertexAttribPointer(0, 3, gl.FLOAT, false, 0, gl.PtrOffset(0))
gl.EnableVertexAttribArray(0)
gl.DrawElements(gl.TRIANGLES, (*(instance.mesh)).lenIndices, gl.UNSIGNED_INT, gl.PtrOffset(0))
}
func (lht Light) setLightParams(shd Shader, str string) {
shd.SetVec3(str+".dir", lht.dir)
shd.SetVec3(str+".color", lht.color)
shd.SetFloat(str+".intensity", lht.intensity)
shd.SetMat4(str+".dirWorldToProj", lht.projView)
}