Skip to content

Commit 1a8b75f

Browse files
committed
Add shader utils generation
Adds generation of UniformsLib, ShaderLib, and ShaderChunk for the python side.
1 parent 386eed2 commit 1a8b75f

File tree

7 files changed

+312
-8
lines changed

7 files changed

+312
-8
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ pythreejs/static/
1616
# Autogen files
1717
*_autogen.py
1818
*.autogen.js
19+
*.autogen.json
1920
js/src/**/index.js
2021
pythreejs/**/__init__.py
2122

examples/Shaders.ipynb

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "markdown",
5+
"metadata": {},
6+
"source": [
7+
"This notebook gives a simple example of how to use the ShaderMaterial to write custom shaders from the Python side. For further information about the shaders, consult the three.js docs."
8+
]
9+
},
10+
{
11+
"cell_type": "code",
12+
"execution_count": null,
13+
"metadata": {},
14+
"outputs": [],
15+
"source": [
16+
"from pythreejs import *\n",
17+
"import ipywidgets\n",
18+
"from IPython.display import display"
19+
]
20+
},
21+
{
22+
"cell_type": "code",
23+
"execution_count": null,
24+
"metadata": {},
25+
"outputs": [],
26+
"source": [
27+
"ShaderMaterial?"
28+
]
29+
},
30+
{
31+
"cell_type": "code",
32+
"execution_count": null,
33+
"metadata": {},
34+
"outputs": [],
35+
"source": [
36+
"vertex_shader = \"\"\"\n",
37+
"uniform float time;\n",
38+
"uniform vec2 resolution;\n",
39+
"\n",
40+
"void main() {\n",
41+
" vec3 pos = vec3(position.x + time * resolution.x, position.y + time * resolution.y, position.z);\n",
42+
" gl_Position = projectionMatrix * modelViewMatrix * vec4( pos, 1.0 );\n",
43+
"}\n",
44+
"\"\"\""
45+
]
46+
},
47+
{
48+
"cell_type": "code",
49+
"execution_count": null,
50+
"metadata": {},
51+
"outputs": [],
52+
"source": [
53+
"fragment_shader = \"\"\"\n",
54+
"void main() {\n",
55+
" #ifdef OVERRIDE_COLOR\n",
56+
" gl_FragColor = vec4(0, 0.5, 0, 1.0);\n",
57+
" #else\n",
58+
" gl_FragColor = vec4(0.5, 0, 0, 1.0);\n",
59+
" #endif\n",
60+
"}\n",
61+
"\"\"\""
62+
]
63+
},
64+
{
65+
"cell_type": "code",
66+
"execution_count": null,
67+
"metadata": {},
68+
"outputs": [],
69+
"source": [
70+
"material = ShaderMaterial(\n",
71+
" uniforms=dict(\n",
72+
" time=dict(value=0.0),\n",
73+
" resolution=dict(value=(1, 1)),\n",
74+
" **UniformsLib['lights']\n",
75+
" ),\n",
76+
" defines=dict(\n",
77+
" OVERRIDE_COLOR=1,\n",
78+
" ),\n",
79+
" vertexShader=vertex_shader,\n",
80+
" fragmentShader=fragment_shader,\n",
81+
" lights=True,\n",
82+
")"
83+
]
84+
},
85+
{
86+
"cell_type": "code",
87+
"execution_count": null,
88+
"metadata": {},
89+
"outputs": [],
90+
"source": [
91+
"material"
92+
]
93+
},
94+
{
95+
"cell_type": "code",
96+
"execution_count": null,
97+
"metadata": {},
98+
"outputs": [],
99+
"source": [
100+
"material.defines = dict()\n",
101+
"material.needsUpdate = True"
102+
]
103+
},
104+
{
105+
"cell_type": "code",
106+
"execution_count": null,
107+
"metadata": {},
108+
"outputs": [],
109+
"source": [
110+
"def update_time(time):\n",
111+
" uniforms = dict(**material.uniforms)\n",
112+
" uniforms.update(time=dict(value=time))\n",
113+
" material.uniforms = uniforms\n",
114+
" material.needsUpdate = True"
115+
]
116+
},
117+
{
118+
"cell_type": "code",
119+
"execution_count": null,
120+
"metadata": {},
121+
"outputs": [],
122+
"source": [
123+
"update_time(-15)"
124+
]
125+
},
126+
{
127+
"cell_type": "code",
128+
"execution_count": null,
129+
"metadata": {},
130+
"outputs": [],
131+
"source": []
132+
}
133+
],
134+
"metadata": {
135+
"kernelspec": {
136+
"display_name": "Python 3",
137+
"language": "python",
138+
"name": "python3"
139+
},
140+
"language_info": {
141+
"codemirror_mode": {
142+
"name": "ipython",
143+
"version": 3
144+
},
145+
"file_extension": ".py",
146+
"mimetype": "text/x-python",
147+
"name": "python",
148+
"nbconvert_exporter": "python",
149+
"pygments_lexer": "ipython3",
150+
"version": "3.5.4"
151+
}
152+
},
153+
"nbformat": 4,
154+
"nbformat_minor": 2
155+
}

js/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@
1212
"scripts": {
1313
"clean": "rimraf dist && rimraf ../pythreejs/static && node ./scripts/clean-generated-files.js",
1414
"autogen-enums": "node ./scripts/generate-enums.js",
15-
"autogen": "node ./scripts/generate-wrappers.js",
15+
"autogen-shaders": "node ./scripts/generate-shader-utils.js",
16+
"autogen-wrappers": "node ./scripts/generate-shader-utils.js",
17+
"autogen": "npm run autogen-wrappers && npm run autogen-shaders",
1618
"build": "webpack && node ./scripts/copy-three.js",
1719
"prepublish": "npm run autogen && npm run build"
1820
},

js/scripts/clean-generated-files.js

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,16 @@ var scriptDir = __dirname;
1010
var baseDir = path.resolve(scriptDir, '..');
1111

1212
// Execute a function for each match to a glob query
13-
//
13+
//
1414
// Parameters:
1515
// globPattern: String glob pattern for node-glob
1616
// mapFn: Function function(pathRelativeToCwd), should return a promise or list of promises
1717
// globOptions: Object of options passed directly to node-glob
1818
//
1919
// Returns: Promise that resolves with array of results from mapFn applies to all glob matches
20-
function mapPromiseFnOverGlob(globPattern, mapFn, globOptions) {
20+
function mapPromiseFnOverGlob(globPattern, mapFn, globOptions) {
2121
return new Promise(function(resolve, reject) {
22-
22+
2323
var promises = [];
2424

2525
// trailing slash will match only directories
@@ -53,8 +53,8 @@ function rmFileGlobAsync(globPattern) {
5353
console.log(filePath);
5454
var absPath = path.resolve(baseDir, filePath);
5555
return fse.removeAsync(absPath);
56-
}, {
57-
cwd: baseDir,
56+
}, {
57+
cwd: baseDir,
5858
nodir: true,
5959
ignore: [
6060
'./node_modules/**'
@@ -65,13 +65,15 @@ function rmFileGlobAsync(globPattern) {
6565
function cleanGeneratedFilesAsync() {
6666
// trailing slash will match only directories
6767
var jsPromise = rmFileGlobAsync('./**/*.autogen.js');
68+
var jsonPromise = rmFileGlobAsync('./**/*.autogen.json');
6869
var jsIndexPromise = rmFileGlobAsync('./**/index.js');
6970

7071
var pyPromise = rmFileGlobAsync('../pythreejs/**/*_autogen.py');
7172
var pyIndexPromise = rmFileGlobAsync('../pythreejs/**/__init__.py');
7273

7374
return Promise.all([
74-
jsPromise,
75+
jsPromise,
76+
jsonPromise,
7577
jsIndexPromise,
7678
pyPromise,
7779
pyIndexPromise,
@@ -82,4 +84,4 @@ if (require.main === module) {
8284
cleanGeneratedFilesAsync().then(function() {
8385
console.log('DONE');
8486
});
85-
}
87+
}

js/scripts/generate-shader-utils.js

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
var _ = require('underscore');
2+
var path = require('path');
3+
var fs = require('fs');
4+
var fse = require('fs-extra');
5+
var Promise = require('bluebird');
6+
var Handlebars = require('handlebars');
7+
8+
Promise.promisifyAll(fs);
9+
Promise.promisifyAll(fse);
10+
11+
var shaderUtilsConfig = require('./three-shader-utils-config');
12+
13+
var scriptDir = __dirname;
14+
var baseDir = path.resolve(scriptDir, '..');
15+
16+
var pySrcDir = path.resolve(baseDir, '..', 'pythreejs');
17+
var templateDir = path.resolve(scriptDir, 'templates');
18+
19+
var AUTOGEN_EXT = 'autogen';
20+
var JSON_AUTOGEN_EXT = '.' + AUTOGEN_EXT + '.json';
21+
22+
23+
// We actually need access to THREE data here
24+
var THREE = require('three');
25+
26+
27+
//
28+
// Templates
29+
//
30+
31+
function compileTemplate(templateName) {
32+
var templateName = path.basename(templateName, '.mustache');
33+
var templatePath = path.resolve(templateDir, templateName + '.mustache');
34+
return Handlebars.compile(fs.readFileSync(templatePath, {
35+
encoding: 'utf-8'
36+
}));
37+
}
38+
39+
var pyWrapperTemplate = compileTemplate('py_shader_utils');
40+
41+
var pathSep = /\\|\//;
42+
43+
44+
//
45+
// Helper functions
46+
//
47+
48+
function mapPromiseFnOverObject(object, mapFn) {
49+
var promises = [];
50+
51+
Object.keys(object).forEach(function(key) {
52+
var value = object[key];
53+
var result = mapFn(key, value);
54+
if (result instanceof Array) {
55+
promises = promises.concat(result);
56+
} else {
57+
promises.push(result);
58+
}
59+
}, this);
60+
61+
return Promise.all(promises);
62+
}
63+
64+
65+
function createPythonWrapper(name, relativePath) {
66+
67+
var data = THREE[name];
68+
69+
var jsonPath = path.resolve(pySrcDir, relativePath + JSON_AUTOGEN_EXT);
70+
var promises = [fse.outputFileAsync(jsonPath, JSON.stringify(data, null, 4))];
71+
72+
var pyPath = path.resolve(pySrcDir, relativePath + '_' + AUTOGEN_EXT + '.py');
73+
var output = pyWrapperTemplate({
74+
name: name,
75+
jsonPath: name + JSON_AUTOGEN_EXT,
76+
77+
now: new Date(),
78+
generatorScriptName: path.basename(__filename),
79+
});
80+
promises.push(fse.outputFileAsync(pyPath, output))
81+
return Promise.all(promises);
82+
}
83+
84+
function createPythonModuleInitFile(modulePath) {
85+
86+
var dirname = path.dirname(modulePath);
87+
var pyInitFilePath = path.resolve(pySrcDir, dirname, '__init__.py');
88+
return fse.ensureFileAsync(pyInitFilePath);
89+
90+
}
91+
92+
function createPythonFiles() {
93+
94+
// Prevent python file generation when outside dir (e.g. npm install in dependent)
95+
if (!fs.existsSync(pySrcDir)) {
96+
return Promise.resolve();
97+
}
98+
99+
return mapPromiseFnOverObject(shaderUtilsConfig, function(name, configObj) {
100+
var relativePath = configObj.relativePath;
101+
return createPythonWrapper(name, relativePath).then(function() {
102+
// ensures each dir has empty __init__.py file for proper importing of sub dirs
103+
return createPythonModuleInitFile(relativePath);
104+
});
105+
});
106+
}
107+
108+
function generateFiles() {
109+
110+
return Promise.all([
111+
createPythonFiles(),
112+
]);
113+
114+
}
115+
116+
if (require.main === module) {
117+
generateFiles().then(function() {
118+
console.log('DONE');
119+
});
120+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
"""{{ name }}
2+
3+
Autogenerated by {{ generatorScriptName }}
4+
Date: {{ now }}
5+
"""
6+
7+
import json
8+
import os
9+
10+
here = os.path.dirname(__file__)
11+
12+
with open(os.path.join(here, '{{ jsonPath }}')) as data_file:
13+
{{ name }} = json.load(data_file)
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
module.exports = {
2+
ShaderChunk: {
3+
relativePath: './renderers/shaders/ShaderChunk',
4+
},
5+
ShaderLib: {
6+
relativePath: './renderers/shaders/ShaderLib',
7+
},
8+
UniformsLib: {
9+
relativePath: './renderers/shaders/UniformsLib',
10+
},
11+
};

0 commit comments

Comments
 (0)