package org.openrndr.extra.dnk3 import org.openrndr.color.ColorRGBa import org.openrndr.draw.* import org.openrndr.extra.dnk3.features.IrradianceSH import org.openrndr.extra.fx.blur.ApproximateGaussianBlur import org.openrndr.math.Matrix44 import org.openrndr.math.Vector3 import java.nio.ByteBuffer class RenderContext( val lights: List>, val meshes: List>, val skinnedMeshes: List>, val instancedMeshes: List>, val pathMeshes: List>, val fogs: List> ) class SceneRenderer { class Configuration { var multisampleLines = false } val configuration = Configuration() val blur = ApproximateGaussianBlur() var shadowLightTargets = mutableMapOf() var meshCubemaps = mutableMapOf() var outputPasses = mutableListOf(DefaultOpaquePass, DefaultTransparentPass) var outputPassTarget: RenderTarget? = null var outputPassTargetMS: RenderTarget? = null val postSteps = mutableListOf() val buffers = mutableMapOf() var drawFinalBuffer = true var first = true fun draw(drawer: Drawer, scene: Scene) { drawer.pushStyle() drawer.depthWrite = true drawer.depthTestPass = DepthTestPass.LESS_OR_EQUAL drawer.cullTestPass = CullTestPass.FRONT scene.dispatcher.execute() // update all the transforms scene.root.scan(Matrix44.IDENTITY) { p -> if (p !== Matrix44.IDENTITY) { worldTransform = p * transform } else { worldTransform = transform } worldTransform } val context = RenderContext( lights = scene.root.findContent { this as? Light }, meshes = scene.root.findContent { this as? Mesh }, skinnedMeshes = scene.root.findContent { this as? SkinnedMesh }, fogs = scene.root.findContent { this as? Fog }, instancedMeshes = scene.root.findContent { this as? InstancedMesh }, pathMeshes = scene.root.findContent { this as? PathMesh} ) // shadow passes run { context.lights.filter { it.content is ShadowLight && (it.content as ShadowLight).shadows is Shadows.MappedShadows }.forEach { val shadowLight = it.content as ShadowLight val pass: RenderPass pass = when (shadowLight.shadows) { is Shadows.PCF, is Shadows.Simple -> { LightPass } is Shadows.VSM -> { VSMLightPass } else -> TODO() } val target = shadowLightTargets.getOrPut(shadowLight) { val mapSize = (shadowLight.shadows as Shadows.MappedShadows).mapSize pass.createPassTarget(mapSize, mapSize, DepthFormat.DEPTH_STENCIL) } target.clearDepth(depth = 1.0) val look = shadowLight.view(it.node) val materialContext = MaterialContext(pass, context.lights, context.fogs, shadowLightTargets, emptyMap(), 0) drawer.isolatedWithTarget(target) { drawer.projection = shadowLight.projection(target) drawer.view = look drawer.model = Matrix44.IDENTITY drawer.clear(ColorRGBa.BLACK) drawer.cullTestPass = CullTestPass.FRONT drawPass(drawer, pass, materialContext, context) } when (shadowLight.shadows) { is Shadows.VSM -> { blur.gain = 1.0 blur.sigma = 3.0 blur.window = 9 blur.spread = 1.0 blur.apply(target.colorBuffer(0), target.colorBuffer(0)) } else -> {} } } } // -- feature passes for (feature in scene.features) { feature.update(drawer, this, scene, feature, context) } // -- output passes run { val irradianceSH = scene.features.find { it is IrradianceSH } as? IrradianceSH for (pass in outputPasses) { val materialContext = MaterialContext(pass, context.lights, context.fogs, shadowLightTargets, meshCubemaps, irradianceSH?.probeCount ?: 0) materialContext.irradianceSH = irradianceSH val defaultPasses = setOf(DefaultTransparentPass, DefaultOpaquePass) if ((pass !in defaultPasses || postSteps.isNotEmpty()) && outputPassTarget == null) { outputPassTarget = pass.createPassTarget(RenderTarget.active.width, RenderTarget.active.height) } if (pass == outputPasses[0]) { outputPassTarget?.let { drawer.withTarget(it) { clear(ColorRGBa.TRANSPARENT) } } } outputPassTarget?.let { target -> pass.combiners.forEach { if (it is ColorBufferFacetCombiner) { val index = target.colorAttachmentIndexByName(it.targetOutput) ?: error("attachment not found ${it.targetOutput}") target.blendMode(index, it.blendMode) } } } outputPassTarget?.bind() drawPass(drawer, pass, materialContext, context) outputPassTarget?.unbind() outputPassTarget?.let { output -> for (combiner in pass.combiners) { buffers[combiner.targetOutput] = (output.colorAttachmentByName(combiner.targetOutput) as? ColorBufferAttachment)?.colorBuffer ?: error("attachment not found ${combiner.targetOutput}") } } } val lightContext = LightContext(context.lights, shadowLightTargets) val postContext = PostContext(lightContext, drawer.view.inversed) for (postStep in postSteps) { postStep.apply(buffers, postContext) } } drawer.popStyle() if (drawFinalBuffer) { outputPassTarget?.let { output -> drawer.isolated { drawer.defaults() drawer.ortho() val outputName = (postSteps.lastOrNull() as? FilterPostStep<*>)?.output ?: "color" val outputBuffer = buffers[outputName] ?: throw IllegalArgumentException("can't find $outputName buffer") drawer.image(outputBuffer) } } } } internal fun drawPass(drawer: Drawer, pass: RenderPass, materialContext: MaterialContext, context: RenderContext, shadeStyleTransformer: ((ShadeStyle)->Unit)? = null ) { drawer.depthWrite = pass.depthWrite val primitives = context.meshes.flatMap { mesh -> mesh.content.primitives.map { primitive -> NodeContent(mesh.node, primitive) } } // -- draw all meshes primitives .filter { (it.content.material.transparent && pass.renderTransparent) || (!it.content.material.transparent && pass.renderOpaque) } .forEach { val primitive = it.content drawer.isolated { if (primitive.material.doubleSided) { drawer.drawStyle.cullTestPass = CullTestPass.ALWAYS } val hasNormalAttribute = primitive.geometry.vertexBuffers.any { it.vertexFormat.hasAttribute("normal") } val primitiveContext = PrimitiveContext(hasNormalAttribute, false) val shadeStyle = primitive.material.generateShadeStyle(materialContext, primitiveContext) shadeStyle.parameter("viewMatrixInverse", drawer.view.inversed) primitive.material.applyToShadeStyle(materialContext, shadeStyle) shadeStyleTransformer?.invoke(shadeStyle) drawer.shadeStyle = shadeStyle drawer.model = it.node.worldTransform if (primitive.geometry.indexBuffer == null) { drawer.vertexBuffer(primitive.geometry.vertexBuffers, primitive.geometry.primitive, primitive.geometry.offset, primitive.geometry.vertexCount) } else { drawer.vertexBuffer(primitive.geometry.indexBuffer!!, primitive.geometry.vertexBuffers, primitive.geometry.primitive, primitive.geometry.offset, primitive.geometry.vertexCount) } } } val skinnedPrimitives = context.skinnedMeshes.flatMap { mesh -> mesh.content.primitives.map { primitive -> NodeContent(mesh.node, Pair(primitive, mesh)) } } skinnedPrimitives .filter { (it.content.first.material.transparent && pass.renderTransparent) || (!it.content.first.material.transparent && pass.renderOpaque) } .forEach { val primitive = it.content.first val skinnedMesh = it.content.second.content drawer.isolated { if (primitive.material.doubleSided) { drawer.drawStyle.cullTestPass = CullTestPass.ALWAYS } val hasNormalAttribute = primitive.geometry.vertexBuffers.any { it.vertexFormat.hasAttribute("normal") } val primitiveContext = PrimitiveContext(hasNormalAttribute, true) val nodeInverse = it.node.worldTransform.inversed val jointTransforms = (skinnedMesh.joints zip skinnedMesh.inverseBindMatrices) .map { (nodeInverse * it.first.worldTransform * it.second) } val shadeStyle = primitive.material.generateShadeStyle(materialContext, primitiveContext) shadeStyle.parameter("jointTransforms", jointTransforms.toTypedArray()) shadeStyle.parameter("viewMatrixInverse", drawer.view.inversed) primitive.material.applyToShadeStyle(materialContext, shadeStyle) drawer.shadeStyle = shadeStyle drawer.model = it.node.worldTransform if (primitive.geometry.indexBuffer == null) { drawer.vertexBuffer(primitive.geometry.vertexBuffers, primitive.geometry.primitive, primitive.geometry.offset, primitive.geometry.vertexCount) } else { drawer.vertexBuffer(primitive.geometry.indexBuffer!!, primitive.geometry.vertexBuffers, primitive.geometry.primitive, primitive.geometry.offset, primitive.geometry.vertexCount) } } } val instancedPrimitives = context.instancedMeshes.flatMap { mesh -> mesh.content.primitives.map { primitive -> NodeContent(mesh.node, MeshPrimitiveInstance(primitive, mesh.content.instances, mesh.content.attributes)) } } // -- draw all instanced meshes instancedPrimitives .filter { (it.content.primitive.material.transparent && pass.renderTransparent) || (!it.content.primitive.material.transparent && pass.renderOpaque) } .forEach { val primitive = it.content drawer.isolated { val primitiveContext = PrimitiveContext(true, false) val shadeStyle = primitive.primitive.material.generateShadeStyle(materialContext, primitiveContext) shadeStyle.parameter("viewMatrixInverse", drawer.view.inversed) primitive.primitive.material.applyToShadeStyle(materialContext, shadeStyle) if (primitive.primitive.material.doubleSided) { drawer.drawStyle.cullTestPass = CullTestPass.ALWAYS } drawer.shadeStyle = shadeStyle drawer.model = it.node.worldTransform drawer.vertexBufferInstances(primitive.primitive.geometry.vertexBuffers, primitive.attributes, DrawPrimitive.TRIANGLES, primitive.instances, primitive.primitive.geometry.offset, primitive.primitive.geometry.vertexCount) } } context.pathMeshes.filter { (it.content.material.transparent && pass.renderTransparent) || (!it.content.material.transparent && pass.renderOpaque) } .forEach { drawer.isolated { val primitiveContext = PrimitiveContext(true, false) val shadeStyle = it.content.material.generateShadeStyle(materialContext, primitiveContext) shadeStyle.parameter("viewMatrixInverse", drawer.view.inversed) it.content.material.applyToShadeStyle(materialContext, shadeStyle) drawer.drawStyle.cullTestPass = CullTestPass.ALWAYS drawer.shadeStyle = shadeStyle drawer.model = it.node.worldTransform drawer.strokeWeight = it.content.weight for (path in it.content.paths) { drawer.path(path.sampleLinear(0.0005)) } } } drawer.depthWrite = true } } fun sceneRenderer(builder: SceneRenderer.() -> Unit): SceneRenderer { val sceneRenderer = SceneRenderer() sceneRenderer.builder() return sceneRenderer } internal fun ByteBuffer.putVector3(v: Vector3) { putFloat(v.x.toFloat()) putFloat(v.y.toFloat()) putFloat(v.z.toFloat()) }