Skip to content

Commit b4d8614

Browse files
committed
depth sprites
1 parent 2646a57 commit b4d8614

File tree

3 files changed

+302
-0
lines changed

3 files changed

+302
-0
lines changed
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
Title: How to draw Depth Sprites
2+
Description: Drawing sprites that sort pixels by depth
3+
TOC: How to draw Depth Sprites
4+
5+
## Question:
6+
7+
How can I draw sprites that sort each pixel by depth?
8+
9+
## Answer:
10+
11+
To use depth sprites you need to enable the [`EXT_frag_depth`](https://www.khronos.org/registry/webgl/extensions/EXT_frag_depth/) extension if it exists. Then you can write to `gl_fragDepthEXT` in your fragment shader. Making depth sprites sounds like more work to me than making 3D models.
12+
13+
In that case you just load 2 textures per sprite, one for color, one for depth and then do something like
14+
15+
#extension GL_EXT_frag_depth : require
16+
17+
varying vec2 texcoord;
18+
19+
uniform sampler2D colorTexture;
20+
uniform sampler2D depthTexture;
21+
uniform float depthScale;
22+
uniform float depthOffset;
23+
24+
void main() {
25+
vec4 color = texture2D(colorTexture, texcoord);
26+
27+
// don't draw if transparent
28+
if (color.a <= 0.01) {
29+
discard;
30+
}
31+
32+
gl_FragColor = color;
33+
34+
float depth = texture2D(depthTexture, texcoord).r;
35+
gl_FragDepthEXT = depthOffset - depth * depthScale;
36+
}
37+
38+
You'd set `depthOffset` and `depthScale` to something like
39+
40+
var yTemp = yPosOfSpriteInPixelsFromTopOfScreen + tallestSpriteHeight;
41+
var depthOffset = 1. - yTemp / 65536;
42+
var depthScale = 1 / 256;
43+
44+
That assumes each value in the depth texture is less per depth change.
45+
46+
As for how to draw in 2D in WebGL [see this article](webgl-2d-drawimage.html).
47+
48+
Here's an example that seems to work. I generated the image because I'm too lazy to draw it in photoshop. Manually drawing depth values is pretty tedious. It assumes the furthest pixel in the image of depth values of 1, the next closest pixels have a depth value of 2, etc.
49+
50+
In other words if you had a small 3x3 isometric cube the depth values would be something like
51+
52+
+---+---+---+---+---+---+---+---+---+---+
53+
| | | | | 1 | 1 | | | | |
54+
+---+---+---+---+---+---+---+---+---+---+
55+
| | | 2 | 2 | 2 | 2 | 2 | 2 | | |
56+
+---+---+---+---+---+---+---+---+---+---+
57+
| 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 |
58+
+---+---+---+---+---+---+---+---+---+---+
59+
| 3 | 3 | 4 | 4 | 4 | 4 | 4 | 4 | 3 | 3 |
60+
+---+---+---+---+---+---+---+---+---+---+
61+
| 3 | 3 | 4 | 4 | 5 | 5 | 4 | 4 | 3 | 3 |
62+
+---+---+---+---+---+---+---+---+---+---+
63+
| 3 | 3 | 4 | 4 | 5 | 5 | 4 | 4 | 3 | 3 |
64+
+---+---+---+---+---+---+---+---+---+---+
65+
| 3 | 3 | 4 | 4 | 5 | 5 | 4 | 4 | 3 | 3 |
66+
+---+---+---+---+---+---+---+---+---+---+
67+
| | | 4 | 4 | 5 | 5 | 4 | 4 | | |
68+
+---+---+---+---+---+---+---+---+---+---+
69+
| | | | | 5 | 5 | | | | |
70+
+---+---+---+---+---+---+---+---+---+---+
71+
72+
{{{example url="../webgl-qna-depth-sprites.html"}}}
73+
74+
The top left is what the image looks like. The top middle is 2 images drawn side by side. The top right is 2 images drawn one further down in y (x, y is the iso-plane). The bottom left is two images one drawn below the other (below the plane). The bottom middle is the same thing just separated more. The bottom right is the same thing except drawn in the opposite order (just to check it works)
75+
76+
To save memory you could put the depth value in the alpha channel of the color texture. If it's 0 discard.

webgl/lessons/webgl-qna.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ A collection of links to random questions and/or answers about topics related to
120120
* [How to figure out how much GPU work to do without crashing WebGL](webgl-qna-how-to-figure-out-how-much-gpu-work-to-do-without-crashing-webgl.html).
121121
* [Why does WebGL take more memory than Canvas 2D](webgl-qna-why-does-webgl-take-more-memory-than-canvas-2d.html)
122122
* [How to combine more text drawing into fewer draw calls](webgl-qna-how-to-combine-more-text-drawing-into-fewer-draw-calls.html)
123+
* [How to draw depth sprites](webgl-qna-depth-sprites.html)
123124

124125
## WebGL2
125126

webgl/webgl-qna-depth-sprites.html

Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<meta charset="utf-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
6+
<style>
7+
body {
8+
margin: 5px;
9+
}
10+
#c {
11+
width: 300px;
12+
height: 150px;
13+
border: 1px solid black;
14+
}
15+
</style>
16+
</head>
17+
<body>
18+
<canvas id="c"></canvas>
19+
</body>
20+
<script type="module">
21+
import * as twgl from 'https://twgljs.org/dist/4.x/twgl-full.module.js';
22+
23+
function makeDepthColor(depth) {
24+
return "rgb(" + depth + "," + depth + "," + depth + ")";
25+
}
26+
27+
function makeSprite(ctx, depth) {
28+
// make an image (these would be made in photoshop ro
29+
// some other paint program but that's too much work for me
30+
ctx.canvas.width = 64;
31+
ctx.canvas.height = 64;
32+
for (let y = 0; y <= 32; ++y) {
33+
const halfWidth = (y < 16 ? 1 + y : 33 - y) * 2;
34+
const width = halfWidth * 2;
35+
const cy = (16 - y);
36+
const cw = Math.max(0, 12 - Math.abs(cy) * 2) | 0;
37+
for (let x = 0; x < width; ++x) {
38+
const cx = x - halfWidth;
39+
const inCenter = Math.abs(cy) < 6 && Math.abs(cx) <= cw;
40+
const onEdge = x < 2 || x >= width - 2 || (inCenter && (Math.abs(cx / 2) | 0) === (cw / 2 | 0));
41+
const height = onEdge ? 12 : (inCenter ? 30 : 10);
42+
const color = inCenter ? (cx < 0 ? "#F44" : "#F66") : (cx < 0 ? "#44F" : "#66F");
43+
ctx.fillStyle = depth ? makeDepthColor(y + 1) : color;
44+
const xx = 32 - halfWidth + x;
45+
const yy = y;
46+
ctx.fillRect(xx, yy + 32 - height, 1, height);
47+
if (!depth) {
48+
ctx.fillStyle = onEdge ? "black" : "#CCF";
49+
ctx.fillRect(xx, yy + 32 - height, 1, 1);
50+
}
51+
}
52+
}
53+
}
54+
55+
function main() {
56+
const m4 = twgl.m4;
57+
const gl = document.querySelector("#c").getContext(
58+
"webgl", {preserveDrawingBuffer: true});
59+
const ext = gl.getExtension("EXT_frag_depth");
60+
if (!ext) {
61+
alert("need EXT_frag_depth");
62+
return;
63+
}
64+
65+
const vs = `
66+
attribute vec4 position;
67+
attribute vec2 texcoord;
68+
69+
varying vec2 v_texcoord;
70+
71+
uniform mat4 u_matrix;
72+
uniform mat4 u_textureMatrix;
73+
74+
void main() {
75+
v_texcoord = (u_textureMatrix * vec4(texcoord, 0, 1)).xy;
76+
gl_Position = u_matrix * position;
77+
}
78+
`;
79+
80+
const fs = `
81+
#extension GL_EXT_frag_depth : require
82+
83+
precision mediump float;
84+
85+
varying vec2 v_texcoord;
86+
87+
uniform sampler2D u_colorTexture;
88+
uniform sampler2D u_depthTexture;
89+
uniform float u_depthScale;
90+
uniform float u_depthOffset;
91+
92+
void main() {
93+
vec4 color = texture2D(u_colorTexture, v_texcoord);
94+
if (color.a < 0.01) {
95+
discard;
96+
}
97+
98+
float depth = texture2D(u_depthTexture, v_texcoord).r;
99+
gl_FragDepthEXT = u_depthOffset - depth * u_depthScale;
100+
gl_FragColor = color;
101+
}
102+
`;
103+
104+
const programInfo = twgl.createProgramInfo(gl, [vs, fs]);
105+
const quadBufferInfo = twgl.createBufferInfoFromArrays(gl, {
106+
position: {
107+
numComponents: 2,
108+
data: [
109+
0, 0,
110+
0, 1,
111+
1, 0,
112+
1, 0,
113+
0, 1,
114+
1, 1,
115+
],
116+
},
117+
texcoord: [
118+
0, 0,
119+
0, 1,
120+
1, 0,
121+
1, 0,
122+
0, 1,
123+
1, 1,
124+
],
125+
});
126+
127+
const ctx = document.createElement("canvas").getContext("2d");
128+
129+
// make the color texture
130+
makeSprite(ctx, false);
131+
const colorTexture = twgl.createTexture(gl, {
132+
src: ctx.canvas,
133+
min: gl.NEAREST,
134+
mag: gl.NEAREST,
135+
});
136+
137+
// make the depth texture
138+
makeSprite(ctx, true);
139+
const depthTexture = twgl.createTexture(gl, {
140+
src: ctx.canvas,
141+
format: gl.LUMINANCE, // because depth is only 1 channel
142+
min: gl.NEAREST,
143+
mag: gl.NEAREST,
144+
});
145+
146+
function drawDepthImage(
147+
colorTex, depthTex, texWidth, texHeight,
148+
x, y, z) {
149+
const dstY = y + z;
150+
const dstX = x;
151+
const dstWidth = texWidth;
152+
const dstHeight = texHeight;
153+
154+
const srcX = 0;
155+
const srcY = 0;
156+
const srcWidth = texWidth;
157+
const srcHeight = texHeight;
158+
159+
gl.useProgram(programInfo.program);
160+
161+
twgl.setBuffersAndAttributes(gl, programInfo, quadBufferInfo);
162+
163+
// this matrix will convert from pixels to clip space
164+
let matrix = m4.ortho(0, gl.canvas.width, gl.canvas.height, 0, -1, 1);
165+
166+
// this matrix will translate our quad to dstX, dstY
167+
matrix = m4.translate(matrix, [dstX, dstY, 0]);
168+
169+
// this matrix will scale our 1 unit quad
170+
// from 1 unit to texWidth, texHeight units
171+
matrix = m4.scale(matrix, [dstWidth, dstHeight, 1]);
172+
173+
// just like a 2d projection matrix except in texture space (0 to 1)
174+
// instead of clip space. This matrix puts us in pixel space.
175+
let texMatrix = m4.scaling([1 / texWidth, 1 / texHeight, 1]);
176+
177+
// because were in pixel space
178+
// the scale and translation are now in pixels
179+
texMatrix = m4.translate(texMatrix, [srcX, srcY, 0]);
180+
texMatrix = m4.scale(texMatrix, [srcWidth, srcHeight, 1]);
181+
182+
twgl.setUniforms(programInfo, {
183+
u_colorTexture: colorTex,
184+
u_depthTexture: depthTex,
185+
u_matrix: matrix,
186+
u_textureMatrix: texMatrix,
187+
u_depthOffset: 1 - (dstY - z) / 65536,
188+
u_depthScale: 1 / 256,
189+
});
190+
191+
twgl.drawBufferInfo(gl, quadBufferInfo);
192+
}
193+
194+
// test render
195+
gl.enable(gl.DEPTH_TEST);
196+
197+
const texWidth = 64;
198+
const texHeight = 64;
199+
200+
// z is how much above/below ground
201+
function draw(x, y, z) {
202+
drawDepthImage(colorTexture, depthTexture, texWidth, texHeight , x, y, z);
203+
}
204+
205+
draw( 0, 0, 0); // draw on left
206+
207+
draw(100, 0, 0); // draw near center
208+
draw(113, 0, 0); // draw overlapping
209+
210+
draw(200, 0, 0); // draw on right
211+
draw(200, 8, 0); // draw on more forward
212+
213+
draw(0, 60, 0); // draw on left
214+
draw(0, 60, 10); // draw on below
215+
216+
draw(100, 60, 0); // draw near center
217+
draw(100, 60, 20); // draw below
218+
219+
draw(200, 60, 20); // draw on right
220+
draw(200, 60, 0); // draw above
221+
}
222+
223+
main();
224+
</script>
225+
</html>

0 commit comments

Comments
 (0)