ジオメトリシェーダで簡易シャドウイング
ジオメトリシェーダで影行列を使った簡易シャドウイングを試してみました。
プログラムは、影行列を使った簡易シャドウイングを修正して、影行列の計算をジオメトリシェーダに実装します。
修正としては、Cコード側にあった影行列の計算をそっくりそのままジオメトリシェーダに実装した形です。
プログラムは、影行列を使った簡易シャドウイングを修正して、影行列の計算をジオメトリシェーダに実装します。
修正としては、Cコード側にあった影行列の計算をそっくりそのままジオメトリシェーダに実装した形です。
シェーダの効率とかはまったく考えていないので、こういうことができるんだぐらいに思ってください。
あくまでも簡易シャドウイングです。シャドウボリュームでなくてごめんなさい。
あくまでも簡易シャドウイングです。シャドウボリュームでなくてごめんなさい。
OpenGL 3.x
GLUT (freeglut 3.7)
GLM 0.9.0.6
GL3W (or GLEW 1.5.7)
GLUT (freeglut 3.7)
GLM 0.9.0.6
GL3W (or GLEW 1.5.7)

影行列
// 影行列の計算
mat4 calcShadowMatrix(vec3 l, vec3 g, vec3 n)
{
float d, c;
n = -n;
d = dot(n,l);
c = dot(g,n) - d;
mat4 m = mat4(
l.x*n.x+c, l.y*n.x, l.z*n.x, n.x,
l.x*n.y, l.y*n.y+c, l.z*n.y, n.y,
l.x*n.z, l.y*n.z, l.z*n.z+c, n.z,
-l.x*c-l.x*d, -l.y*c-l.y*d, -l.z*c-l.z*d, -d
);
return m;
}
- 影の計算方法
(ProjectionMatrix) * (ViewMatrix) * (ShadowMatrix) * (ModelMatrix) * 頂点座標
今回は、すべてシェーダ側で計算します。
ソースコード
- バーテックスシェーダ
#version 330
in vec3 a_position;
in vec3 a_normal;
out vec3 v_normal;
void main(void)
{
v_normal = a_normal;
gl_Position = vec4(a_position.xyz, 1.0);
}
- ジオメトリシェーダ
ここがこのプログラムの主になります。
もとのバーテックスシェーダで実装していた影付の計算をジオメトリシェーダで計算するようにしています。
これは、Model,ViewおよびProjectionマトリックスと変換前の頂点座標を取得するためです。
影行列はcalcShadowMatrixで計算します。
そのマトリックスと頂点座標で影を生成します。
生成の記述は次の通りです。
もとのバーテックスシェーダで実装していた影付の計算をジオメトリシェーダで計算するようにしています。
これは、Model,ViewおよびProjectionマトリックスと変換前の頂点座標を取得するためです。
影行列はcalcShadowMatrixで計算します。
そのマトリックスと頂点座標で影を生成します。
生成の記述は次の通りです。
for (int i=0; i<3; i++) { g_color = vec4(0.2, 0.2, 0.2, 1.0); // shadow color gl_Position = projectionMatrix*viewMatrix*shadowMatrix*modelMatrix*gl_in[i].gl_Position; EmitVertex(); } EndPrimitive();
影を生成することで、ジオメトリシェーダが返す頂点数は3点から6点に増えます。
よって、出力はlayout(triangle_strip, max_vertices=6) out と最大の頂点数を設定します。
よって、出力はlayout(triangle_strip, max_vertices=6) out と最大の頂点数を設定します。
#version 330
uniform mat4 modelMatrix; // モデル・マトリックス
uniform mat4 viewMatrix; // ビュー・マトリックス
uniform mat4 projectionMatrix; // 射影・マトリックス
uniform vec3 lightPos; // 光源位置
layout(triangles) in;
layout(triangle_strip, max_vertices=6) out;
in vec3 v_normal[3];
out vec4 g_color;
vec4 diffuse = vec4(0.3, 0.3, 0.6, 1.0); // diffuse color
// 影行列の計算
mat4 calcShadowMatrix(vec3 l, vec3 g, vec3 n)
{
float d, c;
n = -n;
d = dot(n,l);
c = dot(g,n) - d;
mat4 m = mat4(
l.x*n.x+c, l.y*n.x, l.z*n.x, n.x,
l.x*n.y, l.y*n.y+c, l.z*n.y, n.y,
l.x*n.z, l.y*n.z, l.z*n.z+c, n.z,
-l.x*c-l.x*d, -l.y*c-l.y*d, -l.z*c-l.z*d, -d
);
return m;
}
void main(void)
{
mat4 modelViewMatrix = viewMatrix * modelMatrix;
vec3 lightVec = vec3(viewMatrix * vec4(lightPos.xyz,1.0));
mat3 n_mat = mat3( transpose( inverse(modelViewMatrix) ) ); // normal Matrix
vec3 P, N, L;
for (int i=0; i<3; i++) {
P = vec3(modelViewMatrix * gl_in[i].gl_Position); // Eye vector
N = normalize(n_mat * v_normal[i]); // normal direction
L = normalize(lightVec - P); // light direction
g_color = diffuse * dot(N,L);
gl_Position = projectionMatrix*modelViewMatrix*gl_in[i].gl_Position;
EmitVertex();
}
EndPrimitive();
mat4 shadowMatrix = calcShadowMatrix(lightPos, vec3(0.0,0.0,0.0), vec3(0.0,1.0,0.0));
//
// shadow matrix
//
for (int i=0; i<3; i++) {
g_color = vec4(0.2, 0.2, 0.2, 1.0); // shadow color
gl_Position = projectionMatrix*viewMatrix*shadowMatrix*modelMatrix*gl_in[i].gl_Position;
EmitVertex();
}
EndPrimitive();
}
- フラグメントシェーダ
#version 330
in vec4 g_color;
out vec4 fragColor;
void main(void)
{
fragColor = g_color;
}
- Cコード
#include <cstdlib>
#include <iostream>
#include <list>
//#include <GL/glew.h>
#include <GL3/gl3w.h>
#include <GL/glut.h>
#include "load_shader.h"
// OpenGL Mathematics Library
#include <glm/glm.hpp>
#include <glm/gtc/matrix_projection.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtx/transform2.hpp>
#include <glm/gtc/type_ptr.hpp>
// prototype
void reshape(int w, int h);
void init();
void timer(int t );
void display();
class Cube
{
private:
struct Vertex_t {
GLfloat position[3]; // (vx,vy,vz)
GLfloat normal[3]; // (nx,ny,nz)
};
GLuint vao[1]; // Vertex array buffer objects
GLuint eao[1]; // array buffer objects for elements
GLint positionLocation;
GLint normalLocation;
GLuint numIndecies;
glm::mat4 modelMat;
glm::vec3 position;
glm::vec3 size;
glm::vec3 angle_axis;
float angle;
public:
void setup(GLuint programObj)
{
GLuint offset;
GLuint vbo[1];
Vertex_t vertex[] = { // position(x,y,z) , normal(x,y,z)
{ { -0.5f, -0.5f, -0.5f }, { +0.0f, -1.0f, +0.0f } },
{ { -0.5f, -0.5f, +0.5f }, { +0.0f, -1.0f, +0.0f } },
{ { +0.5f, -0.5f, +0.5f }, { +0.0f, -1.0f, +0.0f } },
{ { +0.5f, -0.5f, -0.5f }, { +0.0f, -1.0f, +0.0f } },
{ { -0.5f, +0.5f, -0.5f }, { +0.0f, +1.0f, +0.0f } },
{ { -0.5f, +0.5f, +0.5f }, { +0.0f, +1.0f, +0.0f } },
{ { +0.5f, +0.5f, +0.5f }, { +0.0f, +1.0f, +0.0f } },
{ { +0.5f, +0.5f, -0.5f }, { +0.0f, +1.0f, +0.0f } },
{ { -0.5f, -0.5f, -0.5f }, { +0.0f, +0.0f, -1.0f } },
{ { -0.5f, +0.5f, -0.5f }, { +0.0f, +0.0f, -1.0f } },
{ { +0.5f, +0.5f, -0.5f }, { +0.0f, +0.0f, -1.0f } },
{ { +0.5f, -0.5f, -0.5f }, { +0.0f, +0.0f, -1.0f } },
{ { -0.5f, -0.5f, +0.5f }, { +0.0f, +0.0f, +1.0f } },
{ { -0.5f, +0.5f, +0.5f }, { +0.0f, +0.0f, +1.0f } },
{ { +0.5f, +0.5f, +0.5f }, { +0.0f, +0.0f, +1.0f } },
{ { +0.5f, -0.5f, +0.5f }, { +0.0f, +0.0f, +1.0f } },
{ { -0.5f, -0.5f, -0.5f }, { -1.0f, +0.0f, +0.0f } },
{ { -0.5f, -0.5f, +0.5f }, { -1.0f, +0.0f, +0.0f } },
{ { -0.5f, +0.5f, +0.5f }, { -1.0f, +0.0f, +0.0f } },
{ { -0.5f, +0.5f, -0.5f }, { -1.0f, +0.0f, +0.0f } },
{ { +0.5f, -0.5f, -0.5f }, { +1.0f, +0.0f, +0.0f } },
{ { +0.5f, -0.5f, +0.5f }, { +1.0f, +0.0f, +0.0f } },
{ { +0.5f, +0.5f, +0.5f }, { +1.0f, +0.0f, +0.0f } },
{ { +0.5f, +0.5f, -0.5f }, { +1.0f, +0.0f, +0.0f } }
};
GLuint indices[] = {
0, 2, 1,
0, 3, 2,
4, 5, 6,
4, 6, 7,
8, 9, 10,
8, 10, 11,
12, 15, 14,
12, 14, 13,
16, 17, 18,
16, 18, 19,
20, 23, 22,
20, 22, 21
};
numIndecies = sizeof(indices)/sizeof(GLuint);
positionLocation = glGetAttribLocation(programObj, "a_position");
normalLocation = glGetAttribLocation(programObj, "a_normal");
// Create and bind VAO
glGenVertexArrays(1, vao);
glBindVertexArray(vao[0]);
// Create VBO
glGenBuffers(1, vbo);
glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertex), vertex, GL_STATIC_DRAW);
offset = 0;
glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex_t), (const void*)offset);
offset += 3 * sizeof(GLfloat);
glVertexAttribPointer(normalLocation, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex_t), (const void*)offset);
// Enable Attribute
glEnableVertexAttribArray(positionLocation);
glEnableVertexAttribArray(normalLocation);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
glDeleteBuffers(1, vbo);
// Create Index Buffer
glGenBuffers(1, eao);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, eao[0]);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, numIndecies*sizeof(GLuint), indices, GL_STATIC_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
angle = 0.0;
}
void render()
{
glBindVertexArray(vao[0]);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, eao[0]);
glDrawElements(GL_TRIANGLES, numIndecies, GL_UNSIGNED_INT, 0);
}
void update() {
modelMat = glm::mat4(1.0f);
modelMat = glm::translate(modelMat, position);
modelMat *= glm::rotate(angle, angle_axis);
}
glm::mat4 getMatrix() {
return modelMat;
}
void setAngle(float a, float ax, float ay, float az) {
angle = a;
angle_axis = glm::vec3(ax, ay, az);
}
void setPosition(float x, float y, float z) {
position = glm::vec3(x, y, z);
}
void setSize(float x, float y, float z) {
size = glm::vec3(x, y, z);
}
};
///////////////////////////////////////////////////////////////////////////////
//
// for GLSL
//
GLuint programObject;
GLint modelMatrixLocation;
GLint viewMatrixLocation;
GLint projectionMatrixLocation;
GLint shadowEnableLocation;
GLint lightPosLocation;
glm::mat4 ViewMatrix;
glm::mat4 ProjectionMatrix;
//
// Model
//
Cube cube;
glm::vec3 light_pos(10.0, 10.0, 0.0); // 光源位置
glm::vec3 cube_pos(0.0, 2.0, 0.0);
//
// initialize
//
void init()
{
glClearColor(0.5, 0.5, 0.5, 1.0);
glClearDepth(1.0f);
ViewMatrix = glm::lookAt(
glm::vec3(4.0f, 5.0f, 6.0f),
glm::vec3(0.0f, 2.0f, 0.0f),
glm::vec3(0.0f, 1.0f, 0.0f)
);
glUniformMatrix4fv(viewMatrixLocation, 1, GL_FALSE, glm::value_ptr(ViewMatrix));
glEnable(GL_DEPTH_TEST);
glEnable(GL_CULL_FACE);
//
// GLSL initialize
//
std::list<GLuint> shaderList;
shaderList.push_back( loadShaderFromFile(GL_VERTEX_SHADER, "simple.vert") );
shaderList.push_back( loadShaderFromFile(GL_GEOMETRY_SHADER, "simple.geom") );
shaderList.push_back( loadShaderFromFile(GL_FRAGMENT_SHADER, "simple.frag") );
programObject = createProgram(shaderList);
modelMatrixLocation = glGetUniformLocation(programObject, "modelMatrix");
viewMatrixLocation = glGetUniformLocation(programObject, "viewMatrix");
projectionMatrixLocation = glGetUniformLocation(programObject, "projectionMatrix");
shadowEnableLocation = glGetUniformLocation(programObject, "shadowEnable");
lightPosLocation = glGetUniformLocation(programObject, "lightPos");
glBindFragDataLocation(programObject, 0, "fragColor");
}
void reshape(int w, int h)
{
glViewport(0, 0, w, h);
ProjectionMatrix = glm::perspective(30.0f, (float)w/(float)h, 1.0f, 100.0f);
}
void timer(int t)
{
glutPostRedisplay();
glutTimerFunc(t, timer, 17);
}
//
// Main Loop
//
void display()
{
static float angle = 0.0;
cube.setAngle(angle, 0.0f, 1.0f, 0.0f);
cube.update();
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glUseProgram(programObject);
glUniformMatrix4fv(viewMatrixLocation, 1, GL_FALSE, glm::value_ptr(ViewMatrix));
glUniformMatrix4fv(projectionMatrixLocation, 1, GL_FALSE, glm::value_ptr(ProjectionMatrix));
glUniform3fv(lightPosLocation, 1, glm::value_ptr(light_pos));
//
// Draw Cube
//
glUniform1i(shadowEnableLocation, 0);
glUniformMatrix4fv(modelMatrixLocation, 1, GL_FALSE, glm::value_ptr(cube.getMatrix()));
cube.render();
glUseProgram(0);
glutSwapBuffers();
angle += 1.0;
if (angle > 180.0) angle = 0.0;
}
int main(int argc, char *argv[])
{
glutInit(&argc, argv);
glutInitWindowSize(400, 300);
glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH);
glutCreateWindow("OpenGL sample");
glutDisplayFunc(display);
glutReshapeFunc(reshape);
glutTimerFunc(100, timer, 17);
// GLenum err;
// //glewExperimental = GL_TRUE;
// err = glewInit();
// if (err != GLEW_OK) {
// std::cerr << "GLEW error : " << glewGetErrorString(err) << "\n";
// std::exit(1);
// }
// if (!glewIsSupported("GL_VERSION_3_2")) {
// std::cerr << "OpenGL 3.2 not supported.\n";
// std::exit(1);
// }
if (gl3wInit()) {
std::cerr << "failed to initialize OpenGL\n";
return 1;
}
if (!gl3wIsSupported(3, 2)) {
std::cerr << "OpenGL 3.2 not supported\n";
return 1;
}
init();
//
// setup Cube
//
cube.setup(programObject);
cube.setAngle(0.0, 0.0f, 1.0f, 0.0f);
cube.setPosition(cube_pos.x, cube_pos.y, cube_pos.z);
glutMainLoop();
return 0;
}
まとめ
ジオメトリシェーダは、難しいと思っていて使うのを敬遠していたが、実際使ってみると以外に簡単でした。
今回の影行列による影生成もやりたいと思ってから実装までそう時間はかかりませんでした。
今回の影行列による影生成もやりたいと思ってから実装までそう時間はかかりませんでした。
ただ効率とかを考えていないのでもっと良い実装があるかもしれません。
それでも、Cコード側で影を意識せずに実装できている点では、私が目的としたものとして満足できています。
それでも、Cコード側で影を意識せずに実装できている点では、私が目的としたものとして満足できています。
添付ファイル