Scratchapixel 2.0
Sign in
This project contains the following files (right-click files you'd like to download):
pinhole.cppgeometry.hboat.obj
//[header] // This program implements a physical pinhole camera model similar to the model // used in popular 3D packages such as Maya. //[/header] //[compile] // Download the pinhole.cpp and geometry.h files to the same folder. // Open a shell/terminal, and run the following command where the files are saved: // // c++ pinhole.cpp -o pinhole -std=c++11 // // Run with: ./pinhole. Open the file ./pinhole.svg in any Internet browser to see // the result. //[/compile] //[ignore] // Copyright (C) 2012 www.scratchapixel.com // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see <http://www.gnu.org/licenses/>. //[/ignore] #include <cstdio> #include <cstdlib> #include <cmath> #include "geometry.h" #include <fstream> //[comment] // List of vertices making up the object //[/comment] const Vec3f verts[146] = { { -2.5703, 0.78053, -2.4e-05}, { -0.89264, 0.022582, 0.018577}, { 1.6878, -0.017131, 0.022032}, { 3.4659, 0.025667, 0.018577}, { -2.5703, 0.78969, -0.001202}, { -0.89264, 0.25121, 0.93573}, { 1.6878, 0.25121, 1.1097}, { 3.5031, 0.25293, 0.93573}, { -2.5703, 1.0558, -0.001347}, { -0.89264, 1.0558, 1.0487}, { 1.6878, 1.0558, 1.2437}, { 3.6342, 1.0527, 1.0487}, { -2.5703, 1.0558, 0}, { -0.89264, 1.0558, 0}, { 1.6878, 1.0558, 0}, { 3.6342, 1.0527, 0}, { -2.5703, 1.0558, 0.001347}, { -0.89264, 1.0558, -1.0487}, { 1.6878, 1.0558, -1.2437}, { 3.6342, 1.0527, -1.0487}, { -2.5703, 0.78969, 0.001202}, { -0.89264, 0.25121, -0.93573}, { 1.6878, 0.25121, -1.1097}, { 3.5031, 0.25293, -0.93573}, { 3.5031, 0.25293, 0}, { -2.5703, 0.78969, 0}, { 1.1091, 1.2179, 0}, { 1.145, 6.617, 0}, { 4.0878, 1.2383, 0}, { -2.5693, 1.1771, -0.081683}, { 0.98353, 6.4948, -0.081683}, { -0.72112, 1.1364, -0.081683}, { 0.9297, 6.454, 0}, { -0.7929, 1.279, 0}, { 0.91176, 1.2994, 0} }; const uint32_t numTris = 51; //[comment] // Triangle index array. A triangle has 3 vertices. Each successive group of 3 // integers in this array represent the positions of the vertices in the vertex // array making up one triangle of that object. For example, the first 3 integers // from this array, 8/7/9 represent the positions of the vertices making up the // the first triangle. You can access these vertices with the following code: // // verts[8]; /* first vertex */ // // verts[7]; /* second vertex */ // // verts[9]; /* third vertex */ // // 6/5/5 are the positions of the vertices in the vertex array making up the second // triangle, and so on. // To find the indices of the n-th triangle, use the following code: // // tris[n * 3]; /* index of the first vertex in the verts array */ // // tris[n * 3 + 1]; /* index of the second vertexin the verts array */ // // tris[n * 3 + 2]; /* index of the third vertex in the verts array */ //[/comment] const uint32_t tris[numTris * 3] = { 4, 0, 5, 0, 1, 5, 1, 2, 5, 5, 2, 6, 3, 7, 2, 2, 7, 6, 5, 9, 4, 4, 9, 8, 5, 6, 9, 9, 6, 10, 7, 11, 6, 6, 11, 10, 9, 13, 8, 8, 13, 12, 10, 14, 9, 9, 14, 13, 10, 11, 14, 14, 11, 15, 17, 16, 13, 12, 13, 16, 13, 14, 17, 17, 14, 18, 15, 19, 14, 14, 19, 18, 16, 17, 20, 20, 17, 21, 18, 22, 17, 17, 22, 21, 18, 19, 22, 22, 19, 23, 20, 21, 0, 21, 1, 0, 22, 2, 21, 21, 2, 1, 22, 23, 2, 2, 23, 3, 3, 23, 24, 3, 24, 7, 24, 23, 15, 15, 23, 19, 24, 15, 7, 7, 15, 11, 0, 25, 20, 0, 4, 25, 20, 25, 16, 16, 25, 12, 25, 4, 12, 12, 4, 8, 26, 27, 28, 29, 30, 31, 32, 34, 33 }; //[comment] // Compute the 2D pixel coordinates of a point defined in world space. This function // requires the point original world coordinates of course, the world-to-camera // matrix (which you can get from computing the inverse of the camera-to-world matrix, // the matrix transforming the camera), the canvas dimension and the image width and // height in pixels. // // The canvas coordinates (bottom, left, top, right) are also passed to the function // so we can test the point raster coordinates against the canvas coordinates. If the // point in raster space lies within the canvas boundaries, the function returns true, // false otherwise. //[/comment] bool computePixelCoordinates( const Vec3f &pWorld, const Matrix44f &worldToCamera, const float &b, const float &l, const float &t, const float &r, const float &near, const uint32_t &imageWidth, const uint32_t &imageHeight, Vec2i &pRaster) { Vec3f pCamera; worldToCamera.multVecMatrix(pWorld, pCamera); Vec2f pScreen; pScreen.x = pCamera.x / -pCamera.z * near; pScreen.y = pCamera.y / -pCamera.z * near; Vec2f pNDC; pNDC.x = (pScreen.x + r) / (2 * r); pNDC.y = (pScreen.y + t) / (2 * t); pRaster.x = (int)(pNDC.x * imageWidth); pRaster.y = (int)((1 - pNDC.y) * imageHeight); bool visible = true; if (pScreen.x < l || pScreen.x > r || pScreen.y < b || pScreen.y > t) visible = false; return visible; } //[comment] // Settings of the physical camera model: // // - focal length in millimetre // // - film dimensions (width and height in inches) // // Other settings: // // - clipping planes (the canvas is positionned at the near clipping plane) // // - image dimensions in pixels // // - fit film mode (overscan or fit) //[/comment] float focalLength = 35; // in mm // 35mm Full Aperture in inches float filmApertureWidth = 0.825; float filmApertureHeight = 0.446; static const float inchToMm = 25.4; float nearClippingPlane = 0.1; float farClipingPlane = 1000; // image resolution in pixels uint32_t imageWidth = 512; uint32_t imageHeight = 512; enum FitResolutionGate { kFill = 0, kOverscan }; FitResolutionGate fitFilm = kOverscan; int main(int argc, char **argv) { //[comment] // First compute the canvas coordinates. The canvas is positionned by choice, // at the near clipping plane. By changing the near clipping plane value, you // will change the position of the canvas, but this won't change the output of the // program. //[/comment] float filmAspectRatio = filmApertureWidth / filmApertureHeight; float deviceAspectRatio = imageWidth / (float)imageHeight; float top = ((filmApertureHeight * inchToMm / 2) / focalLength) * nearClippingPlane; float right = ((filmApertureWidth * inchToMm / 2) / focalLength) * nearClippingPlane; float xscale = 1; float yscale = 1; switch (fitFilm) { default: case kFill: if (filmAspectRatio > deviceAspectRatio) { xscale = deviceAspectRatio / filmAspectRatio; } else { yscale = filmAspectRatio / deviceAspectRatio; } break; case kOverscan: if (filmAspectRatio > deviceAspectRatio) { yscale = filmAspectRatio / deviceAspectRatio; } else { xscale = deviceAspectRatio / filmAspectRatio; } break; } right *= xscale; top *= yscale; float bottom = -top; float left = -right; printf("Screen window coordinates: %f %f %f %f\n", bottom, left, top, right); printf("Film Aspect Ratio: %f\nDevice Aspect Ratio: %f\n", filmAspectRatio, deviceAspectRatio); printf("Angle of view: %f (deg)\n", 2 * atan((filmApertureWidth * inchToMm / 2) / focalLength) * 180 / M_PI); //[comment] // Project the triangles vertices onto the plane. If at least one of the vertices lies outside // the canvas boundaries (if the function computePixelCoordinates returns false), the triangle // is drawn in red and black otherwise. The result is store in a SVG file. //[/comment] std::ofstream ofs; ofs.open("./pinhole.svg"); ofs << "<svg version=\"1.1\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" xmlns=\"http://www.w3.org/2000/svg\" width=\"" << imageWidth << "\" height=\"" << imageHeight << "\">" << std::endl; Matrix44f cameraToWorld(-0.95424, 0, 0.299041, 0, 0.0861242, 0.95763, 0.274823, 0, -0.28637, 0.288002, -0.913809, 0, -3.734612, 7.610426, -14.152769, 1); Matrix44f worldToCamera = cameraToWorld.inverse(); float canvasWidth = 2, canvasHeight = 2; for (uint32_t i = 0; i < numTris; ++i) { const Vec3f &v0World = verts[tris[i * 3]]; const Vec3f &v1World = verts[tris[i * 3 + 1]]; const Vec3f &v2World = verts[tris[i * 3 + 2]]; Vec2i v0Raster, v1Raster, v2Raster; bool visible = true; visible &= computePixelCoordinates(v0World, worldToCamera, bottom, left, top, right, nearClippingPlane, imageWidth, imageHeight, v0Raster); visible &= computePixelCoordinates(v1World, worldToCamera, bottom, left, top, right, nearClippingPlane, imageWidth, imageHeight, v1Raster); visible &= computePixelCoordinates(v2World, worldToCamera, bottom, left, top, right, nearClippingPlane, imageWidth, imageHeight, v2Raster); int val = visible ? 0 : 255; ofs << "<line x1=\"" << v0Raster.x << "\" y1=\"" << v0Raster.y << "\" x2=\"" << v1Raster.x << "\" y2=\"" << v1Raster.y << "\" style=\"stroke:rgb(" << val << ",0,0);stroke-width:1\" />\n"; ofs << "<line x1=\"" << v1Raster.x << "\" y1=\"" << v1Raster.y << "\" x2=\"" << v2Raster.x << "\" y2=\"" << v2Raster.y << "\" style=\"stroke:rgb(" << val << ",0,0);stroke-width:1\" />\n"; ofs << "<line x1=\"" << v2Raster.x << "\" y1=\"" << v2Raster.y << "\" x2=\"" << v0Raster.x << "\" y2=\"" << v0Raster.y << "\" style=\"stroke:rgb(" << val << ",0,0);stroke-width:1\" />\n"; } ofs << "</svg>\n"; ofs.close(); return 0; }