diff --git a/build.gradle b/build.gradle index 61c38b27..53fd0be6 100644 --- a/build.gradle +++ b/build.gradle @@ -14,7 +14,7 @@ buildscript { apply plugin: 'org.jetbrains.dokka' project.ext { - openrndrVersion = "0.3.43-rc.14" + openrndrVersion = "0.3.43-rc.15" kotlinVersion = "1.3.72" spekVersion = "2.0.10" libfreenectVersion = "0.5.7-1.5.3" diff --git a/demo-data/gltf-models/camera/Scene.glb b/demo-data/gltf-models/camera/Scene.glb new file mode 100644 index 00000000..4d66523a Binary files /dev/null and b/demo-data/gltf-models/camera/Scene.glb differ diff --git a/demo-data/gltf-models/irradiance-probes/model.glb b/demo-data/gltf-models/irradiance-probes/model.glb new file mode 100644 index 00000000..241e61c9 Binary files /dev/null and b/demo-data/gltf-models/irradiance-probes/model.glb differ diff --git a/openrndr-demos/src/demo/kotlin/DemoCubemap03.kt b/openrndr-demos/src/demo/kotlin/DemoCubemap03.kt index 79f56831..9b393e60 100644 --- a/openrndr-demos/src/demo/kotlin/DemoCubemap03.kt +++ b/openrndr-demos/src/demo/kotlin/DemoCubemap03.kt @@ -11,14 +11,20 @@ fun main() = application { cubemap1.copyTo(cubemap2, 0, 0) cubemap2.generateMipmaps() - val cma = arrayCubemap(256, 10) + val cma = arrayCubemap(cubemap1.width, 10) + for (i in 0 until 1) { + cubemap1.copyTo(cma, 8) + } + + cma.generateMipmaps() + extend(Orbital()) { } extend { drawer.shadeStyle = shadeStyle { fragmentTransform = """ - x_fill = texture(p_cma, vec4(va_position,0.0)); + x_fill = texture(p_cma, vec4(va_position, 8.0)); """ parameter("cubemap", cubemap2) parameter("cma", cma) diff --git a/orx-dnk3/build.gradle b/orx-dnk3/build.gradle index cef0a1b9..0b69e02e 100644 --- a/orx-dnk3/build.gradle +++ b/orx-dnk3/build.gradle @@ -12,6 +12,8 @@ dependencies { implementation "com.google.code.gson:gson:$gsonVersion" implementation(project(":orx-fx")) implementation(project(":orx-keyframer")) + implementation(project(":orx-shader-phrases")) + implementation(project(":orx-mesh-generators")) demoImplementation(project(":orx-camera")) demoImplementation(project(":orx-mesh-generators")) diff --git a/orx-dnk3/src/demo/kotlin/DemoAnimations01.kt b/orx-dnk3/src/demo/kotlin/DemoAnimations01.kt index 95f60fc7..a5094f2c 100644 --- a/orx-dnk3/src/demo/kotlin/DemoAnimations01.kt +++ b/orx-dnk3/src/demo/kotlin/DemoAnimations01.kt @@ -4,9 +4,8 @@ import org.openrndr.extensions.SingleScreenshot import org.openrndr.extra.dnk3.* import org.openrndr.extra.dnk3.gltf.buildSceneNodes import org.openrndr.extra.dnk3.gltf.loadGltfFromFile +import org.openrndr.extra.dnk3.renderers.dryRenderer import org.openrndr.extras.camera.Orbital -import org.openrndr.math.Matrix33 -import org.openrndr.math.Quaternion import org.openrndr.math.Vector3 import org.openrndr.math.mod_ import org.openrndr.math.transforms.transform @@ -26,10 +25,9 @@ fun main() = application { } } - val gltf = loadGltfFromFile(File("demo-data/gltf-models/oh-no-cubes-2.glb")) + val gltf = loadGltfFromFile(File("demo-data/gltf-models/box-animated/BoxAnimated.glb")) val scene = Scene(SceneNode()) - // -- add some lights val lightNode = SceneNode() lightNode.transform = transform { diff --git a/orx-dnk3/src/demo/kotlin/DemoCamera01.kt b/orx-dnk3/src/demo/kotlin/DemoCamera01.kt index dcba32b7..cd467397 100644 --- a/orx-dnk3/src/demo/kotlin/DemoCamera01.kt +++ b/orx-dnk3/src/demo/kotlin/DemoCamera01.kt @@ -4,9 +4,8 @@ import org.openrndr.extensions.SingleScreenshot import org.openrndr.extra.dnk3.* import org.openrndr.extra.dnk3.gltf.buildSceneNodes import org.openrndr.extra.dnk3.gltf.loadGltfFromFile -import org.openrndr.extras.camera.Orbital +import org.openrndr.extra.dnk3.renderers.dryRenderer import org.openrndr.math.* -import org.openrndr.math.transforms.transform import java.io.File fun main() = application { diff --git a/orx-dnk3/src/demo/kotlin/DemoIrrProbe01.kt b/orx-dnk3/src/demo/kotlin/DemoIrrProbe01.kt new file mode 100644 index 00000000..ab1fc221 --- /dev/null +++ b/orx-dnk3/src/demo/kotlin/DemoIrrProbe01.kt @@ -0,0 +1,118 @@ +@file:ShaderPhrases([]) + +import kotlinx.coroutines.yield +import org.openrndr.* +import org.openrndr.color.ColorRGBa +import org.openrndr.draw.BufferMultisample +import org.openrndr.draw.ColorFormat +import org.openrndr.draw.ColorType +import org.openrndr.draw.DrawPrimitive +import org.openrndr.extensions.SingleScreenshot +import org.openrndr.extra.dnk3.* +import org.openrndr.extra.dnk3.features.IrradianceSH +import org.openrndr.extra.dnk3.features.addIrradianceSH +import org.openrndr.extra.dnk3.gltf.buildSceneNodes +import org.openrndr.extra.dnk3.gltf.loadGltfFromFile +import org.openrndr.extra.dnk3.post.ScreenspaceReflections +import org.openrndr.extra.dnk3.post.VolumetricIrradiance +import org.openrndr.extra.dnk3.renderers.postRenderer +import org.openrndr.extra.shaderphrases.annotations.ShaderPhrases +import org.openrndr.extras.camera.Orbital +import org.openrndr.extras.meshgenerators.sphereMesh +import org.openrndr.ffmpeg.ScreenRecorder +import org.openrndr.filter.color.Delinearize +import org.openrndr.math.Matrix44 +import org.openrndr.math.Spherical +import org.openrndr.math.Vector3 +import org.openrndr.math.transforms.scale +import org.openrndr.math.transforms.transform +import org.openrndr.math.transforms.translate +import java.io.File +import kotlin.math.cos +import kotlin.math.sin + +fun main() = application { + configure { + width = 1280 + height = 720 + multisample = WindowMultisample.SampleCount(8) + } + + program { + extend(ScreenRecorder()) { + multisample = BufferMultisample.SampleCount(8) + } + if (System.getProperty("takeScreenshot") == "true") { + extend(SingleScreenshot()) { + this.outputFile = System.getProperty("screenshotPath") + } + } + + val gltf = loadGltfFromFile(File("demo-data/gltf-models/irradiance-probes/model.glb")) + val scene = Scene(SceneNode()) + + val probeBox = sphereMesh(16, 16, 0.1) + val probeGeometry = Geometry(listOf(probeBox), null, DrawPrimitive.TRIANGLES, 0, probeBox.vertexCount) + + val c = 5 + scene.addIrradianceSH(c, c, c, 3.0 / c, cubemapSize = 32, offset = Vector3(0.0, 0.0, 0.0)) + + + val sceneData = gltf.buildSceneNodes() + scene.root.children.addAll(sceneData.scenes.first()) + + // -- create a renderer + val renderer = postRenderer() + + +// renderer.postSteps.add( +// FilterPostStep(1.0, ScreenspaceReflections(), listOf("color", "clipDepth", "viewNormal"), "reflections", ColorFormat.RGB, ColorType.FLOAT16) { +// val p = Matrix44.scale(drawer.width / 2.0, drawer.height / 2.0, 1.0) * Matrix44.translate(Vector3(1.0, 1.0, 0.0)) * drawer.projection +// this.projection = p +// this.projectionMatrixInverse = drawer.projection.inversed +// } +// ) + +// renderer.postSteps.add( +// FilterPostStep(1.0, VolumetricIrradiance(), listOf("color", "clipDepth"), "volumetric-irradiance", ColorFormat.RGB, ColorType.FLOAT16) { +// this.irradianceSH = scene.features[0] as IrradianceSH +// this.projectionMatrixInverse = drawer.projection.inversed +// this.viewMatrixInverse = drawer.view.inversed +// } +// ) + + renderer.postSteps.add( + FilterPostStep(1.0, Delinearize(), listOf("color"), "ldr", ColorFormat.RGB, ColorType.FLOAT16) + ) + + val orb = extend(Orbital()) { + this.fov = 20.0 + camera.setView(Vector3(-0.49, -0.24, 0.20), Spherical(26.56, 90.0, 6.533), 40.0) + } + + renderer.draw(drawer, scene) + + val dynNode = SceneNode() + val dynMaterial = PBRMaterial() + val dynPrimitive = MeshPrimitive(probeGeometry, dynMaterial) + val dynMesh = Mesh(listOf(dynPrimitive)) + dynNode.entities.add(dynMesh) + scene.root.children.add(dynNode) + + scene.dispatcher.launch { + while (true) { + dynNode.transform = transform { + translate(cos(seconds) * 0.5, 0.5, sin(seconds) * 0.5) + scale(2.0) + } + yield() + } + } + + extend { + drawer.clear(ColorRGBa.BLACK) + renderer.draw(drawer, scene) + drawer.defaults() + } + } +} \ No newline at end of file diff --git a/orx-dnk3/src/demo/kotlin/DemoLights01.kt b/orx-dnk3/src/demo/kotlin/DemoLights01.kt index 0d4023eb..420b7ebe 100644 --- a/orx-dnk3/src/demo/kotlin/DemoLights01.kt +++ b/orx-dnk3/src/demo/kotlin/DemoLights01.kt @@ -4,9 +4,9 @@ import org.openrndr.extensions.SingleScreenshot import org.openrndr.extra.dnk3.* import org.openrndr.extra.dnk3.gltf.buildSceneNodes import org.openrndr.extra.dnk3.gltf.loadGltfFromFile +import org.openrndr.extra.dnk3.renderers.dryRenderer import org.openrndr.extras.camera.Orbital import org.openrndr.math.* -import org.openrndr.math.transforms.transform import java.io.File fun main() = application { diff --git a/orx-dnk3/src/demo/kotlin/DemoLights02.kt b/orx-dnk3/src/demo/kotlin/DemoLights02.kt index 58398b2e..c2e82d9f 100644 --- a/orx-dnk3/src/demo/kotlin/DemoLights02.kt +++ b/orx-dnk3/src/demo/kotlin/DemoLights02.kt @@ -4,9 +4,9 @@ import org.openrndr.extensions.SingleScreenshot import org.openrndr.extra.dnk3.* import org.openrndr.extra.dnk3.gltf.buildSceneNodes import org.openrndr.extra.dnk3.gltf.loadGltfFromFile +import org.openrndr.extra.dnk3.renderers.dryRenderer import org.openrndr.extras.camera.Orbital import org.openrndr.math.* -import org.openrndr.math.transforms.transform import java.io.File fun main() = application { diff --git a/orx-dnk3/src/demo/kotlin/DemoLights03.kt b/orx-dnk3/src/demo/kotlin/DemoLights03.kt index 8b010e9f..b274a94e 100644 --- a/orx-dnk3/src/demo/kotlin/DemoLights03.kt +++ b/orx-dnk3/src/demo/kotlin/DemoLights03.kt @@ -4,9 +4,9 @@ import org.openrndr.extensions.SingleScreenshot import org.openrndr.extra.dnk3.* import org.openrndr.extra.dnk3.gltf.buildSceneNodes import org.openrndr.extra.dnk3.gltf.loadGltfFromFile +import org.openrndr.extra.dnk3.renderers.dryRenderer import org.openrndr.extras.camera.Orbital import org.openrndr.math.* -import org.openrndr.math.transforms.transform import java.io.File fun main() = application { diff --git a/orx-dnk3/src/demo/kotlin/DemoScene01.kt b/orx-dnk3/src/demo/kotlin/DemoScene01.kt index 0aec8020..e5ccaac1 100644 --- a/orx-dnk3/src/demo/kotlin/DemoScene01.kt +++ b/orx-dnk3/src/demo/kotlin/DemoScene01.kt @@ -4,6 +4,7 @@ import org.openrndr.extensions.SingleScreenshot import org.openrndr.extra.dnk3.* import org.openrndr.extra.dnk3.gltf.buildSceneNodes import org.openrndr.extra.dnk3.gltf.loadGltfFromFile +import org.openrndr.extra.dnk3.renderers.dryRenderer import org.openrndr.extras.camera.Orbital import org.openrndr.math.Vector3 import org.openrndr.math.transforms.transform diff --git a/orx-dnk3/src/demo/kotlin/DemoScene02.kt b/orx-dnk3/src/demo/kotlin/DemoScene02.kt index 73659c8f..dc290ece 100644 --- a/orx-dnk3/src/demo/kotlin/DemoScene02.kt +++ b/orx-dnk3/src/demo/kotlin/DemoScene02.kt @@ -5,7 +5,7 @@ import org.openrndr.extra.dnk3.* import org.openrndr.extra.dnk3.gltf.buildSceneNodes import org.openrndr.extra.dnk3.gltf.loadGltfFromFile -import org.openrndr.extra.dnk3.gltf.loadGltfFromGlbFile +import org.openrndr.extra.dnk3.renderers.dryRenderer import org.openrndr.extras.camera.Orbital import org.openrndr.math.Vector3 import org.openrndr.math.transforms.transform @@ -15,7 +15,6 @@ fun main() = application { configure { width = 1280 height = 720 - //multisample = WindowMultisample.SampleCount(8) } program { @@ -26,7 +25,6 @@ fun main() = application { } val gltf = loadGltfFromFile(File("demo-data/gltf-models/duck/Duck.gltf")) -// val gltf = loadGltfFromGlbFile(File("demo-data/gltf-models/splash-sss.glb")) val scene = Scene(SceneNode()) // -- add some lights @@ -36,6 +34,7 @@ fun main() = application { rotate(Vector3.UNIT_X, -90.0) } lightNode.entities.add(DirectionalLight()) + scene.root.entities.add(HemisphereLight().apply { upColor = ColorRGBa.WHITE.shade(1.0) downColor = ColorRGBa.WHITE.shade(0.1) @@ -43,7 +42,6 @@ fun main() = application { scene.root.children.add(lightNode) scene.root.children.addAll(gltf.buildSceneNodes().scenes.first()) - // -- create a renderer val renderer = dryRenderer() extend(Orbital()) { diff --git a/orx-dnk3/src/demo/kotlin/DemoScene03.kt b/orx-dnk3/src/demo/kotlin/DemoScene03.kt index 11cf326a..b3fffdd1 100644 --- a/orx-dnk3/src/demo/kotlin/DemoScene03.kt +++ b/orx-dnk3/src/demo/kotlin/DemoScene03.kt @@ -4,15 +4,11 @@ import org.openrndr.draw.DrawPrimitive import org.openrndr.extensions.SingleScreenshot import org.openrndr.extra.dnk3.* -import org.openrndr.extra.dnk3.gltf.buildSceneNodes -import org.openrndr.extra.dnk3.gltf.loadGltfFromFile -import org.openrndr.extra.dnk3.gltf.loadGltfFromGlbFile +import org.openrndr.extra.dnk3.renderers.dryRenderer import org.openrndr.extras.camera.Orbital -import org.openrndr.extras.meshgenerators.boxMesh import org.openrndr.extras.meshgenerators.sphereMesh import org.openrndr.math.Vector3 import org.openrndr.math.transforms.transform -import java.io.File fun main() = application { configure { diff --git a/orx-dnk3/src/demo/kotlin/DemoSkinning01.kt b/orx-dnk3/src/demo/kotlin/DemoSkinning01.kt index d45e9169..6dbcaaaa 100644 --- a/orx-dnk3/src/demo/kotlin/DemoSkinning01.kt +++ b/orx-dnk3/src/demo/kotlin/DemoSkinning01.kt @@ -4,6 +4,7 @@ import org.openrndr.extensions.SingleScreenshot import org.openrndr.extra.dnk3.* import org.openrndr.extra.dnk3.gltf.buildSceneNodes import org.openrndr.extra.dnk3.gltf.loadGltfFromFile +import org.openrndr.extra.dnk3.renderers.dryRenderer import org.openrndr.extras.camera.Orbital import org.openrndr.math.Vector3 import org.openrndr.math.mod_ diff --git a/orx-dnk3/src/main/kotlin/Camera.kt b/orx-dnk3/src/main/kotlin/Camera.kt index b2785f4e..4b014da9 100644 --- a/orx-dnk3/src/main/kotlin/Camera.kt +++ b/orx-dnk3/src/main/kotlin/Camera.kt @@ -16,6 +16,13 @@ class PerspectiveCamera(var node: SceneNode) : Camera() { var far = 100.0 var near = 0.1 + override fun hashCode(): Int { + var result = aspectRatio.hashCode() + result = 31 * result + fov.hashCode() + result = 31 * result + far.hashCode() + result = 31 * result + near.hashCode() + return result + } } class OrthographicCamera(var node: SceneNode) : Camera() { @@ -29,4 +36,12 @@ class OrthographicCamera(var node: SceneNode) : Camera() { var yMag = 1.0 var near = 0.1 var far = 100.0 + + override fun hashCode(): Int { + var result = xMag.hashCode() + result = 31 * result + yMag.hashCode() + result = 31 * result + near.hashCode() + result = 31 * result + far.hashCode() + return result + } } \ No newline at end of file diff --git a/orx-dnk3/src/main/kotlin/DryRenderer.kt b/orx-dnk3/src/main/kotlin/DryRenderer.kt deleted file mode 100644 index 5d8183f3..00000000 --- a/orx-dnk3/src/main/kotlin/DryRenderer.kt +++ /dev/null @@ -1,6 +0,0 @@ -package org.openrndr.extra.dnk3 - -fun dryRenderer() : SceneRenderer { - val sr = SceneRenderer() - return sr -} \ No newline at end of file diff --git a/orx-dnk3/src/main/kotlin/Entity.kt b/orx-dnk3/src/main/kotlin/Entity.kt index a5335b99..cedf8e55 100644 --- a/orx-dnk3/src/main/kotlin/Entity.kt +++ b/orx-dnk3/src/main/kotlin/Entity.kt @@ -3,6 +3,7 @@ package org.openrndr.extra.dnk3 import org.openrndr.color.ColorRGBa import org.openrndr.draw.* import org.openrndr.math.Matrix44 +import org.openrndr.math.transforms.perspective class Geometry(val vertexBuffers: List, @@ -14,19 +15,28 @@ class Geometry(val vertexBuffers: List, override fun toString(): String { return "Geometry(vertexBuffers: $vertexBuffers, indexBuffers: $indexBuffer, primitive: $primitive, offset: $offset, vertexCount: $vertexCount)" } - + override fun hashCode(): Int { + var result = 0 + result = 31 * result + primitive.ordinal.hashCode() + result = 31 * result + offset.hashCode() + result = 31 * result + vertexCount.hashCode() + return result + } } val DummyGeometry = Geometry(emptyList(), null, DrawPrimitive.TRIANGLES, 0, 0) sealed class Entity - class MeshPrimitive(var geometry: Geometry, var material: Material) { - override fun toString(): String { return "MeshPrimitive(geometry: $geometry, material: $material)" } + override fun hashCode(): Int { + var result = geometry.hashCode() + result = 31 * result + material.hashCode() + return result + } } class MeshPrimitiveInstance(val primitive: MeshPrimitive, val instances: Int, val attributes: List) @@ -36,6 +46,9 @@ class Mesh(primitives: List) : MeshBase(primitives) { override fun toString(): String { return "Mesh(primitives: $primitives)" } + override fun hashCode(): Int { + return primitives.hashCode() + } } class SkinnedMesh(primitives: List, @@ -49,10 +62,7 @@ class InstancedMesh(primitives: List, var attributes: List) : MeshBase(primitives) -class Fog : Entity() { - var color: ColorRGBa = ColorRGBa.WHITE - var end: Double = 100.0 -} +data class Fog(var color: ColorRGBa = ColorRGBa.WHITE, var end : Double = 100.0) : Entity() abstract class Light : Entity() { var color: ColorRGBa = ColorRGBa.WHITE @@ -61,4 +71,18 @@ abstract class Light : Entity() { abstract class Camera : Entity() { abstract val projectionMatrix: Matrix44 abstract val viewMatrix: Matrix44 +} + +abstract class CubemapProbe : Entity() { + open val projectionMatrix: Matrix44 + get() { + return perspective(90.0, 1.0, 0.1, 150.0) + } + var dirty = true +} + +class IrradianceProbe: CubemapProbe() { + override fun hashCode(): Int { + return true.hashCode() + } } \ No newline at end of file diff --git a/orx-dnk3/src/main/kotlin/Facet.kt b/orx-dnk3/src/main/kotlin/Facet.kt index fa8a0fb4..533471b9 100644 --- a/orx-dnk3/src/main/kotlin/Facet.kt +++ b/orx-dnk3/src/main/kotlin/Facet.kt @@ -24,8 +24,6 @@ abstract class FacetCombiner(val facets: Set, val targetOutput: Strin override fun toString(): String { return "FacetCombiner(facets=$facets, targetOutput='$targetOutput')" } - - } abstract class ColorBufferFacetCombiner(facets: Set, @@ -107,6 +105,14 @@ class NormalFacet : ColorBufferFacetCombiner(setOf(FacetType.WORLD_NORMAL), "nor override fun generateShader(): String = "o_$targetOutput = vec4(v_worldNormal.rgb, 1.0);" } +class ViewDepthFacet : ColorBufferFacetCombiner(setOf(FacetType.VIEW_POSITION), "viewDepth", ColorFormat.R, ColorType.FLOAT16) { + override fun generateShader(): String = "o_$targetOutput.r = v_viewPosition.z;" +} +class ClipDepthFacet : ColorBufferFacetCombiner(setOf(FacetType.CLIP_POSITION), "clipDepth", ColorFormat.R, ColorType.FLOAT32) { + override fun generateShader(): String = "o_$targetOutput.r = gl_FragCoord.z;" +} + + class ViewPositionFacet : ColorBufferFacetCombiner(setOf(FacetType.VIEW_POSITION), "viewPosition", ColorFormat.RGB, ColorType.FLOAT32) { override fun generateShader(): String = "o_$targetOutput.rgb = v_viewPosition.rgb;" } @@ -125,11 +131,28 @@ class FragmentIDFacet: ColorBufferFacetCombiner(setOf(FacetType.FRAGMENT_ID), "f } } -class LDRColorFacet : ColorBufferFacetCombiner(setOf(FacetType.DIFFUSE, FacetType.SPECULAR), "color", ColorFormat.RGBa, ColorType.UINT8) { +class LDRColorFacet : ColorBufferFacetCombiner(setOf(FacetType.DIFFUSE, FacetType.SPECULAR, FacetType.EMISSIVE), "color", ColorFormat.RGBa, ColorType.UINT8) { override fun generateShader() = """ - vec3 finalColor = (max(vec3(0.0), f_diffuse.rgb) + max(vec3(0.0),f_specular.rgb) + max(vec3(0.0), f_emission.rgb)) * (1.0 - f_fog.a) + f_fog.rgb * f_fog.a; + vec3 finalColor = (max(vec3(0.0), f_diffuse.rgb) + max(vec3(0.0),f_specular.rgb) + max(vec3(0.0), f_emission.rgb) + max(vec3(0.0), f_ambient.rgb)) * (1.0 - f_fog.a) + f_fog.rgb * f_fog.a; o_$targetOutput = pow(vec4(finalColor.rgb, 1.0), vec4(1.0/2.2)); o_$targetOutput *= m_color.a; + """ +} + +class HDRColorFacet : ColorBufferFacetCombiner(setOf(FacetType.DIFFUSE, FacetType.SPECULAR, FacetType.EMISSIVE), "color", ColorFormat.RGBa, ColorType.FLOAT16) { + override fun generateShader() = """ + vec3 finalColor = (max(vec3(0.0), f_diffuse.rgb) + max(vec3(0.0),f_specular.rgb) + max(vec3(0.0), f_emission.rgb) + max(vec3(0.0), f_ambient.rgb)) * (1.0 - f_fog.a) + f_fog.rgb * f_fog.a; + o_$targetOutput = vec4(finalColor.rgb, 1.0); + o_$targetOutput *= m_color.a; + """ +} + +class DiffuseIrradianceFacet : ColorBufferFacetCombiner(setOf(FacetType.DIFFUSE, FacetType.SPECULAR), "color", ColorFormat.RGBa, ColorType.UINT8) { + override fun generateShader() = """ + vec3 finalColor = (max(vec3(0.0), f_diffuse.rgb) + max(vec3(0.0), f_emission.rgb)); + o_$targetOutput = vec4(finalColor.rgb, 1.0); + + """ } \ No newline at end of file diff --git a/orx-dnk3/src/main/kotlin/Feature.kt b/orx-dnk3/src/main/kotlin/Feature.kt new file mode 100644 index 00000000..27ecd22e --- /dev/null +++ b/orx-dnk3/src/main/kotlin/Feature.kt @@ -0,0 +1,13 @@ +package org.openrndr.extra.dnk3 + +import org.openrndr.draw.Drawer + +interface Feature { + fun update( + drawer: Drawer, + sceneRenderer: SceneRenderer, + scene: Scene, + feature: T, + context: RenderContext + ) +} \ No newline at end of file diff --git a/orx-dnk3/src/main/kotlin/Light.kt b/orx-dnk3/src/main/kotlin/Light.kt index 2ba20b31..660f1db2 100644 --- a/orx-dnk3/src/main/kotlin/Light.kt +++ b/orx-dnk3/src/main/kotlin/Light.kt @@ -18,11 +18,15 @@ interface AttenuatedLight { } class DirectionalLight(var direction: Vector3 = -Vector3.UNIT_Z, override var shadows: Shadows = Shadows.None) : Light(), ShadowLight { - var projectionSize = 10.0 + var projectionSize = 50.0 override fun projection(renderTarget: RenderTarget): Matrix44 { return ortho(-projectionSize / 2.0, projectionSize / 2.0, -projectionSize / 2.0, projectionSize / 2.0, 1.0, 150.0) } + + override fun hashCode(): Int { + return color.hashCode() + } } class SpotLight(var direction: Vector3 = -Vector3.UNIT_Z, var innerAngle: Double = 45.0, var outerAngle: Double = 90.0) : Light(), ShadowLight, AttenuatedLight { @@ -33,16 +37,46 @@ class SpotLight(var direction: Vector3 = -Vector3.UNIT_Z, var innerAngle: Double override fun projection(renderTarget: RenderTarget): Matrix44 { return perspective(outerAngle * 2.0, renderTarget.width * 1.0 / renderTarget.height, 1.0, 150.0) } + + override fun hashCode(): Int { + var result = direction.hashCode() + result = 31 * result + innerAngle.hashCode() + result = 31 * result + outerAngle.hashCode() + result = 31 * result + constantAttenuation.hashCode() + result = 31 * result + linearAttenuation.hashCode() + result = 31 * result + quadraticAttenuation.hashCode() + return result + } } class HemisphereLight(var direction: Vector3 = Vector3.UNIT_Y, var upColor: ColorRGBa = ColorRGBa.WHITE, var downColor: ColorRGBa = ColorRGBa.BLACK) : Light() { var irradianceMap: Cubemap? = null + override fun hashCode(): Int { + var result = direction.hashCode() + result = 31 * result + upColor.hashCode() + result = 31 * result + downColor.hashCode() + return result + } + } class PointLight(var constantAttenuation: Double = 1.0, var linearAttenuation: Double = 0.0, - var quadraticAttenuation: Double = 0.0) : Light() + var quadraticAttenuation: Double = 1.0) : Light() { + override fun hashCode(): Int { + var result = constantAttenuation.hashCode() + result = 31 * result + linearAttenuation.hashCode() + result = 31 * result + quadraticAttenuation.hashCode() + result = 31 * result + color.hashCode() + return result + } +} -class AmbientLight : Light() +class AmbientLight : Light() { + + override fun hashCode(): Int { + return color.hashCode() + } +} diff --git a/orx-dnk3/src/main/kotlin/Material.kt b/orx-dnk3/src/main/kotlin/Material.kt index d0f4fdbb..4de760b3 100644 --- a/orx-dnk3/src/main/kotlin/Material.kt +++ b/orx-dnk3/src/main/kotlin/Material.kt @@ -1,11 +1,11 @@ package org.openrndr.extra.dnk3 -import org.openrndr.draw.Cubemap -import org.openrndr.draw.RenderTarget -import org.openrndr.draw.ShadeStyle -import org.openrndr.draw.shadeStyle +import org.openrndr.draw.* +import org.openrndr.extra.dnk3.features.IrradianceSH +import org.openrndr.math.Vector3 interface Material { + val name: String? var doubleSided: Boolean var transparent: Boolean val fragmentID: Int @@ -14,6 +14,7 @@ interface Material { } class DummyMaterial : Material { + override var name: String? = null override var doubleSided: Boolean = true override var transparent: Boolean = false override var fragmentID = 0 @@ -42,8 +43,14 @@ data class MaterialContext(val pass: RenderPass, val lights: List>, val fogs: List>, val shadowMaps: Map, - val meshCubemaps: Map -) + val meshCubemaps: Map, + val irradianceProbeCount: Int + ) { + var irradianceSH: IrradianceSH? = null +} + + + data class PrimitiveContext(val hasNormalAttribute: Boolean, val hasSkinning: Boolean) diff --git a/orx-dnk3/src/main/kotlin/PBRMaterial.kt b/orx-dnk3/src/main/kotlin/PBRMaterial.kt index 8f6b6e83..050849ca 100644 --- a/orx-dnk3/src/main/kotlin/PBRMaterial.kt +++ b/orx-dnk3/src/main/kotlin/PBRMaterial.kt @@ -2,15 +2,17 @@ package org.openrndr.extra.dnk3 import org.openrndr.color.ColorRGBa import org.openrndr.draw.* +import org.openrndr.extra.dnk3.cubemap.glslEvaluateSH +import org.openrndr.extra.dnk3.cubemap.glslFetchSH +import org.openrndr.extra.dnk3.cubemap.genGlslGatherSH +import org.openrndr.extra.shaderphrases.phrases.phraseTbnMatrix import org.openrndr.math.Vector2 import org.openrndr.math.Vector3 import org.openrndr.math.Vector4 import org.openrndr.math.transforms.normalMatrix import java.nio.ByteBuffer -import javax.naming.Context import kotlin.math.cos - private val noise128 by lazy { val cb = colorBuffer(128, 128) val items = cb.width * cb.height * cb.format.componentCount @@ -123,7 +125,11 @@ object DummySource : TextureSource() { abstract class TextureFromColorBuffer(var texture: ColorBuffer, var textureFunction: TextureFunction) : TextureSource() -class TextureFromCode(val code: String) : TextureSource() +class TextureFromCode(val code: String) : TextureSource() { + override fun hashCode(): Int { + return code.hashCode() + } +} private fun TextureFromCode.fs(index: Int, target: TextureTarget) = """ |vec4 tex$index = vec4(0.0, 0.0, 0.0, 1.0); @@ -137,6 +143,7 @@ private fun TextureFromCode.fs(index: Int, target: TextureTarget) = """ enum class TextureFunction(val function: (String, String) -> String) { TILING({ texture, uv -> "texture($texture, $uv)" }), NOT_TILING({ texture, uv -> "textureNoTile(p_textureNoise, $texture, x_noTileOffset, $uv)" }) + ; } /** @@ -155,8 +162,17 @@ class ModelCoordinates(texture: ColorBuffer, override fun toString(): String { return "ModelCoordinates(texture: $texture, input: $input, $tangentInput: $tangentInput, textureFunction: $textureFunction, pre: $pre, post: $post)" } + + override fun hashCode(): Int { + var result = input.hashCode() + result = 31 * result + (tangentInput?.hashCode() ?: 0) + result = 31 * result + (pre?.hashCode() ?: 0) + result = 31 * result + (post?.hashCode() ?: 0) + return result + } } + class Triplanar(texture: ColorBuffer, var scale: Double = 1.0, var offset: Vector3 = Vector3.ZERO, @@ -170,6 +186,17 @@ class Triplanar(texture: ColorBuffer, texture.wrapU = WrapMode.REPEAT texture.wrapV = WrapMode.REPEAT } + + override fun hashCode(): Int { + var result = scale.hashCode() + result = 31 * result + offset.hashCode() + result = 31 * result + sharpness.hashCode() + result = 31 * result + (pre?.hashCode() ?: 0) + result = 31 * result + (post?.hashCode() ?: 0) + return result + } + + } private fun ModelCoordinates.fs(index: Int) = """ @@ -187,8 +214,7 @@ private fun ModelCoordinates.fs(index: Int) = """ | vec3 tangent = normalize(${tangentInput}.xyz); | vec3 bitangent = cross(normal, tangent) * ${tangentInput}.w; | mat3 tbn = mat3(tangent, bitangent, normal); -| x_texture.rgb = tbn * normalize(x_texture.rgb - vec3(0.5, 0.5, 0.)) ; -| +| x_texture.rgb = tbn * normalize( (x_texture.rgb - vec3(0.5, 0.5, 0.0))*vec3(2.0, 2.0, 1.0)) ; """.trimMargin() } else ""} @@ -250,6 +276,10 @@ sealed class TextureTarget(val name: String) { override fun toString(): String { return "TextureTarget(name: $name)" } + + override fun hashCode(): Int { + return name.hashCode() + } } class Texture(var source: TextureSource, @@ -262,18 +292,63 @@ class Texture(var source: TextureSource, override fun toString(): String { return "Texture(source: $source, target: $target)" } + + override fun hashCode(): Int { + var result = source.hashCode() + result = 31 * result + target.hashCode() + return result + } } private var fragmentIDCounter = 1 +data class SubsurfaceScatter(var enabled: Boolean) { + var color: ColorRGBa = ColorRGBa.WHITE + var shape = 1.0 + + fun fs(): String { + return if (enabled) """ + f_diffuse.rgb += pow(smoothstep(1.0, 0.0, abs(dot(normalize(N),normalize(V)))), p_sssShape) * clamp(evaluateSH(-V, sh), vec3(0.0), vec3(1.0)) * p_sssColor.rgb; + """ else "" + } + + fun applyToShadeStyle(shadeStyle: ShadeStyle) { + if (enabled) { + shadeStyle.parameter("sssColor", color) + shadeStyle.parameter("sssShape", shape) + } + } +} + +data class CubemapReflection(var cubemap: Cubemap? = null) { + var color: ColorRGBa = ColorRGBa.WHITE + + fun fs(): String { + return if (cubemap != null) { + """ + vec2 dfg = PrefilteredDFG_Karis(m_roughness, NoV); + vec3 sc = m_metalness * m_color.rgb + (1.0-m_metalness) * vec3(0.04); + f_specular.rgb += sc * (texture(p_radianceMap, reflect(-V, normalize(f_worldNormal)), m_roughness*7.0 ).rgb * dfg.x + dfg.y) * p_radianceColor.rgb; + """ + } else { "" } + } + fun applyToShadeStyle(shadeStyle: ShadeStyle) { + if (cubemap != null) { + shadeStyle.parameter("radianceMap", cubemap!!) + shadeStyle.parameter("radianceColor", color) + } + } +} + + class PBRMaterial : Material { + override var name: String? = null override fun toString(): String { - return "PBRMaterial(textures: $textures, color: $color, metalness: $metalness, roughness: $roughness, emissive: $emission))" + return "PBRMaterial(name: $name, fragmentID: $fragmentID, doubleSided: $doubleSided, textures: $textures, color: $color, metalness: $metalness, roughness: $roughness, emissive: $emission))" } override var fragmentID = fragmentIDCounter.apply { fragmentIDCounter++ - } override var doubleSided: Boolean = false @@ -284,6 +359,10 @@ class PBRMaterial : Material { var roughness = 1.0 var emission = ColorRGBa.BLACK + var subsurfaceScatter = SubsurfaceScatter(false) + var cubemapReflection = CubemapReflection(null) + + var fragmentPreamble: String? = null var vertexPreamble: String? = null var vertexTransform: String? = null @@ -292,21 +371,6 @@ class PBRMaterial : Material { val shadeStyles = mutableMapOf() -// fun copy(): PBRMaterial { -// val copied = PBRMaterial() -// copied.environmentMap = environmentMap -// copied.color = color -// copied.opacity = opacity -// copied.metalness = metalness -// copied.roughness = roughness -// copied.emission = emission -// copied.vertexPreamble = vertexPreamble -// copied.vertexTransform = vertexTransform -// copied.parameters.putAll(parameters) -// copied.textures.addAll(textures.map { it.copy() }) -// return copied -// } - override fun generateShadeStyle(materialContext: MaterialContext, primitiveContext: PrimitiveContext): ShadeStyle { val cached = shadeStyles.getOrPut(ContextKey(materialContext, primitiveContext)) { val needLight = needLight(materialContext) @@ -321,6 +385,7 @@ class PBRMaterial : Material { vec3 m_normal = vec3(0.0, 0.0, 1.0); vec4 f_fog = vec4(0.0, 0.0, 0.0, 0.0); vec3 f_worldNormal = v_worldNormal; + vec3 f_emission = m_emission; """.trimIndent() val textureFs = if (needLight) { @@ -346,7 +411,6 @@ class PBRMaterial : Material { } }).joinToString("\n") } else "" - val displacers = textures.filter { it.target is TextureTarget.Height } val skinVS = if (primitiveContext.hasSkinning) """ @@ -377,23 +441,32 @@ class PBRMaterial : Material { }.joinToString("\n") else "" val lights = materialContext.lights + + val doubleSidedFS = if (doubleSided) { + """ + if (dot(V, N) <0) { + N *= -1.0; + } + """.trimIndent() + } else "" val lightFS = if (needLight) """ vec3 f_diffuse = vec3(0.0); vec3 f_specular = vec3(0.0); - vec3 f_emission = m_emission; vec3 f_ambient = vec3(0.0); float f_occlusion = 1.0; vec3 N = normalize(f_worldNormal); + vec3 ep = (p_viewMatrixInverse * vec4(0.0, 0.0, 0.0, 1.0)).xyz; vec3 Vr = ep - v_worldPosition; vec3 V = normalize(Vr); + float NoV = ${if (primitiveContext.hasNormalAttribute) "abs(dot(N, V)) + 1e-5" else "1"}; ${if (environmentMap && materialContext.meshCubemaps.isNotEmpty() && primitiveContext.hasNormalAttribute) """ { vec2 dfg = PrefilteredDFG_Karis(m_roughness, NoV); vec3 sc = m_metalness * m_color.rgb + (1.0-m_metalness) * vec3(0.04); - + f_specular.rgb += sc * (texture(p_environmentMap, reflect(-V, normalize(f_worldNormal))).rgb * dfg.x + dfg.y) * m_ambientOcclusion; } """.trimIndent() else ""} @@ -409,7 +482,21 @@ class PBRMaterial : Material { } }.joinToString("\n")} + + ${if (materialContext.irradianceSH?.shMap != null) """ + vec3[9] sh; + gatherSH(p_shMap, v_worldPosition, sh); + vec3 irradiance = clamp(evaluateSH(normalize(N), sh), vec3(0.0), vec3(1.0)) * m_color.rgb; + vec3 ks = F_SchlickRoughness(m_color.rgb * (m_metalness) + 0.04 * (1.0-m_metalness), m_roughness+0.1, min(NoV, 1.0-1.0e-6)); + f_diffuse.rgb = irradiance * ks; + f_ambient.rgb = (1.0-ks) * irradiance; + ${subsurfaceScatter.fs()} + ${cubemapReflection.fs()} + """.trimIndent() else "" + } + ${materialContext.fogs.mapIndexed { index, (node, fog) -> + fog.fs(index) }.joinToString("\n")} @@ -430,24 +517,36 @@ class PBRMaterial : Material { ${(this@PBRMaterial.vertexPreamble) ?: ""} """.trimIndent() fragmentPreamble += """ + ${if (materialContext.irradianceSH?.shMap != null) { + """ + $glslEvaluateSH + $glslFetchSH + ${genGlslGatherSH(materialContext.irradianceSH!!.xCount, materialContext.irradianceSH!!.yCount, + materialContext.irradianceSH!!.zCount, materialContext.irradianceSH!!.spacing, materialContext.irradianceSH!!.offset)} + """ + } else { + "" + } + } |$shaderLinePlaneIntersect |$shaderProjectOnPlane |$shaderSideOfPlane |$shaderGGX |$shaderVSM |$shaderNoRepetition + |$phraseTbnMatrix """.trimMargin() this.suppressDefaultOutput = true this.vertexTransform = vs fragmentTransform = fs materialContext.pass.combiners.map { - if (rt is ProgramRenderTarget || materialContext.pass === DefaultPass || materialContext.pass === DefaultOpaquePass || materialContext.pass == DefaultTransparentPass) { + if (rt is ProgramRenderTarget || materialContext.pass === DefaultPass || materialContext.pass === DefaultOpaquePass || materialContext.pass == DefaultTransparentPass || materialContext.pass == IrradianceProbePass) { this.output(it.targetOutput, ShadeStyleOutput(0)) } else { - val index = rt.colorAttachmentIndexByName(it.targetOutput) ?: error("no such attachment ${it.targetOutput}") + val index = rt.colorAttachmentIndexByName(it.targetOutput)?:error("attachment ${it.targetOutput} not found") val type = rt.colorBuffer(index).type - val format = rt.colorBuffer(0).format + val format = rt.colorBuffer(index).format this.output(it.targetOutput, ShadeStyleOutput(index, format, type)) } } @@ -470,6 +569,10 @@ class PBRMaterial : Material { shadeStyle.parameter("roughness", roughness) shadeStyle.parameter("fragmentID", fragmentID) + if (context.irradianceProbeCount > 0) { + shadeStyle.parameter("shMap", context.irradianceSH?.shMap!!) + } + parameters.forEach { (k, v) -> when (v) { is Double -> shadeStyle.parameter(k, v) @@ -483,6 +586,10 @@ class PBRMaterial : Material { } } if (needLight(context)) { + + subsurfaceScatter.applyToShadeStyle(shadeStyle) + cubemapReflection.applyToShadeStyle(shadeStyle) + textures.forEachIndexed { index, texture -> when (val source = texture.source) { is TextureFromColorBuffer -> { @@ -598,5 +705,23 @@ class PBRMaterial : Material { } } } + + override fun hashCode(): Int { + var result = fragmentID.hashCode() + result = 31 * doubleSided.hashCode() + result = 31 * result + transparent.hashCode() +// result = 31 * result + environmentMap.hashCode() + result = 31 * result + color.hashCode() + result = 31 * result + metalness.hashCode() + result = 31 * result + roughness.hashCode() + result = 31 * result + emission.hashCode() + result = 31 * result + (fragmentPreamble?.hashCode() ?: 0) + result = 31 * result + (vertexPreamble?.hashCode() ?: 0) + result = 31 * result + (vertexTransform?.hashCode() ?: 0) +// result = 31 * result + parameters.hashCode() +// result = 31 * result + textures.hashCode() +// result = 31 * result + shadeStyles.hashCode() + return result + } } diff --git a/orx-dnk3/src/main/kotlin/Post.kt b/orx-dnk3/src/main/kotlin/Post.kt index 23127493..cb5df0b9 100644 --- a/orx-dnk3/src/main/kotlin/Post.kt +++ b/orx-dnk3/src/main/kotlin/Post.kt @@ -9,16 +9,16 @@ interface PostStep { fun apply(buffers: MutableMap, postContext: PostContext) } -class FilterPostStep(val outputScale: Double, - val filter: Filter, +class FilterPostStep(val outputScale: Double, + val filter: T, val inputs: List, val output: String, val outputFormat: ColorFormat, val outputType: ColorType, - val update: (Filter.(PostContext) -> Unit)? = null) : PostStep { + val update: (T.(PostContext) -> Unit)? = null) : PostStep { override fun apply(buffers: MutableMap, postContext: PostContext) { - val inputBuffers = inputs.map { buffers[it]!! } + val inputBuffers = inputs.map { buffers[it]?: error("buffer not found: $it") } val outputBuffer = buffers.getOrPut(output) { colorBuffer((inputBuffers[0].width * outputScale).toInt(), (inputBuffers[0].height * outputScale).toInt(), diff --git a/orx-dnk3/src/main/kotlin/RenderPass.kt b/orx-dnk3/src/main/kotlin/RenderPass.kt index 50ab4b4e..e8df2827 100644 --- a/orx-dnk3/src/main/kotlin/RenderPass.kt +++ b/orx-dnk3/src/main/kotlin/RenderPass.kt @@ -13,6 +13,8 @@ data class RenderPass(val combiners: List, val DefaultPass = RenderPass(listOf(LDRColorFacet())) +val IrradianceProbePass = RenderPass(listOf(DiffuseIrradianceFacet())) + val DefaultOpaquePass = RenderPass(listOf(LDRColorFacet()), renderOpaque = true, renderTransparent = false) val DefaultTransparentPass = RenderPass(listOf(LDRColorFacet()), renderOpaque = false, renderTransparent = true, depthWrite = false) val LightPass = RenderPass(emptyList()) diff --git a/orx-dnk3/src/main/kotlin/Scene.kt b/orx-dnk3/src/main/kotlin/Scene.kt index 419cd730..b516df97 100644 --- a/orx-dnk3/src/main/kotlin/Scene.kt +++ b/orx-dnk3/src/main/kotlin/Scene.kt @@ -2,11 +2,21 @@ package org.openrndr.extra.dnk3 import org.openrndr.Dispatcher import org.openrndr.math.Matrix44 +import org.openrndr.math.Vector3 +import org.openrndr.math.Vector4 +import java.util.* -class Scene(val root: SceneNode = SceneNode(), val dispatcher: Dispatcher = Dispatcher()) +class Scene(val root: SceneNode = SceneNode(), val dispatcher: Dispatcher = Dispatcher()) { + val features = mutableListOf() + override fun hashCode(): Int { + var result = root.hashCode() + result = result * 31 + features.hashCode() + return result + } + fun hash(): String = Base64.getEncoder().encodeToString(hashCode().toString().toByteArray()) +} - -open class SceneNode() { +open class SceneNode { var name: String = "" var entities: MutableList = mutableListOf() var parent: SceneNode? = null @@ -14,8 +24,24 @@ open class SceneNode() { var worldTransform = Matrix44.IDENTITY val children = mutableListOf() var disposed = false + + override fun hashCode(): Int { + var result = name.hashCode() + result = 31 * result + entities.hashCode() +// result = 31 * result + (parent?.hashCode() ?: 0) + result = 31 * result + transform.hashCode() + result = 31 * result + worldTransform.hashCode() + result = 31 * result + children.hashCode() + result = 31 * result + disposed.hashCode() + return result + } } +val SceneNode.worldPosition: Vector3 + get() { + return (worldTransform * Vector4.UNIT_W).xyz + } + class NodeContent(val node: SceneNode, val content: T) { operator fun component1() = node operator fun component2() = content diff --git a/orx-dnk3/src/main/kotlin/SceneRenderer.kt b/orx-dnk3/src/main/kotlin/SceneRenderer.kt index 77b55bc1..5a1ace82 100644 --- a/orx-dnk3/src/main/kotlin/SceneRenderer.kt +++ b/orx-dnk3/src/main/kotlin/SceneRenderer.kt @@ -2,10 +2,19 @@ package org.openrndr.extra.dnk3 import org.openrndr.color.ColorRGBa import org.openrndr.draw.* -import org.openrndr.draw.depthBuffer +import org.openrndr.extra.dnk3.features.IrradianceSH import org.openrndr.extra.fx.blur.ApproximateGaussianBlur import org.openrndr.math.Matrix44 -import org.openrndr.math.transforms.normalMatrix +import org.openrndr.math.Vector3 +import java.nio.ByteBuffer + +class RenderContext( + val lights: List>, + val meshes: List>, + val skinnedMeshes: List>, + val instancedMeshes: List>, + val fogs: List> +) class SceneRenderer { @@ -20,7 +29,6 @@ class SceneRenderer { var shadowLightTargets = mutableMapOf() var meshCubemaps = mutableMapOf() - var cubemapDepthBuffer = depthBuffer(256, 256, DepthFormat.DEPTH16, BufferMultisample.Disabled) var outputPasses = mutableListOf(DefaultOpaquePass, DefaultTransparentPass) var outputPassTarget: RenderTarget? = null @@ -31,6 +39,7 @@ class SceneRenderer { var drawFinalBuffer = true + var first = true fun draw(drawer: Drawer, scene: Scene) { drawer.pushStyle() drawer.depthWrite = true @@ -40,22 +49,27 @@ class SceneRenderer { scene.dispatcher.execute() - // update all the transforms scene.root.scan(Matrix44.IDENTITY) { p -> - worldTransform = p * transform + if (p !== Matrix44.IDENTITY) { + worldTransform = p * transform + } else { + worldTransform = transform + } worldTransform } - val lights = scene.root.findContent { this as? Light } - val meshes = scene.root.findContent { this as? Mesh } - val skinnedMeshes = scene.root.findContent { this as? SkinnedMesh } - - val fogs = scene.root.findContent { this as? Fog } - val instancedMeshes = scene.root.findContent { this as? InstancedMesh } + 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 } + ) + // shadow passes run { - lights.filter { it.content is ShadowLight && (it.content as ShadowLight).shadows is Shadows.MappedShadows }.forEach { + 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) { @@ -74,7 +88,7 @@ class SceneRenderer { target.clearDepth(depth = 1.0) val look = shadowLight.view(it.node) - val materialContext = MaterialContext(pass, lights, fogs, shadowLightTargets, emptyMap()) + val materialContext = MaterialContext(pass, context.lights, context.fogs, shadowLightTargets, emptyMap(), 0) drawer.isolatedWithTarget(target) { drawer.projection = shadowLight.projection(target) drawer.view = look @@ -82,7 +96,7 @@ class SceneRenderer { drawer.clear(ColorRGBa.BLACK) drawer.cullTestPass = CullTestPass.FRONT - drawPass(drawer, pass, materialContext, meshes, instancedMeshes, skinnedMeshes) + drawPass(drawer, pass, materialContext, context) } when (shadowLight.shadows) { is Shadows.VSM -> { @@ -96,10 +110,18 @@ class SceneRenderer { } } + // -- feature passes + for (feature in scene.features) { + feature.update(drawer, this, scene, feature, context) + } + + // -- output passes run { - //val pass = outputPasses + val irradianceSH = scene.features.find { it is IrradianceSH } as? IrradianceSH for (pass in outputPasses) { - val materialContext = MaterialContext(pass, lights, fogs, shadowLightTargets, meshCubemaps) + val materialContext = MaterialContext(pass, context.lights, context.fogs, shadowLightTargets, meshCubemaps, irradianceSH?.probeCount + ?: 0) + materialContext.irradianceSH = irradianceSH val defaultPasses = setOf(DefaultTransparentPass, DefaultOpaquePass) @@ -118,23 +140,23 @@ class SceneRenderer { pass.combiners.forEach { if (it is ColorBufferFacetCombiner) { val index = target.colorAttachmentIndexByName(it.targetOutput) - ?: error("no such attachment ${it.targetOutput}") + ?: error("attachement not found ${it.targetOutput}") target.blendMode(index, it.blendMode) } } } outputPassTarget?.bind() - drawPass(drawer, pass, materialContext, meshes, instancedMeshes, skinnedMeshes) + 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("no such attachment: ${combiner.targetOutput}") + ?: error("attachment not found ${combiner.targetOutput}") } } } - val lightContext = LightContext(lights, shadowLightTargets) + val lightContext = LightContext(context.lights, shadowLightTargets) val postContext = PostContext(lightContext, drawer.view.inversed) for (postStep in postSteps) { @@ -146,10 +168,9 @@ class SceneRenderer { if (drawFinalBuffer) { outputPassTarget?.let { output -> drawer.isolated { + drawer.defaults() drawer.ortho() - drawer.view = Matrix44.IDENTITY - drawer.model = Matrix44.IDENTITY - val outputName = (postSteps.last() as FilterPostStep).output + val outputName = (postSteps.lastOrNull() as? FilterPostStep<*>)?.output ?: "color" val outputBuffer = buffers[outputName] ?: throw IllegalArgumentException("can't find $outputName buffer") drawer.image(outputBuffer) @@ -158,14 +179,12 @@ class SceneRenderer { } } - private fun drawPass(drawer: Drawer, pass: RenderPass, materialContext: MaterialContext, - meshes: List>, - instancedMeshes: List>, - skinnedMeshes: List> + internal fun drawPass(drawer: Drawer, pass: RenderPass, materialContext: MaterialContext, + context: RenderContext ) { drawer.depthWrite = pass.depthWrite - val primitives = meshes.flatMap { mesh -> + val primitives = context.meshes.flatMap { mesh -> mesh.content.primitives.map { primitive -> NodeContent(mesh.node, primitive) } @@ -204,7 +223,7 @@ class SceneRenderer { } - val skinnedPrimitives = skinnedMeshes.flatMap { mesh -> + val skinnedPrimitives = context.skinnedMeshes.flatMap { mesh -> mesh.content.primitives.map { primitive -> NodeContent(mesh.node, Pair(primitive, mesh)) } @@ -230,12 +249,9 @@ class SceneRenderer { val jointTransforms = (skinnedMesh.joints zip skinnedMesh.inverseBindMatrices) .map { (nodeInverse * it.first.worldTransform * it.second) } -// val jointNormalTransforms = jointTransforms.map { Matrix44.IDENTITY } - val shadeStyle = primitive.material.generateShadeStyle(materialContext, primitiveContext) shadeStyle.parameter("jointTransforms", jointTransforms.toTypedArray()) -// shadeStyle.parameter("jointNormalTransforms", jointNormalTransforms.toTypedArray()) shadeStyle.parameter("viewMatrixInverse", drawer.view.inversed) primitive.material.applyToShadeStyle(materialContext, shadeStyle) @@ -258,7 +274,7 @@ class SceneRenderer { } - val instancedPrimitives = instancedMeshes.flatMap { mesh -> + val instancedPrimitives = context.instancedMeshes.flatMap { mesh -> mesh.content.primitives.map { primitive -> NodeContent(mesh.node, MeshPrimitiveInstance(primitive, mesh.content.instances, mesh.content.attributes)) } @@ -295,4 +311,10 @@ 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()) } \ No newline at end of file diff --git a/orx-dnk3/src/main/kotlin/ShaderUtilities.kt b/orx-dnk3/src/main/kotlin/ShaderUtilities.kt index b3fe515e..b81f4744 100644 --- a/orx-dnk3/src/main/kotlin/ShaderUtilities.kt +++ b/orx-dnk3/src/main/kotlin/ShaderUtilities.kt @@ -167,6 +167,10 @@ vec3 F_Schlick(const vec3 f0, float VoH) { // Schlick 1994, "An Inexpensive BRDF Model for Physically-Based Rendering" return f0 + (vec3(1.0) - f0) * pow5(1.0 - VoH); } +vec3 F_SchlickRoughness(vec3 F0, float roughness, float VoH) +{ + return F0 + (max(vec3(1.0 - roughness), F0) - F0) * pow(1.0 - VoH, 5.0); +} float F_Schlick(float f0, float f90, float VoH) { return f0 + (f90 - f0) * pow5(1.0 - VoH); diff --git a/orx-dnk3/src/main/kotlin/cubemap/CubemapFilter.kt b/orx-dnk3/src/main/kotlin/cubemap/CubemapFilter.kt new file mode 100644 index 00000000..b62aa011 --- /dev/null +++ b/orx-dnk3/src/main/kotlin/cubemap/CubemapFilter.kt @@ -0,0 +1,187 @@ +package org.openrndr.extra.dnk3.cubemap + +import org.openrndr.draw.* + +import org.openrndr.color.ColorRGBa +import org.openrndr.internal.Driver +import org.openrndr.math.* +import org.openrndr.math.transforms.ortho + +private val filterDrawStyle = DrawStyle().apply { + blendMode = BlendMode.REPLACE + depthWrite = false + depthTestPass = DepthTestPass.ALWAYS + stencil.stencilTest = StencilTest.DISABLED +} + +private var filterQuad: VertexBuffer? = null +private var filterQuadFormat = vertexFormat { + position(2) + textureCoordinate(2) +} + + +/** + * Filter base class. Renders "full-screen" quads. + */ +open class CubemapFilter(private val shader: Shader? = null, private val watcher: ShaderWatcher? = null) { + + /** + * parameter map + */ + val parameters = mutableMapOf() + var padding = 0 + + var depthBufferOut: DepthBuffer? = null + + companion object { + val filterVertexCode: String get() = Driver.instance.internalShaderResource("filter.vert") + } + + + + + open fun apply(source: Array, target: Array) { + if (target.isEmpty()) { + return + } + + for (side in CubemapSide.values()) { + val renderTarget = renderTarget(target[0].width, target[0].width, 1.0) {} + + shader?.begin() + shader?.uniform("sideNormal", side.forward) + shader?.uniform("sideUp", side.up) + shader?.uniform("sideRight", (side.forward cross side.up)) + shader?.end() + + + target.forEach { + renderTarget.attach(it, side, 0) + } + + for (i in 1 until target.size) { + renderTarget.blendMode(i, BlendMode.REPLACE) + } + + apply(source, renderTarget) + depthBufferOut?.let { + renderTarget.attach(it) + } + + if (depthBufferOut != null) { + renderTarget.detachDepthBuffer() + } + + renderTarget.detachColorBuffers() + renderTarget.destroy() + } + } + + fun apply(source: Array, target: RenderTarget) { + val shader = if (this.watcher != null) watcher.shader!! else this.shader!! + target.bind() + + if (filterQuad == null) { + val fq = VertexBuffer.createDynamic(filterQuadFormat, 6, Session.root) + + fq.shadow.writer().apply { + write(Vector2(0.0, 1.0)); write(Vector2(0.0, 0.0)) + write(Vector2(0.0, 0.0)); write(Vector2(0.0, 1.0)) + write(Vector2(1.0, 0.0)); write(Vector2(1.0, 1.0)) + + write(Vector2(0.0, 1.0)); write(Vector2(0.0, 0.0)) + write(Vector2(1.0, 1.0)); write(Vector2(1.0, 0.0)) + write(Vector2(1.0, 0.0)); write(Vector2(1.0, 1.0)) + } + fq.shadow.upload() + fq.shadow.destroy() + filterQuad = fq + } + + shader.begin() + + source.forEachIndexed { index, cubemap -> + cubemap.bind(index) + cubemap.filter(MinifyingFilter.LINEAR, MagnifyingFilter.LINEAR) + shader.uniform("tex$index", index) + } + + Driver.instance.setState(filterDrawStyle) + + shader.uniform("projectionMatrix", ortho(0.0, target.width.toDouble(), target.height.toDouble(), 0.0, -1.0, 1.0)) + shader.uniform("targetSize", Vector2(target.width.toDouble(), target.height.toDouble())) + shader.uniform("padding", Vector2(padding.toDouble(), padding.toDouble())) + + var textureIndex = source.size + 0 + parameters.forEach { (uniform, value) -> + @Suppress("UNCHECKED_CAST") + when (value) { + is Boolean -> shader.uniform(uniform, value) + is Float -> shader.uniform(uniform, value) + is Double -> shader.uniform(uniform, value.toFloat()) + is Matrix44 -> shader.uniform(uniform, value) + is Vector2 -> shader.uniform(uniform, value) + is Vector3 -> shader.uniform(uniform, value) + is Vector4 -> shader.uniform(uniform, value) + is ColorRGBa -> shader.uniform(uniform, value) + is Int -> shader.uniform(uniform, value) + is Matrix55 -> shader.uniform(uniform, value.floatArray) + is FloatArray -> shader.uniform(uniform, value) + + // EJ: this is not so nice but I have no other ideas for this + is Array<*> -> if (value.size > 0) when (value[0]) { + is Vector2 -> shader.uniform(uniform, value as Array) + is Vector3 -> shader.uniform(uniform, value as Array) + is Vector4 -> shader.uniform(uniform, value as Array) + else -> throw IllegalArgumentException("unsupported array value: ${value[0]!!::class.java}") + //is ColorRGBa -> shader.uniform(uniform, value as Array) + } + + is DepthBuffer -> { + shader.uniform("$uniform", textureIndex) + value.bind(textureIndex) + textureIndex++ + } + + is ColorBuffer -> { + shader.uniform("$uniform", textureIndex) + value.bind(textureIndex) + textureIndex++ + } + + is Cubemap -> { + shader.uniform("$uniform", textureIndex) + value.bind(textureIndex) + textureIndex++ + } + + is ArrayTexture -> { + shader.uniform("$uniform", textureIndex) + value.bind(textureIndex) + textureIndex++ + } + + is BufferTexture -> { + shader.uniform("$uniform", textureIndex) + value.bind(textureIndex) + textureIndex++ + } + } + } + + Driver.instance.drawVertexBuffer(shader, listOf(filterQuad!!), DrawPrimitive.TRIANGLES, 0, 6) + shader.end() + target.unbind() + } + + fun apply(source: Cubemap, target: Cubemap) = apply(arrayOf(source), arrayOf(target)) + fun apply(source: Cubemap, target: Array) = apply(arrayOf(source), target) + fun apply(source: Array, target: Cubemap) = apply(source, arrayOf(target)) + + fun untrack() { + shader?.let { Session.active.untrack(shader) } + } + + protected val format get() = filterQuadFormat +} \ No newline at end of file diff --git a/orx-dnk3/src/main/kotlin/cubemap/CubemapPassthrough.kt b/orx-dnk3/src/main/kotlin/cubemap/CubemapPassthrough.kt new file mode 100644 index 00000000..cc9f5299 --- /dev/null +++ b/orx-dnk3/src/main/kotlin/cubemap/CubemapPassthrough.kt @@ -0,0 +1,6 @@ +package org.openrndr.extra.dnk3.cubemap + +import org.openrndr.draw.filterShaderFromUrl +import org.openrndr.resourceUrl + +class CubemapPassthrough : CubemapFilter(filterShaderFromUrl(resourceUrl("/shaders/cubemap-filters/cubemap-passthrough.frag"))) diff --git a/orx-dnk3/src/main/kotlin/cubemap/IrradianceConvolution.kt b/orx-dnk3/src/main/kotlin/cubemap/IrradianceConvolution.kt new file mode 100644 index 00000000..325de7d2 --- /dev/null +++ b/orx-dnk3/src/main/kotlin/cubemap/IrradianceConvolution.kt @@ -0,0 +1,6 @@ +package org.openrndr.extra.dnk3.cubemap + +import org.openrndr.draw.filterShaderFromUrl +import org.openrndr.resourceUrl + +class IrradianceConvolution : CubemapFilter(filterShaderFromUrl(resourceUrl("/shaders/cubemap-filters/irradiance-convolution.frag"))) diff --git a/orx-dnk3/src/main/kotlin/cubemap/SphericalHarmonics.kt b/orx-dnk3/src/main/kotlin/cubemap/SphericalHarmonics.kt new file mode 100644 index 00000000..788fe297 --- /dev/null +++ b/orx-dnk3/src/main/kotlin/cubemap/SphericalHarmonics.kt @@ -0,0 +1,187 @@ +@file:ShaderPhrases([]) + +package org.openrndr.extra.dnk3.cubemap + +import org.openrndr.draw.* +import org.openrndr.extra.shaderphrases.annotations.ShaderPhrases +import org.openrndr.extra.shaderphrases.phraseResource +import org.openrndr.math.Vector3 +import org.openrndr.math.max +import org.openrndr.resourceUrl +import java.nio.ByteBuffer +import java.nio.ByteOrder +import kotlin.math.sqrt + +class SphericalHarmonics : Filter(filterShaderFromUrl(resourceUrl("/shaders/cubemap-filters/spherical-harmonics.frag"))) { + var input: Cubemap by parameters +} + +/** based on https://andrew-pham.blog/2019/08/26/spherical-harmonics/ */ +fun Cubemap.irradianceCoefficients(): Array { + val cubemap = this + require(cubemap.format == ColorFormat.RGB) + require(cubemap.type == ColorType.FLOAT32) + + val result = Array(9) { Vector3.ZERO } + + var buffer = ByteBuffer.allocateDirect(cubemap.width * cubemap.width * cubemap.format.componentCount * cubemap.type.componentSize) + buffer.order(ByteOrder.nativeOrder()) + + var weightSum = 0.0 + + for (side in CubemapSide.values()) { + //cubemap.side(side).read(buffer) + buffer.rewind() + cubemap.read(side, buffer) + + buffer.rewind() + for (y in 0 until cubemap.width) { + for (x in 0 until cubemap.width) { + val rf = buffer.float.toDouble() + val gf = buffer.float.toDouble() + val bf = buffer.float.toDouble() + + val L = Vector3(rf, gf, bf) + + var u = (x + 0.5) / cubemap.width; + var v = (y + 0.5) / cubemap.width; + u = u * 2.0 - 1.0 + v = v * 2.0 - 1.0 + + val temp = 1.0 + u * u + v * v + val weight = 4.0 / (sqrt(temp) * temp) + + val N = cubemap.mapUVSToN(u, v, side) + val coefficients = genLightingCoefficientsForNormal(N, L) + + for (i in 0 until 9) { + result[i] += coefficients[i] * weight + } + weightSum += weight + } + } + } + + for (i in 0 until 9) { + result[i] = result[i] * (4.0 * Math.PI) / weightSum + } + + return result; +} + +fun genSHCoefficients(N: Vector3): DoubleArray { + val result = DoubleArray(9) + + // Band 0 + result[0] = 0.282095; + + // Band 1 + result[1] = 0.488603 * N.y + result[2] = 0.488603 * N.z + result[3] = 0.488603 * N.x + + // Band 2 + result[4] = 1.092548 * N.x * N.y + result[5] = 1.092548 * N.y * N.z + result[6] = 0.315392 * (3.0 * N.z * N.z - 1.0) + result[7] = 1.092548 * N.x * N.z + result[8] = 0.546274 * (N.x * N.x - N.y * N.y) + + return result; +} + + +fun genLightingCoefficientsForNormal(N: Vector3, L: Vector3): Array { + val coefficients = genSHCoefficients(N) + val result = Array(9) { Vector3.ZERO } + for (i in 0 until 9) { + result[i] = L * coefficients[i] + } + return result +} + +fun Cubemap.mapUVSToN(u: Double, v: Double, side: CubemapSide): Vector3 { + return (side.right * u + side.up * v + side.forward).normalized +} + + +// Evaluates the irradiance perceived in the provided direction +// Analytic method from http://www1.cs.columbia.edu/~ravir/papers/envmap/envmap.pdf eq. 13 +// +fun evaluateSHIrradiance(direction: Vector3, _SH: Array): Vector3 { + val c1 = 0.42904276540489171563379376569857; // 4 * Â2.Y22 = 1/4 * sqrt(15.PI) + val c2 = 0.51166335397324424423977581244463; // 0.5 * Â1.Y10 = 1/2 * sqrt(PI/3) + val c3 = 0.24770795610037568833406429782001; // Â2.Y20 = 1/16 * sqrt(5.PI) + val c4 = 0.88622692545275801364908374167057; // Â0.Y00 = 1/2 * sqrt(PI) + + val x = direction.x; + val y = direction.y; + val z = direction.z; + + return max(Vector3.ZERO, + _SH[8] * (c1 * (x * x - y * y)) // c1.L22.(x²-y²) + + _SH[6] * (c3 * (3.0 * z * z - 1)) // c3.L20.(3.z² - 1) + + _SH[0] * c4 // c4.L00 + + (_SH[4] * x * y + _SH[7] * x * z + _SH[5] * y * z) * 2.0 * c1 // 2.c1.(L2-2.xy + L21.xz + L2-1.yz) + + (_SH[3] * x + _SH[1] * y + _SH[2] * z) * c2 * 2.0); // 2.c2.(L11.x + L1-1.y + L10.z) +} + +val glslEvaluateSH: String by phraseResource("/phrases/irradiance-sh/evaluate-sh.frag") + +val glslFetchSH: String by phraseResource("/phrases/irradiance-sh/fetch-sh.frag") +val glslFetchSH0: String by phraseResource("/phrases/irradiance-sh/fetch-sh0.frag") + +fun genGlslGatherSH(xProbes: Int, yProbes: Int, zProbes: Int, spacing: Double = 1.0, offset: Vector3) = """ +ivec3 gridCoordinates(vec3 p, out vec3 f) { + float x = (p.x - ${offset.x}) / $spacing; + float y = (p.y - ${offset.y})/ $spacing; + float z = (p.z - ${offset.z}) / $spacing; + + int ix = int(floor(x)) + $xProbes / 2; + int iy = int(floor(y)) + $yProbes / 2; + int iz = int(floor(z)) + $zProbes / 2; + + f.x = fract((x)); + f.y = fract((y)); + f.z = fract((z)); + + return ivec3(ix, iy, iz); +} + +int gridIndex(ivec3 p) { + ivec3 c = clamp(p, ivec3(0), ivec3(${xProbes - 1}, ${yProbes - 1}, ${zProbes - 1})); + return c.x + c.y * $xProbes + c.z * ${xProbes * yProbes}; +} + +void gatherSH(samplerBuffer btex, vec3 p, out vec3[9] blend) { + vec3[9] c000; + vec3[9] c001; + vec3[9] c010; + vec3[9] c011; + vec3[9] c100; + vec3[9] c101; + vec3[9] c110; + vec3[9] c111; + + vec3 f; + ivec3 io = gridCoordinates(p, f); + + fetchSH(btex, gridIndex(io + ivec3(0,0,0)), c000); + fetchSH(btex, gridIndex(io + ivec3(0,0,1)), c001); + fetchSH(btex, gridIndex(io + ivec3(0,1,0)), c010); + fetchSH(btex, gridIndex(io + ivec3(0,1,1)), c011); + fetchSH(btex, gridIndex(io + ivec3(1,0,0)), c100); + fetchSH(btex, gridIndex(io + ivec3(1,0,1)), c101); + fetchSH(btex, gridIndex(io + ivec3(1,1,0)), c110); + fetchSH(btex, gridIndex(io + ivec3(1,1,1)), c111); + + for (int i = 0; i < 9; ++i) { + blend[i] = mix( mix( mix(c000[i], c001[i], f.z), mix(c010[i], c011[i], f.z), f.y), mix( mix(c100[i], c101[i], f.z), mix(c110[i], c111[i], f.z), f.y), f.x); + } +} +""".trimIndent() + +val glslGridCoordinates: String by phraseResource("/phrases/irradiance-sh/grid-coordinates.frag") +val glslGridIndex: String by phraseResource("/phrases/irradiance-sh/grid-index.frag") +val glslGatherSH: String by phraseResource("/phrases/irradiance-sh/gather-sh.frag") +val glslGatherSH0: String by phraseResource("/phrases/irradiance-sh/gather-sh0.frag") \ No newline at end of file diff --git a/orx-dnk3/src/main/kotlin/features/IrradianceSH.kt b/orx-dnk3/src/main/kotlin/features/IrradianceSH.kt new file mode 100644 index 00000000..1c3e5b94 --- /dev/null +++ b/orx-dnk3/src/main/kotlin/features/IrradianceSH.kt @@ -0,0 +1,109 @@ +package org.openrndr.extra.dnk3.features + +import org.openrndr.color.ColorRGBa +import org.openrndr.draw.* +import org.openrndr.extra.dnk3.* +import org.openrndr.extra.dnk3.cubemap.irradianceCoefficients +import org.openrndr.math.Matrix44 +import org.openrndr.math.Vector3 +import org.openrndr.math.transforms.transform +import java.io.File +import java.nio.ByteBuffer +import java.nio.ByteOrder + +data class IrradianceSH(val xCount: Int, val yCount: Int, val zCount: Int, val spacing: Double, val offset: Vector3, val cubemapSize: Int) : Feature { + override fun update(drawer: Drawer, sceneRenderer: SceneRenderer, scene: Scene, feature: T, context: RenderContext) { + sceneRenderer.processIrradiance(drawer, scene, feature as IrradianceSH, context) + } + + var shMap: BufferTexture? = null + val probeCount + get() = xCount * yCount * zCount + +} + +fun Scene.addIrradianceSH(xCount: Int, + yCount: Int, + zCount: Int, + spacing: Double, + offset: Vector3 = Vector3.ZERO, + cubemapSize: Int = 256 +) { + features.add(IrradianceSH(xCount * 2 + 1, yCount * 2 + 1, zCount * 2 + 1, spacing, offset, cubemapSize)) + var probeID = 0 + for (k in -zCount..zCount) { + for (j in -yCount..yCount) { + for (i in -xCount..xCount) { + val probeNode = SceneNode() + probeNode.transform = transform { + translate(offset) + translate(i * spacing, j * spacing, k * spacing) + } + probeNode.entities.add(IrradianceProbe()) + probeID++ + root.children.add(probeNode) + } + } + } +} + +fun SceneRenderer.processIrradiance(drawer: Drawer, scene: Scene, feature: IrradianceSH, context: RenderContext) { + val irradianceProbes = scene.root.findContent { this as? IrradianceProbe } + val irradianceProbePositions = irradianceProbes.map { it.node.worldPosition } + + if (feature.shMap == null && irradianceProbes.isNotEmpty()) { + val hash = scene.hash() + val cached = File("data/scene-cache/sh-$hash.orb") + if (cached.exists()) { + feature.shMap = loadBufferTexture(cached) + } else { + var probeID = 0 + val tempCubemap = cubemap(feature.cubemapSize, format = ColorFormat.RGB, type = ColorType.FLOAT32) + var cubemapDepthBuffer = depthBuffer(feature.cubemapSize, feature.cubemapSize, DepthFormat.DEPTH16, BufferMultisample.Disabled) + + feature.shMap = bufferTexture(irradianceProbes.size * 9, format = ColorFormat.RGB, type = ColorType.FLOAT32) + val buffer = ByteBuffer.allocateDirect(irradianceProbePositions.size * 9 * 3 * 4) + buffer.order(ByteOrder.nativeOrder()) + + for ((node, probe) in irradianceProbes) { + if (probe.dirty) { + val pass = IrradianceProbePass + val materialContext = MaterialContext(pass, context.lights, emptyList(), shadowLightTargets, emptyMap(), 0) + val position = node.worldPosition + + for (side in CubemapSide.values()) { + val target = renderTarget(feature.cubemapSize, feature.cubemapSize) { + //this.colorBuffer(tempCubemap.side(side)) + this.cubemap(tempCubemap, side) + this.depthBuffer(cubemapDepthBuffer) + } + drawer.isolatedWithTarget(target) { + drawer.clear(ColorRGBa.BLACK) + drawer.projection = probe.projectionMatrix + drawer.view = Matrix44.IDENTITY + drawer.model = Matrix44.IDENTITY + drawer.lookAt(position, position + side.forward, side.up) + drawPass(drawer, pass, materialContext, context) + } + + target.detachDepthBuffer() + target.detachColorAttachments() + target.destroy() + } + val coefficients = tempCubemap.irradianceCoefficients() + for (coef in coefficients) { + buffer.putVector3((coef)) + } + probeID++ + println("$probeID / ${irradianceProbePositions.size}") + probe.dirty = false + } + } + feature.shMap?.let { + buffer.rewind() + it.write(buffer) + it.saveToFile(File("data/scene-cache/sh-$hash.orb")) + } + } + } +} diff --git a/orx-dnk3/src/main/kotlin/gltf/Gltf.kt b/orx-dnk3/src/main/kotlin/gltf/Gltf.kt index 3697e04b..e63cd925 100644 --- a/orx-dnk3/src/main/kotlin/gltf/Gltf.kt +++ b/orx-dnk3/src/main/kotlin/gltf/Gltf.kt @@ -24,11 +24,11 @@ const val GLTF_BYTE = 5120 const val GLTF_ARRAY_BUFFER = 34962 const val GLTF_ELEMENT_ARRAY_BUFFER = 34963 -class GltfAsset(val generator: String?, val version: String?) +data class GltfAsset(val generator: String?, val version: String?) -class GltfScene(val nodes: IntArray) +data class GltfScene(val nodes: IntArray) -class GltfNode(val name: String, +data class GltfNode(val name: String, val children: IntArray?, val matrix: DoubleArray?, val scale: DoubleArray?, @@ -39,14 +39,14 @@ class GltfNode(val name: String, val camera: Int?, val extensions: GltfNodeExtensions?) -class KHRLightsPunctualIndex(val light: Int) +data class KHRLightsPunctualIndex(val light: Int) -class GltfNodeExtensions(val KHR_lights_punctual: KHRLightsPunctualIndex?) { +data class GltfNodeExtensions(val KHR_lights_punctual: KHRLightsPunctualIndex?) { } -class GltfPrimitive(val attributes: LinkedHashMap, val indices: Int?, val mode: Int?, val material: Int) { +data class GltfPrimitive(val attributes: LinkedHashMap, val indices: Int?, val mode: Int?, val material: Int) { fun createDrawCommand(gltfFile: GltfFile): GltfDrawCommand { val indexBuffer = indices?.let { indices -> @@ -164,27 +164,27 @@ class GltfPrimitive(val attributes: LinkedHashMap, val indices: Int } } -class GltfMesh(val primitives: List, val name: String) { +data class GltfMesh(val primitives: List, val name: String) { fun createDrawCommands(gltfFile: GltfFile): List { return primitives.map { it.createDrawCommand(gltfFile) } } } -class GltfPbrMetallicRoughness(val baseColorFactor: DoubleArray?, +data class GltfPbrMetallicRoughness(val baseColorFactor: DoubleArray?, val baseColorTexture: GltfMaterialTexture?, var metallicRoughnessTexture: GltfMaterialTexture?, val roughnessFactor: Double?, val metallicFactor: Double?) -class GltfMaterialTexture(val index: Int, val scale: Double?, val texCoord: Int?) +data class GltfMaterialTexture(val index: Int, val scale: Double?, val texCoord: Int?) -class GltfImage(val uri: String?, val bufferView: Int?) +data class GltfImage(val uri: String?, val bufferView: Int?) -class GltfSampler(val magFilter: Int, val minFilter: Int, val wrapS: Int, val wrapT: Int) +data class GltfSampler(val magFilter: Int, val minFilter: Int, val wrapS: Int, val wrapT: Int) -class GltfTexture(val sampler: Int, val source: Int) +data class GltfTexture(val sampler: Int, val source: Int) -class GltfMaterial(val name: String, +data class GltfMaterial(val name: String, val alphaMode: String?, val doubleSided: Boolean?, val normalTexture: GltfMaterialTexture?, @@ -195,19 +195,19 @@ class GltfMaterial(val name: String, val extensions: GltfMaterialExtensions? ) -class GltfMaterialExtensions( +data class GltfMaterialExtensions( val KHR_materials_pbrSpecularGlossiness: KhrMaterialsPbrSpecularGlossiness? ) class KhrMaterialsPbrSpecularGlossiness(val diffuseFactor: DoubleArray?, val diffuseTexture: GltfMaterialTexture?) -class GltfBufferView(val buffer: Int, +data class GltfBufferView(val buffer: Int, val byteOffset: Int?, val byteLength: Int, val byteStride: Int?, val target: Int) -class GltfBuffer(val byteLength: Int, val uri: String?) { +data class GltfBuffer(val byteLength: Int, val uri: String?) { fun contents(gltfFile: GltfFile): ByteBuffer = if (uri != null) { if (uri.startsWith("data:")) { val base64 = uri.substring(uri.indexOf(",") + 1) @@ -231,9 +231,9 @@ class GltfBuffer(val byteLength: Int, val uri: String?) { } } -class GltfDrawCommand(val vertexBuffer: VertexBuffer, val indexBuffer: IndexBuffer?, val primitive: DrawPrimitive, var vertexCount: Int) +data class GltfDrawCommand(val vertexBuffer: VertexBuffer, val indexBuffer: IndexBuffer?, val primitive: DrawPrimitive, var vertexCount: Int) -class GltfAccessor( +data class GltfAccessor( val bufferView: Int, val byteOffset: Int, val componentType: Int, @@ -243,28 +243,28 @@ class GltfAccessor( val type: String ) -class GltfAnimation(val name: String?, val channels: List, val samplers: List) -class GltfAnimationSampler(val input: Int, val interpolation: String, val output: Int) +data class GltfAnimation(val name: String?, val channels: List, val samplers: List) +data class GltfAnimationSampler(val input: Int, val interpolation: String, val output: Int) -class GltfChannelTarget(val node: Int?, val path: String?) +data class GltfChannelTarget(val node: Int?, val path: String?) -class GltfChannel(val sampler: Int, val target: GltfChannelTarget) +data class GltfChannel(val sampler: Int, val target: GltfChannelTarget) -class GltfSkin(val inverseBindMatrices: Int, val joints: IntArray, val skeleton: Int) +data class GltfSkin(val inverseBindMatrices: Int, val joints: IntArray, val skeleton: Int) -class KHRLightsPunctualLight(val color: DoubleArray?, val type: String, val intensity: Double?, val range: Double, val spot: KHRLightsPunctualLightSpot?) -class KHRLightsPunctualLightSpot(val innerConeAngle: Double?, val outerConeAngle: Double?) +data class KHRLightsPunctualLight(val color: DoubleArray?, val type: String, val intensity: Double?, val range: Double, val spot: KHRLightsPunctualLightSpot?) +data class KHRLightsPunctualLightSpot(val innerConeAngle: Double?, val outerConeAngle: Double?) -class KHRLightsPunctual(val lights: List) +data class KHRLightsPunctual(val lights: List) -class GltfExtensions(val KHR_lights_punctual: KHRLightsPunctual?) +data class GltfExtensions(val KHR_lights_punctual: KHRLightsPunctual?) -class GltfCameraPerspective(val aspectRatio: Double?, val yfov: Double, val zfar: Double?, val znear: Double) -class GltfCameraOrthographic(val xmag: Double, val ymag: Double, val zfar: Double, val znear: Double) +data class GltfCameraPerspective(val aspectRatio: Double?, val yfov: Double, val zfar: Double?, val znear: Double) +data class GltfCameraOrthographic(val xmag: Double, val ymag: Double, val zfar: Double, val znear: Double) -class GltfCamera(val name: String?, val type: String, val perspective: GltfCameraPerspective?, val orthographic: GltfCameraOrthographic?) +data class GltfCamera(val name: String?, val type: String, val perspective: GltfCameraPerspective?, val orthographic: GltfCameraOrthographic?) class GltfFile( val asset: GltfAsset?, diff --git a/orx-dnk3/src/main/kotlin/gltf/GltfScene.kt b/orx-dnk3/src/main/kotlin/gltf/GltfScene.kt index aa7a6c4d..bc763a9b 100644 --- a/orx-dnk3/src/main/kotlin/gltf/GltfScene.kt +++ b/orx-dnk3/src/main/kotlin/gltf/GltfScene.kt @@ -15,13 +15,10 @@ import java.nio.ByteOrder import kotlin.reflect.KMutableProperty0 class SceneAnimation(var channels: List) { - val duration: Double get() { return channels.maxBy { it.duration }?.duration ?: 0.0 } - - fun applyToTargets(input: Double) { for (channel in channels) { channel.applyToTarget(input) @@ -34,6 +31,7 @@ sealed class AnimationChannel { abstract fun applyToTarget(input: Double) } + class QuaternionChannel(val target: KMutableProperty0, val keyframer: KeyframerChannelQuaternion) : AnimationChannel() { override fun applyToTarget(input: Double) { @@ -63,7 +61,6 @@ class GltfSceneNode : SceneNode() { return "translation: $translation, scale: $scale, rotation: $rotation, children: ${children.size}, entities: ${entities} " } - override var transform: Matrix44 = Matrix44.IDENTITY get() = transform { translate(translation) @@ -90,7 +87,12 @@ fun GltfFile.buildSceneNodes(): GltfSceneData { require(localBufferView.byteLength != null) localBuffer.position(localBufferView.byteOffset) localBuffer.limit(localBufferView.byteOffset + localBufferView.byteLength) - ColorBuffer.fromBuffer(localBuffer) + val cb = ColorBuffer.fromBuffer(localBuffer) + cb.generateMipmaps() + cb.filter(MinifyingFilter.LINEAR_MIPMAP_LINEAR, MagnifyingFilter.LINEAR) + cb.anisotropy = 100.0 + localBuffer.limit(localBuffer.capacity()) + cb } ?: error("no uri and no bufferview") } else { @@ -106,6 +108,7 @@ fun GltfFile.buildSceneNodes(): GltfSceneData { val sceneMaterials = mutableMapOf() fun GltfMaterial.createSceneMaterial(): Material = sceneMaterials.getOrPut(this) { val material = PBRMaterial() + material.name = this.name material.doubleSided = this.doubleSided ?: false material.transparent = this.alphaMode != null @@ -203,7 +206,7 @@ fun GltfFile.buildSceneNodes(): GltfSceneData { drawCommand.primitive, 0, drawCommand.vertexCount) - val material = materials[material].createSceneMaterial() + val material = materials?.getOrNull(material)?.createSceneMaterial() ?: PBRMaterial() return MeshPrimitive(geometry, material) } diff --git a/orx-dnk3/src/main/kotlin/materials/IrradianceDebugMaterial.kt b/orx-dnk3/src/main/kotlin/materials/IrradianceDebugMaterial.kt new file mode 100644 index 00000000..9bac0e77 --- /dev/null +++ b/orx-dnk3/src/main/kotlin/materials/IrradianceDebugMaterial.kt @@ -0,0 +1,71 @@ +package org.openrndr.extra.dnk3.materials + +import org.openrndr.draw.ShadeStyle +import org.openrndr.draw.shadeStyle +import org.openrndr.extra.dnk3.Material +import org.openrndr.extra.dnk3.MaterialContext +import org.openrndr.extra.dnk3.PrimitiveContext +import org.openrndr.extra.dnk3.cubemap.glslEvaluateSH +import org.openrndr.extra.dnk3.cubemap.glslFetchSH +import org.openrndr.extra.dnk3.cubemap.genGlslGatherSH + +class IrradianceDebugMaterial : Material { + override val name: String? = null + + override var doubleSided: Boolean = false + override var transparent: Boolean = false + override val fragmentID: Int = 0 + + override fun generateShadeStyle(context: MaterialContext, primitiveContext: PrimitiveContext): ShadeStyle { + return shadeStyle { + fragmentPreamble = """ + $glslEvaluateSH + $glslFetchSH + ${genGlslGatherSH(context.irradianceSH!!.xCount, context.irradianceSH!!.yCount, context.irradianceSH!!.zCount, context.irradianceSH!!.spacing, context.irradianceSH!!.offset)} + vec3 f_emission = vec3(0.0); + """ + + if (context.irradianceSH != null) { + fragmentTransform = """ + vec3[9] sh; + gatherSH(p_shMap, v_worldPosition, sh); + x_fill.rgb = evaluateSH(normalize(v_worldNormal), sh); + + """.trimIndent() + } else { + fragmentTransform = """ + discard; + """ + } + } + } + + override fun applyToShadeStyle(context: MaterialContext, shadeStyle: ShadeStyle) { + context.irradianceSH?.shMap?.let { + shadeStyle.parameter("shMap", it) + } + + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is IrradianceDebugMaterial) return false + + if (name != other.name) return false + if (doubleSided != other.doubleSided) return false + if (transparent != other.transparent) return false + if (fragmentID != other.fragmentID) return false + + return true + } + + override fun hashCode(): Int { + var result = name?.hashCode() ?: 0 + result = 31 * result + doubleSided.hashCode() + result = 31 * result + transparent.hashCode() + result = 31 * result + fragmentID + return result + } + + +} \ No newline at end of file diff --git a/orx-dnk3/src/main/kotlin/post/ScreenspaceReflections.kt b/orx-dnk3/src/main/kotlin/post/ScreenspaceReflections.kt new file mode 100644 index 00000000..2525b0c6 --- /dev/null +++ b/orx-dnk3/src/main/kotlin/post/ScreenspaceReflections.kt @@ -0,0 +1,38 @@ +package org.openrndr.extra.dnk3.post + +import org.openrndr.draw.Filter +import org.openrndr.draw.Shader +import org.openrndr.draw.filterShaderFromUrl +import org.openrndr.math.Matrix44 +import org.openrndr.resourceUrl + +class ScreenspaceReflections : Filter(preprocessedFilterShaderFromUrl(resourceUrl("/shaders/screenspace-reflections.frag"))) { + var projection: Matrix44 by parameters + var projectionMatrixInverse: Matrix44 by parameters + + var colors: Int by parameters + var projDepth: Int by parameters + var normals: Int by parameters + + var jitterOriginGain: Double by parameters + var iterationLimit: Int by parameters + var distanceLimit: Double by parameters + var gain: Double by parameters + var borderWidth: Double by parameters + + init { + colors = 0 + projDepth = 1 + normals = 2 + + projection = Matrix44.IDENTITY + projectionMatrixInverse = Matrix44.IDENTITY + + distanceLimit = 100.0 + iterationLimit = 128 + jitterOriginGain = 0.0 + + gain = 1.0 + borderWidth = 130.0 + } +} \ No newline at end of file diff --git a/orx-dnk3/src/main/kotlin/post/VolumetricIrradiance.kt b/orx-dnk3/src/main/kotlin/post/VolumetricIrradiance.kt new file mode 100644 index 00000000..f78e4704 --- /dev/null +++ b/orx-dnk3/src/main/kotlin/post/VolumetricIrradiance.kt @@ -0,0 +1,45 @@ +package org.openrndr.extra.dnk3.post + +import org.openrndr.draw.* +import org.openrndr.extra.dnk3.features.IrradianceSH +import org.openrndr.extra.shaderphrases.preprocessShader +import org.openrndr.math.IntVector3 +import org.openrndr.math.Matrix44 +import org.openrndr.resourceUrl +import java.net.URL + +fun preprocessedFilterShaderFromUrl(url: String): Shader { + return filterShaderFromCode( preprocessShader(URL(url).readText()), "filter-shader: $url") +} + +fun preprocessedFilterShaderFromCode(fragmentShaderCode: String, name: String): Shader { + return Shader.createFromCode(Filter.filterVertexCode, fragmentShaderCode, name) +} + +class VolumetricIrradiance : Filter(preprocessedFilterShaderFromUrl(resourceUrl("/shaders/volumetric-irradiance.frag"))) { + + var stepLength: Double by parameters + var irradianceSH: IrradianceSH? = null + + var viewMatrixInverse: Matrix44 by parameters + var projectionMatrixInverse: Matrix44 by parameters + + init { + stepLength = 0.1 + viewMatrixInverse = Matrix44.IDENTITY + projectionMatrixInverse = Matrix44.IDENTITY + } + + override fun apply(source: Array, target: Array) { + irradianceSH?.shMap?.let { + parameters["shMap"] = it + } + irradianceSH?.let { + parameters["shMapDimensions"] = IntVector3(it.xCount, it.yCount, it.zCount) + parameters["shMapOffset"] = it.offset + parameters["shMapSpacing"] = it.spacing + } + super.apply(source, target) + } +} + diff --git a/orx-dnk3/src/main/kotlin/query/Query.kt b/orx-dnk3/src/main/kotlin/query/Query.kt new file mode 100644 index 00000000..f062d7fa --- /dev/null +++ b/orx-dnk3/src/main/kotlin/query/Query.kt @@ -0,0 +1,56 @@ +package org.openrndr.extra.dnk3.query + +import org.openrndr.extra.dnk3.Material +import org.openrndr.extra.dnk3.Mesh +import org.openrndr.extra.dnk3.Scene +import org.openrndr.extra.dnk3.SceneNode + +fun Scene.findNodeByName(name: String): SceneNode? { + return root.findNodeByName(name) +} + +fun SceneNode.findNodeByName(name: String): SceneNode? { + + if (this.name == name) { + return this + } else { + for (child in children) { + val candidate = child.findNodeByName(name) + if (candidate != null) { + return candidate + } + } + } + return null +} + +fun SceneNode.findMaterialByName(name: String): Material? { + return allMaterials().find { it.name == name } +} + +fun Scene.allMaterials(): Set { + return root.allMaterials() +} + +fun SceneNode.allMaterials(): Set { + val materials = mutableSetOf() + fun processNode(node: SceneNode) { + for (entity in node.entities) { + when (entity) { + is Mesh -> { + materials.addAll(entity.primitives.map { it.material }) + } + else -> { + } + } + } + + for (child in node.children) { + processNode(child) + } + } + processNode(this) + return materials +} + + diff --git a/orx-dnk3/src/main/kotlin/renderers/DryRenderer.kt b/orx-dnk3/src/main/kotlin/renderers/DryRenderer.kt new file mode 100644 index 00000000..10b73024 --- /dev/null +++ b/orx-dnk3/src/main/kotlin/renderers/DryRenderer.kt @@ -0,0 +1,8 @@ +package org.openrndr.extra.dnk3.renderers + +import org.openrndr.extra.dnk3.SceneRenderer + +fun dryRenderer() : SceneRenderer { + val sr = SceneRenderer() + return sr +} \ No newline at end of file diff --git a/orx-dnk3/src/main/kotlin/renderers/PostRenderer.kt b/orx-dnk3/src/main/kotlin/renderers/PostRenderer.kt new file mode 100644 index 00000000..9fe2fec7 --- /dev/null +++ b/orx-dnk3/src/main/kotlin/renderers/PostRenderer.kt @@ -0,0 +1,22 @@ +package org.openrndr.extra.dnk3.renderers + +import org.openrndr.draw.BufferMultisample +import org.openrndr.draw.ColorFormat +import org.openrndr.draw.ColorType +import org.openrndr.extra.dnk3.* +import org.openrndr.extra.dnk3.post.SegmentContours +import org.openrndr.extra.dnk3.post.SegmentContoursMSAA8 + +fun postRenderer(multisample: BufferMultisample = BufferMultisample.Disabled): SceneRenderer { + val sr = SceneRenderer() + sr.outputPasses.clear() + sr.outputPasses.add( + RenderPass( + listOf(HDRColorFacet(),FragmentIDFacet(), ClipDepthFacet(), ViewNormalFacet()), + multisample = multisample + ) + ) + + sr.drawFinalBuffer = true + return sr +} \ No newline at end of file diff --git a/orx-dnk3/src/main/kotlin/renderers/SegmentContourRenderer.kt b/orx-dnk3/src/main/kotlin/renderers/SegmentContourRenderer.kt index b928c6b8..ad555fd2 100644 --- a/orx-dnk3/src/main/kotlin/renderers/SegmentContourRenderer.kt +++ b/orx-dnk3/src/main/kotlin/renderers/SegmentContourRenderer.kt @@ -12,7 +12,7 @@ fun segmentContourRenderer(multisample: BufferMultisample = BufferMultisample.Di sr.outputPasses.clear() sr.outputPasses.add( RenderPass( - listOf(FragmentIDFacet()), + listOf(LDRColorFacet(),FragmentIDFacet()), multisample = multisample ) ) diff --git a/orx-dnk3/src/main/kotlin/tools/MeshCollapse.kt b/orx-dnk3/src/main/kotlin/tools/MeshCollapse.kt new file mode 100644 index 00000000..5721e8a0 --- /dev/null +++ b/orx-dnk3/src/main/kotlin/tools/MeshCollapse.kt @@ -0,0 +1,98 @@ +package org.openrndr.extra.dnk3.tools + +import org.openrndr.draw.* +import org.openrndr.extra.dnk3.Geometry +import org.openrndr.extra.dnk3.Mesh +import org.openrndr.extra.dnk3.MeshPrimitive +import org.openrndr.extra.dnk3.PBRMaterial +import java.nio.ByteBuffer +import java.nio.ByteOrder + + +private data class CollapseItem(val vertexFormats: List, + val drawPrimitive: DrawPrimitive, + val hasIndexBuffer: Boolean) + +fun Mesh.collapse() { + val grouped = primitives.groupBy { + CollapseItem(it.geometry.vertexBuffers.map { it.vertexFormat }, it.geometry.primitive, it.geometry.indexBuffer != null) + } + + grouped.map { + val vertexCount = it.value.sumBy { primitive -> + primitive.geometry.vertexCount + } + + val indexCount = if (it.key.hasIndexBuffer) + it.value.sumBy { primitive -> + primitive.geometry.indexBuffer?.indexCount ?: 0 + } + else 0 + + val collapsedVertices = it.key.vertexFormats.map { + vertexBuffer(it, vertexCount) + } + vertexBuffer(vertexFormat { attribute("fragmentID", VertexElementType.INT16) }, vertexCount) + + + val fragmentBuffer = ByteBuffer.allocateDirect(vertexCount * 2) + fragmentBuffer.order(ByteOrder.nativeOrder()) + + for (i in 0 until collapsedVertices.size) { + var offset = 0 + for (fromPrimitive in it.value) { + val fromBuffer = fromPrimitive.geometry.vertexBuffers[i] + + val copy = ByteBuffer.allocateDirect(fromBuffer.vertexCount * fromBuffer.vertexFormat.size) + copy.order(ByteOrder.nativeOrder()) + fromBuffer.read(copy) + copy.rewind() + + collapsedVertices[i].write(copy, offset) + offset += copy.capacity() + + for (v in 0 until fromBuffer.vertexCount) { + fragmentBuffer.putShort(fromPrimitive.material.fragmentID.toShort()) + } + } + } + + val collapsedIndices = if (it.key.hasIndexBuffer) indexBuffer(indexCount, IndexType.INT32) else null + + if (it.key.hasIndexBuffer) { + var offset = 0 + val result = ByteBuffer.allocateDirect(4 * indexCount) + result.order(ByteOrder.nativeOrder()) + + for (fromPrimitive in it.value) { + val fromBuffer = fromPrimitive.geometry.indexBuffer!! + when (fromBuffer.type) { + IndexType.INT16 -> { + val copy = ByteBuffer.allocateDirect(fromBuffer.indexCount * 2) + fromBuffer.read(copy) + copy.rewind() + for (i in 0 until fromBuffer.indexCount) { + val index = (copy.getShort().toInt() and 0xffff) + offset + result.putInt(index) + } + } + IndexType.INT32 -> { + val copy = ByteBuffer.allocateDirect(fromBuffer.indexCount * 4) + fromBuffer.read(copy) + copy.rewind() + for (i in 0 until fromBuffer.indexCount) { + val index = copy.getInt() + offset + result.putInt(index) + } + } + } + offset += fromPrimitive.geometry.vertexCount + } + } + + val collapsedGeometry = Geometry(collapsedVertices, collapsedIndices, it.key.drawPrimitive, 0, if (collapsedIndices == null) + vertexCount else indexCount + ) + + MeshPrimitive(collapsedGeometry, PBRMaterial()) + } +} \ No newline at end of file diff --git a/orx-dnk3/src/main/kotlin/tools/Skybox.kt b/orx-dnk3/src/main/kotlin/tools/Skybox.kt new file mode 100644 index 00000000..5dd7b0c0 --- /dev/null +++ b/orx-dnk3/src/main/kotlin/tools/Skybox.kt @@ -0,0 +1,84 @@ +package org.openrndr.extra.dnk3.tools + +import org.openrndr.draw.* +import org.openrndr.extra.dnk3.* +import org.openrndr.extras.meshgenerators.boxMesh + + +data class SkyboxMaterial(val cubemap: Cubemap, val intensity: Double = 0.0) : Material { + override val name: String = "skybox" + override var doubleSided: Boolean = false + override var transparent: Boolean = false + override val fragmentID: Int = 0 + + override fun generateShadeStyle(materialContext: MaterialContext, primitiveContext: PrimitiveContext): ShadeStyle { + return shadeStyle { + vertexTransform = """ + vec2 i = vec2(1.0, 0.0); + x_viewMatrix = x_viewNormalMatrix; + """.trimIndent() + + val combinerFS = materialContext.pass.combiners.map { + it.generateShader() + }.joinToString("\n") + + fragmentPreamble = """ + vec4 f_diffuse = vec4(0.0, 0.0, 0.0, 1.0); + vec3 f_specular = vec3(0.0); + vec3 f_ambient = vec3(0.0); + vec3 f_emission = vec3(0.0); + int f_fragmentID = 0; + vec4 m_color = vec4(1.0); + vec4 f_fog = vec4(0.0); + + """.trimIndent() + fragmentTransform = """ + f_diffuse = texture(p_skybox, va_position); + f_diffuse.rgb *= p_intensity; + """ + combinerFS + + suppressDefaultOutput = true + val rt = RenderTarget.active + materialContext.pass.combiners.map { + if (rt is ProgramRenderTarget || materialContext.pass === DefaultPass || materialContext.pass === DefaultOpaquePass || materialContext.pass == DefaultTransparentPass || materialContext.pass == IrradianceProbePass) { + this.output(it.targetOutput, ShadeStyleOutput(0)) + } else { + val index = rt.colorAttachmentIndexByName(it.targetOutput) + ?: error("attachment ${it.targetOutput} not found") + val type = rt.colorBuffer(index).type + val format = rt.colorBuffer(index).format + this.output(it.targetOutput, ShadeStyleOutput(index, format, type)) + } + } + } + } + + override fun applyToShadeStyle(context: MaterialContext, shadeStyle: ShadeStyle) { + shadeStyle.parameter("skybox", cubemap) + shadeStyle.parameter("intensity", intensity) + } + + + override fun hashCode(): Int { + var result = intensity.hashCode() + result = 31 * result + name.hashCode() + result = 31 * result + doubleSided.hashCode() + result = 31 * result + transparent.hashCode() + result = 31 * result + fragmentID + return result + } + + +} + +fun Scene.addSkybox(cubemapUrl: String, size: Double = 100.0, intensity:Double = 1.0) { + val cubemap = Cubemap.fromUrl(cubemapUrl, Session.active) + val box = boxMesh(size, size, size, 1, 1, 1, true) + val node = SceneNode() + val material = SkyboxMaterial(cubemap, intensity) + val geometry = Geometry(listOf(box), null, DrawPrimitive.TRIANGLES, 0, box.vertexCount) + val primitive = MeshPrimitive(geometry, material) + val mesh = Mesh(listOf(primitive)) + node.entities.add(mesh) + root.children.add(node) +} \ No newline at end of file diff --git a/orx-dnk3/src/main/resources/phrases/irradiance-sh/evaluate-sh.frag b/orx-dnk3/src/main/resources/phrases/irradiance-sh/evaluate-sh.frag new file mode 100644 index 00000000..6dbd4aa7 --- /dev/null +++ b/orx-dnk3/src/main/resources/phrases/irradiance-sh/evaluate-sh.frag @@ -0,0 +1,17 @@ +vec3 evaluateSH(vec3 direction, vec3[9] _SH) { + const float c1 = 0.42904276540489171563379376569857; // 4 * Â2.Y22 = 1/4 * sqrt(15.PI) + const float c2 = 0.51166335397324424423977581244463; // 0.5 * Â1.Y10 = 1/2 * sqrt(PI/3) + const float c3 = 0.24770795610037568833406429782001; // Â2.Y20 = 1/16 * sqrt(5.PI) + const float c4 = 0.88622692545275801364908374167057; // Â0.Y00 = 1/2 * sqrt(PI) + + float x = direction.x; + float y = direction.y; + float z = direction.z; + + return max(vec3(0.0), + _SH[8] * (c1 * (x * x - y * y)) // c1.L22.(x²-y²) + + _SH[6] * (c3 * (3.0 * z * z - 1)) // c3.L20.(3.z² - 1) + + _SH[0] * c4 // c4.L00 + + (_SH[4] * x * y + _SH[7] * x * z + _SH[5] * y * z) * 2.0 * c1 // 2.c1.(L2-2.xy + L21.xz + L2-1.yz) + + (_SH[3] * x + _SH[1] * y + _SH[2] * z) * c2 * 2.0); // 2.c2.(L11.x + L1-1.y + L10.z) +} \ No newline at end of file diff --git a/orx-dnk3/src/main/resources/phrases/irradiance-sh/fetch-sh.frag b/orx-dnk3/src/main/resources/phrases/irradiance-sh/fetch-sh.frag new file mode 100644 index 00000000..5e4bc087 --- /dev/null +++ b/orx-dnk3/src/main/resources/phrases/irradiance-sh/fetch-sh.frag @@ -0,0 +1,12 @@ +void fetchSH(samplerBuffer btex, int probeID, out vec3[9] _SH) { + int offset = probeID * 9; + _SH[0] = texelFetch(btex, offset).rgb; + _SH[1] = texelFetch(btex, offset+1).rgb; + _SH[2] = texelFetch(btex, offset+2).rgb; + _SH[3] = texelFetch(btex, offset+3).rgb; + _SH[4] = texelFetch(btex, offset+4).rgb; + _SH[5] = texelFetch(btex, offset+5).rgb; + _SH[6] = texelFetch(btex, offset+6).rgb; + _SH[7] = texelFetch(btex, offset+7).rgb; + _SH[8] = texelFetch(btex, offset+8).rgb; +} \ No newline at end of file diff --git a/orx-dnk3/src/main/resources/phrases/irradiance-sh/fetch-sh0.frag b/orx-dnk3/src/main/resources/phrases/irradiance-sh/fetch-sh0.frag new file mode 100644 index 00000000..b07cd066 --- /dev/null +++ b/orx-dnk3/src/main/resources/phrases/irradiance-sh/fetch-sh0.frag @@ -0,0 +1,4 @@ +void fetchSH0(samplerBuffer btex, int probeID, out vec3 _SH) { + int offset = probeID * 9; + _SH = texelFetch(btex, offset).rgb; +} \ No newline at end of file diff --git a/orx-dnk3/src/main/resources/phrases/irradiance-sh/gather-sh.frag b/orx-dnk3/src/main/resources/phrases/irradiance-sh/gather-sh.frag new file mode 100644 index 00000000..2bce506a --- /dev/null +++ b/orx-dnk3/src/main/resources/phrases/irradiance-sh/gather-sh.frag @@ -0,0 +1,26 @@ +void gatherSH(samplerBuffer btex, vec3 p, ivec3 probeCounts, vec3 offset, float spacing, out vec3[9] blend) { + vec3[9] c000; + vec3[9] c001; + vec3[9] c010; + vec3[9] c011; + vec3[9] c100; + vec3[9] c101; + vec3[9] c110; + vec3[9] c111; + + vec3 f; + ivec3 io = gridCoordinates(p, f, probeCounts, offset, spacing); + + fetchSH(btex, gridIndex(io + ivec3(0,0,0), probeCounts), c000); + fetchSH(btex, gridIndex(io + ivec3(0,0,1), probeCounts), c001); + fetchSH(btex, gridIndex(io + ivec3(0,1,0), probeCounts), c010); + fetchSH(btex, gridIndex(io + ivec3(0,1,1), probeCounts), c011); + fetchSH(btex, gridIndex(io + ivec3(1,0,0), probeCounts), c100); + fetchSH(btex, gridIndex(io + ivec3(1,0,1), probeCounts), c101); + fetchSH(btex, gridIndex(io + ivec3(1,1,0), probeCounts), c110); + fetchSH(btex, gridIndex(io + ivec3(1,1,1), probeCounts), c111); + + for (int i = 0; i < 9; ++i) { + blend[i] = mix( mix( mix(c000[i], c001[i], f.z), mix(c010[i], c011[i], f.z), f.y), mix( mix(c100[i], c101[i], f.z), mix(c110[i], c111[i], f.z), f.y), f.x); + } +} \ No newline at end of file diff --git a/orx-dnk3/src/main/resources/phrases/irradiance-sh/gather-sh0.frag b/orx-dnk3/src/main/resources/phrases/irradiance-sh/gather-sh0.frag new file mode 100644 index 00000000..28926e59 --- /dev/null +++ b/orx-dnk3/src/main/resources/phrases/irradiance-sh/gather-sh0.frag @@ -0,0 +1,25 @@ +void gatherSH0(samplerBuffer btex, vec3 p, ivec3 probeCounts, vec3 offset, float spacing, out vec3 blend) { + vec3 c000; + vec3 c001; + vec3 c010; + vec3 c011; + vec3 c100; + vec3 c101; + vec3 c110; + vec3 c111; + + vec3 f; + ivec3 io = gridCoordinates(p, f, probeCounts, offset, spacing); + + fetchSH0(btex, gridIndex(io + ivec3(0,0,0), probeCounts), c000); + fetchSH0(btex, gridIndex(io + ivec3(0,0,1), probeCounts), c001); + fetchSH0(btex, gridIndex(io + ivec3(0,1,0), probeCounts), c010); + fetchSH0(btex, gridIndex(io + ivec3(0,1,1), probeCounts), c011); + fetchSH0(btex, gridIndex(io + ivec3(1,0,0), probeCounts), c100); + fetchSH0(btex, gridIndex(io + ivec3(1,0,1), probeCounts), c101); + fetchSH0(btex, gridIndex(io + ivec3(1,1,0), probeCounts), c110); + fetchSH0(btex, gridIndex(io + ivec3(1,1,1), probeCounts), c111); + + blend = mix( mix( mix(c000, c001, f.z), mix(c010, c011, f.z), f.y), mix( mix(c100, c101, f.z), mix(c110, c111, f.z), f.y), f.x); + +} \ No newline at end of file diff --git a/orx-dnk3/src/main/resources/phrases/irradiance-sh/grid-coordinates.frag b/orx-dnk3/src/main/resources/phrases/irradiance-sh/grid-coordinates.frag new file mode 100644 index 00000000..82ec0204 --- /dev/null +++ b/orx-dnk3/src/main/resources/phrases/irradiance-sh/grid-coordinates.frag @@ -0,0 +1,15 @@ +ivec3 gridCoordinates(vec3 p, out vec3 f, ivec3 probeCounts, vec3 offset, float spacing) { + float x = (p.x - offset.x) / spacing; + float y = (p.y - offset.y)/ spacing; + float z = (p.z - offset.z) / spacing; + + int ix = int(floor(x)) + probeCounts.x / 2; + int iy = int(floor(y)) + probeCounts.y / 2; + int iz = int(floor(z)) + probeCounts.z / 2; + + f.x = fract((x)); + f.y = fract((y)); + f.z = fract((z)); + + return ivec3(ix, iy, iz); +} diff --git a/orx-dnk3/src/main/resources/phrases/irradiance-sh/grid-index.frag b/orx-dnk3/src/main/resources/phrases/irradiance-sh/grid-index.frag new file mode 100644 index 00000000..58582e60 --- /dev/null +++ b/orx-dnk3/src/main/resources/phrases/irradiance-sh/grid-index.frag @@ -0,0 +1,4 @@ +int gridIndex(ivec3 p, ivec3 probeCounts) { + ivec3 c = clamp(p, ivec3(0), probeCounts - ivec3(1)); + return c.x + c.y * probeCounts.x + c.z * probeCounts.x * probeCounts.y; +} \ No newline at end of file diff --git a/orx-dnk3/src/main/resources/shaders/cubemap-filters/cubemap-passthrough.frag b/orx-dnk3/src/main/resources/shaders/cubemap-filters/cubemap-passthrough.frag new file mode 100644 index 00000000..4de2509e --- /dev/null +++ b/orx-dnk3/src/main/resources/shaders/cubemap-filters/cubemap-passthrough.frag @@ -0,0 +1,22 @@ +#version 330 + +uniform samplerCube tex0; +uniform vec3 sideUp; +uniform vec3 sideRight; +uniform vec3 sideNormal; +in vec2 v_texCoord0; + +out vec4 o_output; + +#define PI 3.1415926536 + +void main() { + vec3 irradiance = vec3(0.0); + + vec2 uv = (v_texCoord0 - vec2(0.5))*2.0; + vec3 normal = normalize(uv.x * sideRight + uv.y * sideUp + sideNormal); + + o_output.rgb = texture(tex0, normal).rgb; + o_output.a = 1.0; + +} \ No newline at end of file diff --git a/orx-dnk3/src/main/resources/shaders/cubemap-filters/irradiance-convolution.frag b/orx-dnk3/src/main/resources/shaders/cubemap-filters/irradiance-convolution.frag new file mode 100644 index 00000000..044b1ba3 --- /dev/null +++ b/orx-dnk3/src/main/resources/shaders/cubemap-filters/irradiance-convolution.frag @@ -0,0 +1,40 @@ +#version 330 + +uniform samplerCube tex0; +uniform vec3 sideUp; +uniform vec3 sideRight; +uniform vec3 sideNormal; +in vec2 v_texCoord0; + +out vec4 o_output; + +#define PI 3.1415926536 + +void main() { + vec3 irradiance = vec3(0.0); + + vec2 uv = (v_texCoord0 - vec2(0.5))*2.0; + vec3 normal = normalize(uv.x * sideRight + uv.y * sideUp + sideNormal); + + vec3 up = vec3(0.0, 1.0, 0.0); + vec3 right = cross(up, normal); + up = cross(normal, right); + + float sampleDelta = 0.025; + int nrSamples = 0; + for(float phi = 0.0; phi < 2.0 * PI; phi += sampleDelta) { + for(float theta = 0.0; theta < 0.5 * PI; theta += sampleDelta) { + // spherical to cartesian (in tangent space) + vec3 tangentSample = vec3(sin(theta) * cos(phi), sin(theta) * sin(phi), cos(theta)); + // tangent space to world + vec3 sampleVec = tangentSample.x * right + tangentSample.y * up + tangentSample.z * normal; + + irradiance += texture(tex0, sampleVec).rgb * cos(theta) * sin(theta); + nrSamples++; + } + } + irradiance = PI * irradiance * (1.0 / float(nrSamples)); + o_output.rgb = irradiance.rgb; + o_output.a = 1.0; + +} \ No newline at end of file diff --git a/orx-dnk3/src/main/resources/shaders/cubemap-filters/spherical-harmonics.frag b/orx-dnk3/src/main/resources/shaders/cubemap-filters/spherical-harmonics.frag new file mode 100644 index 00000000..882d7ba7 --- /dev/null +++ b/orx-dnk3/src/main/resources/shaders/cubemap-filters/spherical-harmonics.frag @@ -0,0 +1,11 @@ +uniform samplerCube tex0; + +in v_texCoord0; +uniform vec2 targetSize; + +out o_term0; +out o_term1; +out o_term2; +void main() { + +} \ No newline at end of file diff --git a/orx-dnk3/src/main/resources/shaders/screenspace-reflections.frag b/orx-dnk3/src/main/resources/shaders/screenspace-reflections.frag new file mode 100644 index 00000000..0259fc8d --- /dev/null +++ b/orx-dnk3/src/main/resources/shaders/screenspace-reflections.frag @@ -0,0 +1,349 @@ +#version 330 +// --- varyings --- +in vec2 v_texCoord0; + +// --- G buffer --- +uniform sampler2D colors; +uniform sampler2D projDepth; +uniform sampler2D normals; + +// --- transforms --- +uniform mat4 projection; +uniform mat4 projectionMatrixInverse; + +// --- output --- +layout(location = 0) out vec4 o_color; + + +// --- parameters --- +uniform float jitterOriginGain; +uniform int iterationLimit; +uniform float distanceLimit; +uniform float gain; +uniform float borderWidth; + +float distanceSquared(vec2 a, vec2 b) { + vec2 d = b-a; + return dot(d,d); +} + +#pragma import org.openrndr.extra.shaderphrases.phrases.Depth.projectionToViewCoordinate; +#pragma import org.openrndr.extra.shaderphrases.phrases.Depth.projectionToViewDepth; + +#pragma import org.openrndr.extra.noise.phrases.NoisePhrasesKt.phraseHash22; + + +// this is from http://casual-effects.blogspot.nl/2014/08/screen-space-ray-tracing.html + +void swap(inout float a, inout float b) { + float temp = a; + a = b; + b = temp; +} + + +bool traceScreenSpaceRay1 + (vec3 csOrigin, + vec3 csDirection, + mat4x4 projectToPixelMatrix, + sampler2D csZBuffer, + vec2 csZBufferSize, + float csZThickness, + float nearPlaneZ, + float stride, + float jitterFraction, + float maxSteps, + in float maxRayTraceDistance, + out vec2 hitPixel, + out vec3 csHitPoint, + out vec3 csHitNormal +// ,out vec3 debugColor + ) { + vec3 debugColor = vec3(0); + // Clip ray to a near plane in 3D (doesn't have to be *the* near plane, although that would be a good idea) + float rayLength = ((csOrigin.z + csDirection.z * maxRayTraceDistance) > nearPlaneZ) ? + (nearPlaneZ - csOrigin.z) / csDirection.z : + maxRayTraceDistance; + vec3 csEndPoint = csDirection * rayLength + csOrigin; + + // Project into screen space + vec4 H0 = projectToPixelMatrix * vec4(csOrigin, 1.0); + vec4 H1 = projectToPixelMatrix * vec4(csEndPoint, 1.0); + + // There are a lot of divisions by w that can be turned into multiplications + // at some minor precision loss...and we need to interpolate these 1/w values + // anyway. + // + // Because the caller was required to clip to the near plane, + // this homogeneous division (projecting from 4D to 2D) is guaranteed + // to succeed. + float k0 = 1.0 / H0.w; + float k1 = 1.0 / H1.w; + + // Switch the original points to values that interpolate linearly in 2D + vec3 Q0 = csOrigin * k0; + vec3 Q1 = csEndPoint * k1; + + // Screen-space endpoints + vec2 P0 = H0.xy * k0; + vec2 P1 = H1.xy * k1; + + // [Optional clipping to frustum sides here] + + // Initialize to off screen + hitPixel = vec2(-1.0, -1.0); + + // If the line is degenerate, make it cover at least one pixel + // to avoid handling zero-pixel extent as a special case later + P1 += vec2((distanceSquared(P0, P1) < 0.0001) ? 0.01 : 0.0); + + vec2 delta = P1 - P0; + + // Permute so that the primary iteration is in x to reduce + // large branches later + bool permute = (abs(delta.x) < abs(delta.y)); + if (permute) { + // More-vertical line. Create a permutation that swaps x and y in the output + // by directly swizzling the inputs. + delta = delta.yx; + P1 = P1.yx; + P0 = P0.yx; + } + + // From now on, "x" is the primary iteration direction and "y" is the secondary one + float stepDirection = sign(delta.x); + float invdx = stepDirection / delta.x; + vec2 dP = vec2(stepDirection, invdx * delta.y); + + // Track the derivatives of Q and k + vec3 dQ = (Q1 - Q0) * invdx; + float dk = (k1 - k0) * invdx; + + // Because we test 1/2 a texel forward along the ray, on the very last iteration + // the interpolation can go past the end of the ray. Use these bounds to clamp it. + float zMin = min(csEndPoint.z, csOrigin.z); + float zMax = max(csEndPoint.z, csOrigin.z); + + // Scale derivatives by the desired pixel stride + dP *= stride; dQ *= stride; dk *= stride; + + // Offset the starting values by the jitter fraction + P0 += dP * jitterFraction; Q0 += dQ * jitterFraction; k0 += dk * jitterFraction; + + // Slide P from P0 to P1, (now-homogeneous) Q from Q0 to Q1, and k from k0 to k1 + vec3 Q = Q0; + float k = k0; + + // We track the ray depth at +/- 1/2 pixel to treat pixels as clip-space solid + // voxels. Because the depth at -1/2 for a given pixel will be the same as at + // +1/2 for the previous iteration, we actually only have to compute one value + // per iteration. + float prevZMaxEstimate = csOrigin.z; + float stepCount = 0.0; + float rayZMax = prevZMaxEstimate, rayZMin = prevZMaxEstimate; + float sceneZMax = rayZMax + 1e4; + + // P1.x is never modified after this point, so pre-scale it by + // the step direction for a signed comparison + float end = P1.x * stepDirection; + + // We only advance the z field of Q in the inner loop, since + // Q.xy is never used until after the loop terminates. + + vec2 P; + for (P = P0; + ((P.x * stepDirection) <= end) && + (stepCount < maxSteps) && + ((rayZMax < sceneZMax - csZThickness) || + (rayZMin > sceneZMax)) && + (sceneZMax != 0.0); + P += dP, Q.z += dQ.z, k += dk, stepCount += 1.0) { + + // The depth range that the ray covers within this loop + // iteration. Assume that the ray is moving in increasing z + // and swap if backwards. Because one end of the interval is + // shared between adjacent iterations, we track the previous + // value and then swap as needed to ensure correct ordering + rayZMin = prevZMaxEstimate; + + // Compute the value at 1/2 step into the future + rayZMax = (dQ.z * 0.5 + Q.z) / (dk * 0.5 + k); + + // -- this is not in the other implementation + rayZMax = clamp(rayZMax, zMin, zMax); + + prevZMaxEstimate = rayZMax; + + // Since we don't know if the ray is stepping forward or backward in depth, + // maybe swap. Note that we preserve our original z "max" estimate first. + if (rayZMin > rayZMax) { swap(rayZMin, rayZMax); } + + // Camera-space z of the background + hitPixel = permute ? P.yx : P; + + vec4 depthData = texelFetch(csZBuffer, ivec2(hitPixel), 0); + sceneZMax = projectionToViewCoordinate(v_texCoord0, depthData.x, projectionMatrixInverse).z; + + } // pixel on ray + + // Undo the last increment, which ran after the test variables + // were set up. + P -= dP; Q.z -= dQ.z; k -= dk; stepCount -= 1.0; + + bool hit = (rayZMax >= sceneZMax - csZThickness) && (rayZMin <= sceneZMax); + + // If using non-unit stride and we hit a depth surface... + if ((stride > 1) && hit) { + // Refine the hit point within the last large-stride step + + // Retreat one whole stride step from the previous loop so that + // we can re-run that iteration at finer scale + P -= dP; Q.z -= dQ.z; k -= dk; stepCount -= 1.0; + + // Take the derivatives back to single-pixel stride + float invStride = 1.0 / stride; + dP *= invStride; dQ.z *= invStride; dk *= invStride; + + // For this test, we don't bother checking thickness or passing the end, since we KNOW there will + // be a hit point. As soon as + // the ray passes behind an object, call it a hit. Advance (stride + 1) steps to fully check this + // interval (we could skip the very first iteration, but then we'd need identical code to prime the loop) + float refinementStepCount = 0; + + // This is the current sample point's z-value, taken back to camera space + prevZMaxEstimate = Q.z / k; + rayZMin = prevZMaxEstimate; + + // Ensure that the FOR-loop test passes on the first iteration since we + // won't have a valid value of sceneZMax to test. + sceneZMax = rayZMin - 1e7; + + for (; + (refinementStepCount <= stride*1.4) && + (rayZMin > sceneZMax) && (sceneZMax != 0.0); + P += dP, Q.z += dQ.z, k += dk, refinementStepCount += 1.0) { + + rayZMin = prevZMaxEstimate; + + // Compute the ray camera-space Z value at 1/2 fine step (pixel) into the future + rayZMax = (dQ.z * 0.5 + Q.z) / (dk * 0.5 + k); + rayZMax = clamp(rayZMax, zMin, zMax); + + prevZMaxEstimate = rayZMax; + rayZMin = min(rayZMax, rayZMin); + + hitPixel = permute ? P.yx : P; + + vec4 depthData = texelFetch(csZBuffer, ivec2(hitPixel), 0); + sceneZMax = projectionToViewCoordinate(v_texCoord0, depthData.x, projectionMatrixInverse).z; + + + csHitNormal = texelFetch(normals, ivec2(hitPixel), 0).xyz; + +// sceneZMax = texelFetch(csZBuffer, ivec2(hitPixel), 0).r; + + } + + // Undo the last increment, which happened after the test variables were set up + Q.z -= dQ.z; refinementStepCount -= 1; + + // Count the refinement steps as fractions of the original stride. Save a register + // by not retaining invStride until here + stepCount += refinementStepCount / stride; + // debugColor = vec3(refinementStepCount / stride); + } // refinement + + Q.xy += dQ.xy * stepCount; + csHitPoint = Q * (1.0 / k); + + // Support debugging. This will compile away if debugColor is unused + if ((P.x * stepDirection) > end) { + // Hit the max ray distance -> blue + debugColor = vec3(0,0,1); + } else if (stepCount >= maxSteps) { + // Ran out of steps -> red + debugColor = vec3(1,0,0); + } else if (sceneZMax == 0.0) { + // Went off screen -> yellow + debugColor = vec3(1,1,0); + } else { + // Encountered a valid hit -> green + // ((rayZMax >= sceneZMax - csZThickness) && (rayZMin <= sceneZMax)) + debugColor = vec3(0,1,0); + } + + // Does the last point discovered represent a valid hit? + return hit; +} + + +void main() { + vec2 hitPixel = vec2(0.0, 0.0); + vec3 hitPoint = vec3(0.0, 0.0, 0.0); + vec3 hitNormal = vec3(0.0, 0.0, 0.0); + + vec2 jitter = abs(hash22(v_texCoord0)); + + + vec2 ts = vec2(textureSize(projDepth, 0).xy); + vec3 viewNormal = normalize(texture(normals, v_texCoord0).xyz);// + (texture(noise, v_texCoord0*0.1).xyz - 0.5) * 0.0; + float depth = texture(projDepth, v_texCoord0).r; + vec3 viewPos = projectionToViewCoordinate(v_texCoord0, depth, projectionMatrixInverse); + + + vec3 reflected = normalize(reflect(normalize(viewPos), normalize(-viewNormal))); + + + float angle = abs(dot(reflected, viewNormal)); + float frontalFade = clamp(-reflected.z,0, 1); + if ( true ) { + bool hit = traceScreenSpaceRay1( + viewPos, + reflected, + projection, + projDepth, + ts, + 0.1, + 0.0, // near plane z + 1.0,// + projPos.z*2.0, // stride + 10.0, // jitterfraction + iterationLimit*8,// + int((1.0-projPos.z)*iterationLimit), + 100.0, // max distance + + hitPixel, + hitPoint, hitNormal); + + float distanceFade = 1.0;//max( 0.0, (distanceLimit -length(hitPoint-viewPos))/ distanceLimit); + vec4 p = projection * vec4(hitPoint, 1.0); + + float k = 1.0 / p.w; + + vec2 pos = vec2(p.xy*k); + vec2 ad = vec2(ts/2- abs(pos - ts/2)); + float borderFade = 1.0; //smoothstep(0, borderWidth, min(ad.x, ad.y)); + + float l = 0.0; + int l0 = int(l); + int l1 = l0 + 1; + + float lf = l - l0; + + vec4 reflectedColor0 = texelFetch(colors, ivec2(p.xy*k)/(1< 10.0) { + traverse = direction*10.0; + worldCoordinate = cameraPosition - traverse; + } + + int steps = min(100, int(length(traverse) / 0.1)); + vec3 step = traverse / steps; + + vec3 marchPosition = worldCoordinate; + vec3 accumulated = inputColor; + float jitter = hash22(v_texCoord0).x; + marchPosition += jitter * step*0.5; + for (int stepIndex = 0; stepIndex < steps; ++stepIndex) { + float density = pow(abs(simplex31(marchPosition*0.25)), 4.0) * 0.1; + vec3 sh0; + gatherSH0(shMap, marchPosition, shMapDimensions, shMapOffset, shMapSpacing, sh0); + accumulated = accumulated * (1.0-density) + sh0 * density; + marchPosition += step; + } + o_output = vec4(accumulated, 1.0); +} diff --git a/orx-noise/src/main/kotlin/phrases/NoisePhrases.kt b/orx-noise/src/main/kotlin/phrases/NoisePhrases.kt index 006e5444..62fea141 100644 --- a/orx-noise/src/main/kotlin/phrases/NoisePhrases.kt +++ b/orx-noise/src/main/kotlin/phrases/NoisePhrases.kt @@ -11,6 +11,15 @@ val phraseHash22 = """vec2 hash22(vec2 p) { val phraseHash21 = "float hash21(vec2 p) { return fract(1e4 * sin(17.0 * p.x + p.y * 0.1) * (0.1 + abs(sin(p.y * 13.0 + p.x)))); }" +val phraseHash33 = """ +#define MOD3 vec3(.1031,.11369,.13787) +vec3 hash33(vec3 p3) { + p3 = fract(p3 * MOD3); + p3 += dot(p3, p3.yxz+19.19); + return -1.0 + 2.0 * fract(vec3((p3.x + p3.y)*p3.z, (p3.x+p3.z)*p3.y, (p3.y+p3.z)*p3.x)); +} +""" + val phraseValueNoise21 = """ float noise(vec2 x) { diff --git a/orx-noise/src/main/kotlin/phrases/Simplex.kt b/orx-noise/src/main/kotlin/phrases/Simplex.kt new file mode 100644 index 00000000..d3edb73b --- /dev/null +++ b/orx-noise/src/main/kotlin/phrases/Simplex.kt @@ -0,0 +1,7 @@ +@file:ShaderPhrases([]) +package org.openrndr.extra.noise.phrases + +import org.openrndr.extra.shaderphrases.annotations.ShaderPhrases +import org.openrndr.extra.shaderphrases.phraseResource + +val phraseSimplex3 by phraseResource("/org/openrndr/extra/noise/phrases/gl3/simplex-3.frag") \ No newline at end of file diff --git a/orx-noise/src/main/resources/org/openrndr/extra/noise/phrases/gl3/simplex-3.frag b/orx-noise/src/main/resources/org/openrndr/extra/noise/phrases/gl3/simplex-3.frag new file mode 100644 index 00000000..f1d52d8f --- /dev/null +++ b/orx-noise/src/main/resources/org/openrndr/extra/noise/phrases/gl3/simplex-3.frag @@ -0,0 +1,22 @@ +#pragma import org.openrndr.extra.noise.phrases.NoisePhrasesKt.phraseHash33; + +float simplex31(vec3 p) { + const float K1 = 0.333333333; + const float K2 = 0.166666667; + + vec3 i = floor(p + (p.x + p.y + p.z) * K1); + vec3 d0 = p - (i - (i.x + i.y + i.z) * K2); + + // thx nikita: https://www.shadertoy.com/view/XsX3zB + vec3 e = step(vec3(0.0), d0 - d0.yzx); + vec3 i1 = e * (1.0 - e.zxy); + vec3 i2 = 1.0 - e.zxy * (1.0 - e); + + vec3 d1 = d0 - (i1 - 1.0 * K2); + vec3 d2 = d0 - (i2 - 2.0 * K2); + vec3 d3 = d0 - (1.0 - 3.0 * K2); + + vec4 h = max(0.6 - vec4(dot(d0, d0), dot(d1, d1), dot(d2, d2), dot(d3, d3)), 0.0); + vec4 n = h * h * h * h * vec4(dot(d0, hash33(i)), dot(d1, hash33(i + i1)), dot(d2, hash33(i + i2)), dot(d3, hash33(i + 1.0))); + return dot(vec4(31.316), n); +} \ No newline at end of file diff --git a/orx-shader-phrases/src/main/kotlin/PhraseResource.kt b/orx-shader-phrases/src/main/kotlin/PhraseResource.kt index 056bcb6f..8726e895 100644 --- a/orx-shader-phrases/src/main/kotlin/PhraseResource.kt +++ b/orx-shader-phrases/src/main/kotlin/PhraseResource.kt @@ -16,6 +16,7 @@ class PhraseResource(private val resourceUrl: String) : ReadOnlyProperty { diff --git a/orx-shader-phrases/src/main/kotlin/ShaderPreprocessor.kt b/orx-shader-phrases/src/main/kotlin/ShaderPreprocessor.kt index 21da323a..26e547f1 100644 --- a/orx-shader-phrases/src/main/kotlin/ShaderPreprocessor.kt +++ b/orx-shader-phrases/src/main/kotlin/ShaderPreprocessor.kt @@ -9,50 +9,56 @@ import org.openrndr.extra.shaderphrases.annotations.ShaderPhrases * @param source GLSL source code encoded as string * @return GLSL source code with injected shader phrases */ -fun preprocessShader(source: String): String { +fun preprocessShader(source: String, symbols: Set = emptySet()): String { + val newSymbols = mutableSetOf() + newSymbols.addAll(symbols) + val lines = source.split("\n") val processed = lines.mapIndexed { index, it -> if (it.startsWith("#pragma import")) { val tokens = it.split(" ") - val full = tokens[2] - val fullTokens = full.split(".") + val symbol = tokens[2].trim().replace(";", "") + val fullTokens = symbol.split(".") val fieldName = fullTokens.last().replace(";", "").trim() val packageClassTokens = fullTokens.dropLast(1) val packageClass = packageClassTokens.joinToString(".") - try { - /* Note that JVM-style reflection is used here because of short-comings in the Kotlin reflection + if (symbol !in newSymbols) { + newSymbols.add(symbol) + try { + /* Note that JVM-style reflection is used here because of short-comings in the Kotlin reflection library (as of 1.3.61), most notably reflection support for file facades is missing. */ - val c = Class.forName(packageClass) - if (c.annotations.any { it.annotationClass == ShaderPhrases::class }) { - if (fieldName == "*") { - c.declaredMethods.filter { it.returnType.name == "java.lang.String" }.map { - "/* imported from $packageClass.$it */\n${it.invoke(null)}\n" - }.joinToString("\n") + - - c.declaredFields.filter { it.type.name == "java.lang.String" }.map { - "/* imported from $packageClass.$it */\n${it.get(null)}\n" - }.joinToString("\n") - } else { - var result:String? - try { - val methodName = "get${fieldName.take(1).toUpperCase() + fieldName.drop(1)}" - result = c.getMethod(methodName).invoke(null) as String - result - } catch (e: NoSuchMethodException) { + val c = Class.forName(packageClass) + if (c.annotations.any { it.annotationClass == ShaderPhrases::class }) { + if (fieldName == "*") { + c.declaredMethods.filter { it.returnType.name == "java.lang.String" }.map { + "/* imported from $packageClass.$it */\n${it.invoke(null)}\n" + }.joinToString("\n") + + c.declaredFields.filter { it.type.name == "java.lang.String" }.map { + "/* imported from $packageClass.$it */\n${it.get(null)}\n" + }.joinToString("\n") + } else { + var result: String? try { - result = c.getDeclaredField(fieldName).get(null) as String - result - } catch (e: NoSuchFieldException) { - error("field \"$fieldName\" not found in \"#pragma import $packageClass.$fieldName\" on line ${index + 1}") + val methodName = "get${fieldName.take(1).toUpperCase() + fieldName.drop(1)}" + result = preprocessShader(c.getMethod(methodName).invoke(null) as String, newSymbols) + } catch (e: NoSuchMethodException) { + try { + result = preprocessShader(c.getDeclaredField(fieldName).get(null) as String, newSymbols) + } catch (e: NoSuchFieldException) { + error("field \"$fieldName\" not found in \"#pragma import $packageClass.$fieldName\" on line ${index + 1}") + } } + result } + } else { + throw IllegalArgumentException("class $packageClass has no ShaderPhrases annotation") } - } else { - throw IllegalArgumentException("class $packageClass has no ShaderPhrases annotation") + } catch (e: ClassNotFoundException) { + error("class \"$packageClass\" not found in \"#pragma import $packageClass\" on line ${index + 1}") } - } catch (e: ClassNotFoundException) { - error("class \"$packageClass\" not found in \"#pragma import $packageClass\" on line ${index + 1}") + } else { + "" } } else { it @@ -67,6 +73,6 @@ fun preprocessShader(source: String): String { * @param url url pointing to GLSL shader source * @return GLSL source code with injected shader phrases */ -fun preprocessShaderFromUrl(url: String): String { - return preprocessShader(codeFromURL(url)) +fun preprocessShaderFromUrl(url: String, symbols: Set): String { + return preprocessShader(codeFromURL(url), symbols) } \ No newline at end of file diff --git a/orx-shader-phrases/src/main/kotlin/phrases/Depth.kt b/orx-shader-phrases/src/main/kotlin/phrases/Depth.kt index f5628375..8124586c 100644 --- a/orx-shader-phrases/src/main/kotlin/phrases/Depth.kt +++ b/orx-shader-phrases/src/main/kotlin/phrases/Depth.kt @@ -4,6 +4,7 @@ package org.openrndr.extra.shaderphrases.phrases import org.openrndr.extra.shaderphrases.annotations.ShaderPhrases + /** * phrase for conversion from view to projection depth * @param viewDepth depth in view space ([0.0 .. -far]) @@ -19,15 +20,23 @@ float viewToProjectionDepth(float viewDepth, mat4 projection) { """ /** - * phrase for conversion from view to projection depth + * phrase for conversion from projection to view depth * @param projectionDepth depth in projection space ([0.0 .. 1.0]) * @param projectionInversed inverse of the projection matrix * @return depth in view space ([0.0 .. -far]] */ const val projectionToViewDepth = """ float projectionToViewDepth(float projectionDepth, mat4 projectionInverse) { - float z = projectionDepth * projectionInverse[2].z + projectionInverse[3].z; - float w = projectionDepth * projectionInverse[2].w + projectionInverse[3].w; + float z = (projectionDepth*2.0-1.0) * projectionInverse[2].z + projectionInverse[3].z; + float w = (projectionDepth*2.0-1.0) * projectionInverse[2].w + projectionInverse[3].w; return z / w; } """ + +const val projectionToViewCoordinate = """ +vec3 projectionToViewCoordinate(vec2 uv, float projectionDepth, mat4 projectionInverse) { + vec4 projectionCoordinate = vec4(uv * 2.0 - 1.0, projectionDepth*2.0-1.0, 1.0); + vec4 viewCoordinate = projectionInverse * projectionCoordinate; + return viewCoordinate.xyz / viewCoordinate.w; +} +""" diff --git a/orx-shader-phrases/src/main/kotlin/phrases/NormalMapping.kt b/orx-shader-phrases/src/main/kotlin/phrases/NormalMapping.kt new file mode 100644 index 00000000..f2f90416 --- /dev/null +++ b/orx-shader-phrases/src/main/kotlin/phrases/NormalMapping.kt @@ -0,0 +1,13 @@ +@file:JvmName("NormalMapping") +@file:ShaderPhrases + +package org.openrndr.extra.shaderphrases.phrases + +import org.openrndr.extra.shaderphrases.annotations.ShaderPhrases + +val phraseTbnMatrix = """ +mat3 tbnMatrix(vec4 tangent, vec3 normal) { + vec3 bitangent = cross(normal, tangent.xyz) * tangent.w; + return mat3(tangent.xyz, bitangent, normal); +} +""".trimIndent() \ No newline at end of file