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>
|
||||
</head>
|
||||
<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>
|
||||
<script src="render-webgl.js"></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 renderer = new RenderWebGL(canvas);
|
||||
var drawableID = renderer.createDrawable();
|
||||
@ -17,12 +36,14 @@
|
||||
renderer.draw();
|
||||
requestAnimationFrame(drawStep);
|
||||
}
|
||||
renderer.updateDrawableProperties(drawableID, {
|
||||
position: [0, 0],
|
||||
scale: 300,
|
||||
direction: 90
|
||||
});
|
||||
var direction = 90;
|
||||
var posX = 0;
|
||||
var posY = 0;
|
||||
var scale = 100;
|
||||
function thinkStep() {
|
||||
direction += 0.1;
|
||||
//direction += 0.1;
|
||||
|
||||
var props = {};
|
||||
//props.position = [posX, posY];
|
||||
|
@ -4,6 +4,7 @@ var xhr = require('xhr');
|
||||
|
||||
/**
|
||||
* 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 gl The OpenGL context.
|
||||
* @constructor
|
||||
@ -15,24 +16,50 @@ function Drawable(renderer, gl) {
|
||||
this._renderer = renderer;
|
||||
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 = {
|
||||
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
|
||||
var numEffects = Drawable.EFFECTS.length;
|
||||
for (var index = 0; index < numEffects; ++index) {
|
||||
var effectName = Drawable.EFFECTS[index];
|
||||
var converter = Drawable._effectCoverter[effectName];
|
||||
var converter = Drawable._effectConverter[effectName];
|
||||
this._uniforms['u_' + effectName] = converter(0);
|
||||
}
|
||||
|
||||
this._position = twgl.v3.create(0, 0);
|
||||
this._scale = 100;
|
||||
this._direction = 90;
|
||||
this._dimensions = twgl.v3.create(0, 0);
|
||||
this._transformDirty = true;
|
||||
this._shaderIndex = 0;
|
||||
this._costumeResolution = 2; // TODO: only for bitmaps
|
||||
@ -49,7 +76,7 @@ module.exports = Drawable;
|
||||
* @type {Object.<string,function>}
|
||||
* @private
|
||||
*/
|
||||
Drawable._effectCoverter = {
|
||||
Drawable._effectConverter = {
|
||||
color: function(x) {
|
||||
return (360 * x / 200) % 360;
|
||||
},
|
||||
@ -60,9 +87,7 @@ Drawable._effectCoverter = {
|
||||
return x * Math.PI / 180;
|
||||
},
|
||||
pixelate: function(x) {
|
||||
// TODO: if (targetObj == stagePane) n *= stagePane.scaleX;
|
||||
// TODO: n = Math.min(n, Math.min(srcWidth, srcHeight));
|
||||
return Math.abs(x) / 10 + 1;
|
||||
return Math.abs(x) / 10;
|
||||
},
|
||||
mosaic: function(x) {
|
||||
x = Math.round((Math.abs(x) + 10) / 10);
|
||||
@ -81,7 +106,7 @@ Drawable._effectCoverter = {
|
||||
* The name of each supported effect.
|
||||
* @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.
|
||||
@ -131,7 +156,7 @@ Drawable._DEFAULT_SKIN = {
|
||||
squirrel: '7e24c99c1b853e52f8e7f9004416fa34.png',
|
||||
bus: '66895930177178ea01d9e610917f8acf.png',
|
||||
scratch_cat: '09dc888b0b7df19f70d81588ae73420e.svg'
|
||||
}.scratch_cat;
|
||||
}.squirrel;
|
||||
|
||||
/**
|
||||
* Dispose of this Drawable. Do not use it after calling this method.
|
||||
@ -281,7 +306,7 @@ Drawable.prototype._setSkinCore = function (source, costumeResolution) {
|
||||
if (!err) {
|
||||
instance._costumeResolution = costumeResolution || 1;
|
||||
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 (!this._uniforms.u_texture) {
|
||||
this._uniforms.u_texture = texture;
|
||||
this._setDimensions(0, 0);
|
||||
this._setSkinSize(0, 0);
|
||||
}
|
||||
}
|
||||
else {
|
||||
@ -353,7 +378,7 @@ Drawable.prototype.updateProperties = function (properties) {
|
||||
else {
|
||||
this._shaderIndex &= ~mask;
|
||||
}
|
||||
var converter = Drawable._effectCoverter[propertyName];
|
||||
var converter = Drawable._effectConverter[propertyName];
|
||||
this._uniforms['u_' + propertyName] = converter(rawValue);
|
||||
}
|
||||
}
|
||||
@ -365,10 +390,11 @@ Drawable.prototype.updateProperties = function (properties) {
|
||||
* @param {int} height The height of the new skin.
|
||||
* @private
|
||||
*/
|
||||
Drawable.prototype._setDimensions = function (width, height) {
|
||||
if (this._dimensions[0] != width || this._dimensions[1] != height) {
|
||||
this._dimensions[0] = width;
|
||||
this._dimensions[1] = height;
|
||||
Drawable.prototype._setSkinSize = function (width, height) {
|
||||
if (this._uniforms.u_skinSize[0] != width
|
||||
|| this._uniforms.u_skinSize[1] != height) {
|
||||
this._uniforms.u_skinSize[0] = width;
|
||||
this._uniforms.u_skinSize[1] = height;
|
||||
this.setTransformDirty();
|
||||
}
|
||||
};
|
||||
@ -378,12 +404,20 @@ Drawable.prototype._setDimensions = function (width, height) {
|
||||
* @private
|
||||
*/
|
||||
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 projection = this._renderer.getProjectionMatrix();
|
||||
twgl.m4.translate(projection, this._position, mvp);
|
||||
|
||||
var rotation = (270 - this._direction) * Math.PI / 180;
|
||||
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;
|
||||
};
|
||||
|
@ -120,6 +120,7 @@ RenderWebGL.prototype.draw = function () {
|
||||
twgl.setBuffersAndAttributes(gl, currentShader, this._bufferInfo);
|
||||
}
|
||||
twgl.setUniforms(currentShader, drawable.getUniforms());
|
||||
twgl.setUniforms(currentShader, {u_fudge: window.fudge || 0});
|
||||
twgl.drawBufferInfo(gl, gl.TRIANGLES, this._bufferInfo);
|
||||
}
|
||||
};
|
||||
|
@ -1,5 +1,7 @@
|
||||
precision mediump float;
|
||||
|
||||
uniform float u_fudge;
|
||||
|
||||
#ifdef ENABLE_color
|
||||
uniform float u_color;
|
||||
#endif
|
||||
@ -11,6 +13,8 @@ uniform float u_whirl;
|
||||
#endif
|
||||
#ifdef ENABLE_pixelate
|
||||
uniform float u_pixelate;
|
||||
uniform float u_scale;
|
||||
uniform vec2 u_skinSize;
|
||||
#endif
|
||||
#ifdef ENABLE_mosaic
|
||||
uniform float u_mosaic;
|
||||
@ -103,7 +107,12 @@ void main()
|
||||
#endif // ENABLE_mosaic
|
||||
|
||||
#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
|
||||
|
||||
#ifdef ENABLE_whirl
|
||||
|
Loading…
x
Reference in New Issue
Block a user