这个问题与我的另一个问题有很强的关系: Isometric rendering without tiles, is that goal reachable?
我想在等距世界(html5画布)中深度排序对象。 世界不是平铺的,因此世界上的每个项目都可以放置在每个x,y,z坐标上。由于它不是平铺世界,深度排序很难做到。 我甚至想要如果项目相交,那么可见部分就像是在完全三维世界中相交的部分一样被绘制出来。 正如人们在我的另一个问题中回答的那样,这可以通过将每个2d图像表示为3d模型来完成。 我想继续讨论以下关于该问题的评论:
使用webGL时,您不必使用3D。 WebGL绘制多边形,并且可以非常快速地将2D图像绘制成4个顶点,从而形成三角形的小扇形。您仍然可以使用zbuffer并将角(verts)设置为z距离。如果不存在webGL,大多数2D游戏库都使用webGL来渲染2D并回退到画布。在github上还有一个canvas API的webGL实现,您可以修改它以满足您的需求。 (comment link)
因此,您可以将“逻辑”视为3d模型。 webGL的z缓冲区提供正确的渲染。渲染像素本身是2D图像的像素。但我不知道该怎么做。有人可以进一步解释如何完成这项工作吗?我阅读了很多信息,但这些都是关于真正的3D。
答案 0 :(得分:2)
可以像你在其他问题中指出的那样使用深度精灵(ps,你真的应该将这些图像放在这个问题中)
要使用深度精灵,您需要启用EXT_frag_depth
扩展名(如果存在)。然后,您可以在片段着色器中写入gl_fragDepthEXT
。与制作3D模型相比,制作深度精灵听起来对我来说更有用。
在这种情况下,你只需要为每个精灵加载2个纹理,一个用于颜色,一个用于深度,然后执行类似
的操作 #extension GL_EXT_frag_depth : require
varying vec2 texcoord;
uniform sampler2D colorTexture;
uniform sampler2D depthTexture;
uniform float depthScale;
uniform float depthOffset;
void main() {
vec4 color = texture2D(colorTexture, texcoord);
// don't draw if transparent
if (color.a <= 0.01) {
discard;
}
gl_FragColor = color;
float depth = texture2D(depthTexture, texcoord).r;
gl_FragDepthEXT = depthOffset - depth * depthScale;
}
您可以将depthOffset
和depthScale
设置为
var yTemp = yPosOfSpriteInPixelsFromTopOfScreen + tallestSpriteHeight;
var depthOffset = 1. - yTemp / 65536;
var depthScale = 1 / 256;
假设每个深度变化的深度纹理中的每个值都较少。
关于如何在WebGL中绘制2D see this article。
这是一个似乎有用的例子。我生成了图像,因为我懒得在photoshop中绘制它。手动绘制深度值非常繁琐。假设深度值为1的图像中最远的像素,下一个最接近的像素的深度值为2,等等。
换句话说,如果您有一个小的3x3等距立方体,深度值将类似于
+---+---+---+---+---+---+---+---+---+---+
| | | | | 1 | 1 | | | | |
+---+---+---+---+---+---+---+---+---+---+
| | | 2 | 2 | 2 | 2 | 2 | 2 | | |
+---+---+---+---+---+---+---+---+---+---+
| 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 |
+---+---+---+---+---+---+---+---+---+---+
| 3 | 3 | 4 | 4 | 4 | 4 | 4 | 4 | 3 | 3 |
+---+---+---+---+---+---+---+---+---+---+
| 3 | 3 | 4 | 4 | 5 | 5 | 4 | 4 | 3 | 3 |
+---+---+---+---+---+---+---+---+---+---+
| 3 | 3 | 4 | 4 | 5 | 5 | 4 | 4 | 3 | 3 |
+---+---+---+---+---+---+---+---+---+---+
| 3 | 3 | 4 | 4 | 5 | 5 | 4 | 4 | 3 | 3 |
+---+---+---+---+---+---+---+---+---+---+
| | | 4 | 4 | 5 | 5 | 4 | 4 | | |
+---+---+---+---+---+---+---+---+---+---+
| | | | | 5 | 5 | | | | |
+---+---+---+---+---+---+---+---+---+---+
function makeDepthColor(depth) {
return "rgb(" + depth + "," + depth + "," + depth + ")";
}
function makeSprite(ctx, depth) {
// make an image (these would be made in photoshop ro
// some other paint program but that's too much work for me
ctx.canvas.width = 64;
ctx.canvas.height = 64;
for (y = 0; y <= 32; ++y) {
var halfWidth = (y < 16 ? 1 + y : 33 - y) * 2;
var width = halfWidth * 2;
var cy = (16 - y);
var cw = Math.max(0, 12 - Math.abs(cy) * 2) | 0;
for (var x = 0; x < width; ++x) {
var cx = x - halfWidth;
var inCenter = Math.abs(cy) < 6 && Math.abs(cx) <= cw;
var onEdge = x < 2 || x >= width - 2 || (inCenter && (Math.abs(cx / 2) | 0) === (cw / 2 | 0));
var height = onEdge ? 12 : (inCenter ? 30 : 10);
var color = inCenter ? (cx < 0 ? "#F44" : "#F66") : (cx < 0 ? "#44F" : "#66F");
ctx.fillStyle = depth ? makeDepthColor(y + 1) : color;
var xx = 32 - halfWidth + x;
var yy = y;
ctx.fillRect(xx, yy + 32 - height, 1, height);
if (!depth) {
ctx.fillStyle = onEdge ? "black" : "#CCF";
ctx.fillRect(xx, yy + 32 - height, 1, 1);
}
}
}
}
function main() {
var m4 = twgl.m4;
var gl = document.querySelector("canvas").getContext(
"webgl", {preserveDrawingBuffer: true});
var ext = gl.getExtension("EXT_frag_depth");
if (!ext) {
alert("need EXT_frag_depth");
return;
}
var vs = `
attribute vec4 position;
attribute vec2 texcoord;
varying vec2 v_texcoord;
uniform mat4 u_matrix;
uniform mat4 u_textureMatrix;
void main() {
v_texcoord = (u_textureMatrix * vec4(texcoord, 0, 1)).xy;
gl_Position = u_matrix * position;
}
`;
var fs = `
#extension GL_EXT_frag_depth : require
precision mediump float;
varying vec2 v_texcoord;
uniform sampler2D u_colorTexture;
uniform sampler2D u_depthTexture;
uniform float u_depthScale;
uniform float u_depthOffset;
void main() {
vec4 color = texture2D(u_colorTexture, v_texcoord);
if (color.a < 0.01) {
discard;
}
float depth = texture2D(u_depthTexture, v_texcoord).r;
gl_FragDepthEXT = u_depthOffset - depth * u_depthScale;
gl_FragColor = color;
}
`;
var programInfo = twgl.createProgramInfo(gl, [vs, fs]);
var quadBufferInfo = twgl.createBufferInfoFromArrays(gl, {
position: {
numComponents: 2,
data: [
0, 0,
0, 1,
1, 0,
1, 0,
0, 1,
1, 1,
],
},
texcoord: [
0, 0,
0, 1,
1, 0,
1, 0,
0, 1,
1, 1,
],
});
var ctx = document.createElement("canvas").getContext("2d");
// make the color texture
makeSprite(ctx, false);
var colorTexture = twgl.createTexture(gl, {
src: ctx.canvas,
min: gl.NEAREST,
mag: gl.NEAREST,
});
// make the depth texture
makeSprite(ctx, true);
var depthTexture = twgl.createTexture(gl, {
src: ctx.canvas,
format: gl.LUMINANCE, // because depth is only 1 channel
min: gl.NEAREST,
mag: gl.NEAREST,
});
function drawDepthImage(
colorTex, depthTex, texWidth, texHeight,
x, y, z) {
var dstY = y + z;
var dstX = x;
var dstWidth = texWidth;
var dstHeight = texHeight;
var srcX = 0;
var srcY = 0;
var srcWidth = texWidth;
var srcHeight = texHeight;
gl.useProgram(programInfo.program);
twgl.setBuffersAndAttributes(gl, programInfo, quadBufferInfo);
// this matirx will convert from pixels to clip space
var matrix = m4.ortho(0, gl.canvas.width, gl.canvas.height, 0, -1, 1);
// this matrix will translate our quad to dstX, dstY
matrix = m4.translate(matrix, [dstX, dstY, 0]);
// this matrix will scale our 1 unit quad
// from 1 unit to texWidth, texHeight units
matrix = m4.scale(matrix, [dstWidth, dstHeight, 1]);
// just like a 2d projection matrix except in texture space (0 to 1)
// instead of clip space. This matrix puts us in pixel space.
var texMatrix = m4.scaling([1 / texWidth, 1 / texHeight, 1]);
// because were in pixel space
// the scale and translation are now in pixels
var texMatrix = m4.translate(texMatrix, [srcX, srcY, 0]);
var texMatrix = m4.scale(texMatrix, [srcWidth, srcHeight, 1]);
twgl.setUniforms(programInfo, {
u_colorTexture: colorTex,
u_depthTexture: depthTex,
u_matrix: matrix,
u_textureMatrix: texMatrix,
u_depthOffset: 1 - (dstY - z) / 65536,
u_depthScale: 1 / 256,
});
twgl.drawBufferInfo(gl, quadBufferInfo);
}
// test render
gl.enable(gl.DEPTH_TEST);
var texWidth = 64;
var texHeight = 64;
// z is how much above/below ground
function draw(x, y, z) {
drawDepthImage(colorTexture, depthTexture, texWidth, texHeight , x, y, z);
}
draw( 0, 0, 0); // draw on left
draw(100, 0, 0); // draw near center
draw(113, 0, 0); // draw overlapping
draw(200, 0, 0); // draw on right
draw(200, 8, 0); // draw on more forward
draw(0, 60, 0); // draw on left
draw(0, 60, 10); // draw on below
draw(100, 60, 0); // draw near center
draw(100, 60, 20); // draw below
draw(200, 60, 20); // draw on right
draw(200, 60, 0); // draw above
}
main();
&#13;
<script src="https://twgljs.org/dist/2.x/twgl-full.min.js"></script>
<canvas></canvas>
&#13;
左上角是图像的样子。顶部中间是并排绘制的2幅图像。右上角是在y中向下绘制的2个图像(x,y是等平面)。左下角是两个图像,一个在另一个下面绘制(在平面下方)。底部中间是分开更多的同一件事。右下角是相同的东西,除了以相反的顺序绘制(只是检查它是否有效)
为了节省内存,您可以将深度值放在颜色纹理的Alpha通道中。如果它丢弃了。
不幸的是,根据webglstats.com,只有75%的桌面和0%的手机支持EXT_frag_depth
。虽然WebGL2需要支持gl_FragDepth
和AFAIK,但大多数手机支持WebGL2所基于的OpenGL ES 3.0
,因此在未来几个月内,大多数Android手机和大多数PC将获得WebGL2。另一方面,iOS与往常一样,Apple在将它们在iOS上发布WebGL2时是秘密。很明显,他们从未计划发布WebGL2,因为在2年多的时间里,WebGL没有为WebKit做过单一的提交。
对于不支持WebGL2上的WebGL2或EXT_frag_depth
的系统,您可以使用顶点着色器模拟EXT_frag_depth
。您将深度纹理传递到顶点着色器并使用gl.POINTS
绘制,每个像素一个点。这样你就可以选择每个点的深度。
它可以工作,但最终可能会很慢。可能比在JavaScript中直接写入数组并使用Canvas2DRenderingContext.putImageData
这是一个例子
function makeDepthColor(depth) {
return "rgb(" + depth + "," + depth + "," + depth + ")";
}
function makeSprite(ctx, depth) {
// make an image (these would be made in photoshop ro
// some other paint program but that's too much work for me
ctx.canvas.width = 64;
ctx.canvas.height = 64;
for (y = 0; y <= 32; ++y) {
var halfWidth = (y < 16 ? 1 + y : 33 - y) * 2;
var width = halfWidth * 2;
var cy = (16 - y);
var cw = Math.max(0, 12 - Math.abs(cy) * 2) | 0;
for (var x = 0; x < width; ++x) {
var cx = x - halfWidth;
var inCenter = Math.abs(cy) < 6 && Math.abs(cx) <= cw;
var onEdge = x < 2 || x >= width - 2 || (inCenter && (Math.abs(cx / 2) | 0) === (cw / 2 | 0));
var height = onEdge ? 12 : (inCenter ? 30 : 10);
var color = inCenter ? (cx < 0 ? "#F44" : "#F66") : (cx < 0 ? "#44F" : "#66F");
ctx.fillStyle = depth ? makeDepthColor(y + 1) : color;
var xx = 32 - halfWidth + x;
var yy = y;
ctx.fillRect(xx, yy + 32 - height, 1, height);
if (!depth) {
ctx.fillStyle = onEdge ? "black" : "#CCF";
ctx.fillRect(xx, yy + 32 - height, 1, 1);
}
}
}
}
function main() {
var m4 = twgl.m4;
var gl = document.querySelector("canvas").getContext(
"webgl", {preserveDrawingBuffer: true});
var numVertexTextures = gl.getParameter(gl.MAX_VERTEX_TEXTURE_IMAGE_UNITS);
if (numVertexTextures < 2) {
alert("GPU doesn't support textures in vertex shaders");
return;
}
var vs = `
attribute float count;
uniform vec2 u_dstSize;
uniform mat4 u_matrix;
uniform mat4 u_textureMatrix;
uniform sampler2D u_colorTexture;
uniform sampler2D u_depthTexture;
uniform float u_depthScale;
uniform float u_depthOffset;
varying vec4 v_color;
void main() {
float px = mod(count, u_dstSize.x);
float py = floor(count / u_dstSize.x);
vec4 position = vec4((vec2(px, py) + 0.5) / u_dstSize, 0, 1);
vec2 texcoord = (u_textureMatrix * position).xy;
float depth = texture2D(u_depthTexture, texcoord).r;
gl_Position = u_matrix * position;
gl_Position.z = u_depthOffset - depth * u_depthScale;
v_color = texture2D(u_colorTexture, texcoord);
}
`;
var fs = `
precision mediump float;
varying vec4 v_color;
void main() {
if (v_color.a < 0.01) {
discard;
}
gl_FragColor = v_color;
}
`;
// make a count
var maxImageWidth = 256;
var maxImageHeight = 256;
var maxPixelsInImage = maxImageWidth * maxImageHeight
var count = new Float32Array(maxPixelsInImage);
for (var ii = 0; ii < count.length; ++ii) {
count[ii] = ii;
}
var programInfo = twgl.createProgramInfo(gl, [vs, fs]);
var quadBufferInfo = twgl.createBufferInfoFromArrays(gl, {
count: { numComponents: 1, data: count, }
});
var ctx = document.createElement("canvas").getContext("2d");
// make the color texture
makeSprite(ctx, false);
var colorTexture = twgl.createTexture(gl, {
src: ctx.canvas,
min: gl.NEAREST,
mag: gl.NEAREST,
});
// make the depth texture
makeSprite(ctx, true);
var depthTexture = twgl.createTexture(gl, {
src: ctx.canvas,
format: gl.LUMINANCE, // because depth is only 1 channel
min: gl.NEAREST,
mag: gl.NEAREST,
});
function drawDepthImage(
colorTex, depthTex, texWidth, texHeight,
x, y, z) {
var dstY = y + z;
var dstX = x;
var dstWidth = texWidth;
var dstHeight = texHeight;
var srcX = 0;
var srcY = 0;
var srcWidth = texWidth;
var srcHeight = texHeight;
gl.useProgram(programInfo.program);
twgl.setBuffersAndAttributes(gl, programInfo, quadBufferInfo);
// this matirx will convert from pixels to clip space
var matrix = m4.ortho(0, gl.canvas.width, gl.canvas.height, 0, -1, 1);
// this matrix will translate our quad to dstX, dstY
matrix = m4.translate(matrix, [dstX, dstY, 0]);
// this matrix will scale our 1 unit quad
// from 1 unit to texWidth, texHeight units
matrix = m4.scale(matrix, [dstWidth, dstHeight, 1]);
// just like a 2d projection matrix except in texture space (0 to 1)
// instead of clip space. This matrix puts us in pixel space.
var texMatrix = m4.scaling([1 / texWidth, 1 / texHeight, 1]);
// because were in pixel space
// the scale and translation are now in pixels
var texMatrix = m4.translate(texMatrix, [srcX, srcY, 0]);
var texMatrix = m4.scale(texMatrix, [srcWidth, srcHeight, 1]);
twgl.setUniforms(programInfo, {
u_colorTexture: colorTex,
u_depthTexture: depthTex,
u_matrix: matrix,
u_textureMatrix: texMatrix,
u_depthOffset: 1 - (dstY - z) / 65536,
u_depthScale: 1 / 256,
u_dstSize: [dstWidth, dstHeight],
});
var numDstPixels = dstWidth * dstHeight;
twgl.drawBufferInfo(gl, quadBufferInfo, gl.POINTS, numDstPixels);
}
// test render
gl.enable(gl.DEPTH_TEST);
var texWidth = 64;
var texHeight = 64;
// z is how much above/below ground
function draw(x, y, z) {
drawDepthImage(colorTexture, depthTexture, texWidth, texHeight , x, y, z);
}
draw( 0, 0, 0); // draw on left
draw(100, 0, 0); // draw near center
draw(113, 0, 0); // draw overlapping
draw(200, 0, 0); // draw on right
draw(200, 8, 0); // draw on more forward
draw(0, 60, 0); // draw on left
draw(0, 60, 10); // draw on below
draw(100, 60, 0); // draw near center
draw(100, 60, 20); // draw below
draw(200, 60, 20); // draw on right
draw(200, 60, 0); // draw above
}
main();
&#13;
<script src="https://twgljs.org/dist/2.x/twgl-full.min.js"></script>
<canvas></canvas>
&#13;
请注意,如果它太慢,我实际上并不认为在软件中使用JavaScript来保证它太慢。您可以使用asm.js来制作渲染器。您可以在JavaScript中设置和操作数据,然后调用asm.js例程来进行软件渲染。
作为示例this demo is entirely software rendered in asm.js和this one
一样如果结果太慢,则另一种方式需要某种3D数据用于2D图像。你可以使用立方体,如果2D图像总是立方体,但我已经可以从你的样本图片中看到这两个柜子需要一个3D模型,因为顶部比身体宽几个像素,而后面有一个支撑梁。
在任何情况下,假设您为对象制作3D模型,您将使用模板缓冲区+深度缓冲区。
对于每个对象
启用STENCIL_TEST
和DEPTH_TEST
gl.enable(gl.STENCIL_TEST);
gl.enable(gl.DEPTH_TEST);
将模板func设置为ALWAYS
,对迭代计数的引用和掩码为255
var test = gl.ALWAYS;
var ref = ndx; // 1 for object 1, 2 for object 2, etc.
var mask = 255;
gl.stencilFunc(test, ref, mask);
如果深度测试通过,则将模板操作设置为REPLACE
和KEEP
否则
var stencilTestFailOp = gl.KEEP;
var depthTestFailOp = gl.KEEP;
var bothPassOp = gl.REPLACE;
gl.stencilOp(stencilTestFailOp, depthTestFailOp, bothPassOp);
现在绘制您的立方体(或任何3D模型代表您的2D图像)
此时,模板缓冲区将在绘制立方体的任何地方都有ref
的2D蒙版。因此,现在使用模板绘制2D图像,仅绘制成功绘制立方体的位置
绘制图像
关闭DEPTH_TEST
gl.disable(gl.DEPTH_TEST);
设置模板功能,因此我们只绘制模板等于ref
的位置
var test = gl.EQUAL;
var mask = 255;
gl.stencilFunc(test, ref, mask);
针对所有情况将模板操作设置为KEEP
var stencilTestFailOp = gl.KEEP;
var depthTestFailOp = gl.KEEP;
var bothPassOp = gl.KEEP;
gl.stencilOp(stencilTestFailOp, depthTestFailOp, bothPassOp);
绘制2D图像
这将最终仅绘制多维数据集绘制的位置。
对每个对象重复。
您可能希望在每个对象之后或每254个对象之后清除模板缓冲区,并确保ref
始终在1到255之间,因为模板缓冲区只有8位,这意味着当您绘制对象256时它将使用与对象#1相同的值,因此如果模板缓冲区中存在任何这些值,则可能会意外地在那里绘制。
objects.forEach(object, ndx) {
if (ndx % 255 === 0) {
gl.clear(gl.STENCIL_BUFFER_BIT);
}
var ref = ndx % 255 + 1; // 1 to 255
... do as above ...
答案 1 :(得分:1)
您可以为每个对象使用单独的预渲染深度贴图执行此操作。使用其他法线贴图,您也可以模拟延迟光照。但是这种技术需要为每个对象创建3D,以便为正确的交叉对象渲染漫反射,法线和深度图。
请参阅https://www.youtube.com/watch?v=-Q6ISVaM5Ww
上的XNA演示此演示中的每个对象都只是一个带有漫反射,法线和深度贴图的渲染精灵。
在视频结束时,您会看到它是如何工作的。作者有一个额外的解释&amp; shader code-examples在他的博客https://infictitious.blogspot.de/2012/09/25d-xna-rpg-engine-some-technical.html
中我认为WebGL也是如此。