Add orx-dnk3a

This commit is contained in:
Edwin Jakobs
2020-05-21 10:42:02 +02:00
parent 0a1fd58012
commit e478ae5969
31 changed files with 2672 additions and 1 deletions

View File

@@ -14,7 +14,7 @@ buildscript {
apply plugin: 'org.jetbrains.dokka' apply plugin: 'org.jetbrains.dokka'
project.ext { project.ext {
openrndrVersion = "0.3.42" openrndrVersion = "0.3.43-rc.1"
kotlinVersion = "1.3.72" kotlinVersion = "1.3.72"
spekVersion = "2.0.10" spekVersion = "2.0.10"
libfreenectVersion = "0.5.7-1.5.3" libfreenectVersion = "0.5.7-1.5.3"

View File

@@ -0,0 +1,142 @@
{
"asset": {
"generator": "COLLADA2GLTF",
"version": "2.0"
},
"scene": 0,
"scenes": [
{
"nodes": [
0
]
}
],
"nodes": [
{
"children": [
1
],
"matrix": [
1.0,
0.0,
0.0,
0.0,
0.0,
0.0,
-1.0,
0.0,
0.0,
1.0,
0.0,
0.0,
0.0,
0.0,
0.0,
1.0
]
},
{
"mesh": 0
}
],
"meshes": [
{
"primitives": [
{
"attributes": {
"NORMAL": 1,
"POSITION": 2
},
"indices": 0,
"mode": 5,
"material": 0
}
],
"name": "Mesh"
}
],
"accessors": [
{
"bufferView": 0,
"byteOffset": 0,
"componentType": 5123,
"count": 36,
"max": [
23
],
"min": [
0
],
"type": "SCALAR"
},
{
"bufferView": 1,
"byteOffset": 0,
"componentType": 5126,
"count": 24,
"max": [
1.0,
1.0,
1.0
],
"min": [
-1.0,
-1.0,
-1.0
],
"type": "VEC3"
},
{
"bufferView": 1,
"byteOffset": 288,
"componentType": 5126,
"count": 24,
"max": [
0.5,
0.5,
0.5
],
"min": [
-0.5,
-0.5,
-0.5
],
"type": "VEC3"
}
],
"materials": [
{
"pbrMetallicRoughness": {
"baseColorFactor": [
0.800000011920929,
0.0,
0.0,
1.0
],
"metallicFactor": 0.0
},
"name": "Red"
}
],
"bufferViews": [
{
"buffer": 0,
"byteOffset": 576,
"byteLength": 72,
"target": 34963
},
{
"buffer": 0,
"byteOffset": 0,
"byteLength": 576,
"byteStride": 12,
"target": 34962
}
],
"buffers": [
{
"byteLength": 648,
"uri": "Box0.bin"
}
]
}

Binary file not shown.

View File

@@ -0,0 +1,219 @@
{
"asset": {
"generator": "COLLADA2GLTF",
"version": "2.0"
},
"scene": 0,
"scenes": [
{
"nodes": [
0
]
}
],
"nodes": [
{
"children": [
2,
1
],
"matrix": [
0.009999999776482582,
0.0,
0.0,
0.0,
0.0,
0.009999999776482582,
0.0,
0.0,
0.0,
0.0,
0.009999999776482582,
0.0,
0.0,
0.0,
0.0,
1.0
]
},
{
"matrix": [
-0.7289686799049377,
0.0,
-0.6845470666885376,
0.0,
-0.4252049028873444,
0.7836934328079224,
0.4527972936630249,
0.0,
0.5364750623703003,
0.6211478114128113,
-0.571287989616394,
0.0,
400.1130065917969,
463.2640075683594,
-431.0780334472656,
1.0
],
"camera": 0
},
{
"mesh": 0
}
],
"cameras": [
{
"perspective": {
"aspectRatio": 1.5,
"yfov": 0.6605925559997559,
"zfar": 10000.0,
"znear": 1.0
},
"type": "perspective"
}
],
"meshes": [
{
"primitives": [
{
"attributes": {
"NORMAL": 1,
"POSITION": 2,
"TEXCOORD_0": 3
},
"indices": 0,
"mode": 4,
"material": 0
}
],
"name": "LOD3spShape"
}
],
"accessors": [
{
"bufferView": 0,
"byteOffset": 0,
"componentType": 5123,
"count": 12636,
"max": [
2398
],
"min": [
0
],
"type": "SCALAR"
},
{
"bufferView": 1,
"byteOffset": 0,
"componentType": 5126,
"count": 2399,
"max": [
0.9995989799499512,
0.999580979347229,
0.9984359741210938
],
"min": [
-0.9990839958190918,
-1.0,
-0.9998319745063782
],
"type": "VEC3"
},
{
"bufferView": 1,
"byteOffset": 28788,
"componentType": 5126,
"count": 2399,
"max": [
96.17990112304688,
163.97000122070313,
53.92519760131836
],
"min": [
-69.29850006103516,
9.929369926452637,
-61.32819747924805
],
"type": "VEC3"
},
{
"bufferView": 2,
"byteOffset": 0,
"componentType": 5126,
"count": 2399,
"max": [
0.9833459854125976,
0.9800369739532472
],
"min": [
0.026409000158309938,
0.01996302604675293
],
"type": "VEC2"
}
],
"materials": [
{
"pbrMetallicRoughness": {
"baseColorTexture": {
"index": 0
},
"metallicFactor": 0.0
},
"emissiveFactor": [
0.0,
0.0,
0.0
],
"name": "blinn3-fx"
}
],
"textures": [
{
"sampler": 0,
"source": 0
}
],
"images": [
{
"uri": "DuckCM.png"
}
],
"samplers": [
{
"magFilter": 9729,
"minFilter": 9986,
"wrapS": 10497,
"wrapT": 10497
}
],
"bufferViews": [
{
"buffer": 0,
"byteOffset": 76768,
"byteLength": 25272,
"target": 34963
},
{
"buffer": 0,
"byteOffset": 0,
"byteLength": 57576,
"byteStride": 12,
"target": 34962
},
{
"buffer": 0,
"byteOffset": 57576,
"byteLength": 19192,
"byteStride": 8,
"target": 34962
}
],
"buffers": [
{
"byteLength": 102040,
"uri": "Duck0.bin"
}
]
}

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 577 KiB

View File

@@ -0,0 +1,193 @@
{
"accessors" : [
{
"bufferView" : 0,
"byteOffset" : 0,
"componentType" : 5123,
"count" : 11808,
"max" : [
11807
],
"min" : [
0
],
"type" : "SCALAR"
},
{
"bufferView" : 1,
"byteOffset" : 0,
"componentType" : 5126,
"count" : 11808,
"max" : [
1.336914,
0.950195,
0.825684
],
"min" : [
-1.336914,
-0.974609,
-0.800781
],
"type" : "VEC3"
},
{
"bufferView" : 2,
"byteOffset" : 0,
"componentType" : 5126,
"count" : 11808,
"max" : [
0.996339,
0.999958,
0.999929
],
"min" : [
-0.996339,
-0.985940,
-0.999994
],
"type" : "VEC3"
},
{
"bufferView" : 3,
"byteOffset" : 0,
"componentType" : 5126,
"count" : 11808,
"max" : [
0.998570,
0.999996,
0.999487,
1.000000
],
"min" : [
-0.999233,
-0.999453,
-0.999812,
1.000000
],
"type" : "VEC4"
},
{
"bufferView" : 4,
"byteOffset" : 0,
"componentType" : 5126,
"count" : 11808,
"max" : [
0.999884,
0.884359
],
"min" : [
0.000116,
0.000116
],
"type" : "VEC2"
}
],
"asset" : {
"generator" : "VKTS glTF 2.0 exporter",
"version" : "2.0"
},
"bufferViews" : [
{
"buffer" : 0,
"byteLength" : 23616,
"byteOffset" : 0,
"target" : 34963
},
{
"buffer" : 0,
"byteLength" : 141696,
"byteOffset" : 23616,
"target" : 34962
},
{
"buffer" : 0,
"byteLength" : 141696,
"byteOffset" : 165312,
"target" : 34962
},
{
"buffer" : 0,
"byteLength" : 188928,
"byteOffset" : 307008,
"target" : 34962
},
{
"buffer" : 0,
"byteLength" : 94464,
"byteOffset" : 495936,
"target" : 34962
}
],
"buffers" : [
{
"byteLength" : 590400,
"uri" : "Suzanne.bin"
}
],
"images" : [
{
"uri" : "Suzanne_BaseColor.png"
},
{
"uri" : "Suzanne_MetallicRoughness.png"
}
],
"materials" : [
{
"name" : "Suzanne",
"pbrMetallicRoughness" : {
"baseColorTexture" : {
"index" : 0
},
"metallicRoughnessTexture" : {
"index" : 1
}
}
}
],
"meshes" : [
{
"name" : "Suzanne",
"primitives" : [
{
"attributes" : {
"NORMAL" : 2,
"POSITION" : 1,
"TANGENT" : 3,
"TEXCOORD_0" : 4
},
"indices" : 0,
"material" : 0,
"mode" : 4
}
]
}
],
"nodes" : [
{
"mesh" : 0,
"name" : "Suzanne"
}
],
"samplers" : [
{}
],
"scene" : 0,
"scenes" : [
{
"nodes" : [
0
]
}
],
"textures" : [
{
"sampler" : 0,
"source" : 0
},
{
"sampler" : 0,
"source" : 1
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 840 KiB

20
orx-dnk3/README.md Normal file
View File

@@ -0,0 +1,20 @@
# orx-dnk3
A scene graph based 3d renderer with support for Gltf based assets
Status: in development
Supported Gltf features
- [x] Scene hierarchy
- [x] Loading mesh data
- [x] Glb
- [ ] Materials
- [x] Basic materials
- [x] Normal maps
- [x] Metallic/roughness maps
- [ ] Skinning
- [x] Double-sided materials
- [ ] Transparency
- [ ] Animations
- [ ] Cameras
- [ ] Lights

20
orx-dnk3/build.gradle Normal file
View File

@@ -0,0 +1,20 @@
sourceSets {
demo {
java {
srcDirs = ["src/demo/kotlin"]
compileClasspath += main.getCompileClasspath()
runtimeClasspath += main.getRuntimeClasspath()
}
}
}
dependencies {
implementation "com.google.code.gson:gson:$gsonVersion"
implementation(project(":orx-fx"))
demoImplementation(project(":orx-camera"))
demoImplementation("org.openrndr:openrndr-core:$openrndrVersion")
demoImplementation("org.openrndr:openrndr-extensions:$openrndrVersion")
demoRuntimeOnly("org.openrndr:openrndr-gl3:$openrndrVersion")
demoRuntimeOnly("org.openrndr:openrndr-gl3-natives-$openrndrOS:$openrndrVersion")
demoImplementation(sourceSets.getByName("main").output)
}

View File

@@ -0,0 +1,47 @@
import org.openrndr.application
import org.openrndr.draw.DrawPrimitive
import org.openrndr.draw.shadeStyle
import org.openrndr.extensions.SingleScreenshot
import org.openrndr.extra.dnk3.gltf.loadGltfFromFile
import org.openrndr.extras.camera.Orbital
import org.openrndr.math.Vector3
import java.io.File
fun main() = application {
program {
if (System.getProperty("takeScreenshot") == "true") {
extend(SingleScreenshot()) {
this.outputFile = System.getProperty("screenshotPath")
}
}
val gltf = loadGltfFromFile(File("demo-data/gltf-models/duck/Duck.gltf"))
val meshes = gltf.meshes.map {
it.createDrawCommands(gltf)
}
extend(Orbital()) {
far = 400.0
lookAt = Vector3(0.0, 50.0, 0.0)
eye = Vector3(100.0, 200.0, 150.0)
fov = 45.0
}
extend {
drawer.shadeStyle = shadeStyle {
fragmentTransform = """
x_fill.rgb = vec3(v_viewNormal.z);
""".trimIndent()
}
for (mesh in meshes) {
for (primitive in mesh) {
if (primitive.indexBuffer == null) {
drawer.vertexBuffer(primitive.vertexBuffer, DrawPrimitive.TRIANGLES)
} else {
drawer.vertexBuffer(primitive.indexBuffer!!, listOf(primitive.vertexBuffer), DrawPrimitive.TRIANGLES)
}
}
}
}
}
}

View File

@@ -0,0 +1,55 @@
import org.openrndr.application
import org.openrndr.color.ColorRGBa
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.math.Vector3
import org.openrndr.math.transforms.transform
import java.io.File
fun main() = application {
configure {
width = 1280
height = 720
//multisample = WindowMultisample.SampleCount(8)
}
program {
if (System.getProperty("takeScreenshot") == "true") {
extend(SingleScreenshot()) {
this.outputFile = System.getProperty("screenshotPath")
}
}
val gltf = loadGltfFromFile(File("demo-data/gltf-models/suzanne/Suzanne.gltf"))
val scene = Scene(SceneNode())
// -- add some lights
val lightNode = SceneNode()
lightNode.transform = transform {
translate(0.0, 10.0, 0.0)
rotate(Vector3.UNIT_X, -65.0)
}
lightNode.entities.add(DirectionalLight())
scene.root.entities.add(HemisphereLight().apply {
upColor = ColorRGBa.BLUE.shade(0.4)
downColor = ColorRGBa.GRAY.shade(0.1)
})
scene.root.children.add(lightNode)
scene.root.children.addAll(gltf.buildSceneNodes().first())
// -- create a renderer
val renderer = dryRenderer()
extend(Orbital()) {
far = 50.0
eye = Vector3(1.5, 0.0, 3.0)
fov = 40.0
}
extend {
drawer.clear(ColorRGBa.PINK)
renderer.draw(drawer, scene)
}
}
}

View File

@@ -0,0 +1,57 @@
import org.openrndr.application
import org.openrndr.color.ColorRGBa
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.math.Vector3
import org.openrndr.math.transforms.transform
import java.io.File
fun main() = application {
configure {
width = 1280
height = 720
//multisample = WindowMultisample.SampleCount(8)
}
program {
if (System.getProperty("takeScreenshot") == "true") {
extend(SingleScreenshot()) {
this.outputFile = System.getProperty("screenshotPath")
}
}
val gltf = loadGltfFromFile(File("demo-data/gltf-models/duck/Duck.gltf"))
val scene = Scene(SceneNode())
// -- add some lights
val lightNode = SceneNode()
lightNode.transform = transform {
translate(0.0, 10.0, 0.0)
rotate(Vector3.UNIT_X, -65.0)
}
lightNode.entities.add(DirectionalLight())
scene.root.entities.add(HemisphereLight().apply {
upColor = ColorRGBa.WHITE.shade(0.4)
downColor = ColorRGBa.WHITE.shade(0.1)
})
scene.root.children.add(lightNode)
scene.root.children.addAll(gltf.buildSceneNodes().first())
// -- create a renderer
val renderer = dryRenderer()
extend(Orbital()) {
far = 50.0
lookAt = Vector3(0.0, 0.7, 0.0)
eye = Vector3(3.0, 0.7, -2.0)
fov = 40.0
}
extend {
drawer.clear(ColorRGBa.PINK)
renderer.draw(drawer, scene)
}
}
}

View File

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

View File

@@ -0,0 +1,37 @@
package org.openrndr.extra.dnk3
import org.openrndr.color.ColorRGBa
import org.openrndr.draw.*
class Geometry(val vertexBuffers: List<VertexBuffer>,
val indexBuffer: IndexBuffer?,
val primitive: DrawPrimitive,
val offset: Int,
val vertexCount: Int)
val DummyGeometry = Geometry(emptyList(), null, DrawPrimitive.TRIANGLES, 0, 0)
sealed class Entity
class MeshPrimitive(var geometry: Geometry, var material: Material)
class MeshPrimitiveInstance(val primitive: MeshPrimitive, val instances: Int, val attributes: List<VertexBuffer>)
abstract class MeshBase(var primitives: List<MeshPrimitive>) : Entity()
class Mesh(primitives: List<MeshPrimitive>) : MeshBase(primitives)
class InstancedMesh(primitives: List<MeshPrimitive>,
var instances: Int,
var attributes: List<VertexBuffer>) : MeshBase(primitives)
class Fog : Entity() {
var color: ColorRGBa = ColorRGBa.WHITE
var end: Double = 100.0
}
abstract class Light : Entity() {
var color: ColorRGBa = ColorRGBa.WHITE
}

View File

@@ -0,0 +1,120 @@
package org.openrndr.extra.dnk3
import org.openrndr.draw.BlendMode
import org.openrndr.draw.ColorFormat
import org.openrndr.draw.ColorType
enum class FacetType(val shaderFacet: String) {
WORLD_POSITION("f_worldPosition"),
VIEW_POSITION("f_viewPosition"),
CLIP_POSITION("f_clipPosition"),
WORLD_NORMAL("f_worldNormal"),
VIEW_NORMAL("f_viewNormal"),
SPECULAR("f_specular"),
DIFFUSE("f_diffuse"),
EMISSIVE("f_emission"),
AMBIENT("f_ambient"),
OCCLUSION("f_occlusion"),
COLOR("m_color"),
}
abstract class FacetCombiner(val facets: Set<FacetType>, val targetOutput: String) {
abstract fun generateShader(): String
}
abstract class ColorBufferFacetCombiner(facets: Set<FacetType>,
targetOutput: String,
val format: ColorFormat,
val type: ColorType,
val blendMode: BlendMode = BlendMode.REPLACE) : FacetCombiner(facets, targetOutput)
class MomentsFacet : ColorBufferFacetCombiner(setOf(FacetType.WORLD_POSITION), "moments", ColorFormat.RG, ColorType.FLOAT16) {
override fun generateShader(): String {
return """
float depth = length(v_viewPosition);
float dx = dFdx(depth);
float dy = dFdy(depth);
o_$targetOutput = vec4(depth, depth*depth + 0.25 * dx*dx+dy*dy, 0.0, 1.0);
"""
}
}
class DiffuseSpecularFacet : ColorBufferFacetCombiner(setOf(FacetType.DIFFUSE, FacetType.SPECULAR),
"diffuseSpecular", ColorFormat.RGB, ColorType.FLOAT16) {
override fun generateShader(): String =
"o_$targetOutput = vec4( max(vec3(0.0), f_diffuse.rgb) + max(vec3(0.0), f_specular.rgb), 1.0);"
}
class DiffuseSpecularAlphaFacet : ColorBufferFacetCombiner(setOf(FacetType.DIFFUSE, FacetType.SPECULAR),
"diffuseSpecular", ColorFormat.RGB, ColorType.FLOAT16) {
override fun generateShader(): String =
"o_$targetOutput = vec4( (max(vec3(0.0), f_diffuse.rgb) + max(vec3(0.0), f_specular.rgb)) * f_alpha, f_alpha);"
}
class AmbientOcclusionFacet : ColorBufferFacetCombiner(setOf(FacetType.AMBIENT, FacetType.OCCLUSION),
"ambientOcclusion", ColorFormat.RGBa, ColorType.FLOAT16) {
override fun generateShader(): String =
"o_$targetOutput = vec4(f_ambient, f_occlusion);"
}
class MaterialFacet : ColorBufferFacetCombiner(setOf(FacetType.DIFFUSE),
"material", ColorFormat.RGBa, ColorType.UINT8) {
override fun generateShader(): String =
"o_$targetOutput = vec4(m_metalness, m_roughness, 0.0, 1.0);"
}
class BaseColorFacet : ColorBufferFacetCombiner(setOf(FacetType.COLOR),
"baseColor", ColorFormat.RGB, ColorType.UINT8) {
override fun generateShader(): String = "o_$targetOutput = vec4(m_color.rgb, 1.0);"
}
class DiffuseFacet : ColorBufferFacetCombiner(setOf(FacetType.DIFFUSE),
"diffuse", ColorFormat.RGB, ColorType.FLOAT16) {
override fun generateShader(): String =
"o_$targetOutput = vec4( max(vec3(0.0), f_diffuse.rgb), 1.0 );"
}
class SpecularFacet : ColorBufferFacetCombiner(setOf(FacetType.SPECULAR),
"diffuseSpecular", ColorFormat.RGB, ColorType.FLOAT16) {
override fun generateShader(): String =
"o_$targetOutput = vec4( max(vec3(0.0), f_specular.rgb), 1.0);"
}
class EmissiveFacet: ColorBufferFacetCombiner(setOf(FacetType.EMISSIVE),
"emissive", ColorFormat.RGB, ColorType.FLOAT16) {
override fun generateShader(): String =
"o_$targetOutput = vec4(f_emission, 1.0);"
}
class EmissiveAlphaFacet: ColorBufferFacetCombiner(setOf(FacetType.EMISSIVE),
"emissive", ColorFormat.RGB, ColorType.FLOAT16, BlendMode.OVER) {
override fun generateShader(): String =
"o_$targetOutput = vec4(f_emission, f_alpha);"
}
class PositionFacet : ColorBufferFacetCombiner(setOf(FacetType.WORLD_POSITION), "position", ColorFormat.RGB, ColorType.FLOAT16) {
override fun generateShader(): String = "o_$targetOutput = vec4(v_worldPosition.rgb, 1.0);"
}
class NormalFacet : ColorBufferFacetCombiner(setOf(FacetType.WORLD_NORMAL), "normal", ColorFormat.RGB, ColorType.FLOAT16) {
override fun generateShader(): String = "o_$targetOutput = vec4(v_worldNormal.rgb, 1.0);"
}
class ViewPositionFacet : ColorBufferFacetCombiner(setOf(FacetType.VIEW_POSITION), "viewPosition", ColorFormat.RGB, ColorType.FLOAT32) {
override fun generateShader(): String = "o_$targetOutput.rgb = v_viewPosition.rgb;"
}
class ViewNormalFacet : ColorBufferFacetCombiner(setOf(FacetType.VIEW_NORMAL), "viewNormal", ColorFormat.RGB, ColorType.FLOAT16) {
override fun generateShader(): String = "o_$targetOutput.rgb = normalize( (u_viewNormalMatrix * vec4(f_worldNormal,0.0)).xyz );"
}
class ClipPositionFacet : ColorBufferFacetCombiner(setOf(FacetType.CLIP_POSITION), "position", ColorFormat.RGB, ColorType.FLOAT16) {
override fun generateShader() = "o_$targetOutput.rgb = gl_FragCoord.xyz;"
}
class LDRColorFacet : ColorBufferFacetCombiner(setOf(FacetType.DIFFUSE, FacetType.SPECULAR), "color", ColorFormat.RGBa, ColorType.UINT8) {
override fun generateShader() = """
vec3 oofinalColor = (f_diffuse.rgb + f_specular.rgb + f_emission.rgb) * (1.0 - f_fog.a) + f_fog.rgb * f_fog.a;
o_$targetOutput.rgba = pow(vec4(oofinalColor, 1.0), vec4(1.0/2.2));
o_$targetOutput.a = f_alpha;
"""
}

View File

@@ -0,0 +1,48 @@
package org.openrndr.extra.dnk3
import org.openrndr.color.ColorRGBa
import org.openrndr.draw.Cubemap
import org.openrndr.draw.RenderTarget
import org.openrndr.math.Matrix44
import org.openrndr.math.Vector3
import org.openrndr.math.transforms.ortho
import org.openrndr.math.transforms.perspective
data class LightContext(val lights: List<NodeContent<Light>>,
val shadowMaps: Map<ShadowLight, RenderTarget>)
interface AttenuatedLight {
var constantAttenuation: Double
var linearAttenuation: Double
var quadraticAttenuation: Double
}
class DirectionalLight(var direction: Vector3 = -Vector3.UNIT_Z, override var shadows: Shadows = Shadows.None) : Light(), ShadowLight {
var projectionSize = 10.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)
}
}
class SpotLight(var direction: Vector3 = -Vector3.UNIT_Z, var innerAngle: Double = 45.0, var outerAngle: Double = 90.0) : Light(), ShadowLight, AttenuatedLight {
override var constantAttenuation = 1.0
override var linearAttenuation = 0.0
override var quadraticAttenuation = 0.0
override var shadows: Shadows = Shadows.None
override fun projection(renderTarget: RenderTarget): Matrix44 {
return perspective(outerAngle * 2.0, renderTarget.width * 1.0 / renderTarget.height, 1.0, 150.0)
}
}
class HemisphereLight(var direction: Vector3 = Vector3.UNIT_Y,
var upColor: ColorRGBa = ColorRGBa.WHITE,
var downColor: ColorRGBa = ColorRGBa.BLACK) : Light() {
var irradianceMap: Cubemap? = null
}
class PointLight(var constantAttenuation: Double = 1.0,
var linearAttenuation: Double = 0.0,
var quadraticAttenuation: Double = 0.0) : Light()
class AmbientLight : Light()

View File

@@ -0,0 +1,40 @@
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
interface Material {
var doubleSided: Boolean
var transparent: Boolean
fun generateShadeStyle(context: MaterialContext): ShadeStyle
fun applyToShadeStyle(context: MaterialContext, shadeStyle: ShadeStyle)
}
class DummyMaterial : Material {
override var doubleSided: Boolean = true
override var transparent: Boolean = false
override fun generateShadeStyle(context: MaterialContext): ShadeStyle {
return shadeStyle {
fragmentTransform = """
x_fill.rgb = vec3(normalize(v_viewNormal).z);
""".trimIndent()
}
}
override fun applyToShadeStyle(context: MaterialContext, shadeStyle: ShadeStyle) {
}
}
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>
)

View File

@@ -0,0 +1,555 @@
package org.openrndr.extra.dnk3
import org.openrndr.color.ColorRGBa
import org.openrndr.draw.*
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 kotlin.math.cos
private val noise128 by lazy {
val cb = colorBuffer(128, 128)
val items = cb.width * cb.height * cb.format.componentCount
val buffer = ByteBuffer.allocateDirect(items)
for (y in 0 until cb.height) {
for (x in 0 until cb.width) {
for (i in 0 until 4)
buffer.put((Math.random() * 255).toByte())
}
}
buffer.rewind()
cb.write(buffer)
cb.generateMipmaps()
cb.filter(MinifyingFilter.LINEAR_MIPMAP_LINEAR, MagnifyingFilter.LINEAR)
cb.wrapU = WrapMode.REPEAT
cb.wrapV = WrapMode.REPEAT
cb
}
private fun PointLight.fs(index: Int): String = """
|{
| vec3 Lr = p_lightPosition$index - v_worldPosition;
| float distance = length(Lr);
| float attenuation = 1.0 / (p_lightConstantAttenuation$index +
| p_lightLinearAttenuation$index * distance + p_lightQuadraticAttenuation$index * distance * distance);
| vec3 L = normalize(Lr);
|
| float side = dot(L, N) ;
| f_diffuse += attenuation * max(0, side / 3.1415) * p_lightColor$index.rgb * m_color.rgb;
| f_specular += attenuation * ggx(N, V, L, m_roughness, m_f0) * p_lightColor$index.rgb * m_color.rgb;
}
""".trimMargin()
private fun AmbientLight.fs(index: Int): String = "f_ambient += p_lightColor$index.rgb * ((1.0 - m_metalness) * m_color.rgb);"
private fun DirectionalLight.fs(index: Int) = """
|{
| vec3 L = normalize(-p_lightDirection$index);
| float attenuation = 1.0;
| vec3 H = normalize(V + L);
| float NoL = clamp(dot(N, L), 0.0, 1.0);
| float LoH = clamp(dot(L, H), 0.0, 1.0);
| float NoH = clamp(dot(N, H), 0.0, 1.0);
| vec3 Lr = (p_lightPosition$index - v_worldPosition);
//| vec3 L = normalize(Lr);
| ${shadows.fs(index)}
|
| f_diffuse += NoL * attenuation * Fd_Burley(m_roughness * m_roughness, NoV, NoL, LoH) * p_lightColor$index.rgb * m_color.rgb ;
| float Dg = D_GGX(m_roughness * m_roughness, NoH, H);
| float Vs = V_SmithGGXCorrelated(m_roughness * m_roughness, NoV, NoL);
| vec3 F = F_Schlick(m_color * (m_metalness) + 0.04 * (1.0-m_metalness), LoH);
| vec3 Fr = (Dg * Vs) * F;
| f_specular += NoL * attenuation * Fr * p_lightColor$index.rgb;
|}
""".trimMargin()
private fun HemisphereLight.fs(index: Int): String = """
|{
| float f = dot(N, p_lightDirection$index) * 0.5 + 0.5;
| vec3 irr = ${irradianceMap?.let { "texture(p_lightIrradianceMap$index, N).rgb" } ?: "vec3(1.0)"};
| f_diffuse += mix(p_lightDownColor$index.rgb, p_lightUpColor$index.rgb, f) * irr * ((1.0 - m_metalness) * m_color.rgb);// * m_ambientOcclusion;
|}
""".trimMargin()
private fun SpotLight.fs(index: Int): String {
val shadows = shadows
return """
|{
| vec3 Lr = p_lightPosition$index - v_worldPosition;
| float distance = length(Lr);
| float attenuation = 1.0 / (p_lightConstantAttenuation$index +
| p_lightLinearAttenuation$index * distance + p_lightQuadraticAttenuation$index * distance * distance);
| attenuation = 1.0;
| vec3 L = normalize(Lr);
| float NoL = clamp(dot(N, L), 0.0, 1.0);
| float side = dot(L, N);
| float hit = max(dot(-L, p_lightDirection$index), 0.0);
| float falloff = clamp((hit - p_lightOuterCos$index) / (p_lightInnerCos$index - p_lightOuterCos$index), 0.0, 1.0);
| attenuation *= falloff;
| ${shadows.fs(index)}
| {
| vec3 H = normalize(V + L);
| float LoH = clamp(dot(L, H), 0.0, 1.0);
| float NoH = clamp(dot(N, H), 0.0, 1.0);
| f_diffuse += NoL * (0.1+0.9*attenuation) * Fd_Burley(m_roughness * m_roughness, NoV, NoL, LoH) * p_lightColor$index.rgb * m_color.rgb ;
| float Dg = D_GGX(m_roughness * m_roughness, NoH, H);
| float Vs = V_SmithGGXCorrelated(m_roughness * m_roughness, NoV, NoL);
| vec3 F = F_Schlick(m_color * (m_metalness) + 0.04 * (1.0-m_metalness), LoH);
| vec3 Fr = (Dg * Vs) * F;
| f_specular += NoL * attenuation * Fr * p_lightColor$index.rgb;
| }
}
""".trimMargin()
}
private fun Fog.fs(index: Int): String = """
|{
| float dz = min(1.0, -v_viewPosition.z/p_fogEnd$index);
| f_fog = vec4(p_fogColor$index.rgb, dz);
|}
""".trimMargin()
sealed class TextureSource
object DummySource : TextureSource()
abstract class TextureFromColorBuffer(var texture: ColorBuffer, var textureFunction: TextureFunction) : TextureSource()
class TextureFromCode(val code: String) : TextureSource()
private fun TextureFromCode.fs(index: Int, target: TextureTarget) = """
|vec4 tex$index = vec4(0.0, 0.0, 0.0, 1.0);
|{
|vec4 texOut;
|$code;
|tex$index = texOut;
|}
"""
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)" })
}
/**
* @param texture the texture to sample from
* @param input input coordinates, default is "va_texCoord0.xy"
* @param textureFunction the texture function to use, default is TextureFunction.TILING
* @param pre the pre-fetch shader code to inject, can only adjust "x_texCoord"
* @param post the post-fetch shader code to inject, can only adjust "x_texture"
*/
class ModelCoordinates(texture: ColorBuffer,
var input: String = "va_texCoord0.xy",
var tangentInput : String? = null,
textureFunction: TextureFunction = TextureFunction.TILING,
var pre: String? = null,
var post: String? = null) : TextureFromColorBuffer(texture, textureFunction)
class Triplanar(texture: ColorBuffer,
var scale: Double = 1.0,
var offset: Vector3 = Vector3.ZERO,
var sharpness: Double = 2.0,
textureFunction: TextureFunction = TextureFunction.TILING,
var pre: String? = null,
var post: String? = null) : TextureFromColorBuffer(texture, textureFunction) {
init {
texture.filter(MinifyingFilter.LINEAR_MIPMAP_LINEAR, MagnifyingFilter.LINEAR)
texture.wrapU = WrapMode.REPEAT
texture.wrapV = WrapMode.REPEAT
}
}
private fun ModelCoordinates.fs(index: Int) = """
|vec4 tex$index = vec4(0.0, 0.0, 0.0, 1.0);
|{
| vec2 x_texCoord = $input;
| vec2 x_noTileOffset = vec2(0.0);
| vec4 x_texture;
| ${if (pre != null) "{ $pre } " else ""}
| x_texture = ${textureFunction.function("p_texture$index", "x_texCoord")};
| ${if (post != null) "{ $post } " else ""}
| ${if (tangentInput != null) { """
| vec3 normal = normalize(va_normal.xyz);
| 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.)) ;
|
""".trimMargin()
} else ""}
| tex$index = x_texture;
|}
""".trimMargin()
private fun Triplanar.fs(index: Int, target: TextureTarget) = """
|vec4 tex$index = vec4(0.0, 0.0, 0.0, 1.0);
|{
| vec3 x_normal = va_normal;
| vec3 x_position = va_position;
| float x_scale = p_textureTriplanarScale$index;
| vec3 x_offset = p_textureTriplanarOffset$index;
| vec2 x_noTileOffset = vec2(0.0);
| ${if (pre != null) "{ $pre } " else ""}
| vec3 n = normalize(x_normal);
| vec3 an = abs(n);
| vec2 uvY = x_position.xz * x_scale + x_offset.x;
| vec2 uvX = x_position.zy * x_scale + x_offset.y;
| vec2 uvZ = x_position.xy * x_scale + x_offset.z;
| vec4 tY = ${textureFunction.function("p_texture$index", "uvY")};
| vec4 tX = ${textureFunction.function("p_texture$index", "uvX")};
| vec4 tZ = ${textureFunction.function("p_texture$index", "uvZ")};
| vec3 weights = pow(an, vec3(p_textureTriplanarSharpness$index));
| weights = weights / (weights.x + weights.y + weights.z);
| tex$index = tX * weights.x + tY * weights.y + weights.z * tZ;
| ${if (target == TextureTarget.NORMAL) """
| vec3 tnX = normalize( tX.xyz - vec3(0.5, 0.5, 0.0));
| vec3 tnY = normalize( tY.xyz - vec3(0.5, 0.5, 0.0)) * vec3(1.0, -1.0, 1.0);
| vec3 tnZ = normalize( tZ.xyz - vec3(0.5, 0.5, 0.0));
| vec3 nX = vec3(0.0, tnX.yx);
| vec3 nY = vec3(tnY.x, 0.0, tnY.y);
| vec3 nZ = vec3(tnZ.xy, 0.0);
| vec3 normal = normalize(nX * weights.x + nY * weights.y + nZ * weights.z + n);
| tex$index = vec4(normal, 0.0);
""".trimMargin() else ""}
|}
${if (post != null) """
vec4 x_texture = tex$index;
{
$post
}
tex$index = x_texture;
""".trimIndent() else ""}
""".trimMargin()
sealed class TextureTarget {
object NONE : TextureTarget()
object COLOR : TextureTarget()
object ROUGHNESS : TextureTarget()
object METALNESS : TextureTarget()
object METALNESS_ROUGHNESS : TextureTarget()
object EMISSION : TextureTarget()
object NORMAL : TextureTarget()
object AMBIENT_OCCLUSION : TextureTarget()
class Height(var scale: Double = 1.0) : TextureTarget()
}
class Texture(var source: TextureSource,
var target: TextureTarget) {
fun copy(): Texture {
val copied = Texture(source, target)
return copied
}
}
class PBRMaterial : Material {
override var doubleSided: Boolean = false
override var transparent: Boolean = false
var environmentMap = false
var color = ColorRGBa.WHITE
var metalness = 0.5
var roughness = 1.0
var opacity = 1.0
var emission = ColorRGBa.BLACK
var vertexPreamble: String? = null
var vertexTransform: String? = null
var parameters = mutableMapOf<String, Any>()
var textures = mutableListOf<Texture>()
val shadeStyles = mutableMapOf<MaterialContext, 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(context: MaterialContext): ShadeStyle {
val cached = shadeStyles.getOrPut(context) {
val needLight = needLight(context)
val preambleFS = """
vec3 m_color = p_color.rgb;
float m_f0 = 0.5;
float m_roughness = p_roughness;
float m_metalness = p_metalness;
float m_opacity = p_opacity;
float m_ambientOcclusion = 1.0;
vec3 m_emission = p_emission.rgb;
vec3 m_normal = vec3(0.0, 0.0, 1.0);
float f_alpha = m_opacity;
vec4 f_fog = vec4(0.0, 0.0, 0.0, 0.0);
vec3 f_worldNormal = v_worldNormal;
""".trimIndent()
val textureFs = if (needLight) {
(textures.mapIndexed { index, it ->
when (val source = it.source) {
DummySource -> "vec4 tex$index = vec4(1.0);"
is ModelCoordinates -> source.fs(index)
is Triplanar -> source.fs(index, it.target)
is TextureFromCode -> source.fs(index, it.target)
else -> TODO()
}
} + textures.mapIndexed { index, texture ->
when (texture.target) {
TextureTarget.NONE -> ""
TextureTarget.COLOR -> "m_color.rgb *= pow(tex$index.rgb, vec3(2.2));"
TextureTarget.METALNESS -> "m_metalness = tex$index.r;"
TextureTarget.ROUGHNESS -> "m_roughness = tex$index.r;"
TextureTarget.METALNESS_ROUGHNESS -> "m_metalness = tex$index.r; m_roughness = tex$index.g;"
TextureTarget.EMISSION -> "m_emission += tex$index.rgb;"
TextureTarget.NORMAL -> "f_worldNormal = normalize((v_modelNormalMatrix * vec4(tex$index.xyz,0.0)).xyz);"
TextureTarget.AMBIENT_OCCLUSION -> "m_ambientOcclusion *= tex$index.r;"
is TextureTarget.Height -> ""
}
}).joinToString("\n")
} else ""
val displacers = textures.filter { it.target is TextureTarget.Height }
val textureVS = if (displacers.isNotEmpty()) textures.mapIndexed { index, it ->
if (it.target is TextureTarget.Height) {
when (val source = it.source) {
DummySource -> "vec4 tex$index = vec4(1.0);"
is ModelCoordinates -> source.fs(index)
is Triplanar -> source.fs(index, it.target)
is TextureFromCode -> source.fs(index, it.target)
else -> TODO()
} + """
x_position += x_normal * tex$index.r * p_textureHeightScale$index;
""".trimIndent()
} else ""
}.joinToString("\n") else ""
val lights = context.lights
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 = abs(dot(N, V)) + 1e-5;
${if (environmentMap && context.meshCubemaps.isNotEmpty()) """
{
vec2 dfg = PrefilteredDFG_Karis(m_roughness, NoV);
vec3 sc = m_metalness * m_color.rgb + (1.0-m_metalness) * vec3(0.04);
f_specular.rgb += sc * (texture(p_environmentMap, reflect(-V, normalize(f_worldNormal))).rgb * dfg.x + dfg.y) * m_ambientOcclusion;
}
""".trimIndent() else ""}
${lights.mapIndexed { index, (node, light) ->
when (light) {
is AmbientLight -> light.fs(index)
is PointLight -> light.fs(index)
is SpotLight -> light.fs(index)
is DirectionalLight -> light.fs(index)
is HemisphereLight -> light.fs(index)
else -> TODO()
}
}.joinToString("\n")}
${context.fogs.mapIndexed { index, (node, fog) ->
fog.fs(index)
}.joinToString("\n")}
""".trimIndent() else ""
val rt = RenderTarget.active
val combinerFS = context.pass.combiners.map {
it.generateShader()
}.joinToString("\n")
val fs = preambleFS + textureFs + lightFS + combinerFS
val vs = (this@PBRMaterial.vertexTransform ?: "") + textureVS
shadeStyle {
vertexPreamble = """
$shaderNoRepetitionVert
${(this@PBRMaterial.vertexPreamble)?:""}
""".trimIndent()
fragmentPreamble = """
|$shaderLinePlaneIntersect
|$shaderProjectOnPlane
|$shaderSideOfPlane
|$shaderGGX
|$shaderVSM
|$shaderNoRepetition
""".trimMargin()
this.suppressDefaultOutput = true
this.vertexTransform = vs
fragmentTransform = fs
context.pass.combiners.map {
if (rt.colorBuffers.size <= 1) {
this.output(it.targetOutput, 0)
} else
this.output(it.targetOutput, rt.colorBufferIndex(it.targetOutput))
}
}
}
return cached
}
private fun needLight(context: MaterialContext): Boolean {
val needSpecular = context.pass.combiners.any { FacetType.SPECULAR in it.facets }
val needDiffuse = context.pass.combiners.any { FacetType.DIFFUSE in it.facets }
val needLight = needSpecular || needDiffuse
return needLight
}
override fun applyToShadeStyle(context: MaterialContext, shadeStyle: ShadeStyle) {
shadeStyle.parameter("emission", emission)
shadeStyle.parameter("color", color)
shadeStyle.parameter("metalness", metalness)
shadeStyle.parameter("roughness", roughness)
shadeStyle.parameter("opacity", opacity)
parameters.forEach { (k, v) ->
when (v) {
is Double -> shadeStyle.parameter(k, v)
is Int -> shadeStyle.parameter(k, v)
is Vector2 -> shadeStyle.parameter(k, v)
is Vector3 -> shadeStyle.parameter(k, v)
is Vector4 -> shadeStyle.parameter(k, v)
is BufferTexture -> shadeStyle.parameter(k, v)
is ColorBuffer -> shadeStyle.parameter(k, v)
else -> TODO("support ${v::class.java}")
}
}
if (needLight(context)) {
textures.forEachIndexed { index, texture ->
when (val source = texture.source) {
is TextureFromColorBuffer -> {
shadeStyle.parameter("texture$index", source.texture)
if (source.textureFunction == TextureFunction.NOT_TILING) {
shadeStyle.parameter("textureNoise", noise128)
}
}
}
when (val source = texture.source) {
is Triplanar -> {
shadeStyle.parameter("textureTriplanarSharpness$index", source.sharpness)
shadeStyle.parameter("textureTriplanarScale$index", source.scale)
shadeStyle.parameter("textureTriplanarOffset$index", source.offset)
}
}
if (texture.target is TextureTarget.Height) {
val target = texture.target as TextureTarget.Height
shadeStyle.parameter("textureHeightScale$index", target.scale)
}
}
val lights = context.lights
lights.forEachIndexed { index, (node, light) ->
shadeStyle.parameter("lightColor$index", light.color)
when (light) {
is AmbientLight -> {
}
is PointLight -> {
shadeStyle.parameter("lightPosition$index", (node.worldTransform * Vector4.UNIT_W).xyz)
shadeStyle.parameter("lightConstantAttenuation$index", light.constantAttenuation)
shadeStyle.parameter("lightLinearAttenuation$index", light.linearAttenuation)
shadeStyle.parameter("lightQuadraticAttenuation$index", light.quadraticAttenuation)
}
is SpotLight -> {
shadeStyle.parameter("lightPosition$index", (node.worldTransform * Vector4.UNIT_W).xyz)
shadeStyle.parameter("lightDirection$index", ((normalMatrix(node.worldTransform)) * light.direction).normalized)
shadeStyle.parameter("lightConstantAttenuation$index", light.constantAttenuation)
shadeStyle.parameter("lightLinearAttenuation$index", light.linearAttenuation)
shadeStyle.parameter("lightQuadraticAttenuation$index", light.quadraticAttenuation)
shadeStyle.parameter("lightInnerCos$index", cos(Math.toRadians(light.innerAngle)))
shadeStyle.parameter("lightOuterCos$index", cos(Math.toRadians(light.outerAngle)))
if (light.shadows is Shadows.MappedShadows) {
context.shadowMaps[light]?.let {
val look = light.view(node)
shadeStyle.parameter("lightTransform$index",
light.projection(it) * look)
if (light.shadows is Shadows.DepthMappedShadows) {
shadeStyle.parameter("lightShadowMap$index", it.depthBuffer ?: TODO())
}
if (light.shadows is Shadows.ColorMappedShadows) {
shadeStyle.parameter("lightShadowMap$index", it.colorBuffer(0))
}
}
}
}
is DirectionalLight -> {
shadeStyle.parameter("lightPosition$index", (node.worldTransform * Vector4.UNIT_W).xyz)
shadeStyle.parameter("lightDirection$index", ((normalMatrix(node.worldTransform)) * light.direction).normalized)
if (light.shadows is Shadows.MappedShadows) {
context.shadowMaps[light]?.let {
val look = light.view(node)
shadeStyle.parameter("lightTransform$index",
light.projection(it) * look)
if (light.shadows is Shadows.DepthMappedShadows) {
shadeStyle.parameter("lightShadowMap$index", it.depthBuffer ?: TODO())
}
if (light.shadows is Shadows.ColorMappedShadows) {
shadeStyle.parameter("lightShadowMap$index", it.colorBuffer(0))
}
}
}
}
is HemisphereLight -> {
shadeStyle.parameter("lightDirection$index", ((normalMatrix(node.worldTransform)) * light.direction).normalized)
shadeStyle.parameter("lightUpColor$index", light.upColor)
shadeStyle.parameter("lightDownColor$index", light.downColor)
light.irradianceMap?.let {
shadeStyle.parameter("lightIrradianceMap$index", it)
}
}
}
}
context.fogs.forEachIndexed { index, (node, fog) ->
shadeStyle.parameter("fogColor$index", fog.color)
shadeStyle.parameter("fogEnd$index", fog.end)
}
} else {
textures.forEachIndexed { index, texture ->
if (texture.target is TextureTarget.Height) {
when (val source = texture.source) {
is TextureFromColorBuffer -> shadeStyle.parameter("texture$index", source.texture)
}
when (val source = texture.source) {
is Triplanar -> {
shadeStyle.parameter("textureTriplanarSharpness$index", source.sharpness)
shadeStyle.parameter("textureTriplanarScale$index", source.scale)
shadeStyle.parameter("textureTriplanarOffset$index", source.offset)
}
}
val target = texture.target as TextureTarget.Height
shadeStyle.parameter("textureHeightScale$index", target.scale)
}
}
}
}
}

View File

@@ -0,0 +1,61 @@
package org.openrndr.extra.dnk3
import org.openrndr.draw.*
import org.openrndr.math.Matrix44
data class PostContext(val lightContext: LightContext, val inverseViewMatrix: Matrix44)
interface PostStep {
fun apply(buffers: MutableMap<String, ColorBuffer>, postContext: PostContext)
}
class FilterPostStep(val outputScale: Double,
val filter: Filter,
val inputs: List<String>,
val output: String,
val outputFormat: ColorFormat,
val outputType: ColorType,
val update: (Filter.(PostContext) -> Unit)? = null) : PostStep {
override fun apply(buffers: MutableMap<String, ColorBuffer>, postContext: PostContext) {
val inputBuffers = inputs.map { buffers[it]!! }
val outputBuffer = buffers.getOrPut(output) {
colorBuffer((inputBuffers[0].width * outputScale).toInt(),
(inputBuffers[0].height * outputScale).toInt(),
format = outputFormat,
type = outputType)
}
update?.invoke(filter, postContext)
filter.apply(inputBuffers.toTypedArray(), outputBuffer)
}
}
class FunctionPostStep(val function:(MutableMap<String, ColorBuffer>)->Unit) : PostStep {
override fun apply(buffers: MutableMap<String, ColorBuffer>, postContext: PostContext) {
function(buffers)
}
}
class FilterPostStepBuilder<T : Filter>(val filter: T) {
var outputScale = 1.0
val inputs = mutableListOf<String>()
var output = "untitled"
var outputFormat = ColorFormat.RGBa
var outputType = ColorType.UINT8
var update: (T.(PostContext) -> Unit)? = null
internal fun build(): PostStep {
@Suppress("UNCHECKED_CAST", "PackageDirectoryMismatch")
return FilterPostStep(outputScale, filter, inputs, output, outputFormat, outputType, update as (Filter.(PostContext) -> Unit)?)
}
}
fun <T : Filter> postStep(filter: T, configure: FilterPostStepBuilder<T>.() -> Unit) : PostStep {
val psb = FilterPostStepBuilder(filter)
psb.configure()
return psb.build()
}
fun postStep(function: (MutableMap<String, ColorBuffer>)->Unit) : PostStep {
return FunctionPostStep(function)
}

View File

@@ -0,0 +1,24 @@
package org.openrndr.extra.dnk3
import org.openrndr.draw.BufferMultisample
import org.openrndr.draw.DepthFormat
import org.openrndr.draw.RenderTarget
import org.openrndr.draw.renderTarget
class RenderPass(val combiners: List<FacetCombiner>, val renderOpaque: Boolean = true, val renderTransparent: Boolean = false)
val DefaultPass = RenderPass(listOf(LDRColorFacet()))
val LightPass = RenderPass(emptyList())
val VSMLightPass = RenderPass(listOf(MomentsFacet()))
fun RenderPass.createPassTarget(width: Int, height: Int, depthFormat: DepthFormat = DepthFormat.DEPTH24, multisample: BufferMultisample = BufferMultisample.Disabled): RenderTarget {
return renderTarget(width, height, multisample = multisample) {
for (combiner in combiners) {
when (combiner) {
is ColorBufferFacetCombiner ->
colorBuffer(combiner.targetOutput, combiner.format, combiner.type)
}
}
depthBuffer(depthFormat)
}
}

View File

@@ -0,0 +1,67 @@
package org.openrndr.extra.dnk3
import org.openrndr.math.Matrix44
class Scene(val root: SceneNode = SceneNode(),
val updateFunctions: MutableList<() -> Unit> = mutableListOf())
open class SceneNode(var entities: MutableList<Entity> = mutableListOf()) {
var parent: SceneNode? = null
var transform = Matrix44.IDENTITY
var worldTransform = Matrix44.IDENTITY
val children = mutableListOf<SceneNode>()
var disposed = false
}
class NodeContent<T>(val node: SceneNode, val content: T) {
operator fun component1() = node
operator fun component2() = content
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as NodeContent<*>
if (node != other.node) return false
if (content != other.content) return false
return true
}
override fun hashCode(): Int {
var result = node.hashCode()
result = 31 * result + content.hashCode()
return result
}
}
fun SceneNode.visit(visitor: SceneNode.() -> Unit) {
visitor()
children.forEach { it.visit(visitor) }
}
fun <P> SceneNode.scan(initial: P, scanner: SceneNode.(P) -> P) {
val p = scanner(initial)
children.forEach { it.scan(p, scanner) }
}
fun SceneNode.findNodes(selector: SceneNode.() -> Boolean): List<SceneNode> {
val result = mutableListOf<SceneNode>()
visit {
if (selector()) result.add(this)
}
return result
}
fun <P : Entity> SceneNode.findContent(selector: Entity.() -> P?): List<NodeContent<P>> {
val result = mutableListOf<NodeContent<P>>()
visit {
entities.forEach {
val s = it.selector()
if (s != null) {
result.add(NodeContent(this, s))
}
}
}
return result
}

View File

@@ -0,0 +1,240 @@
package org.openrndr.extra.dnk3
import org.openrndr.color.ColorRGBa
import org.openrndr.draw.*
import org.openrndr.draw.depthBuffer
import org.openrndr.extra.fx.blur.ApproximateGaussianBlur
import org.openrndr.math.Matrix44
class SceneRenderer {
class Configuration {
var multisampleLines = false
}
val configuration = Configuration()
val blur = ApproximateGaussianBlur()
var shadowLightTargets = mutableMapOf<ShadowLight, RenderTarget>()
var meshCubemaps = mutableMapOf<Mesh, Cubemap>()
var cubemapDepthBuffer = depthBuffer(256, 256, DepthFormat.DEPTH16, BufferMultisample.Disabled)
var outputPasses = mutableListOf(DefaultPass)
var outputPassTarget: RenderTarget? = null
var outputPassTargetMS: RenderTarget? = null
val postSteps = mutableListOf<PostStep>()
val buffers = mutableMapOf<String, ColorBuffer>()
var drawFinalBuffer = true
fun draw(drawer: Drawer, scene: Scene) {
drawer.pushStyle()
drawer.depthWrite = true
drawer.depthTestPass = DepthTestPass.LESS_OR_EQUAL
drawer.cullTestPass = CullTestPass.FRONT
scene.updateFunctions.forEach {
it()
}
// update all the transforms
scene.root.scan(Matrix44.IDENTITY) { p ->
worldTransform = p * transform
worldTransform
}
val lights = scene.root.findContent { this as? Light }
val meshes = scene.root.findContent { this as? Mesh }
val fogs = scene.root.findContent { this as? Fog }
val instancedMeshes = scene.root.findContent { this as? InstancedMesh }
run {
lights.filter { it.content is ShadowLight && (it.content as ShadowLight).shadows is Shadows.MappedShadows }.forEach {
val shadowLight = it.content as ShadowLight
val pass: RenderPass
pass = when (shadowLight.shadows) {
is Shadows.PCF, is Shadows.Simple -> {
LightPass
}
is Shadows.VSM -> {
VSMLightPass
}
else -> TODO()
}
val target = shadowLightTargets.getOrPut(shadowLight) {
val mapSize = (shadowLight.shadows as Shadows.MappedShadows).mapSize
pass.createPassTarget(mapSize, mapSize, DepthFormat.DEPTH16)
}
target.clearDepth(depth = 1.0)
val look = shadowLight.view(it.node)
val materialContext = MaterialContext(pass, lights, fogs, shadowLightTargets, emptyMap())
drawer.isolatedWithTarget(target) {
drawer.projection = shadowLight.projection(target)
drawer.view = look
drawer.model = Matrix44.IDENTITY
drawer.clear(ColorRGBa.BLACK)
drawer.cullTestPass = CullTestPass.FRONT
drawPass(drawer, pass, materialContext, meshes, instancedMeshes)
}
when (shadowLight.shadows) {
is Shadows.VSM -> {
blur.gain = 1.0
blur.sigma = 3.0
blur.window = 9
blur.spread = 1.0
blur.apply(target.colorBuffer(0), target.colorBuffer(0))
}
}
}
}
run {
//val pass = outputPasses
for (pass in outputPasses) {
val materialContext = MaterialContext(pass, lights, fogs, shadowLightTargets, meshCubemaps)
if ((pass != DefaultPass || postSteps.isNotEmpty()) && outputPassTarget == null) {
outputPassTarget = pass.createPassTarget(RenderTarget.active.width, RenderTarget.active.height)
}
if (pass == outputPasses[0]) {
outputPassTarget?.let {
drawer.withTarget(it) {
background(ColorRGBa.PINK)
}
}
}
outputPassTarget?.let { target ->
pass.combiners.forEach {
if (it is ColorBufferFacetCombiner) {
val index = target.colorBufferIndex(it.targetOutput)
target.blendMode(index, it.blendMode)
}
}
}
outputPassTarget?.bind()
drawPass(drawer, pass, materialContext, meshes, instancedMeshes)
outputPassTarget?.unbind()
outputPassTarget?.let { output ->
for (combiner in pass.combiners) {
buffers[combiner.targetOutput] = output.colorBuffer(combiner.targetOutput)
}
}
}
val lightContext = LightContext(lights, shadowLightTargets)
val postContext = PostContext(lightContext, drawer.view.inversed)
for (postStep in postSteps) {
// if (postStep is FilterPostStep) {
// if (postStep.filter is Ssao) {
// postStep.filter.projection = drawer.projection
// }
// if (postStep.filter is Sslr) {
// 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
// postStep.filter.projection = p
// }
// }
postStep.apply(buffers, postContext)
}
}
drawer.popStyle()
if (drawFinalBuffer) {
outputPassTarget?.let { output ->
drawer.isolated {
drawer.ortho()
drawer.view = Matrix44.IDENTITY
drawer.model = Matrix44.IDENTITY
val outputName = (postSteps.last() as FilterPostStep).output
val outputBuffer = buffers[outputName]
?: throw IllegalArgumentException("can't find $outputName buffer")
drawer.image(outputBuffer)
}
}
}
}
private fun drawPass(drawer: Drawer, pass: RenderPass, materialContext: MaterialContext,
meshes: List<NodeContent<Mesh>>,
instancedMeshes: List<NodeContent<InstancedMesh>>) {
val primitives = meshes.flatMap { mesh ->
mesh.content.primitives.map { primitive ->
NodeContent(mesh.node, primitive)
}
}
// -- draw all meshes
primitives
.filter { (it.content.material.transparent && pass.renderTransparent) || (!it.content.material.transparent && pass.renderOpaque) }
.forEach {
val primitive = it.content
drawer.isolated {
if (primitive.material.doubleSided) {
drawer.drawStyle.cullTestPass = CullTestPass.ALWAYS
}
val shadeStyle = primitive.material.generateShadeStyle(materialContext)
shadeStyle.parameter("viewMatrixInverse", drawer.view.inversed)
primitive.material.applyToShadeStyle(materialContext, shadeStyle)
drawer.shadeStyle = shadeStyle
drawer.model = it.node.worldTransform
if (primitive.geometry.indexBuffer == null) {
drawer.vertexBuffer(primitive.geometry.vertexBuffers,
primitive.geometry.primitive,
primitive.geometry.offset,
primitive.geometry.vertexCount)
} else {
drawer.vertexBuffer(primitive.geometry.indexBuffer!!,
primitive.geometry.vertexBuffers,
primitive.geometry.primitive,
primitive.geometry.offset,
primitive.geometry.vertexCount)
}
}
}
val instancedPrimitives = instancedMeshes.flatMap { mesh ->
mesh.content.primitives.map { primitive ->
NodeContent(mesh.node, MeshPrimitiveInstance(primitive, mesh.content.instances, mesh.content.attributes))
}
}
// -- draw all instanced meshes
instancedPrimitives
.filter { (it.content.primitive.material.transparent && pass.renderTransparent) || (!it.content.primitive.material.transparent && pass.renderOpaque) }
.forEach {
val primitive = it.content
drawer.isolated {
val shadeStyle = primitive.primitive.material.generateShadeStyle(materialContext)
shadeStyle.parameter("viewMatrixInverse", drawer.view.inversed)
primitive.primitive.material.applyToShadeStyle(materialContext, shadeStyle)
if (primitive.primitive.material.doubleSided) {
drawer.drawStyle.cullTestPass = CullTestPass.ALWAYS
}
drawer.shadeStyle = shadeStyle
drawer.model = it.node.worldTransform
drawer.vertexBufferInstances(primitive.primitive.geometry.vertexBuffers,
primitive.attributes,
DrawPrimitive.TRIANGLES,
primitive.instances,
primitive.primitive.geometry.offset,
primitive.primitive.geometry.vertexCount)
}
}
}
}
fun sceneRenderer(builder: SceneRenderer.() -> Unit): SceneRenderer {
val sceneRenderer = SceneRenderer()
sceneRenderer.builder()
return sceneRenderer
}

View File

@@ -0,0 +1,228 @@
package org.openrndr.extra.dnk3
val shaderNoRepetition = """
float sum( vec3 v ) { return v.x+v.y+v.z; }
// based on https://www.shadertoy.com/view/Xtl3zf
vec4 textureNoTile(in sampler2D noiseTex, in sampler2D tex, in vec2 noiseOffset, in vec2 x)
{
float v = 1.0;
float k = texture(noiseTex, noiseOffset + x*0.01 ).x; // cheap (cache friendly) lookup
vec2 duvdx = dFdx( x );
vec2 duvdy = dFdx( x );
float l = k*8.0;
float f = fract(l);
#if 0
float ia = floor(l); // my method
float ib = ia + 1.0;
#else
float ia = floor(l+0.5); // suslik's method (see comments)
float ib = floor(l);
f = min(f, 1.0-f)*2.0;
#endif
vec2 offa = sin(vec2(3.0,7.0)*ia); // can replace with any other hash
vec2 offb = sin(vec2(3.0,7.0)*ib); // can replace with any other hash
vec3 cola = textureGrad( tex, x + v*offa, duvdx, duvdy ).xyz;
vec3 colb = textureGrad( tex, x + v*offb, duvdx, duvdy ).xyz;
return vec4(mix( cola, colb, smoothstep(0.2,0.8,f-0.1*sum(cola-colb)) ), 1.0);
}
"""
val shaderNoRepetitionVert = """
// shaderNoRepetitionVert
float sum( vec3 v ) { return v.x+v.y+v.z; }
// based on https://www.shadertoy.com/view/Xtl3zf
vec4 textureNoTile(in sampler2D tex, in vec2 noiseOffset, in vec2 x)
{
float v = 1.0;
float k = texture(tex, noiseOffset + 0.005*x ).x; // cheap (cache friendly) lookup
float l = k*8.0;
float f = fract(l);
#if 0
float ia = floor(l); // my method
float ib = ia + 1.0;
#else
float ia = floor(l+0.5); // suslik's method (see comments)
float ib = floor(l);
f = min(f, 1.0-f)*2.0;
#endif
vec2 offa = sin(vec2(3.0,7.0)*ia); // can replace with any other hash
vec2 offb = sin(vec2(3.0,7.0)*ib); // can replace with any other hash
vec3 cola = texture( tex, x + v*offa).xyz;
vec3 colb = texture( tex, x + v*offb).xyz;
return vec4(mix( cola, colb, smoothstep(0.2,0.8,f-0.1*sum(cola-colb)) ), 1.0);
}
"""
val shaderProjectOnPlane = """
// shaderProjectOnPlane
vec3 projectOnPlane(vec3 p, vec3 pc, vec3 pn) {
float distance = dot(pn, p-pc);
return p - distance * pn;
}
""".trimIndent()
val shaderSideOfPlane = """
int sideOfPlane(in vec3 p, in vec3 pc, in vec3 pn){
if (dot(p-pc,pn) >= 0.0) return 1; else return 0;
}
""".trimIndent()
val shaderLinePlaneIntersect = """
vec3 linePlaneIntersect(in vec3 lp, in vec3 lv, in vec3 pc, in vec3 pn){
return lp+lv*(dot(pn,pc-lp)/dot(pn,lv));
}
""".trimIndent()
val shaderVSM = """
|float linstep(float min, float max, float v)
|{
| return clamp((v - min) / (max - min), 0, 1);
|}
|// https://developer.nvidia.com/gpugems/GPUGems3/gpugems3_ch08.html
|float chebyshevUpperBound(vec2 moments, float t, float minVariance) {
| // One-tailed inequality valid if t > Moments.x
| float p = (t <= moments.x) ? 1.0 : 0.0;
| // Compute variance.
| float variance = moments.y - (moments.x * moments.x);
| variance = max(variance, minVariance);
| // Compute probabilistic upper bound.
| float d = t - moments.x;
| float p_max = variance / (variance + d*d);
| p_max = smoothstep(0.6, 1, p_max);
| return max(p, p_max);
}
""".trimIndent()
/*
N - world space normal
V - eye - world vertex position
L - world light pos - world vertex position
*/
val shaderGGX = """
#define bias 0.125
#define HASHSCALE 443.8975
vec2 hash22(vec2 p) {
vec3 p3 = fract(vec3(p.xyx) * HASHSCALE);
p3 += dot(p3, p3.yzx+19.19);
return fract(vec2((p3.x + p3.y)*p3.z, (p3.x+p3.z)*p3.y));
}
#define PI 3.1415926535
float pow5(float x) {
float x2 = x * x;
return x2 * x2 * x;
}
float D_GGX(float linearRoughness, float NoH, const vec3 h) {
// Walter et al. 2007, "Microfacet Models for Refraction through Rough Surfaces"
float oneMinusNoHSquared = 1.0 - NoH * NoH;
float a = NoH * linearRoughness;
float k = linearRoughness / (oneMinusNoHSquared + a * a);
float d = k * k * (1.0 / PI);
return d;
}
float D_GGXm(float linearRoughness, float NoH, const vec3 h, const vec3 n) {
vec3 NxH = cross(n, h);
float oneMinusNoHSquared = dot(NxH, NxH);
// Walter et al. 2007, "Microfacet Models for Refraction through Rough Surfaces"
//float oneMinusNoHSquared = 1.0 - NoH * NoH;
float a = NoH * linearRoughness;
float k = linearRoughness / (oneMinusNoHSquared + a * a);
float d = k * k * (1.0 / PI);
return d;
}
float V_SmithGGXCorrelated(float linearRoughness, float NoV, float NoL) {
// Heitz 2014, "Understanding the Masking-Shadowing Function in Microfacet-Based BRDFs"
float a2 = linearRoughness * linearRoughness;
float GGXV = NoL * sqrt((NoV - a2 * NoV) * NoV + a2);
float GGXL = NoV * sqrt((NoL - a2 * NoL) * NoL + a2);
return 0.5 / (GGXV + GGXL);
}
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);
}
float F_Schlick(float f0, float f90, float VoH) {
return f0 + (f90 - f0) * pow5(1.0 - VoH);
}
float Fd_Burley(float linearRoughness, float NoV, float NoL, float LoH) {
// Burley 2012, "Physically-Based Shading at Disney"
float f90 = 0.5 + 2.0 * linearRoughness * LoH * LoH;
float lightScatter = F_Schlick(1.0, f90, NoL);
float viewScatter = F_Schlick(1.0, f90, NoV);
return lightScatter * viewScatter * (1.0 / PI);
}
vec2 PrefilteredDFG_Karis(float roughness, float NoV) {
//https://www.shadertoy.com/view/XlKSDR
// Karis 2014, "Physically Based Material on Mobile"
const vec4 c0 = vec4(-1.0, -0.0275, -0.572, 0.022);
const vec4 c1 = vec4( 1.0, 0.0425, 1.040, -0.040);
vec4 r = roughness * c0 + c1;
float a004 = min(r.x * r.x, exp2(-9.28 * NoV)) * r.x + r.y;
return vec2(-1.04, 1.04) * a004 + r.zw;
}
float saturate(float x) {
return clamp(x, 0.0, 1.0);
}
float G1V(float dotNV, float k)
{
return 1.0f/(dotNV*(1.0f-k)+k);
}
float ggx(vec3 N, vec3 V, vec3 L, float roughness, float F0)
{
float alpha = roughness*roughness;
vec3 H = normalize(V+L);
float dotNL = saturate(dot(N,L));
float dotNV = saturate(dot(N,V));
float dotNH = saturate(dot(N,H));
float dotLH = saturate(dot(L,H));
float F, D, vis;
// D
float alphaSqr = alpha*alpha;
float pi = 3.14159f;
float denom = dotNH * dotNH *(alphaSqr-1.0) + 1.0f;
D = alphaSqr/(pi * denom * denom);
// F
float dotLH5 = pow(1.0f-dotLH,5);
F = F0 + (1.0-F0)*(dotLH5);
// V
float k = alpha/2.0f;
vis = G1V(dotNL,k)*G1V(dotNV,k);
float specular = dotNL * D * F * vis;
return specular;
}
""".trimIndent()

View File

@@ -0,0 +1,99 @@
package org.openrndr.extra.dnk3
import org.openrndr.draw.RenderTarget
import org.openrndr.math.Matrix44
sealed class Shadows {
object None : Shadows()
abstract class MappedShadows(val mapSize: Int) : Shadows()
abstract class DepthMappedShadows(mapSize: Int) : MappedShadows(mapSize)
abstract class ColorMappedShadows(mapSize: Int) : MappedShadows(mapSize)
class Simple(mapSize: Int = 1024) : DepthMappedShadows(mapSize)
class PCF(mapSize: Int = 1024, val sampleCount: Int = 12) : DepthMappedShadows(mapSize)
class VSM(mapSize: Int = 1024) : ColorMappedShadows(mapSize)
}
interface ShadowLight {
var shadows: Shadows
fun projection(renderTarget: RenderTarget): Matrix44
fun view(node: SceneNode): Matrix44 {
return node.worldTransform.inversed
}
}
// shaders
fun Shadows.VSM.fs(index: Int) : String = """
|{
| vec4 smc = (p_lightTransform$index * vec4(v_worldPosition,1.0));
| vec3 lightProj = (smc.xyz/smc.w) * 0.5 + 0.5;
| if (lightProj.x > 0.0 && lightProj.x < 1.0 && lightProj.y > 0 && lightProj.y < 1) {
| vec2 moments = texture(p_lightShadowMap$index, lightProj.xy).xy;
| attenuation *= (chebyshevUpperBound(moments, length(Lr), 50.0));
| }
|}
""".trimMargin()
fun Shadows.Simple.fs(index: Int): String = """
|{
| vec4 smc = (p_lightTransform$index * vec4(v_worldPosition,1.0));
| vec3 lightProj = (smc.xyz/smc.w) * 0.5 + 0.5;
| if (lightProj.x > 0.0 && lightProj.x < 1.0 && lightProj.y > 0 && lightProj.y < 1) {
| vec3 smz = texture(p_lightShadowMap$index, lightProj.xy).rgb;
| vec2 step = 1.0 / textureSize(p_lightShadowMap$index,0);
| float result = 0.0;
| float compToZ = (lightProj.z- 0.0020 * tan(acos(NoL))) - 0.0003;
| float currentDepth = lightProj.z;
| float closestDepth = smz.x;
| float shadow = (currentDepth - 0.0020 * tan(acos(NoL))) - 0.0003 >= closestDepth ? 0.0 : 1.0;
| attenuation *= shadow;
| }
|}
""".trimMargin()
fun Shadows.PCF.fs(index: Int): String = """
|{
| float lrl = length(Lr)/100.0;
| vec2 fTaps_Poisson[12];
| fTaps_Poisson[0] = vec2(-.326,-.406);
| fTaps_Poisson[1] = vec2(-.840,-.074);
| fTaps_Poisson[2] = vec2(-.696, .457);
| fTaps_Poisson[3] = vec2(-.203, .621);
| fTaps_Poisson[4] = vec2( .962,-.195);
| fTaps_Poisson[5] = vec2( .473,-.480);
| fTaps_Poisson[6] = vec2( .519, .767);
| fTaps_Poisson[7] = vec2( .185,-.893);
| fTaps_Poisson[8] = vec2( .507, .064);
| fTaps_Poisson[9] = vec2( .896, .412);
| fTaps_Poisson[10] = vec2(-.322,-.933);
| fTaps_Poisson[11] = vec2(-.792,-.598);
| vec4 smc = (p_lightTransform$index * vec4(v_worldPosition,1.0));
| vec3 lightProj = (smc.xyz/smc.w) * 0.5 + 0.5;
| if (lightProj.x > 0.0 && lightProj.x < 1.0 && lightProj.y > 0 && lightProj.y < 1) {
| vec3 smz = texture(p_lightShadowMap$index, lightProj.xy).rgb;
| vec2 stepSize = 1.0 / textureSize(p_lightShadowMap$index,0);
| float result = 0.0;
| float compToZ = (lightProj.z- 0.0020 * tan(acos(NoL))) - 0.0003;
| float noise = hash22(lightProj.xy*10.0).x;
| float r = noise * 3.1415926535 * 2.0;
| mat2 rot = mat2( vec2(cos(r), -sin(r)), vec2(sin(r),cos(r)));
| for (int i = 0; i < 12; ++i) {
| float depth = texture(p_lightShadowMap$index, lightProj.xy + rot*fTaps_Poisson[i]*i*lrl*stepSize ).r;
| result += step(compToZ, depth);
| }
| result /= 12;
| float currentDepth = lightProj.z;
| float closestDepth = smz.x;
| float shadow = result;// (currentDepth - 0.0020 * tan(acos(NoL))) - 0.0003 >= closestDepth ? 0.0 : 1.0;
| attenuation *= shadow;
| }
|}
""".trimMargin()
fun Shadows.fs(index: Int): String = when (this) {
is Shadows.PCF -> this.fs(index)
is Shadows.Simple -> this.fs(index)
is Shadows.VSM -> this.fs(index)
is Shadows.None -> ""
else -> TODO()
}

View File

@@ -0,0 +1,46 @@
package org.openrndr.extra.dnk3.gltf
import com.google.gson.Gson
import java.io.File
import java.io.RandomAccessFile
import java.nio.ByteBuffer
import java.nio.ByteOrder
fun loadGltfFromGlbFile(file: File): GltfFile {
val channel = RandomAccessFile(file, "r").channel
val headerBuffer = ByteBuffer.allocate(12).order(ByteOrder.nativeOrder())
headerBuffer.rewind()
channel.read(headerBuffer)
headerBuffer.rewind()
val magic = headerBuffer.int
val version = headerBuffer.int
val length = headerBuffer.int
fun readChunk(): ByteBuffer {
val chunkHeader = ByteBuffer.allocate(8).order(ByteOrder.nativeOrder())
channel.read(chunkHeader)
chunkHeader.rewind()
val chunkLength = chunkHeader.int
val chunkType = chunkHeader.int
val chunkBuffer =
if (chunkType == 0x004E4942) ByteBuffer.allocateDirect(chunkLength) else ByteBuffer.allocate(chunkLength)
(chunkBuffer as ByteBuffer)
channel.read(chunkBuffer)
return chunkBuffer
}
val jsonBuffer = readChunk()
jsonBuffer.rewind()
val jsonByteArray = ByteArray(jsonBuffer.capacity())
jsonBuffer.get(jsonByteArray)
val json = String(jsonByteArray)
val gson = Gson()
val bufferBuffer = if (channel.position() < length) readChunk() else null
return gson.fromJson(json, GltfFile::class.java).apply {
this.file = file
this.bufferBuffer = bufferBuffer
}
}

View File

@@ -0,0 +1,220 @@
@file:Suppress("MemberVisibilityCanBePrivate", "unused")
package org.openrndr.extra.dnk3.gltf
import com.google.gson.Gson
import org.openrndr.draw.*
import java.io.File
import java.io.RandomAccessFile
import java.nio.Buffer
import java.nio.ByteBuffer
import java.nio.ByteOrder
import kotlin.math.max
const val GLTF_FLOAT = 5126
const val GLTF_UNSIGNED_INT = 5125
const val GLTF_INT = 5124
const val GLTF_UNSIGNED_SHORT = 5123
const val GLTF_SHORT = 5122
const val GLTF_UNSIGNED_BYTE = 5121
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?)
class GltfScene(val nodes: IntArray)
class GltfNode(val children: IntArray?,
val matrix: DoubleArray?,
val scale: DoubleArray?,
val rotation: DoubleArray?,
val translation: DoubleArray?,
val mesh: Int?)
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 ->
val accessor = gltfFile.accessors[indices]
val indexType = when (accessor.componentType) {
GLTF_UNSIGNED_SHORT -> IndexType.INT16
GLTF_UNSIGNED_INT -> IndexType.INT32
else -> error("unsupported index type: ${accessor.componentType}")
}
val bufferView = gltfFile.bufferViews[accessor.bufferView]
val buffer = gltfFile.buffers[bufferView.buffer]
val contents = buffer.contents(gltfFile)
(contents as Buffer).position((bufferView.byteOffset ?: 0) + (accessor.byteOffset))
(contents as Buffer).limit((bufferView.byteOffset ?: 0) + (accessor.byteOffset)
+ accessor.count * indexType.sizeInBytes)
val ib = indexBuffer(accessor.count, indexType)
ib.write(contents)
ib
}
var maxCount = 0
val accessors = mutableListOf<GltfAccessor>()
val format = vertexFormat {
for ((name, index) in attributes.toSortedMap()) {
val accessor = gltfFile.accessors[index]
maxCount = max(accessor.count, maxCount)
when (name) {
"NORMAL" -> {
normal(3)
accessors.add(accessor)
}
"POSITION" -> {
position(3)
accessors.add(accessor)
}
"TANGENT" -> {
attribute("tangent", VertexElementType.VECTOR4_FLOAT32)
accessors.add(accessor)
}
"TEXCOORD_0" -> {
val dimensions = when (accessor.type) {
"SCALAR" -> 1
"VEC2" -> 2
"VEC3" -> 3
else -> error("unsupported texture coordinate type ${accessor.type}")
}
textureCoordinate(dimensions, 0)
accessors.add(accessor)
}
}
}
}
val buffers =
accessors.map { it.bufferView }
.distinct()
.associate {
Pair(gltfFile.bufferViews[it].buffer,
gltfFile.buffers[gltfFile.bufferViews[it].buffer].contents(gltfFile))
}
val vb = vertexBuffer(format, maxCount)
vb.put {
for (i in 0 until maxCount) {
for (a in accessors) {
val bufferView = gltfFile.bufferViews[a.bufferView]
val buffer = buffers[bufferView.buffer] ?: error("no buffer ${bufferView.buffer}")
val componentSize = when (a.componentType) {
GLTF_BYTE, GLTF_UNSIGNED_BYTE -> 1
GLTF_SHORT, GLTF_UNSIGNED_SHORT -> 2
GLTF_FLOAT, GLTF_UNSIGNED_INT, GLTF_INT -> 4
else -> error("unsupported type")
}
val componentCount = when (a.type) {
"SCALAR" -> 1
"VEC2" -> 2
"VEC3" -> 3
"VEC4" -> 4
"MAT2" -> 4
"MAT3" -> 9
"MAT4" -> 16
else -> error("unsupported type")
}
val size = componentCount * componentSize
val offset = (bufferView.byteOffset ?: 0) + a.byteOffset + i * (bufferView.byteStride ?: size)
copyBuffer(buffer, offset, size)
}
}
}
val drawPrimitive = when (mode) {
null, 4 -> DrawPrimitive.TRIANGLES
5 -> DrawPrimitive.TRIANGLE_STRIP
else -> error("unsupported mode $mode")
}
return GltfDrawCommand(vb, indexBuffer, drawPrimitive, indexBuffer?.indexCount ?: maxCount)
}
}
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?,
val baseColorTexture: GltfMaterialTexture?,
var metallicRoughnessTexture: GltfMaterialTexture?,
val roughnessFactor: Double?,
val metallicFactor: Double?)
class GltfMaterialTexture(val index: Int, val scale: Double?, val texCoord: Int?)
class GltfImage(val uri: String)
class GltfSampler(val magFilter: Int, val minFilter: Int, val wrapS: Int, val wrapT: Int)
class GltfTexture(val sampler: Int, val source: Int)
class GltfMaterial(val name: String,
val doubleSided: Boolean?,
val normalTexture: GltfMaterialTexture?,
val occlusionTexture: GltfMaterialTexture?,
val emissiveTexture: GltfMaterialTexture?,
val emissiveFactor: DoubleArray?,
val pbrMetallicRoughness: GltfPbrMetallicRoughness?)
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?) {
fun contents(gltfFile: GltfFile): ByteBuffer = if (uri != null) {
val raf = RandomAccessFile(File(gltfFile.file.parentFile, uri), "r")
val buffer = ByteBuffer.allocateDirect(byteLength)
buffer.order(ByteOrder.nativeOrder())
buffer.rewind()
raf.channel.read(buffer)
buffer.rewind()
buffer
} else {
gltfFile.bufferBuffer ?: error("no embedded buffer from glb")
}
}
class GltfDrawCommand(val vertexBuffer: VertexBuffer, val indexBuffer: IndexBuffer?, val primitive: DrawPrimitive, var vertexCount: Int)
class GltfAccessor(
val bufferView: Int,
val byteOffset: Int,
val componentType: Int,
val count: Int,
val max: DoubleArray,
val min: DoubleArray,
val type: String
)
class GltfFile(
val asset: GltfAsset?,
val scene: Int?,
val scenes: List<GltfScene>,
val nodes: List<GltfNode>,
val meshes: List<GltfMesh>,
val accessors: List<GltfAccessor>,
val materials: List<GltfMaterial>,
val bufferViews: List<GltfBufferView>,
val buffers: List<GltfBuffer>,
val images: List<GltfImage>?,
val textures: List<GltfTexture>?,
val samplers: List<GltfSampler>?
) {
@Transient lateinit var file: File
@Transient var bufferBuffer : ByteBuffer? = null
}
fun loadGltfFromFile(file: File): GltfFile {
val gson = Gson()
val json = file.readText()
return gson.fromJson(json, GltfFile::class.java).apply {
this.file = file
}
}

View File

@@ -0,0 +1,126 @@
package org.openrndr.extra.dnk3.gltf
import org.openrndr.color.ColorRGBa
import org.openrndr.draw.*
import org.openrndr.extra.dnk3.*
import org.openrndr.math.Matrix44
import org.openrndr.math.Quaternion
import org.openrndr.math.transforms.transform
import java.io.File
/** Tools to convert GltfFile into a DNK3 scene */
fun GltfFile.buildSceneNodes(): List<List<SceneNode>> {
val sceneImages = mutableMapOf<GltfImage, ColorBuffer>()
fun GltfImage.createSceneImage(): ColorBuffer {
return sceneImages.getOrPut(this) { loadImage(File(file.parent, uri)) }
}
val sceneMaterials = mutableMapOf<GltfMaterial, Material>()
fun GltfMaterial.createSceneMaterial(): Material = sceneMaterials.getOrPut(this) {
val material = PBRMaterial()
material.doubleSided = this.doubleSided ?: false
pbrMetallicRoughness?.let { pbr ->
material.roughness = pbr.roughnessFactor ?: 1.0
material.metalness = pbr.metallicFactor ?: 1.0
pbr.baseColorTexture?.let { texture ->
val cb = images!![textures!![texture.index].source].createSceneImage()
cb.filter(MinifyingFilter.LINEAR_MIPMAP_LINEAR, MagnifyingFilter.LINEAR)
cb.wrapU = WrapMode.REPEAT
cb.wrapV = WrapMode.REPEAT
val sceneTexture = Texture(ModelCoordinates(texture = cb, pre = "x_texCoord.y = 1.0-x_texCoord.y;"), TextureTarget.COLOR)
material.textures.add(sceneTexture)
}
pbr.metallicRoughnessTexture?.let { texture ->
val cb = images!![textures!![texture.index].source].createSceneImage()
cb.filter(MinifyingFilter.LINEAR_MIPMAP_LINEAR, MagnifyingFilter.LINEAR)
cb.wrapU = WrapMode.REPEAT
cb.wrapV = WrapMode.REPEAT
val sceneTexture = Texture(ModelCoordinates(texture = cb, pre = "x_texCoord.y = 1.0-x_texCoord.y;"), TextureTarget.METALNESS_ROUGHNESS)
material.textures.add(sceneTexture)
}
}
occlusionTexture?.let { texture ->
val cb = images!![textures!![texture.index].source].createSceneImage()
cb.filter(MinifyingFilter.LINEAR_MIPMAP_LINEAR, MagnifyingFilter.LINEAR)
cb.wrapU = WrapMode.REPEAT
cb.wrapV = WrapMode.REPEAT
val sceneTexture = Texture(ModelCoordinates(texture = cb, pre = "x_texCoord.y = 1.0-x_texCoord.y;"), TextureTarget.AMBIENT_OCCLUSION)
material.textures.add(sceneTexture)
}
normalTexture?.let { texture ->
val cb = images!![textures!![texture.index].source].createSceneImage()
cb.filter(MinifyingFilter.LINEAR_MIPMAP_LINEAR, MagnifyingFilter.LINEAR)
cb.wrapU = WrapMode.REPEAT
cb.wrapV = WrapMode.REPEAT
val sceneTexture = Texture(ModelCoordinates(texture = cb, tangentInput = "va_tangent", pre = "x_texCoord.y = 1.0-x_texCoord.y;"), TextureTarget.NORMAL)
material.textures.add(sceneTexture)
}
emissiveFactor?.let {
material.emission = ColorRGBa(it[0], it[1], it[2], 1.0)
}
material
}
fun GltfPrimitive.createScenePrimitive(): MeshPrimitive {
val drawCommand = createDrawCommand(this@buildSceneNodes)
val geometry = Geometry(listOf(drawCommand.vertexBuffer),
drawCommand.indexBuffer,
drawCommand.primitive,
0,
drawCommand.vertexCount)
val material = materials[material].createSceneMaterial()
return MeshPrimitive(geometry, material)
}
val sceneMeshes = mutableMapOf<GltfMesh, Mesh>()
fun GltfMesh.createSceneMesh(): Mesh = sceneMeshes.getOrPut(this) {
Mesh(primitives.map {
it.createScenePrimitive()
})
}
fun GltfNode.createSceneNode(): SceneNode {
val node = SceneNode()
mesh?.let {
node.entities.add(meshes[it].createSceneMesh())
}
val localTransform = transform {
translation?.let {
translate(it[0], it[1], it[2])
}
rotation?.let {
val q = Quaternion(it[0], it[1], it[2], it[3])
multiply(q.matrix.matrix44)
}
scale?.let {
scale(it[0], it[1], it[2])
}
}
node.transform = this.matrix?.let {
Matrix44.fromDoubleArray(it).transposed
} ?: localTransform
for (child in children.orEmpty) {
node.children.add(nodes[child].createSceneNode())
}
return node
}
return scenes.map { scene ->
scene.nodes.map { node ->
nodes[node].createSceneNode()
}
}
}
private val IntArray?.orEmpty: IntArray get() = this ?: IntArray(0)

View File

@@ -3,6 +3,7 @@ rootProject.name = 'orx'
include 'orx-boofcv', include 'orx-boofcv',
'orx-camera', 'orx-camera',
'orx-compositor', 'orx-compositor',
'orx-dnk3',
'orx-easing', 'orx-easing',
'orx-file-watcher', 'orx-file-watcher',
'orx-parameters', 'orx-parameters',