Zunächst möchte ich sagen, dass ich viel über Schattenmapping mit Tiefenkarten und Cubemaps gelesen habe und verstehe, wie sie funktionieren. Außerdem habe ich Berufserfahrung mit OpenGL, aber ich habe ein Problem bei der Implementierung Omnidirektionale Schattenkartierungstechnik unter Verwendung einer Einzelpunktlichtquelle in meiner 3D-Grafik-Engine namens "EZ3". Meine Engine verwendet WebGL als 3D-Grafik-API und JavaScript als Programmiersprache. Dies ist für meine Bachelorarbeit in Informatik.
Grundsätzlich habe ich meinen Schattenzuordnungsalgorithmus so implementiert, aber ich werde mich nur auf Punktlichter konzentrieren, da ich mit ihnen omnidirektionale Schattenzuordnungen archivieren kann.
Zuerst aktiv ich Front-Face-Culling wie folgt:
if (this.state.faceCulling !== Material.FRONT) {
if (this.state.faceCulling === Material.NONE)
gl.enable(gl.CULL_FACE);
gl.cullFace(gl.FRONT);
this.state.faceCulling = Material.FRONT;
}
Zweitens erstelle ich ein Tiefenprogramm, um Tiefenwerte für jede Cubemap-Fläche aufzuzeichnen. Dies ist mein Tiefenprogrammcode in GLSL 1.0:
Vertex Shader:
precision highp float;
attribute vec3 position;
uniform mat4 uModelView;
uniform mat4 uProjection;
void main() {
gl_Position = uProjection * uModelView * vec4(position, 1.0);
}
Fragment Shader:
precision highp float;
vec4 packDepth(const in float depth) {
const vec4 bitShift = vec4(256.0 * 256.0 * 256.0, 256.0 * 256.0, 256.0, 1.0);
const vec4 bitMask = vec4(0.0, 1.0 / 256.0, 1.0 / 256.0, 1.0 / 256.0);
vec4 res = mod(depth * bitShift * vec4(255), vec4(256)) / vec4(255);
res -= res.xxyz * bitMask;
return res;
}
void main() {
gl_FragData[0] = packDepth(gl_FragCoord.z);
}
Drittens ist dies der Körper meiner JavaScript-Funktion, der die omnidirektionale Schattenzuordnung "archiviert"
program.bind(gl);
for (i = 0; i < lights.length; i++) {
light = lights[i];
// Updates pointlight's projection matrix
light.updateProjection();
// Binds point light's depth framebuffer
light.depthFramebuffer.bind(gl);
// Updates point light's framebuffer in order to create it
// or if it's resolution changes, it'll be created again.
light.depthFramebuffer.update(gl);
// Sets viewport dimensions with depth framebuffer's dimensions
this.viewport(new Vector2(), light.depthFramebuffer.size);
if (light instanceof PointLight) {
up = new Vector3();
view = new Matrix4();
origin = new Vector3();
target = new Vector3();
for (j = 0; j < 6; j++) {
// Check in which cubemap's face we are ...
switch (j) {
case Cubemap.POSITIVE_X:
target.set(1, 0, 0);
up.set(0, -1, 0);
break;
case Cubemap.NEGATIVE_X:
target.set(-1, 0, 0);
up.set(0, -1, 0);
break;
case Cubemap.POSITIVE_Y:
target.set(0, 1, 0);
up.set(0, 0, 1);
break;
case Cubemap.NEGATIVE_Y:
target.set(0, -1, 0);
up.set(0, 0, -1);
break;
case Cubemap.POSITIVE_Z:
target.set(0, 0, 1);
up.set(0, -1, 0);
break;
case Cubemap.NEGATIVE_Z:
target.set(0, 0, -1);
up.set(0, -1, 0);
break;
}
// Creates a view matrix using target and up vectors according to each face of pointlight's
// cubemap. Furthermore, I translate it in minus light position in order to place
// the point light in the world's origin and render each cubemap's face at this
// point of view
view.lookAt(origin, target, up);
view.mul(new EZ3.Matrix4().translate(light.position.clone().negate()));
// Flips the Y-coordinate of each cubemap face
// scaling the projection matrix by (1, -1, 1).
// This is a perspective projection matrix which has:
// 90 degress of FOV.
// 1.0 of aspect ratio.
// Near clipping plane at 0.01.
// Far clipping plane at 2000.0.
projection = light.projection.clone();
projection.scale(new EZ3.Vector3(1, -1, 1));
// Attaches a cubemap face to current framebuffer in order to record depth values for the face with this line
// gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_CUBE_MAP_POSITIVE_X + j, id, 0);
light.depthFramebuffer.texture.attach(gl, j);
// Clears current framebuffer's color with these lines:
// gl.clearColor(1.0,1.0,1.0,1.0);
// gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
this.clear(color);
// Renders shadow caster meshes using the depth program
for (k = 0; k < shadowCasters.length; k++)
this._renderShadowCaster(shadowCasters[k], program, view, projection);
}
} else {
// Directional light & Spotlight case ...
}
}
Viertens berechne ich auf diese Weise die omnidirektionale Schattenzuordnung mithilfe meiner Tiefen-Cubemap in meinem Haupt-Vertex-Shader und Fragment-Shader:
Vertex Shader:
precision highp float;
attribute vec3 position;
uniform mat4 uModel;
uniform mat4 uModelView;
uniform mat4 uProjection;
varying vec3 vPosition;
void main() {
vPosition = vec3(uModel * vec4(position, 1.0));
gl_Position = uProjection * uModelView * vec4(position, 1.0);
}
Fragment Shader:
float unpackDepth(in vec4 color) {
return dot(color, vec4(1.0 / (256.0 * 256.0 * 256.0), 1.0 / (256.0 * 256.0), 1.0 / 256.0, 1.0 ));
}
float pointShadow(const in PointLight light, const in samplerCube shadowSampler) {
vec3 direction = vPosition - light.position;
float vertexDepth = clamp(length(direction), 0.0, 1.0);
float shadowMapDepth = unpackDepth(textureCube(shadowSampler, direction));
return (vertexDepth > shadowMapDepth) ? light.shadowDarkness : 1.0;
}
Schließlich ist dies das Ergebnis, das ich bekomme, meine Szene hat eine Ebene, einen Würfel und eine Kugel. Außerdem ist die rote helle Kugel die Punktlichtquelle:
Wie Sie sehen können, scheint mir die Cubemap des Framebuffers mit Punktlichttiefe keine gute Interpolation zwischen ihren Gesichtern zu bewirken.
Bis jetzt habe ich keine Ahnung, wie ich das lösen soll.
quelle
Antworten:
LÖSUNG
Nach ein paar Tagen wurde mir klar, dass ich meine Projektionsmatrix mit einem FOV-Winkel in Grad berechnete und es im Bogenmaß sein sollte . Ich habe die Konvertierung gemacht und jetzt funktioniert alles super. Die Interpolation zwischen den Gesichtern der Cubemap meines Tiefen-Framebuffers ist jetzt perfekt. Aus diesem Grund ist es wichtig, den Winkel jeder einzelnen trigonometrischen Funktion im Bogenmaß zu behandeln.
Außerdem wurde mir klar, dass Sie Ihre Ansichtsmatrix entweder wie in der Frage gesagt und auf folgende Weise berechnen können:
Dieser Ansatz bedeutet, dass Ihr Blickwinkel in der Mitte des Punktlichts platziert ist und Sie nur in jede Richtung Ihrer Cubemap rendern. Aber in welche Richtungen? Nun , diese Richtungen werden berechnet, indem jedes Ziel, das ich im Schaltblock habe (entsprechend dem Gesicht jeder Cubemap), mit der Position Ihres Punktlichts addiert wird .
Darüber hinaus ist es nicht erforderlich, die Y-Koordinate der Projektionsmatrix umzudrehen. In diesem Fall ist es in Ordnung, die perspektivische Projektionsmatrix von pointlight an Ihren GLSL-Shader zu senden, ohne sie um (1, -1, 1) zu skalieren, da ich mit arbeite Texturen ohne umgedrehte Y-Koordinate . Ich denke, Sie sollten die Y-Koordinate der Projektionsmatrix Ihres Punktlichts nur umdrehen, wenn Sie mit der Y-Koordinate einer umgedrehten Textur arbeiten , um einen korrekten omnidirektionalen Schattenabbildungseffekt zu erzielen.
Schließlich werde ich hier die endgültige Version meines omnidirektionalen Shadow Mapping-Algorithmus auf der CPU / GPU-Seite belassen. Auf der CPU-Seite erkläre ich jeden Schritt, den Sie ausführen müssen, um eine korrekte Schattenkarte für das Gesicht jeder Cubemap zu berechnen. Auf der GPU-Seite werde ich andererseits die Vertex / Fragment-Shader- und omnidirektionale Shadow-Mapping-Funktion meines Tiefenprogramms in meinem Hauptfragment-Shader erläutern, um jemandem zu helfen, der diese Technik erlernen oder zukünftige Zweifel an diesem Algorithmus lösen könnte ::
Zentralprozessor
In der Funktion renderMeshDepth habe ich:
GPU
Tiefenprogramm Vertex Shader:
Tiefenprogramm Fragment Shader:
Omnidirektionale Schattenzuordnungsfunktion in meinem Hauptfragment-Shader:
Hier haben Sie ein endgültiges Rendering des Algorithmus
Viel Spaß beim Codieren schöner Grafiken, viel Glück :)
CZ
quelle