Add orx-dnk3a
This commit is contained in:
@@ -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"
|
||||||
|
|||||||
142
demo-data/gltf-models/box/Box.gltf
Normal file
142
demo-data/gltf-models/box/Box.gltf
Normal 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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
BIN
demo-data/gltf-models/box/Box0.bin
Normal file
BIN
demo-data/gltf-models/box/Box0.bin
Normal file
Binary file not shown.
219
demo-data/gltf-models/duck/Duck.gltf
Normal file
219
demo-data/gltf-models/duck/Duck.gltf
Normal 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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
BIN
demo-data/gltf-models/duck/Duck0.bin
Normal file
BIN
demo-data/gltf-models/duck/Duck0.bin
Normal file
Binary file not shown.
BIN
demo-data/gltf-models/duck/DuckCM.png
Normal file
BIN
demo-data/gltf-models/duck/DuckCM.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
BIN
demo-data/gltf-models/suzanne/Suzanne.bin
Normal file
BIN
demo-data/gltf-models/suzanne/Suzanne.bin
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 577 KiB |
193
demo-data/gltf-models/suzanne/Suzanne.gltf
Normal file
193
demo-data/gltf-models/suzanne/Suzanne.gltf
Normal 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
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
BIN
demo-data/gltf-models/suzanne/Suzanne_BaseColor.png
Normal file
BIN
demo-data/gltf-models/suzanne/Suzanne_BaseColor.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 MiB |
BIN
demo-data/gltf-models/suzanne/Suzanne_MetallicRoughness.png
Normal file
BIN
demo-data/gltf-models/suzanne/Suzanne_MetallicRoughness.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 840 KiB |
20
orx-dnk3/README.md
Normal file
20
orx-dnk3/README.md
Normal 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
20
orx-dnk3/build.gradle
Normal 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)
|
||||||
|
}
|
||||||
47
orx-dnk3/src/demo/kotlin/DemoObject01.kt
Normal file
47
orx-dnk3/src/demo/kotlin/DemoObject01.kt
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
55
orx-dnk3/src/demo/kotlin/DemoScene01.kt
Normal file
55
orx-dnk3/src/demo/kotlin/DemoScene01.kt
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
57
orx-dnk3/src/demo/kotlin/DemoScene02.kt
Normal file
57
orx-dnk3/src/demo/kotlin/DemoScene02.kt
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
6
orx-dnk3/src/main/kotlin/DryRenderer.kt
Normal file
6
orx-dnk3/src/main/kotlin/DryRenderer.kt
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
package org.openrndr.extra.dnk3
|
||||||
|
|
||||||
|
fun dryRenderer() : SceneRenderer {
|
||||||
|
val sr = SceneRenderer()
|
||||||
|
return sr
|
||||||
|
}
|
||||||
37
orx-dnk3/src/main/kotlin/Entity.kt
Normal file
37
orx-dnk3/src/main/kotlin/Entity.kt
Normal 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
|
||||||
|
}
|
||||||
|
|
||||||
120
orx-dnk3/src/main/kotlin/Facet.kt
Normal file
120
orx-dnk3/src/main/kotlin/Facet.kt
Normal 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;
|
||||||
|
"""
|
||||||
|
}
|
||||||
48
orx-dnk3/src/main/kotlin/Light.kt
Normal file
48
orx-dnk3/src/main/kotlin/Light.kt
Normal 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()
|
||||||
40
orx-dnk3/src/main/kotlin/Material.kt
Normal file
40
orx-dnk3/src/main/kotlin/Material.kt
Normal 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>
|
||||||
|
)
|
||||||
|
|
||||||
555
orx-dnk3/src/main/kotlin/PBRMaterial.kt
Normal file
555
orx-dnk3/src/main/kotlin/PBRMaterial.kt
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
61
orx-dnk3/src/main/kotlin/Post.kt
Normal file
61
orx-dnk3/src/main/kotlin/Post.kt
Normal 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)
|
||||||
|
}
|
||||||
24
orx-dnk3/src/main/kotlin/RenderPass.kt
Normal file
24
orx-dnk3/src/main/kotlin/RenderPass.kt
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
67
orx-dnk3/src/main/kotlin/Scene.kt
Normal file
67
orx-dnk3/src/main/kotlin/Scene.kt
Normal 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
|
||||||
|
}
|
||||||
|
|
||||||
240
orx-dnk3/src/main/kotlin/SceneRenderer.kt
Normal file
240
orx-dnk3/src/main/kotlin/SceneRenderer.kt
Normal 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
|
||||||
|
}
|
||||||
228
orx-dnk3/src/main/kotlin/ShaderUtilities.kt
Normal file
228
orx-dnk3/src/main/kotlin/ShaderUtilities.kt
Normal 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()
|
||||||
99
orx-dnk3/src/main/kotlin/Shadows.kt
Normal file
99
orx-dnk3/src/main/kotlin/Shadows.kt
Normal 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()
|
||||||
|
}
|
||||||
46
orx-dnk3/src/main/kotlin/gltf/Glb.kt
Normal file
46
orx-dnk3/src/main/kotlin/gltf/Glb.kt
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
220
orx-dnk3/src/main/kotlin/gltf/Gltf.kt
Normal file
220
orx-dnk3/src/main/kotlin/gltf/Gltf.kt
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
126
orx-dnk3/src/main/kotlin/gltf/GltfScene.kt
Normal file
126
orx-dnk3/src/main/kotlin/gltf/GltfScene.kt
Normal 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)
|
||||||
@@ -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',
|
||||||
|
|||||||
Reference in New Issue
Block a user