Fix pixelate effect
This implementation of the pixel effect is now very similar to the PixelBender implementation in Scratch 2.0 I also added a slider to demo.html as a debugging aid: it manipulates a value that is passed into the shader and used in whatever way helps.
This commit is contained in:
parent
41d3d6f6ba
commit
920271d076
@ -5,10 +5,29 @@
|
|||||||
<title>Scratch WebGL rendering demo</title>
|
<title>Scratch WebGL rendering demo</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<canvas id="scratch-stage" width="10" height="10" style="border:1px dashed black;"></canvas>
|
<canvas id="scratch-stage" width="10" height="10" style="border:1px dashed black; margin-top: 9px;"></canvas>
|
||||||
|
<p>
|
||||||
|
<input type="range" id="fudge" style="width:50%" min="0" max="100" step="any" oninput="onFudgeChanged(this.value)" onchange="onFudgeChanged(this.value)">
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Min: <input id="fudgeMin" type="number" onchange="onFudgeMinChanged(this.value)">
|
||||||
|
Max: <input id="fudgeMax" type="number" onchange="onFudgeMaxChanged(this.value)">
|
||||||
|
</p>
|
||||||
</body>
|
</body>
|
||||||
<script src="render-webgl.js"></script>
|
<script src="render-webgl.js"></script>
|
||||||
<script>
|
<script>
|
||||||
|
window.fudge = 0;
|
||||||
|
var fudgeInput = document.getElementById('fudge');
|
||||||
|
function onFudgeMinChanged(newValue) {
|
||||||
|
fudgeInput.min = newValue;
|
||||||
|
}
|
||||||
|
function onFudgeMaxChanged(newValue) {
|
||||||
|
fudgeInput.max = newValue;
|
||||||
|
}
|
||||||
|
function onFudgeChanged(newValue) {
|
||||||
|
window.fudge = newValue;
|
||||||
|
}
|
||||||
|
|
||||||
var canvas = document.getElementById('scratch-stage');
|
var canvas = document.getElementById('scratch-stage');
|
||||||
var renderer = new RenderWebGL(canvas);
|
var renderer = new RenderWebGL(canvas);
|
||||||
var drawableID = renderer.createDrawable();
|
var drawableID = renderer.createDrawable();
|
||||||
@ -17,12 +36,14 @@
|
|||||||
renderer.draw();
|
renderer.draw();
|
||||||
requestAnimationFrame(drawStep);
|
requestAnimationFrame(drawStep);
|
||||||
}
|
}
|
||||||
|
renderer.updateDrawableProperties(drawableID, {
|
||||||
|
position: [0, 0],
|
||||||
|
scale: 300,
|
||||||
|
direction: 90
|
||||||
|
});
|
||||||
var direction = 90;
|
var direction = 90;
|
||||||
var posX = 0;
|
|
||||||
var posY = 0;
|
|
||||||
var scale = 100;
|
|
||||||
function thinkStep() {
|
function thinkStep() {
|
||||||
direction += 0.1;
|
//direction += 0.1;
|
||||||
|
|
||||||
var props = {};
|
var props = {};
|
||||||
//props.position = [posX, posY];
|
//props.position = [posX, posY];
|
||||||
|
@ -4,6 +4,7 @@ var xhr = require('xhr');
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* An object which can be drawn by the renderer.
|
* An object which can be drawn by the renderer.
|
||||||
|
* TODO: double-buffer all rendering state (position, skin, shader index, etc.)
|
||||||
* @param renderer The renderer which owns this Drawable.
|
* @param renderer The renderer which owns this Drawable.
|
||||||
* @param gl The OpenGL context.
|
* @param gl The OpenGL context.
|
||||||
* @constructor
|
* @constructor
|
||||||
@ -15,24 +16,50 @@ function Drawable(renderer, gl) {
|
|||||||
this._renderer = renderer;
|
this._renderer = renderer;
|
||||||
this._gl = gl;
|
this._gl = gl;
|
||||||
|
|
||||||
// TODO: double-buffer uniforms
|
/**
|
||||||
|
* The uniforms to be used by the vertex and pixel shaders.
|
||||||
|
* Some of these are used by other parts of the renderer as well.
|
||||||
|
* @type {Object.<string,*>}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
this._uniforms = {
|
this._uniforms = {
|
||||||
u_texture: null,
|
/**
|
||||||
u_mvp: twgl.m4.identity()
|
* The model-view-projection matrix.
|
||||||
|
* @type {module:twgl/m4.Mat4}
|
||||||
|
*/
|
||||||
|
u_mvp: twgl.m4.identity(),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The scaling factor to use when drawing.
|
||||||
|
* This is updated at the same time as u_mvp.
|
||||||
|
* @type {number}
|
||||||
|
*/
|
||||||
|
u_scale: 1,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The nominal (not necessarily current) size of the current skin.
|
||||||
|
* @type {number[]}
|
||||||
|
*/
|
||||||
|
u_skinSize: [0, 0],
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The actual WebGL texture object for the skin.
|
||||||
|
* @type {WebGLTexture}
|
||||||
|
*/
|
||||||
|
u_texture: null
|
||||||
};
|
};
|
||||||
|
|
||||||
// Effect values are uniforms too
|
// Effect values are uniforms too
|
||||||
var numEffects = Drawable.EFFECTS.length;
|
var numEffects = Drawable.EFFECTS.length;
|
||||||
for (var index = 0; index < numEffects; ++index) {
|
for (var index = 0; index < numEffects; ++index) {
|
||||||
var effectName = Drawable.EFFECTS[index];
|
var effectName = Drawable.EFFECTS[index];
|
||||||
var converter = Drawable._effectCoverter[effectName];
|
var converter = Drawable._effectConverter[effectName];
|
||||||
this._uniforms['u_' + effectName] = converter(0);
|
this._uniforms['u_' + effectName] = converter(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
this._position = twgl.v3.create(0, 0);
|
this._position = twgl.v3.create(0, 0);
|
||||||
this._scale = 100;
|
this._scale = 100;
|
||||||
this._direction = 90;
|
this._direction = 90;
|
||||||
this._dimensions = twgl.v3.create(0, 0);
|
|
||||||
this._transformDirty = true;
|
this._transformDirty = true;
|
||||||
this._shaderIndex = 0;
|
this._shaderIndex = 0;
|
||||||
this._costumeResolution = 2; // TODO: only for bitmaps
|
this._costumeResolution = 2; // TODO: only for bitmaps
|
||||||
@ -49,7 +76,7 @@ module.exports = Drawable;
|
|||||||
* @type {Object.<string,function>}
|
* @type {Object.<string,function>}
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
Drawable._effectCoverter = {
|
Drawable._effectConverter = {
|
||||||
color: function(x) {
|
color: function(x) {
|
||||||
return (360 * x / 200) % 360;
|
return (360 * x / 200) % 360;
|
||||||
},
|
},
|
||||||
@ -60,9 +87,7 @@ Drawable._effectCoverter = {
|
|||||||
return x * Math.PI / 180;
|
return x * Math.PI / 180;
|
||||||
},
|
},
|
||||||
pixelate: function(x) {
|
pixelate: function(x) {
|
||||||
// TODO: if (targetObj == stagePane) n *= stagePane.scaleX;
|
return Math.abs(x) / 10;
|
||||||
// TODO: n = Math.min(n, Math.min(srcWidth, srcHeight));
|
|
||||||
return Math.abs(x) / 10 + 1;
|
|
||||||
},
|
},
|
||||||
mosaic: function(x) {
|
mosaic: function(x) {
|
||||||
x = Math.round((Math.abs(x) + 10) / 10);
|
x = Math.round((Math.abs(x) + 10) / 10);
|
||||||
@ -81,7 +106,7 @@ Drawable._effectCoverter = {
|
|||||||
* The name of each supported effect.
|
* The name of each supported effect.
|
||||||
* @type {Array}
|
* @type {Array}
|
||||||
*/
|
*/
|
||||||
Drawable.EFFECTS = Object.keys(Drawable._effectCoverter);
|
Drawable.EFFECTS = Object.keys(Drawable._effectConverter);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The cache of all shaders compiled so far. These are generated on demand.
|
* The cache of all shaders compiled so far. These are generated on demand.
|
||||||
@ -131,7 +156,7 @@ Drawable._DEFAULT_SKIN = {
|
|||||||
squirrel: '7e24c99c1b853e52f8e7f9004416fa34.png',
|
squirrel: '7e24c99c1b853e52f8e7f9004416fa34.png',
|
||||||
bus: '66895930177178ea01d9e610917f8acf.png',
|
bus: '66895930177178ea01d9e610917f8acf.png',
|
||||||
scratch_cat: '09dc888b0b7df19f70d81588ae73420e.svg'
|
scratch_cat: '09dc888b0b7df19f70d81588ae73420e.svg'
|
||||||
}.scratch_cat;
|
}.squirrel;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dispose of this Drawable. Do not use it after calling this method.
|
* Dispose of this Drawable. Do not use it after calling this method.
|
||||||
@ -281,7 +306,7 @@ Drawable.prototype._setSkinCore = function (source, costumeResolution) {
|
|||||||
if (!err) {
|
if (!err) {
|
||||||
instance._costumeResolution = costumeResolution || 1;
|
instance._costumeResolution = costumeResolution || 1;
|
||||||
instance._uniforms.u_texture = texture;
|
instance._uniforms.u_texture = texture;
|
||||||
instance._setDimensions(source.width, source.height);
|
instance._setSkinSize(source.width, source.height);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -298,7 +323,7 @@ Drawable.prototype._setSkinCore = function (source, costumeResolution) {
|
|||||||
if (willCallCallback) {
|
if (willCallCallback) {
|
||||||
if (!this._uniforms.u_texture) {
|
if (!this._uniforms.u_texture) {
|
||||||
this._uniforms.u_texture = texture;
|
this._uniforms.u_texture = texture;
|
||||||
this._setDimensions(0, 0);
|
this._setSkinSize(0, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@ -353,7 +378,7 @@ Drawable.prototype.updateProperties = function (properties) {
|
|||||||
else {
|
else {
|
||||||
this._shaderIndex &= ~mask;
|
this._shaderIndex &= ~mask;
|
||||||
}
|
}
|
||||||
var converter = Drawable._effectCoverter[propertyName];
|
var converter = Drawable._effectConverter[propertyName];
|
||||||
this._uniforms['u_' + propertyName] = converter(rawValue);
|
this._uniforms['u_' + propertyName] = converter(rawValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -365,10 +390,11 @@ Drawable.prototype.updateProperties = function (properties) {
|
|||||||
* @param {int} height The height of the new skin.
|
* @param {int} height The height of the new skin.
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
Drawable.prototype._setDimensions = function (width, height) {
|
Drawable.prototype._setSkinSize = function (width, height) {
|
||||||
if (this._dimensions[0] != width || this._dimensions[1] != height) {
|
if (this._uniforms.u_skinSize[0] != width
|
||||||
this._dimensions[0] = width;
|
|| this._uniforms.u_skinSize[1] != height) {
|
||||||
this._dimensions[1] = height;
|
this._uniforms.u_skinSize[0] = width;
|
||||||
|
this._uniforms.u_skinSize[1] = height;
|
||||||
this.setTransformDirty();
|
this.setTransformDirty();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -378,12 +404,20 @@ Drawable.prototype._setDimensions = function (width, height) {
|
|||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
Drawable.prototype._calculateTransform = function () {
|
Drawable.prototype._calculateTransform = function () {
|
||||||
var rotation = (270 - this._direction) * Math.PI / 180;
|
|
||||||
var scale = this._scale / 100 / this._costumeResolution;
|
|
||||||
var projection = this._renderer.getProjectionMatrix();
|
|
||||||
var mvp = this._uniforms.u_mvp;
|
var mvp = this._uniforms.u_mvp;
|
||||||
|
|
||||||
|
var projection = this._renderer.getProjectionMatrix();
|
||||||
twgl.m4.translate(projection, this._position, mvp);
|
twgl.m4.translate(projection, this._position, mvp);
|
||||||
|
|
||||||
|
var rotation = (270 - this._direction) * Math.PI / 180;
|
||||||
twgl.m4.rotateZ(mvp, rotation, mvp);
|
twgl.m4.rotateZ(mvp, rotation, mvp);
|
||||||
twgl.m4.scale(mvp, twgl.v3.mulScalar(this._dimensions, scale), mvp);
|
|
||||||
|
var scale = this._scale / 100;
|
||||||
|
scale /= this._costumeResolution;
|
||||||
|
this._uniforms.u_scale = scale;
|
||||||
|
var scaledSize = twgl.v3.mulScalar(this._uniforms.u_skinSize, scale);
|
||||||
|
scaledSize[2] = 0; // was NaN because u_skinSize has only 2 components
|
||||||
|
twgl.m4.scale(mvp, scaledSize, mvp);
|
||||||
|
|
||||||
this._transformDirty = false;
|
this._transformDirty = false;
|
||||||
};
|
};
|
||||||
|
@ -120,6 +120,7 @@ RenderWebGL.prototype.draw = function () {
|
|||||||
twgl.setBuffersAndAttributes(gl, currentShader, this._bufferInfo);
|
twgl.setBuffersAndAttributes(gl, currentShader, this._bufferInfo);
|
||||||
}
|
}
|
||||||
twgl.setUniforms(currentShader, drawable.getUniforms());
|
twgl.setUniforms(currentShader, drawable.getUniforms());
|
||||||
|
twgl.setUniforms(currentShader, {u_fudge: window.fudge || 0});
|
||||||
twgl.drawBufferInfo(gl, gl.TRIANGLES, this._bufferInfo);
|
twgl.drawBufferInfo(gl, gl.TRIANGLES, this._bufferInfo);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
precision mediump float;
|
precision mediump float;
|
||||||
|
|
||||||
|
uniform float u_fudge;
|
||||||
|
|
||||||
#ifdef ENABLE_color
|
#ifdef ENABLE_color
|
||||||
uniform float u_color;
|
uniform float u_color;
|
||||||
#endif
|
#endif
|
||||||
@ -11,6 +13,8 @@ uniform float u_whirl;
|
|||||||
#endif
|
#endif
|
||||||
#ifdef ENABLE_pixelate
|
#ifdef ENABLE_pixelate
|
||||||
uniform float u_pixelate;
|
uniform float u_pixelate;
|
||||||
|
uniform float u_scale;
|
||||||
|
uniform vec2 u_skinSize;
|
||||||
#endif
|
#endif
|
||||||
#ifdef ENABLE_mosaic
|
#ifdef ENABLE_mosaic
|
||||||
uniform float u_mosaic;
|
uniform float u_mosaic;
|
||||||
@ -103,7 +107,12 @@ void main()
|
|||||||
#endif // ENABLE_mosaic
|
#endif // ENABLE_mosaic
|
||||||
|
|
||||||
#ifdef ENABLE_pixelate
|
#ifdef ENABLE_pixelate
|
||||||
texcoord0 = floor(texcoord0 / u_pixelate) * u_pixelate + u_pixelate_half;
|
{
|
||||||
|
// TODO: understand why this padding helps clean up "pixel" edges
|
||||||
|
const vec2 pixelPadding = vec2(0.125, 0.125);
|
||||||
|
vec2 pixelTexelSize = u_skinSize * u_scale / u_pixelate;
|
||||||
|
texcoord0 = (floor(texcoord0 * pixelTexelSize + pixelPadding)) / pixelTexelSize;
|
||||||
|
}
|
||||||
#endif // ENABLE_pixelate
|
#endif // ENABLE_pixelate
|
||||||
|
|
||||||
#ifdef ENABLE_whirl
|
#ifdef ENABLE_whirl
|
||||||
|
Loading…
x
Reference in New Issue
Block a user