* Incomplete port of myou-engine-js to nimskull, after many months of work, and a few extra features that weren't exactly necessary for a "first commit" to work. Excuse the lack of commit history up to this point. * Bare bones structure of the documentation and the process to update it. * Restructure of the whole project to have a more sensible organization. * Making submodules of forks of larger libraries. * README, licenses, AUTHORS.md.
620 lines
25 KiB
Nim
620 lines
25 KiB
Nim
# The contents of this file are subject to the Common Public Attribution License
|
|
# Version 1.0 (the “License”); you may not use this file except in compliance
|
|
# with the License. You may obtain a copy of the License at
|
|
# https://myou.dev/licenses/LICENSE-CPAL. The License is based on the Mozilla
|
|
# Public License Version 1.1 but Sections 14 and 15 have been added to cover use
|
|
# of software over a computer network and provide for limited attribution for
|
|
# the Original Developer. In addition, Exhibit A has been modified to be
|
|
# consistent with Exhibit B.
|
|
#
|
|
# Software distributed under the License is distributed on an “AS IS” basis,
|
|
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
|
|
# the specific language governing rights and limitations under the License.
|
|
#
|
|
# The Original Code is Myou Engine.
|
|
#
|
|
# the Original Developer is the Initial Developer.
|
|
#
|
|
# The Initial Developer of the Original Code is the Myou Engine developers.
|
|
# All portions of the code written by the Myou Engine developers are Copyright
|
|
# (c) 2024. All Rights Reserved.
|
|
#
|
|
# Alternatively, the contents of this file may be used under the terms of the
|
|
# GNU Affero General Public License version 3 (the [AGPL-3] License), in which
|
|
# case the provisions of [AGPL-3] License are applicable instead of those above.
|
|
#
|
|
# If you wish to allow use of your version of this file only under the terms of
|
|
# the [AGPL-3] License and not to allow others to use your version of this file
|
|
# under the CPAL, indicate your decision by deleting the provisions above and
|
|
# replace them with the notice and other provisions required by the [AGPL-3]
|
|
# License. If you do not delete the provisions above, a recipient may use your
|
|
# version of this file under either the CPAL or the [AGPL-3] License.
|
|
|
|
import ../types
|
|
import ../platform/gl
|
|
import arr_ref
|
|
import vmath except Quat
|
|
|
|
# Forward declarations
|
|
func newRenderCameraData*(world_matrix, proj_matrix: Mat4, cull_planes: array[6, Vec4], viewport_size: Vec2): RenderCameraData
|
|
func updateCullPlanes*(self: var RenderCameraData, cull_planes: array[6, Vec4])
|
|
proc newRenderManager*(engine: MyouEngine): RenderManager
|
|
proc initialize*(self: RenderManager)
|
|
proc uninitialize*(self: RenderManager)
|
|
proc set_premultiplied_alpha*(use_premultipied: bool)
|
|
proc draw_all*(self: RenderManager)
|
|
proc draw_mesh*(self: RenderManager, mesh: Mesh, mesh2world: Mat4, cam_data: RenderCameraData, pass: int = -1, material_override: Material = nil)
|
|
proc draw_background*(self: RenderManager, scene: Scene, cam_data: RenderCameraData)
|
|
proc draw_quad*(self: RenderManager, material: Material, scene: Scene, cam_data: RenderCameraData)
|
|
proc draw_viewport*(self: RenderManager, viewport: Viewport, rect: (int32,int32,int32,int32), dest_buffer: Framebuffer, passes: seq[int])
|
|
proc draw_cubemap*(self: RenderManager, scene: Scene, cubemap_fb: Framebuffer, cube2world, world2cube: Mat4, near, far: float32, background_only: bool)
|
|
proc get_render_uniform_blocks*(): string
|
|
# End forward declarations
|
|
|
|
import elvis
|
|
import std/algorithm
|
|
import std/options
|
|
import std/strformat
|
|
import std/strutils
|
|
import std/tables
|
|
import ../math_utils/g3
|
|
import ../myou_engine
|
|
import ../objects/cubemap_probe
|
|
import ../objects/gameobject
|
|
# import ../objects/light
|
|
import ../objects/mesh
|
|
import ../platform/platform
|
|
import ../quat
|
|
import ../scene
|
|
import ../util
|
|
import ./framebuffer
|
|
import ./material
|
|
import ./texture
|
|
import ./ubo
|
|
|
|
func newRenderCameraData*(world_matrix, proj_matrix: Mat4, cull_planes: array[6, Vec4], viewport_size: Vec2): RenderCameraData =
|
|
result.cam2world = world_matrix.remove_scale_skew
|
|
result.world2cam = result.cam2world.inverse
|
|
result.projection_matrix = proj_matrix
|
|
# TODO: should we get this as argument? Camera has it
|
|
result.projection_matrix_inverse = proj_matrix.inverse
|
|
result.clipping_plane = vec4(0, 0, -1, 0)
|
|
result.updateCullPlanes(cull_planes)
|
|
result.viewport_size = viewport_size
|
|
result.viewport_size_inv = 1'f32/viewport_size
|
|
|
|
func updateCullPlanes*(self: var RenderCameraData, cull_planes: array[6, Vec4]) =
|
|
# TODO: check if this works just by multiplying the planes w matrix
|
|
var p4 = vec4()
|
|
var n4 = vec4()
|
|
for i, plane in cull_planes:
|
|
n4 = plane
|
|
n4.w = 0
|
|
p4 = n4 * -plane.w
|
|
p4.w = 1
|
|
p4 = self.cam2world * p4
|
|
n4 = self.cam2world * n4
|
|
self.cull_planes[i] = plane_from_norm_point(n4.xyz, p4.xyz)
|
|
|
|
proc newRenderManager*(engine: MyouEngine): RenderManager =
|
|
result = new RenderManager
|
|
result.engine = engine
|
|
result.use_frustum_culling = true
|
|
result.use_sort_faces = true
|
|
result.use_debug_draw = true
|
|
result.max_buffer_size_upload = int.high
|
|
result.cull_face_enabled = true
|
|
# result.active_texture = -1
|
|
result.effect_ratio = 1
|
|
result.num_views = 1
|
|
|
|
proc initialize*(self: RenderManager) =
|
|
glGetIntegerv(GL_MAX_TEXTURE_SIZE, addr self.max_texture_size.GLint)
|
|
glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, addr self.max_textures.GLint)
|
|
glGetIntegerv(GL_MAX_UNIFORM_BLOCK_SIZE, addr self.max_uniform_block_size.GLint)
|
|
glGetIntegerv(GL_MAX_UNIFORM_BUFFER_BINDINGS, addr self.max_uniform_buffer_bindings.GLint)
|
|
self.max_textures = min(self.max_textures, HARDCODED_MAXTEXTURES)
|
|
self.max_uniform_buffer_bindings = min(self.max_uniform_buffer_bindings, HARDCODED_MAXUBOS)
|
|
# TODO: is this fine?
|
|
setMaxTextures self.max_textures
|
|
self.bound_ubos.setLen self.max_uniform_buffer_bindings
|
|
self.next_ubo = 0
|
|
# TODO: detect float texture/framebuffer supportglClearDepthf(1)
|
|
glEnable(GL_DEPTH_TEST)
|
|
glDepthFunc(GL_LEQUAL)
|
|
glEnable(GL_CULL_FACE)
|
|
glCullFace(GL_BACK)
|
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
|
|
self.bg_mesh = newMesh(self.engine, "_bg_mesh", common_attributes={vertex})
|
|
self.bg_mesh.load_from_va_ia(@[-1'f32, -1, -1, 3, -1, -1, -1, 3, -1])
|
|
self.bg_mesh.radius = Inf
|
|
self.bg_mesh.materials = @[nil.Material]
|
|
self.no_material = self.engine.newSolidMaterial("_no_material", vec4(1,0,1,1));
|
|
self.camera_render_ubo = self.newUBO("CameraRenderUniform", CameraRenderUniform, 1)
|
|
# since this is a ref, it won't be destroyed until it's used
|
|
let zero = newArrRef[uint8](3)
|
|
zero.fill 0
|
|
self.blank_texture = self.engine.newTexture("",1,1,1,RGB_u8,pixels=zero.to float32)
|
|
self.initialized = true
|
|
for fun in self.queue:
|
|
try:
|
|
fun()
|
|
except Exception as e:
|
|
# TODO: use logging
|
|
for line in e.getStackTrace.split '\n':
|
|
echo line
|
|
echo getCurrentExceptionMsg()
|
|
self.queue.set_len 0
|
|
|
|
proc uninitialize*(self: RenderManager) =
|
|
self.initialized = false
|
|
self.bg_mesh.destroy()
|
|
self.camera_render_ubo.destroy()
|
|
|
|
|
|
proc set_premultiplied_alpha*(use_premultipied: bool) =
|
|
if use_premultipied:
|
|
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA)
|
|
else:
|
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
|
|
|
|
template set_cull_face(self: RenderManager, enable: bool) =
|
|
if self.cull_face_enabled != enable:
|
|
self.cull_face_enabled = enable
|
|
if enable:
|
|
glEnable(GL_CULL_FACE)
|
|
else:
|
|
glDisable(GL_CULL_FACE)
|
|
|
|
template set_flip_normals(self: RenderManager, flip: bool) =
|
|
if self.front_face_is_cw != flip:
|
|
self.front_face_is_cw = flip
|
|
if flip:
|
|
glFrontFace(GL_CW)
|
|
else:
|
|
glFrontFace(GL_CCW)
|
|
|
|
proc draw_all*(self: RenderManager) =
|
|
self.render_tick += 1
|
|
self.indices_drawn = 0
|
|
self.meshes_drawn = 0
|
|
|
|
resetNextTextureSlot()
|
|
self.next_ubo = 0
|
|
|
|
# calculate all matrices first
|
|
for screen in self.engine.screens:
|
|
if not screen.enabled:
|
|
continue
|
|
for vp in screen.viewports:
|
|
let scene = vp.camera.scene
|
|
if not scene.enabled and scene.last_update_matrices_tick < self.render_tick:
|
|
continue
|
|
scene.update_all_matrices()
|
|
scene.update_lights()
|
|
# TODO: render probes required by cameras of enabled screens
|
|
# TODO: render shadows required by cameras of enabled screens
|
|
for screen in self.engine.screens:
|
|
if not screen.enabled:
|
|
continue
|
|
discard screen.platform_switch_screen()
|
|
screen.pre_draw(screen)
|
|
for viewport in screen.viewports:
|
|
let scene = viewport.camera.scene
|
|
if not scene.enabled:
|
|
continue
|
|
# TODO: effect chains which contain effects and passes
|
|
self.draw_viewport(viewport, viewport.rect_pix, screen.framebuffer, @[0, 1])
|
|
screen.post_draw(screen)
|
|
glUseProgram(0)
|
|
glBindVertexArray(0)
|
|
|
|
proc draw_mesh*(self: RenderManager, mesh: Mesh, mesh2world: Mat4, cam_data: RenderCameraData, pass: int = -1, material_override: Material = nil) =
|
|
if mesh.sqscale < 0.000001:
|
|
mesh.culled_in_last_frame = true
|
|
return
|
|
if self.use_frustum_culling and mesh.parent.is_armature:
|
|
# Cull object if it's outside camera frustum
|
|
let pos4 = mesh.world_center
|
|
# TODO: USE SCALE
|
|
let r = -mesh.radius
|
|
for plane in cam_data.cull_planes:
|
|
if dot(plane, pos4) < r:
|
|
mesh.culled_in_last_frame = true
|
|
return
|
|
mesh.culled_in_last_frame = false
|
|
# TODO: Also check with cam_data.clipping_plane!
|
|
|
|
# TODO: Select alternative mesh / LoD
|
|
var amesh = mesh
|
|
if not (amesh.data != nil and amesh.data.loaded):
|
|
return
|
|
self.set_flip_normals mesh.flip
|
|
let data = amesh.data
|
|
|
|
# for ubo in self.bound_ubos:
|
|
# if ubo != nil:
|
|
# ubo.unbind()
|
|
# self.next_ubo = 0
|
|
|
|
# unbindAllTextures()
|
|
|
|
# Main routine for each submesh
|
|
# (vao may be null but that's handled later)
|
|
for submesh_idx, vao in data.vaos:
|
|
if vao == 0:
|
|
continue
|
|
|
|
if not (pass == -1 or mesh.passes[submesh_idx] == pass):
|
|
continue
|
|
|
|
var mat = if material_override == nil:
|
|
amesh.materials.get_or_default(submesh_idx) ?: self.no_material
|
|
else:
|
|
material_override
|
|
|
|
let shader = mat.get_shader(mesh)
|
|
if shader.program == 0:
|
|
continue
|
|
shader.use()
|
|
self.set_cull_face(not mat.double_sided)
|
|
|
|
var ubos_to_bind: seq[UBO]
|
|
var ubo_indices: seq[GLuint]
|
|
var ubos_to_update: seq[UBO]
|
|
|
|
let mvm = cam_data.world2cam * mesh2world
|
|
let nm = mvm.to_normal_matrix()
|
|
|
|
if shader.object_render_ubo_index.is_valid:
|
|
let ubo = mesh.object_render_ubo
|
|
ubos_to_bind.add ubo
|
|
ubo_indices.add shader.object_render_ubo_index
|
|
ubo.storage(ObjectRenderUniform)[0] = ObjectRenderUniform(
|
|
model_view_matrix: mvm,
|
|
model_view_matrix_inverse: mvm.inverse,
|
|
normal_matrix: [
|
|
vec4(nm[0], 0.0),
|
|
vec4(nm[1], 0.0),
|
|
vec4(nm[2], 0.0),
|
|
],
|
|
)
|
|
ubos_to_update.add ubo
|
|
|
|
# TODO: move this UBO, consolidate with cameradata
|
|
# TODO: update only in draw_viewport
|
|
if shader.camera_render_ubo_index.is_valid:
|
|
let ubo = self.camera_render_ubo
|
|
ubos_to_bind.add ubo
|
|
ubo_indices.add shader.camera_render_ubo_index
|
|
ubo.storage(CameraRenderUniform)[0] = CameraRenderUniform(
|
|
view_matrix: cam_data.world2cam,
|
|
view_matrix_inverse: cam_data.cam2world,
|
|
projection_matrix: cam_data.projection_matrix,
|
|
projection_matrix_inverse: cam_data.projection_matrix_inverse,
|
|
viewport_size: cam_data.viewport_size,
|
|
viewport_size_inv: cam_data.viewport_size_inv,
|
|
)
|
|
ubos_to_update.add ubo
|
|
|
|
if shader.object_ubo_index.is_valid:
|
|
let ubo = mesh.object_ubo
|
|
ubos_to_bind.add ubo
|
|
ubo_indices.add shader.object_ubo_index
|
|
const diag = vec3(1).normalize
|
|
ubo.storage(ObjectUniform)[0] = ObjectUniform(
|
|
model_matrix: mesh2world,
|
|
model_matrix_inverse: mesh2world.inverse,
|
|
# TODO: condition for when these two are used?
|
|
mesh_center: mix(mesh.bound_box[1], mesh.bound_box[0], 0.5),
|
|
mesh_inv_dimensions: vec3(2) / min(mesh.bound_box[1] - mesh.bound_box[0], vec3(0.000001)),
|
|
average_world_scale: (mesh.world_matrix * vec4(diag,0)).xyz.length,
|
|
color: mesh.object_color,
|
|
)
|
|
ubos_to_update.add ubo
|
|
|
|
# UBOs
|
|
for i,idx in shader.ubo_indices:
|
|
let ubo = shader.ubos[i]
|
|
ubos_to_bind.add ubo
|
|
ubo_indices.add idx
|
|
|
|
ubos_to_bind.bind_all()
|
|
for i,ubo in ubos_to_bind:
|
|
ubo.set_prog_index(shader.program, ubo_indices[i])
|
|
for ubo in ubos_to_update:
|
|
ubo.update()
|
|
|
|
var textures_to_bind: seq[Texture]
|
|
var texture_locations = shader.texture_locations
|
|
for name, texture in mat.textures:
|
|
if not defined(release):
|
|
assert texture != nil, &"texture {name} is nil"
|
|
# TODO: move this to bind_it/bind_all
|
|
if texture.loaded and not texture.is_framebuffer_active:
|
|
textures_to_bind.add texture
|
|
else:
|
|
textures_to_bind.add self.blank_texture
|
|
|
|
let scene = mesh.scene
|
|
if scene != nil:
|
|
let shadow_maps = scene.shadow_maps
|
|
for i,loc in shader.shadowmap_locations:
|
|
if i == shadow_maps.len:
|
|
break
|
|
if loc == -1:
|
|
continue
|
|
let tex = shadow_maps[i]
|
|
if tex != nil:
|
|
textures_to_bind.add tex
|
|
texture_locations.add loc
|
|
|
|
let cubemaps = scene.cubemaps
|
|
for i,loc in shader.cubemap_locations:
|
|
if i == cubemaps.len:
|
|
break
|
|
if loc == -1:
|
|
continue
|
|
let tex = cubemaps[i].texture
|
|
textures_to_bind.add tex
|
|
texture_locations.add loc
|
|
|
|
bind_all(textures_to_bind, texture_locations)
|
|
|
|
let index_buffer = data.index_buffers[submesh_idx]
|
|
let num_indices = data.num_indices[submesh_idx]
|
|
glBindVertexArray(vao)
|
|
if not data.use_tf:
|
|
if index_buffer != 0:
|
|
glDrawElements(data.draw_method.GLenum, num_indices.GLsizei, data.index_types[submesh_idx].GLenum, cast[pointer](0))
|
|
else:
|
|
glDrawArrays(data.draw_method.GLenum, 0, num_indices)
|
|
else:
|
|
assert index_buffer == 0, "Loopback transform feedback is not compatible with indexed meshes"
|
|
glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, data.tf_vbos[1][submesh_idx])
|
|
glBeginTransformFeedback(data.draw_method.GLenum)
|
|
glDrawArrays(data.draw_method.GLenum, 0, num_indices)
|
|
glEndTransformFeedback()
|
|
|
|
# glBindVertexArray(0)
|
|
self.meshes_drawn += 1
|
|
self.indices_drawn += num_indices
|
|
|
|
if data.use_tf:
|
|
# Swap vaos which include both the regular mesh vao
|
|
# and each loopback tf (one for reading, the other for writing)
|
|
glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, 0)
|
|
# TODO: check if this swap avoids allocations
|
|
swap(data.tf_vbos[0], data.tf_vbos[1])
|
|
let vaos = data.vaos
|
|
data.vaos = data.tf_vaos
|
|
data.tf_vaos = vaos
|
|
|
|
proc draw_background*(self: RenderManager, scene: Scene, cam_data: RenderCameraData) =
|
|
self.bg_mesh.scene = scene
|
|
self.draw_mesh(self.bg_mesh, cam_data.cam2world, cam_data, -1, scene.world_material)
|
|
|
|
proc draw_quad*(self: RenderManager, material: Material, scene: Scene, cam_data: RenderCameraData) =
|
|
self.bg_mesh.scene = scene
|
|
self.draw_mesh(self.bg_mesh, cam_data.cam2world, cam_data, -1, material)
|
|
|
|
proc draw_viewport*(self: RenderManager, viewport: Viewport, rect: (int32,int32,int32,int32), dest_buffer: Framebuffer, passes: seq[int]) =
|
|
# Configure camera data
|
|
let cam = viewport.debug_camera ?: viewport.camera
|
|
let scene = cam.scene
|
|
if self.was_right_eye != viewport.is_right_eye:
|
|
self.was_right_eye = viewport.is_right_eye
|
|
if scene.on_swap_eye != nil:
|
|
scene.on_swap_eye(self.was_right_eye)
|
|
var cd = newRenderCameraData(cam.world_matrix, cam.projection_matrix,
|
|
cam.cull_planes, vec2(rect[2].float32, rect[3].float32))
|
|
var cam_pos = cam.world_matrix[3].xyz
|
|
if self.show_debug_frustum_culling:
|
|
let cd2 = newRenderCameraData(viewport.camera.world_matrix, viewport.camera.projection_matrix,
|
|
cam.cull_planes, cd.viewport_size)
|
|
cd.cull_planes = cd2.cull_planes
|
|
cam.world_to_screen_matrix = cam.projection_matrix * cd.world2cam
|
|
# Main drawing code to destination buffer (usually the screen)
|
|
dest_buffer.enable(some(rect))
|
|
var clear_bits: GLbitfield
|
|
when defined(myouAlwaysClearColor):
|
|
clear_bits = GL_COLOR_BUFFER_BIT
|
|
if viewport.clear_color: clear_bits |= GL_COLOR_BUFFER_BIT
|
|
if viewport.clear_depth: clear_bits |= GL_DEPTH_BUFFER_BIT
|
|
var c = scene.background_color
|
|
if scene.world_material != nil and c.a >= 1:
|
|
when not defined(myouAlwaysClearColor):
|
|
# Don't bother clearing color since we'll be rendering over it
|
|
clear_bits = clear_bits and not GL_COLOR_BUFFER_BIT
|
|
elif (clear_bits and GL_COLOR_BUFFER_BIT).bool:
|
|
glClearColor(c.r, c.g, c.b, c.a)
|
|
if clear_bits.bool: glClear(clear_bits)
|
|
glDisable(GL_BLEND)
|
|
glDepthMask(true)
|
|
if self.use_draw_background_first:
|
|
# Scene background
|
|
if scene.world_material != nil and scene.background_color.a > 0.001:
|
|
if scene.background_color.a < 1:
|
|
glEnable(GL_BLEND)
|
|
self.draw_background(scene, cd)
|
|
if scene.background_color.a < 1:
|
|
glDisable(GL_BLEND)
|
|
|
|
# # PASS -1 ("always in background" meshes)
|
|
# if scene.bg_pass.len != 0:
|
|
# for ob in scene.bg_pass:
|
|
# if ob.visible == true:
|
|
# self.draw_mesh(ob, ob.world_matrix, cd, 0)
|
|
# glClear(GL_DEPTH_BUFFER_BIT)
|
|
|
|
# PASS 0 (opaque)
|
|
if passes.find(0) >= 0 and scene.mesh_passes[0].len != 0:
|
|
# Sort by distence to camera
|
|
# TODO: sort by material first, then reduce precision of depth
|
|
let z = cd.cam2world[2].xyz
|
|
for ob in scene.mesh_passes[0]:
|
|
let v = ob.world_matrix[3].xyz
|
|
ob.sqdist = -dot(v, z) - (ob.zindex * (ob.dimensions.x + ob.dimensions.y + ob.dimensions.z) * 0.166666)
|
|
# debugger if ob._sqdist != ob._sqdist
|
|
scene.mesh_passes[0].sort proc(a, b: auto): auto =
|
|
return cmp(b.sqdist, a.sqdist)
|
|
|
|
if self.use_sort_faces_opaque:
|
|
# Sort some meshes, for now just one per frame, with more iterations
|
|
# for nearby meshes (TODO: Calculate which one has more divergence)
|
|
# TODO!! Mixed meshes!
|
|
let num_meshes = scene.mesh_passes[0].len
|
|
var idx = self.render_tick mod num_meshes
|
|
if (self.render_tick and 1) == 0:
|
|
idx = idx shr 1
|
|
idx = num_meshes - idx - 1
|
|
let ob = scene.mesh_passes[0][idx]
|
|
ob.sort_sign = FrontToBack
|
|
# TODO: sort LoD mesh
|
|
ob.sort_faces(cam_pos)
|
|
for i, ob in scene.mesh_passes[0]:
|
|
if ob.visible: # and not ob.bg and not ob.fg
|
|
self.draw_mesh(ob, ob.world_matrix, cd, 0)
|
|
|
|
if not self.use_draw_background_first:
|
|
# Scene background
|
|
if scene.world_material != nil and scene.background_color.a > 0.001:
|
|
if scene.background_color.a < 1:
|
|
glEnable(GL_BLEND)
|
|
self.draw_background(scene, cd)
|
|
if scene.background_color.a < 1:
|
|
glDisable(GL_BLEND)
|
|
if passes.find(1) >= 0 and scene.mesh_passes[1].len != 0:
|
|
glDepthMask(false)
|
|
glEnable(GL_BLEND)
|
|
# Sort by distence to camera
|
|
for ob in scene.mesh_passes[1]:
|
|
let v = ob.world_matrix[3].xyz
|
|
ob.sqdist = dist_sq(-v, cam_pos) - ob.zindex * ob.zindex
|
|
# TODO: squared dist and zindex?
|
|
# ob._sqdist = -vec3.dist(v,cam_pos) - (ob.zindex * \
|
|
# (ob.dimensions.x+ob.dimensions.y+ob.dimensions.z)*0.166666)
|
|
scene.mesh_passes[1].sort proc(a, b: auto): auto =
|
|
return cmp(b.sqdist, a.sqdist)
|
|
|
|
if self.use_sort_faces:
|
|
# Sort some meshes, for now just one per frame, with more iterations
|
|
# for nearby meshes (TODO: Calculate which one has more divergence)
|
|
let num_meshes = scene.mesh_passes[1].len
|
|
var idx = self.render_tick mod num_meshes
|
|
if (self.render_tick and 1) == 0:
|
|
idx = idx shr 1
|
|
idx = num_meshes - idx - 1
|
|
let ob = scene.mesh_passes[1][idx]
|
|
# (ob.last_lod[cam_name].?mesh ? ob).sort_faces(cam_pos)
|
|
ob.sort_faces(cam_pos)
|
|
for ob in scene.mesh_passes[1]:
|
|
if ob.visible:
|
|
self.draw_mesh(ob, ob.world_matrix, cd, 1)
|
|
glDisable(GL_BLEND)
|
|
glDepthMask(true)
|
|
# PASS 2 was here (translucent objects)
|
|
# by making a copy of the buffer to use as input texture.
|
|
# If we implement TAA, we may just read the previous opaque frame instead.
|
|
# For that, the new pass system will nave to support multiple buffers
|
|
# to alternate between each frame.
|
|
|
|
# # foreground (always-on-top) objects
|
|
# if scene.fg_pass.len != 0:
|
|
# glClear(GL_DEPTH_BUFFER_BIT)
|
|
# var do_blend = false
|
|
# for ob in scene.fg_pass:
|
|
# if ob.visible == true:
|
|
# if ob.passes[0] == 1:
|
|
# if not do_blend:
|
|
# glDepthMask(false)
|
|
# glEnable(GL_BLEND)
|
|
# do_blend = true
|
|
# self.draw_mesh(ob, ob.world_matrix, cd, 1)
|
|
# else:
|
|
# if do_blend:
|
|
# glDisable(GL_BLEND)
|
|
# glDepthMask(true)
|
|
# do_blend = false
|
|
# self.draw_mesh(ob, ob.world_matrix, cd, 0)
|
|
# if do_blend:
|
|
# glDisable(GL_BLEND)
|
|
# glDepthMask(true)
|
|
|
|
# FOREGROUND_PLANES (similar to background, but drawn after)
|
|
if scene.foreground_planes.len != 0:
|
|
glDepthMask(false)
|
|
var blending = false
|
|
for fg_plane in scene.foreground_planes:
|
|
if blending != fg_plane.is_alpha:
|
|
if fg_plane.is_alpha:
|
|
glEnable(GL_BLEND)
|
|
else:
|
|
glDisable(GL_BLEND)
|
|
blending = fg_plane.is_alpha
|
|
self.draw_quad(fg_plane.material, scene, cd)
|
|
if blending:
|
|
glDisable(GL_BLEND)
|
|
glDepthMask(true)
|
|
|
|
# TODO: reimplement DebugDraw
|
|
|
|
# if self.use_debug_draw and scene.debug_draw != nil:
|
|
# scene.debug_draw.draw(cam)
|
|
|
|
proc draw_cubemap*(self: RenderManager, scene: Scene, cubemap_fb: Framebuffer, cube2world, world2cube: Mat4, near, far: float32, background_only: bool) =
|
|
# TODO:
|
|
# * use frustum culling
|
|
# * override LoD detection or set a camera for this
|
|
# * maybe use multidraw
|
|
assert cubemap_fb.texture.tex_type == TexCube, "Framebuffer must have a cubemap texture"
|
|
let use_frustum_culling = self.use_frustum_culling
|
|
self.use_frustum_culling = false
|
|
self.num_views = 1
|
|
var cd: RenderCameraData
|
|
cd.viewport_size = vec2(cubemap_fb.width.float32)
|
|
cd.viewport_size_inv = 1'f32/cd.viewport_size
|
|
let scale = vec3(
|
|
cube2world[0].xyz.length,
|
|
cube2world[1].xyz.length,
|
|
cube2world[2].xyz.length,
|
|
)
|
|
let inv_scale = 1'f32/scale
|
|
let s_near = near * min(min(inv_scale.x, inv_scale.y), inv_scale.z)
|
|
# let s_far = far * max(max(inv_scale.x, inv_scale.y), inv_scale.z)
|
|
let near_box = s_near * scale
|
|
let world2cube = cube2world.inverse
|
|
for side in 0 ..< 6:
|
|
cubemap_fb.enable(layer=side)
|
|
cd.world2cam = getCubemapSideMatrix(side)
|
|
cd.cam2world = transpose(cd.world2cam)
|
|
let near_plane = abs(cd.world2cam * near_box)
|
|
cd.world2cam = cd.world2cam * world2cube * scale(scale)
|
|
cd.cam2world = scale(inv_scale) * cube2world * cd.cam2world
|
|
cd.projection_matrix = frustum(
|
|
-near_plane.x, near_plane.x,
|
|
-near_plane.y, near_plane.y,
|
|
near_plane.z, far)
|
|
cd.projection_matrix_inverse = inverse(cd.projection_matrix)
|
|
# cd.updateCullPlanes()
|
|
let (r,g,b,_) = scene.background_color.toTuple
|
|
glClearColor(r, g, b, 1)
|
|
glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT)
|
|
# TODO!!! Disable specular
|
|
# also increase diffuse to compensate?
|
|
# TODO: shadows?
|
|
if not background_only:
|
|
for ob in scene.mesh_passes[0]:
|
|
# if ob.probe_cube != nil:
|
|
# if probe.object == ob and probe.cubemap == cubemap:
|
|
# continue
|
|
if ob.visible and ob.data != nil:
|
|
self.draw_mesh(ob, ob.world_matrix, cd, 0)
|
|
if scene.world_material != nil:
|
|
self.draw_background(scene, cd)
|
|
cubemap_fb.disable()
|
|
self.use_frustum_culling = use_frustum_culling
|
|
|
|
proc get_render_uniform_blocks*(): string =
|
|
return staticRead "../shaders/render_ubos.glsl"
|
|
|