[orx-dnk3] Add feature architecture and post processing effects

This commit is contained in:
Edwin Jakobs
2020-07-06 13:00:58 +02:00
parent 0b0691e9ae
commit 419c38cc25
63 changed files with 2164 additions and 187 deletions

View File

@@ -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"

Binary file not shown.

Binary file not shown.

View File

@@ -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)

View File

@@ -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"))

View File

@@ -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 {

View File

@@ -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 {

View 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()
}
}
}

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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

View File

@@ -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()) {

View File

@@ -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 {

View File

@@ -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_

View File

@@ -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
}
}

View File

@@ -1,6 +0,0 @@
package org.openrndr.extra.dnk3
fun dryRenderer() : SceneRenderer {
val sr = SceneRenderer()
return sr
}

View File

@@ -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
@@ -62,3 +72,17 @@ 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()
}
}

View File

@@ -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);
"""
}

View 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
)
}

View File

@@ -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()
}
}

View File

@@ -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)

View File

@@ -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,16 +441,25 @@ 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) """
@@ -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
}
}

View File

@@ -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(),

View File

@@ -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())

View File

@@ -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

View File

@@ -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 ->
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))
}
@@ -296,3 +312,9 @@ fun sceneRenderer(builder: SceneRenderer.() -> Unit): SceneRenderer {
sceneRenderer.builder()
return sceneRenderer
}
internal fun ByteBuffer.putVector3(v: Vector3) {
putFloat(v.x.toFloat())
putFloat(v.y.toFloat())
putFloat(v.z.toFloat())
}

View File

@@ -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);

View 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
}

View 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")))

View File

@@ -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")))

View 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")

View 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"))
}
}
}
}

View File

@@ -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?,

View File

@@ -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)
}

View File

@@ -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
}
}

View 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
}
}

View 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)
}
}

View 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
}

View 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
}

View 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
}

View File

@@ -12,7 +12,7 @@ fun segmentContourRenderer(multisample: BufferMultisample = BufferMultisample.Di
sr.outputPasses.clear()
sr.outputPasses.add(
RenderPass(
listOf(FragmentIDFacet()),
listOf(LDRColorFacet(),FragmentIDFacet()),
multisample = multisample
)
)

View 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())
}
}

View 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)
}

View File

@@ -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)
}

View File

@@ -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;
}

View File

@@ -0,0 +1,4 @@
void fetchSH0(samplerBuffer btex, int probeID, out vec3 _SH) {
int offset = probeID * 9;
_SH = texelFetch(btex, offset).rgb;
}

View File

@@ -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);
}
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -0,0 +1,11 @@
uniform samplerCube tex0;
in v_texCoord0;
uniform vec2 targetSize;
out o_term0;
out o_term1;
out o_term2;
void main() {
}

View 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;
}
}

View File

@@ -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);
}

View File

@@ -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) {

View 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")

View File

@@ -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);
}

View File

@@ -16,6 +16,7 @@ class PhraseResource<R>(private val resourceUrl: String) : ReadOnlyProperty<R, S
}
/**
*
* PhraseResource delegate builder function
*/
fun phraseResource(resource: String) : PhraseResource<Any?> {

View File

@@ -9,17 +9,22 @@ 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(".")
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. */
@@ -29,24 +34,22 @@ fun preprocessShader(source: String): String {
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?
var result: String?
try {
val methodName = "get${fieldName.take(1).toUpperCase() + fieldName.drop(1)}"
result = c.getMethod(methodName).invoke(null) as String
result
result = preprocessShader(c.getMethod(methodName).invoke(null) as String, newSymbols)
} catch (e: NoSuchMethodException) {
try {
result = c.getDeclaredField(fieldName).get(null) as String
result
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")
@@ -54,6 +57,9 @@ fun preprocessShader(source: String): String {
} 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)
}

View File

@@ -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;
}
"""

View 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()