[orx-dnk3] Add feature architecture and post processing effects
This commit is contained in:
@@ -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"
|
||||
|
||||
BIN
demo-data/gltf-models/camera/Scene.glb
Normal file
BIN
demo-data/gltf-models/camera/Scene.glb
Normal file
Binary file not shown.
BIN
demo-data/gltf-models/irradiance-probes/model.glb
Normal file
BIN
demo-data/gltf-models/irradiance-probes/model.glb
Normal file
Binary file not shown.
@@ -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)
|
||||
|
||||
@@ -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"))
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
118
orx-dnk3/src/demo/kotlin/DemoIrrProbe01.kt
Normal file
118
orx-dnk3/src/demo/kotlin/DemoIrrProbe01.kt
Normal file
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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_
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
package org.openrndr.extra.dnk3
|
||||
|
||||
fun dryRenderer() : SceneRenderer {
|
||||
val sr = SceneRenderer()
|
||||
return sr
|
||||
}
|
||||
@@ -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<VertexBuffer>,
|
||||
@@ -14,19 +15,28 @@ class Geometry(val vertexBuffers: List<VertexBuffer>,
|
||||
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<VertexBuffer>)
|
||||
@@ -36,6 +46,9 @@ class Mesh(primitives: List<MeshPrimitive>) : MeshBase(primitives) {
|
||||
override fun toString(): String {
|
||||
return "Mesh(primitives: $primitives)"
|
||||
}
|
||||
override fun hashCode(): Int {
|
||||
return primitives.hashCode()
|
||||
}
|
||||
}
|
||||
|
||||
class SkinnedMesh(primitives: List<MeshPrimitive>,
|
||||
@@ -49,10 +62,7 @@ class InstancedMesh(primitives: List<MeshPrimitive>,
|
||||
var attributes: List<VertexBuffer>) : 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()
|
||||
}
|
||||
}
|
||||
@@ -24,8 +24,6 @@ abstract class FacetCombiner(val facets: Set<FacetType>, val targetOutput: Strin
|
||||
override fun toString(): String {
|
||||
return "FacetCombiner(facets=$facets, targetOutput='$targetOutput')"
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
abstract class ColorBufferFacetCombiner(facets: Set<FacetType>,
|
||||
@@ -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);
|
||||
|
||||
|
||||
"""
|
||||
}
|
||||
13
orx-dnk3/src/main/kotlin/Feature.kt
Normal file
13
orx-dnk3/src/main/kotlin/Feature.kt
Normal file
@@ -0,0 +1,13 @@
|
||||
package org.openrndr.extra.dnk3
|
||||
|
||||
import org.openrndr.draw.Drawer
|
||||
|
||||
interface Feature {
|
||||
fun <T : Feature> update(
|
||||
drawer: Drawer,
|
||||
sceneRenderer: SceneRenderer,
|
||||
scene: Scene,
|
||||
feature: T,
|
||||
context: RenderContext
|
||||
)
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<NodeContent<Light>>,
|
||||
val fogs: List<NodeContent<Fog>>,
|
||||
val shadowMaps: Map<ShadowLight, RenderTarget>,
|
||||
val meshCubemaps: Map<Mesh, Cubemap>
|
||||
)
|
||||
val meshCubemaps: Map<Mesh, Cubemap>,
|
||||
val irradianceProbeCount: Int
|
||||
) {
|
||||
var irradianceSH: IrradianceSH? = null
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
data class PrimitiveContext(val hasNormalAttribute: Boolean, val hasSkinning: Boolean)
|
||||
|
||||
|
||||
@@ -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<ContextKey, ShadeStyle>()
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,16 +9,16 @@ interface PostStep {
|
||||
fun apply(buffers: MutableMap<String, ColorBuffer>, postContext: PostContext)
|
||||
}
|
||||
|
||||
class FilterPostStep(val outputScale: Double,
|
||||
val filter: Filter,
|
||||
class FilterPostStep<T:Filter>(val outputScale: Double,
|
||||
val filter: T,
|
||||
val inputs: List<String>,
|
||||
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<String, ColorBuffer>, 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(),
|
||||
|
||||
@@ -13,6 +13,8 @@ data class RenderPass(val combiners: List<FacetCombiner>,
|
||||
|
||||
|
||||
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())
|
||||
|
||||
@@ -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<Feature>()
|
||||
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<Entity> = mutableListOf()
|
||||
var parent: SceneNode? = null
|
||||
@@ -14,8 +24,24 @@ open class SceneNode() {
|
||||
var worldTransform = Matrix44.IDENTITY
|
||||
val children = mutableListOf<SceneNode>()
|
||||
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<T>(val node: SceneNode, val content: T) {
|
||||
operator fun component1() = node
|
||||
operator fun component2() = content
|
||||
|
||||
@@ -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<NodeContent<Light>>,
|
||||
val meshes: List<NodeContent<Mesh>>,
|
||||
val skinnedMeshes: List<NodeContent<SkinnedMesh>>,
|
||||
val instancedMeshes: List<NodeContent<InstancedMesh>>,
|
||||
val fogs: List<NodeContent<Fog>>
|
||||
)
|
||||
|
||||
class SceneRenderer {
|
||||
|
||||
@@ -20,7 +29,6 @@ class SceneRenderer {
|
||||
var shadowLightTargets = mutableMapOf<ShadowLight, RenderTarget>()
|
||||
var meshCubemaps = mutableMapOf<Mesh, Cubemap>()
|
||||
|
||||
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<NodeContent<Mesh>>,
|
||||
instancedMeshes: List<NodeContent<InstancedMesh>>,
|
||||
skinnedMeshes: List<NodeContent<SkinnedMesh>>
|
||||
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())
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
187
orx-dnk3/src/main/kotlin/cubemap/CubemapFilter.kt
Normal file
187
orx-dnk3/src/main/kotlin/cubemap/CubemapFilter.kt
Normal file
@@ -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<String, Any>()
|
||||
var padding = 0
|
||||
|
||||
var depthBufferOut: DepthBuffer? = null
|
||||
|
||||
companion object {
|
||||
val filterVertexCode: String get() = Driver.instance.internalShaderResource("filter.vert")
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
open fun apply(source: Array<Cubemap>, target: Array<Cubemap>) {
|
||||
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<Cubemap>, 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<Vector2>)
|
||||
is Vector3 -> shader.uniform(uniform, value as Array<Vector3>)
|
||||
is Vector4 -> shader.uniform(uniform, value as Array<Vector4>)
|
||||
else -> throw IllegalArgumentException("unsupported array value: ${value[0]!!::class.java}")
|
||||
//is ColorRGBa -> shader.uniform(uniform, value as Array<ColorRGBa>)
|
||||
}
|
||||
|
||||
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<Cubemap>) = apply(arrayOf(source), target)
|
||||
fun apply(source: Array<Cubemap>, target: Cubemap) = apply(source, arrayOf(target))
|
||||
|
||||
fun untrack() {
|
||||
shader?.let { Session.active.untrack(shader) }
|
||||
}
|
||||
|
||||
protected val format get() = filterQuadFormat
|
||||
}
|
||||
6
orx-dnk3/src/main/kotlin/cubemap/CubemapPassthrough.kt
Normal file
6
orx-dnk3/src/main/kotlin/cubemap/CubemapPassthrough.kt
Normal file
@@ -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")))
|
||||
@@ -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")))
|
||||
187
orx-dnk3/src/main/kotlin/cubemap/SphericalHarmonics.kt
Normal file
187
orx-dnk3/src/main/kotlin/cubemap/SphericalHarmonics.kt
Normal file
@@ -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<Vector3> {
|
||||
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<Vector3> {
|
||||
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>): 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")
|
||||
109
orx-dnk3/src/main/kotlin/features/IrradianceSH.kt
Normal file
109
orx-dnk3/src/main/kotlin/features/IrradianceSH.kt
Normal file
@@ -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 <T : Feature> 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"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<String, Int>, val indices: Int?, val mode: Int?, val material: Int) {
|
||||
data class GltfPrimitive(val attributes: LinkedHashMap<String, Int>, 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<String, Int>, val indices: Int
|
||||
}
|
||||
}
|
||||
|
||||
class GltfMesh(val primitives: List<GltfPrimitive>, val name: String) {
|
||||
data class GltfMesh(val primitives: List<GltfPrimitive>, val name: String) {
|
||||
fun createDrawCommands(gltfFile: GltfFile): List<GltfDrawCommand> {
|
||||
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<GltfChannel>, val samplers: List<GltfAnimationSampler>)
|
||||
class GltfAnimationSampler(val input: Int, val interpolation: String, val output: Int)
|
||||
data class GltfAnimation(val name: String?, val channels: List<GltfChannel>, val samplers: List<GltfAnimationSampler>)
|
||||
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<KHRLightsPunctualLight>)
|
||||
data class KHRLightsPunctual(val lights: List<KHRLightsPunctualLight>)
|
||||
|
||||
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?,
|
||||
|
||||
@@ -15,13 +15,10 @@ import java.nio.ByteOrder
|
||||
import kotlin.reflect.KMutableProperty0
|
||||
|
||||
class SceneAnimation(var channels: List<AnimationChannel>) {
|
||||
|
||||
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<Quaternion>,
|
||||
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<GltfMaterial, Material>()
|
||||
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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
38
orx-dnk3/src/main/kotlin/post/ScreenspaceReflections.kt
Normal file
38
orx-dnk3/src/main/kotlin/post/ScreenspaceReflections.kt
Normal file
@@ -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
|
||||
}
|
||||
}
|
||||
45
orx-dnk3/src/main/kotlin/post/VolumetricIrradiance.kt
Normal file
45
orx-dnk3/src/main/kotlin/post/VolumetricIrradiance.kt
Normal file
@@ -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<ColorBuffer>, target: Array<ColorBuffer>) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
56
orx-dnk3/src/main/kotlin/query/Query.kt
Normal file
56
orx-dnk3/src/main/kotlin/query/Query.kt
Normal file
@@ -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<Material> {
|
||||
return root.allMaterials()
|
||||
}
|
||||
|
||||
fun SceneNode.allMaterials(): Set<Material> {
|
||||
val materials = mutableSetOf<Material>()
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
8
orx-dnk3/src/main/kotlin/renderers/DryRenderer.kt
Normal file
8
orx-dnk3/src/main/kotlin/renderers/DryRenderer.kt
Normal file
@@ -0,0 +1,8 @@
|
||||
package org.openrndr.extra.dnk3.renderers
|
||||
|
||||
import org.openrndr.extra.dnk3.SceneRenderer
|
||||
|
||||
fun dryRenderer() : SceneRenderer {
|
||||
val sr = SceneRenderer()
|
||||
return sr
|
||||
}
|
||||
22
orx-dnk3/src/main/kotlin/renderers/PostRenderer.kt
Normal file
22
orx-dnk3/src/main/kotlin/renderers/PostRenderer.kt
Normal file
@@ -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
|
||||
}
|
||||
@@ -12,7 +12,7 @@ fun segmentContourRenderer(multisample: BufferMultisample = BufferMultisample.Di
|
||||
sr.outputPasses.clear()
|
||||
sr.outputPasses.add(
|
||||
RenderPass(
|
||||
listOf(FragmentIDFacet()),
|
||||
listOf(LDRColorFacet(),FragmentIDFacet()),
|
||||
multisample = multisample
|
||||
)
|
||||
)
|
||||
|
||||
98
orx-dnk3/src/main/kotlin/tools/MeshCollapse.kt
Normal file
98
orx-dnk3/src/main/kotlin/tools/MeshCollapse.kt
Normal file
@@ -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<VertexFormat>,
|
||||
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())
|
||||
}
|
||||
}
|
||||
84
orx-dnk3/src/main/kotlin/tools/Skybox.kt
Normal file
84
orx-dnk3/src/main/kotlin/tools/Skybox.kt
Normal file
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
void fetchSH0(samplerBuffer btex, int probeID, out vec3 _SH) {
|
||||
int offset = probeID * 9;
|
||||
_SH = texelFetch(btex, offset).rgb;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
uniform samplerCube tex0;
|
||||
|
||||
in v_texCoord0;
|
||||
uniform vec2 targetSize;
|
||||
|
||||
out o_term0;
|
||||
out o_term1;
|
||||
out o_term2;
|
||||
void main() {
|
||||
|
||||
}
|
||||
349
orx-dnk3/src/main/resources/shaders/screenspace-reflections.frag
Normal file
349
orx-dnk3/src/main/resources/shaders/screenspace-reflections.frag
Normal file
@@ -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<<l0), l0);
|
||||
vec4 reflectedColor1 = texelFetch(colors, ivec2(p.xy*k)/(1<<l1), l1);
|
||||
|
||||
vec4 reflectedColor = reflectedColor0 * (1.0-lf) + reflectedColor1 * lf;
|
||||
|
||||
// vec2 uv = vec2(p.xy*k) / textureSize(colors, 0);
|
||||
|
||||
//reflectedColor = textureLod(colors, uv, l);
|
||||
|
||||
float hitFade = hit? 1.0: 0.0;
|
||||
float angleFade = 1.0;/// smoothstep(0.0, 0.3, angle);;//angle < 0.5? 0.0 : 1.0;
|
||||
float faceFade = 1.0; //step(0.00001, dot(-normalize(hitNormal), reflected));
|
||||
o_color.rgb = (1.0 * reflectedColor.rgb * hitFade * frontalFade * distanceFade * borderFade * angleFade * faceFade) + texture(colors, v_texCoord0).rgb;
|
||||
o_color.a = 1.0;
|
||||
} else {
|
||||
o_color = texture(colors, v_texCoord0).rgba;
|
||||
o_color.a = 1.0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
#version 330 core
|
||||
|
||||
#pragma import org.openrndr.extra.shaderphrases.phrases.Depth.projectionToViewCoordinate;
|
||||
#pragma import org.openrndr.extra.dnk3.cubemap.SphericalHarmonicsKt.glslFetchSH0;
|
||||
#pragma import org.openrndr.extra.dnk3.cubemap.SphericalHarmonicsKt.glslGridCoordinates;
|
||||
#pragma import org.openrndr.extra.dnk3.cubemap.SphericalHarmonicsKt.glslGridIndex;
|
||||
#pragma import org.openrndr.extra.dnk3.cubemap.SphericalHarmonicsKt.glslGatherSH0;
|
||||
#pragma import org.openrndr.extra.noise.phrases.NoisePhrasesKt.phraseHash22;
|
||||
#pragma import org.openrndr.extra.noise.phrases.SimplexKt.phraseSimplex3;
|
||||
|
||||
in vec2 v_texCoord0;
|
||||
uniform sampler2D tex0; // image
|
||||
uniform sampler2D tex1; // projDepth
|
||||
|
||||
uniform samplerBuffer shMap;
|
||||
uniform ivec3 shMapDimensions;
|
||||
uniform vec3 shMapOffset;
|
||||
uniform float shMapSpacing;
|
||||
|
||||
uniform mat4 projectionMatrixInverse;
|
||||
uniform mat4 viewMatrixInverse;
|
||||
uniform float stepLength;
|
||||
|
||||
out vec4 o_output;
|
||||
|
||||
void main() {
|
||||
vec3 inputColor = texture(tex0, v_texCoord0).rgb;
|
||||
float projDepth = texture(tex1, v_texCoord0).r;
|
||||
vec3 viewCoordinate = projectionToViewCoordinate(v_texCoord0, projDepth, projectionMatrixInverse);
|
||||
|
||||
vec3 worldCoordinate = (viewMatrixInverse * vec4(viewCoordinate, 1.0)).xyz;
|
||||
vec3 cameraPosition = (viewMatrixInverse * vec4(vec3(0.0), 1.0)).xyz;
|
||||
|
||||
// trace in world space
|
||||
vec3 traverse = cameraPosition - worldCoordinate;
|
||||
vec3 direction = normalize(traverse);
|
||||
if (length(traverse) > 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);
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
7
orx-noise/src/main/kotlin/phrases/Simplex.kt
Normal file
7
orx-noise/src/main/kotlin/phrases/Simplex.kt
Normal file
@@ -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")
|
||||
@@ -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);
|
||||
}
|
||||
@@ -16,6 +16,7 @@ class PhraseResource<R>(private val resourceUrl: String) : ReadOnlyProperty<R, S
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* PhraseResource delegate builder function
|
||||
*/
|
||||
fun phraseResource(resource: String) : PhraseResource<Any?> {
|
||||
|
||||
@@ -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<String> = emptySet()): String {
|
||||
val newSymbols = mutableSetOf<String>()
|
||||
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>): String {
|
||||
return preprocessShader(codeFromURL(url), symbols)
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
"""
|
||||
|
||||
13
orx-shader-phrases/src/main/kotlin/phrases/NormalMapping.kt
Normal file
13
orx-shader-phrases/src/main/kotlin/phrases/NormalMapping.kt
Normal file
@@ -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()
|
||||
Reference in New Issue
Block a user