Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Shader hooks #7149

Merged
merged 23 commits into from
Sep 17, 2024
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
853 changes: 851 additions & 2 deletions src/webgl/material.js

Large diffs are not rendered by default.

167 changes: 152 additions & 15 deletions src/webgl/p5.RendererGL.js
Original file line number Diff line number Diff line change
Expand Up @@ -1727,7 +1727,7 @@ p5.RendererGL = class RendererGL extends p5.Renderer {
sphereMapping
);
}
this.uNMatrix.inverseTranspose(this.uMVMatrix);
this.uNMatrix.inverseTranspose(this.uViewMatrix);
this.uNMatrix.invert3x3(this.uNMatrix);
this.sphereMapping.setUniform('uFovY', this._curCamera.cameraFOV);
this.sphereMapping.setUniform('uAspect', this._curCamera.aspectRatio);
Expand Down Expand Up @@ -1798,6 +1798,15 @@ p5.RendererGL = class RendererGL extends p5.Renderer {
return this._getImmediateLineShader();
}

materialShader() {
if (!this._pInst._glAttributes.perPixelLighting) {
throw new Error(
'The material shader does not support hooks without perPixelLighting. Try turning it back on.'
);
}
return this._getLightShader();
}

_getLightShader() {
if (!this._defaultLightShader) {
if (this._pInst._glAttributes.perPixelLighting) {
Expand All @@ -1806,7 +1815,34 @@ p5.RendererGL = class RendererGL extends p5.Renderer {
this._webGL2CompatibilityPrefix('vert', 'highp') +
defaultShaders.phongVert,
this._webGL2CompatibilityPrefix('frag', 'highp') +
defaultShaders.phongFrag
defaultShaders.phongFrag,
{
vertex: {
'void beforeVertex': '() {}',
'vec3 getLocalPosition': '(vec3 position) { return position; }',
'vec3 getWorldPosition': '(vec3 position) { return position; }',
'vec3 getLocalNormal': '(vec3 normal) { return normal; }',
'vec3 getWorldNormal': '(vec3 normal) { return normal; }',
'vec2 getUV': '(vec2 uv) { return uv; }',
'vec4 getVertexColor': '(vec4 color) { return color; }',
'void afterVertex': '() {}'
},
fragment: {
'void beforeFragment': '() {}',
'Inputs getPixelInputs': '(Inputs inputs) { return inputs; }',
'vec4 combineColors': `(ColorComponents components) {
vec4 color = vec4(0.);
color.rgb += components.diffuse * components.baseColor;
color.rgb += components.ambient * components.ambientColor;
color.rgb += components.specular * components.specularColor;
color.rgb += components.emissive;
color.a = components.opacity;
return color;
}`,
'vec4 getFinalColor': '(vec4 color) { return color; }',
'void afterFragment': '() {}'
}
}
);
} else {
this._defaultLightShader = new p5.Shader(
Expand Down Expand Up @@ -1836,55 +1872,163 @@ p5.RendererGL = class RendererGL extends p5.Renderer {
return this._defaultImmediateModeShader;
}

normalShader() {
return this._getNormalShader();
}

_getNormalShader() {
if (!this._defaultNormalShader) {
this._defaultNormalShader = new p5.Shader(
this,
this._webGL2CompatibilityPrefix('vert', 'mediump') +
defaultShaders.normalVert,
this._webGL2CompatibilityPrefix('frag', 'mediump') +
defaultShaders.normalFrag
defaultShaders.normalFrag,
{
vertex: {
'void beforeVertex': '() {}',
'vec3 getLocalPosition': '(vec3 position) { return position; }',
'vec3 getWorldPosition': '(vec3 position) { return position; }',
'vec3 getLocalNormal': '(vec3 normal) { return normal; }',
'vec3 getWorldNormal': '(vec3 normal) { return normal; }',
'vec2 getUV': '(vec2 uv) { return uv; }',
'vec4 getVertexColor': '(vec4 color) { return color; }',
'void afterVertex': '() {}'
},
fragment: {
'void beforeFragment': '() {}',
'vec4 getFinalColor': '(vec4 color) { return color; }',
'void afterFragment': '() {}'
}
}
);
}

return this._defaultNormalShader;
}

colorShader() {
return this._getColorShader();
}

_getColorShader() {
if (!this._defaultColorShader) {
this._defaultColorShader = new p5.Shader(
this,
this._webGL2CompatibilityPrefix('vert', 'mediump') +
defaultShaders.normalVert,
this._webGL2CompatibilityPrefix('frag', 'mediump') +
defaultShaders.basicFrag
defaultShaders.basicFrag,
{
vertex: {
'void beforeVertex': '() {}',
'vec3 getLocalPosition': '(vec3 position) { return position; }',
'vec3 getWorldPosition': '(vec3 position) { return position; }',
'vec3 getLocalNormal': '(vec3 normal) { return normal; }',
'vec3 getWorldNormal': '(vec3 normal) { return normal; }',
'vec2 getUV': '(vec2 uv) { return uv; }',
'vec4 getVertexColor': '(vec4 color) { return color; }',
'void afterVertex': '() {}'
},
fragment: {
'void beforeFragment': '() {}',
'vec4 getFinalColor': '(vec4 color) { return color; }',
'void afterFragment': '() {}'
}
}
);
}

return this._defaultColorShader;
}

/**
* TODO(dave): un-private this when there is a way to actually override the
* shader used for points
*
* Get the shader used when drawing points with <a href="#/p5/point">`point()`</a>.
*
* You can call <a href="#/p5.Shader/modify">`pointShader().modify()`</a>
* and change any of the following hooks:
* - `void beforeVertex`: Called at the start of the vertex shader.
* - `vec3 getLocalPosition`: Update the position of vertices before transforms are applied. It takes in `vec3 position` and must return a modified version.
* - `vec3 getWorldPosition`: Update the position of vertices after transforms are applied. It takes in `vec3 position` and pust return a modified version.
* - `float getPointSize`: Update the size of the point. It takes in `float size` and must return a modified version.
* - `void afterVertex`: Called at the end of the vertex shader.
* - `void beforeFragment`: Called at the start of the fragment shader.
* - `bool shouldDiscard`: Points are drawn inside a square, with the corners discarded in the fragment shader to create a circle. Use this to change this logic. It takes in a `bool willDiscard` and must return a modified version.
* - `vec4 getFinalColor`: Update the final color after mixing. It takes in a `vec4 color` and must return a modified version.
* - `void afterFragment`: Called at the end of the fragment shader.
*
* Call `pointShader().inspectHooks()` to see all the possible hooks and
* their default implementations.
*
* @returns {p5.Shader} The `point()` shader
* @private()
*/
pointShader() {
return this._getPointShader();
}

_getPointShader() {
if (!this._defaultPointShader) {
this._defaultPointShader = new p5.Shader(
this,
this._webGL2CompatibilityPrefix('vert', 'mediump') +
defaultShaders.pointVert,
this._webGL2CompatibilityPrefix('frag', 'mediump') +
defaultShaders.pointFrag
defaultShaders.pointFrag,
{
vertex: {
'void beforeVertex': '() {}',
'vec3 getLocalPosition': '(vec3 position) { return position; }',
'vec3 getWorldPosition': '(vec3 position) { return position; }',
'float getPointSize': '(float size) { return size; }',
'void afterVertex': '() {}'
},
fragment: {
'void beforeFragment': '() {}',
'vec4 getFinalColor': '(vec4 color) { return color; }',
'bool shouldDiscard': '(bool outside) { return outside; }',
'void afterFragment': '() {}'
}
}
);
}
return this._defaultPointShader;
}

strokeShader() {
return this._getLineShader();
}

_getLineShader() {
if (!this._defaultLineShader) {
this._defaultLineShader = new p5.Shader(
this,
this._webGL2CompatibilityPrefix('vert', 'mediump') +
defaultShaders.lineVert,
this._webGL2CompatibilityPrefix('frag', 'mediump') +
defaultShaders.lineFrag
defaultShaders.lineFrag,
{
vertex: {
'void beforeVertex': '() {}',
'vec3 getLocalPosition': '(vec3 position) { return position; }',
'vec3 getWorldPosition': '(vec3 position) { return position; }',
'float getStrokeWeight': '(float weight) { return weight; }',
'vec2 getLineCenter': '(vec2 center) { return center; }',
'vec2 getLinePosition': '(vec2 position) { return position; }',
'vec4 getVertexColor': '(vec4 color) { return color; }',
'void afterVertex': '() {}'
},
fragment: {
'void beforeFragment': '() {}',
'Inputs getPixelInputs': '(Inputs inputs) { return inputs; }',
'vec4 getFinalColor': '(vec4 color) { return color; }',
'bool shouldDiscard': '(bool outside) { return outside; }',
'void afterFragment': '() {}'
}
}
);
}

Expand Down Expand Up @@ -2102,7 +2246,7 @@ p5.RendererGL = class RendererGL extends p5.Renderer {
fillShader.setUniform('uSpecular', this._useSpecularMaterial);
fillShader.setUniform('uEmissive', this._useEmissiveMaterial);
fillShader.setUniform('uShininess', this._useShininess);
fillShader.setUniform('metallic', this._useMetalness);
fillShader.setUniform('uMetallic', this._useMetalness);

this._setImageLightUniforms(fillShader);

Expand Down Expand Up @@ -2175,14 +2319,7 @@ p5.RendererGL = class RendererGL extends p5.Renderer {
let diffusedLight = this.getDiffusedTexture(this.activeImageLight);
shader.setUniform('environmentMapDiffused', diffusedLight);
let specularLight = this.getSpecularTexture(this.activeImageLight);
// In p5js the range of shininess is >= 1,
// Therefore roughness range will be ([0,1]*8)*20 or [0, 160]
// The factor of 8 is because currently the getSpecularTexture
// only calculated 8 different levels of roughness
// The factor of 20 is just to spread up this range so that,
// [1, max] of shininess is converted to [0,160] of roughness
let roughness = 20 / this._useShininess;
shader.setUniform('levelOfDetail', roughness * 8);

shader.setUniform('environmentMapSpecular', specularLight);
}
}
Expand Down
Loading
Loading