提交 e4e6ad32 编写于 作者: M Matt Pharr

Add --interactive option

Currently only works with the --gpu / --wavefront integrator.
WASD-style controls; could use refinement.
Control-R to record frames.
上级 fc86eb47
......@@ -61,6 +61,9 @@ jobs:
- name: Install OpenEXR
run: sudo apt-get -y install libopenexr-dev
- name: Install OpenGL
run: sudo apt-get install -y --no-install-recommends libx11-dev libxcursor-dev libxrandr-dev libxinerama-dev libxi-dev libxext-dev libxfixes-dev libgl1-mesa-dev
- name: Install compilers
run: sudo apt-get -y install ${{ matrix.compilers.cc }} ${{ matrix.compilers.cxx }}
......
......@@ -65,6 +65,10 @@ jobs:
if: ${{ matrix.os == 'ubuntu-20.04' }}
run: sudo apt-get -y install libopenexr-dev
- name: Install OpenGL
if: ${{ matrix.os == 'ubuntu-20.04' }}
run: sudo apt-get install -y --no-install-recommends libx11-dev libxcursor-dev libxrandr-dev libxinerama-dev libxi-dev libxext-dev libxfixes-dev libgl1-mesa-dev
- name: Configure
run: |
cd build
......
......@@ -69,7 +69,6 @@ check_ext ("qoi" "qoi" 028c75fd26e5e0758c7c711216c00404994c1ad3)
check_ext ("stb" "stb/tools" af1a5bc352164740c1cc1354942b1c6b72eacb8a)
check_ext ("utf8proc" "utf8proc/bench" 2484e2ed5e1d9c19edcccf392a7d9920ad90dfaf)
check_ext ("zlib" "zlib/doc" 54d591eabf9fe0e84c725638f8d5d8d202a093fa)
add_compile_definitions ("$<$<CONFIG:DEBUG>:PBRT_DEBUG_BUILD>")
enable_testing ()
......@@ -77,6 +76,8 @@ enable_testing ()
find_package (Sanitizers)
find_package (Threads)
find_package(OpenGL REQUIRED)
set_property (GLOBAL PROPERTY USE_FOLDERS ON)
if (MSVC)
......@@ -601,6 +602,7 @@ SET (PBRT_UTIL_SOURCE
src/pbrt/util/error.cpp
src/pbrt/util/file.cpp
src/pbrt/util/float.cpp
src/pbrt/util/gui.cpp
src/pbrt/util/image.cpp
src/pbrt/util/log.cpp
src/pbrt/util/loopsubdiv.cpp
......@@ -640,6 +642,7 @@ SET (PBRT_UTIL_SOURCE_HEADERS
src/pbrt/util/error.h
src/pbrt/util/file.h
src/pbrt/util/float.h
src/pbrt/util/gui.h
src/pbrt/util/hash.h
src/pbrt/util/image.h
src/pbrt/util/log.h
......@@ -679,6 +682,7 @@ if (PBRT_CUDA_ENABLED)
)
set (PBRT_GPU_SOURCE_HEADERS
src/pbrt/gpu/aggregate.h
src/pbrt/gpu/cudagl.h
src/pbrt/gpu/denoiser.h
src/pbrt/gpu/memory.h
src/pbrt/gpu/optix.h
......@@ -842,7 +846,10 @@ target_include_directories (pbrt_lib PUBLIC
${DOUBLE_CONVERSION_INCLUDE}
${NANOVDB_INCLUDE}
${CMAKE_CURRENT_BINARY_DIR}
${GLFW_INCLUDE}
${GLAD_INCLUDE}
)
if (PBRT_CUDA_ENABLED AND PBRT_OPTIX7_PATH)
target_include_directories (pbrt_lib SYSTEM PUBLIC ${PBRT_OPTIX7_PATH}/include)
endif ()
......@@ -868,7 +875,9 @@ set (ALL_PBRT_LIBS
double-conversion
${PBRT_CUDA_LIB}
utf8proc
)
glfw
glad
OpenGL::GL)
if (PBRT_CUDA_ENABLED)
set_property (TARGET pbrt_lib PROPERTY CUDA_SEPARABLE_COMPILATION ON)
......
......@@ -54,6 +54,7 @@ Rendering options:
#endif
R"(
--help Print this help text.
--interactive Enable interactive rendering mode.
--mse-reference-image Filename for reference image to use for MSE computation.
--mse-reference-out File to write MSE error vs spp results.
--nthreads <num> Use specified number of threads for rendering.
......@@ -176,6 +177,7 @@ int main(int argc, char *argv[]) {
ParseArg(&iter, args.end(), "log-utilization", &options.logUtilization,
onError) ||
ParseArg(&iter, args.end(), "log-file", &options.logFile, onError) ||
ParseArg(&iter, args.end(), "interactive", &options.interactive, onError) ||
ParseArg(&iter, args.end(), "mse-reference-image", &options.mseReferenceImage,
onError) ||
ParseArg(&iter, args.end(), "mse-reference-out", &options.mseReferenceOutput,
......@@ -248,6 +250,10 @@ int main(int argc, char *argv[]) {
options.wavefront = false;
}
if (options.interactive && !(options.useGPU || options.wavefront))
ErrorExit("The --interactive option is only supported with the --gpu "
"and --wavefront integrators.");
options.logLevel = LogLevelFromString(logLevel);
// Initialize pbrt
......
//
// Copyright (c) 2021, NVIDIA CORPORATION. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
// * Neither the name of NVIDIA CORPORATION nor the names of its
// contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
#ifndef PBRT_GPU_CUDAGL_H
#define PBRT_GPU_CUDAGL_H
#include <pbrt/gpu/util.h>
#include <pbrt/util/error.h>
#include <glad/glad.h>
#include <cuda.h>
#include <cuda_gl_interop.h>
#include <cuda_runtime.h>
#define GL_CHECK(call) \
do { \
call; \
if (GLenum err = glGetError(); err != GL_NO_ERROR) \
LOG_FATAL("GL error: %s for " #call, getGLErrorString(err)); \
} while (0)
#define GL_CHECK_ERRORS() \
do { \
if (GLenum err = glGetError(); err != GL_NO_ERROR) \
LOG_FATAL("GL error: %s", getGLErrorString(err)); \
} while (0)
namespace pbrt {
// BufferDisplay functionality
// Copyright (c) 2021, NVIDIA CORPORATION. All rights reserved.
// BSD 3-clause license
extern const char *getGLErrorString(GLenum error);
enum BufferImageFormat { UNSIGNED_BYTE4, FLOAT4, FLOAT3 };
class BufferDisplay {
public:
BufferDisplay(BufferImageFormat format = BufferImageFormat::UNSIGNED_BYTE4);
void display(const int32_t screen_res_x, const int32_t screen_res_y,
const int32_t framebuf_res_x, const int32_t framebuf_res_y,
const uint32_t pbo) const;
private:
GLuint m_render_tex = 0u;
GLuint m_program = 0u;
GLint m_render_tex_uniform_loc = -1;
GLuint m_quad_vertex_buffer = 0;
BufferImageFormat m_image_format;
};
static GLuint createGLShader(const std::string& source, GLuint shader_type) {
GLuint shader = glCreateShader(shader_type);
const GLchar* source_data = reinterpret_cast<const GLchar*>(source.data());
glShaderSource(shader, 1, &source_data, nullptr);
glCompileShader(shader);
GLint is_compiled = 0;
glGetShaderiv(shader, GL_COMPILE_STATUS, &is_compiled);
if (is_compiled == GL_FALSE) {
GLint max_length = 0;
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &max_length);
std::string info_log(max_length, '\0');
GLchar* info_log_data = reinterpret_cast<GLchar*>(&info_log[0]);
glGetShaderInfoLog(shader, max_length, nullptr, info_log_data);
glDeleteShader(shader);
LOG_FATAL("Shader compilation failed: %s", info_log);
}
GL_CHECK_ERRORS();
return shader;
}
static GLuint createGLProgram(const std::string& vert_source,
const std::string& frag_source) {
GLuint vert_shader = createGLShader(vert_source, GL_VERTEX_SHADER);
if (vert_shader == 0)
return 0;
GLuint frag_shader = createGLShader(frag_source, GL_FRAGMENT_SHADER);
if (frag_shader == 0) {
glDeleteShader(vert_shader);
return 0;
}
GLuint program = glCreateProgram();
glAttachShader(program, vert_shader);
glAttachShader(program, frag_shader);
glLinkProgram(program);
GLint is_linked = 0;
glGetProgramiv(program, GL_LINK_STATUS, &is_linked);
if (is_linked == GL_FALSE) {
GLint max_length = 0;
glGetProgramiv(program, GL_INFO_LOG_LENGTH, &max_length);
std::string info_log(max_length, '\0');
GLchar* info_log_data = reinterpret_cast<GLchar*>(&info_log[0]);
glGetProgramInfoLog(program, max_length, nullptr, info_log_data);
LOG_FATAL("Program linking failed: %s", info_log);
glDeleteProgram(program);
glDeleteShader(vert_shader);
glDeleteShader(frag_shader);
return 0;
}
glDetachShader(program, vert_shader);
glDetachShader(program, frag_shader);
GL_CHECK_ERRORS();
return program;
}
static GLint getGLUniformLocation(GLuint program, const std::string& name) {
GLint loc = glGetUniformLocation(program, name.c_str());
CHECK_NE(loc, -1);
return loc;
}
static size_t pixelFormatSize(BufferImageFormat format) {
switch (format) {
case BufferImageFormat::UNSIGNED_BYTE4:
return sizeof(char) * 4;
case BufferImageFormat::FLOAT3:
return sizeof(float) * 3;
case BufferImageFormat::FLOAT4:
return sizeof(float) * 4;
default:
LOG_FATAL("sutil::pixelFormatSize: Unrecognized buffer format");
}
}
inline BufferDisplay::BufferDisplay(BufferImageFormat image_format) : m_image_format(image_format) {
GLuint m_vertex_array;
GL_CHECK(glGenVertexArrays(1, &m_vertex_array));
GL_CHECK(glBindVertexArray(m_vertex_array));
std::string vert_source = R"(
#version 330 core
layout(location = 0) in vec3 vertexPosition_modelspace;
out vec2 UV;
void main()
{
gl_Position = vec4(vertexPosition_modelspace,1);
UV = (vec2( vertexPosition_modelspace.x, vertexPosition_modelspace.y )+vec2(1,1))/2.0;
UV.y = 1 - UV.y;
}
)";
std::string frag_source = R"(
#version 330 core
in vec2 UV;
out vec3 color;
uniform sampler2D render_tex;
void main()
{
color = texture( render_tex, UV ).xyz;
}
)";
m_program = createGLProgram(vert_source, frag_source);
m_render_tex_uniform_loc = getGLUniformLocation(m_program, "render_tex");
GL_CHECK(glGenTextures(1, &m_render_tex));
GL_CHECK(glBindTexture(GL_TEXTURE_2D, m_render_tex));
GL_CHECK(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST));
GL_CHECK(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST));
GL_CHECK(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE));
GL_CHECK(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE));
static const GLfloat g_quad_vertex_buffer_data[] = {
-1.0f, -1.0f, 0.0f, 1.0f, -1.0f, 0.0f, -1.0f, 1.0f, 0.0f,
-1.0f, 1.0f, 0.0f, 1.0f, -1.0f, 0.0f, 1.0f, 1.0f, 0.0f,
};
GL_CHECK(glGenBuffers(1, &m_quad_vertex_buffer));
GL_CHECK(glBindBuffer(GL_ARRAY_BUFFER, m_quad_vertex_buffer));
GL_CHECK(glBufferData(GL_ARRAY_BUFFER, sizeof(g_quad_vertex_buffer_data),
g_quad_vertex_buffer_data, GL_STATIC_DRAW));
GL_CHECK_ERRORS();
}
inline void BufferDisplay::display(const int32_t screen_res_x, const int32_t screen_res_y,
const int32_t framebuf_res_x, const int32_t framebuf_res_y,
const uint32_t pbo) const {
GL_CHECK(glBindFramebuffer(GL_FRAMEBUFFER, 0));
GL_CHECK(glViewport(0, 0, framebuf_res_x, framebuf_res_y));
GL_CHECK(glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT));
GL_CHECK(glUseProgram(m_program));
// Bind our texture in Texture Unit 0
GL_CHECK(glActiveTexture(GL_TEXTURE0));
GL_CHECK(glBindTexture(GL_TEXTURE_2D, m_render_tex));
GL_CHECK(glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo));
GL_CHECK(glPixelStorei(GL_UNPACK_ALIGNMENT, 4)); // TODO!!!!!!
size_t elmt_size = pixelFormatSize(m_image_format);
if (elmt_size % 8 == 0)
glPixelStorei(GL_UNPACK_ALIGNMENT, 8);
else if (elmt_size % 4 == 0)
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
else if (elmt_size % 2 == 0)
glPixelStorei(GL_UNPACK_ALIGNMENT, 2);
else
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
bool convertToSrgb = true;
if (m_image_format == BufferImageFormat::UNSIGNED_BYTE4) {
// input is assumed to be in srgb since it is only 1 byte per channel in size
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, screen_res_x, screen_res_y, 0, GL_RGBA,
GL_UNSIGNED_BYTE, nullptr);
convertToSrgb = false;
} else if (m_image_format == BufferImageFormat::FLOAT3)
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB32F, screen_res_x, screen_res_y, 0, GL_RGB,
GL_FLOAT, nullptr);
else if (m_image_format == BufferImageFormat::FLOAT4)
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, screen_res_x, screen_res_y, 0, GL_RGBA,
GL_FLOAT, nullptr);
else
LOG_FATAL("Unknown buffer format");
GL_CHECK(glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0));
GL_CHECK(glUniform1i(m_render_tex_uniform_loc, 0));
// 1st attribute buffer : vertices
GL_CHECK(glEnableVertexAttribArray(0));
GL_CHECK(glBindBuffer(GL_ARRAY_BUFFER, m_quad_vertex_buffer));
GL_CHECK(glVertexAttribPointer(0, // attribute 0. No particular reason for 0, but
// must match the layout in the shader.
3, // size
GL_FLOAT, // type
GL_FALSE, // normalized?
0, // stride
(void*)0 // array buffer offset
));
if (convertToSrgb)
GL_CHECK(glEnable(GL_FRAMEBUFFER_SRGB));
else
GL_CHECK(glDisable(GL_FRAMEBUFFER_SRGB));
GL_CHECK(glDrawArrays(GL_TRIANGLES, 0, 6)); // 2*3 indices starting at 0 -> 2 triangles
GL_CHECK(glDisableVertexAttribArray(0));
GL_CHECK(glDisable(GL_FRAMEBUFFER_SRGB));
GL_CHECK_ERRORS();
}
// Specialized from the original to only support GL_INTEROP
template <typename PIXEL_FORMAT>
class CUDAOutputBuffer {
public:
CUDAOutputBuffer(int32_t width, int32_t height);
~CUDAOutputBuffer();
void StartAsynchronousReadback();
const PIXEL_FORMAT *GetReadbackPixels();
void Draw(int windowWidth, int windowHeight) {
display->display(m_width, m_height, windowWidth, windowHeight,
getPBO());
}
PIXEL_FORMAT *Map();
void Unmap();
private:
void setStream(CUstream stream) { m_stream = stream; }
// Allocate or update device pointer as necessary for CUDA access
void makeCurrent() { CUDA_CHECK(cudaSetDevice(m_device_idx)); }
// Get output buffer
GLuint getPBO();
void deletePBO();
int32_t m_width = 0u;
int32_t m_height = 0u;
cudaGraphicsResource *m_cuda_gfx_resource = nullptr;
GLuint m_pbo = 0u;
PIXEL_FORMAT *m_device_pixels = nullptr;
PIXEL_FORMAT *m_host_pixels = nullptr;
bool readbackActive = false;
cudaEvent_t readbackFinishedEvent;
CUstream m_stream = 0u;
int32_t m_device_idx = 0;
BufferDisplay *display = nullptr;
};
template <typename PIXEL_FORMAT>
CUDAOutputBuffer<PIXEL_FORMAT>::CUDAOutputBuffer(int32_t width, int32_t height) {
CHECK(width > 0 && height > 0);
// If using GL Interop, expect that the active device is also the display device.
int current_device, is_display_device;
CUDA_CHECK(cudaGetDevice(&current_device));
CUDA_CHECK(cudaDeviceGetAttribute(&is_display_device, cudaDevAttrKernelExecTimeout,
current_device));
if (!is_display_device)
LOG_FATAL("GL interop is only available on display device.");
CUDA_CHECK(cudaGetDevice(&m_device_idx));
m_width = width;
m_height = height;
makeCurrent();
// GL buffer gets resized below
GL_CHECK(glGenBuffers(1, &m_pbo));
GL_CHECK(glBindBuffer(GL_ARRAY_BUFFER, m_pbo));
GL_CHECK(glBufferData(GL_ARRAY_BUFFER, sizeof(PIXEL_FORMAT) * m_width * m_height,
nullptr, GL_STREAM_DRAW));
GL_CHECK(glBindBuffer(GL_ARRAY_BUFFER, 0u));
CUDA_CHECK(cudaGraphicsGLRegisterBuffer(&m_cuda_gfx_resource, m_pbo,
cudaGraphicsMapFlagsWriteDiscard));
CUDA_CHECK(cudaEventCreate(&readbackFinishedEvent));
CUDA_CHECK(cudaMallocHost(&m_host_pixels, m_width * m_height * sizeof(PIXEL_FORMAT)));
display = new BufferDisplay(BufferImageFormat::FLOAT3);
}
template <typename PIXEL_FORMAT>
CUDAOutputBuffer<PIXEL_FORMAT>::~CUDAOutputBuffer() {
makeCurrent();
delete display;
if (m_pbo != 0u) {
GL_CHECK(glBindBuffer(GL_ARRAY_BUFFER, 0));
GL_CHECK(glDeleteBuffers(1, &m_pbo));
}
}
template <typename PIXEL_FORMAT>
PIXEL_FORMAT* CUDAOutputBuffer<PIXEL_FORMAT>::Map() {
makeCurrent();
size_t buffer_size = 0u;
CUDA_CHECK(cudaGraphicsMapResources(1, &m_cuda_gfx_resource, m_stream));
CUDA_CHECK(cudaGraphicsResourceGetMappedPointer(
reinterpret_cast<void**>(&m_device_pixels), &buffer_size, m_cuda_gfx_resource));
return m_device_pixels;
}
template <typename PIXEL_FORMAT>
void CUDAOutputBuffer<PIXEL_FORMAT>::Unmap() {
makeCurrent();
CUDA_CHECK(cudaGraphicsUnmapResources(1, &m_cuda_gfx_resource, m_stream));
}
template <typename PIXEL_FORMAT>
GLuint CUDAOutputBuffer<PIXEL_FORMAT>::getPBO() {
if (m_pbo == 0u)
GL_CHECK(glGenBuffers(1, &m_pbo));
return m_pbo;
}
template <typename PIXEL_FORMAT>
void CUDAOutputBuffer<PIXEL_FORMAT>::deletePBO() {
GL_CHECK(glBindBuffer(GL_ARRAY_BUFFER, 0));
GL_CHECK(glDeleteBuffers(1, &m_pbo));
m_pbo = 0;
}
template <typename PIXEL_FORMAT>
void CUDAOutputBuffer<PIXEL_FORMAT>::StartAsynchronousReadback() {
CHECK(!readbackActive);
makeCurrent();
CUDA_CHECK(cudaMemcpyAsync(m_host_pixels, m_device_pixels,
m_width * m_height * sizeof(PIXEL_FORMAT),
cudaMemcpyDeviceToHost));
CUDA_CHECK(cudaEventRecord(readbackFinishedEvent));
readbackActive = true;
}
template <typename PIXEL_FORMAT>
const PIXEL_FORMAT *CUDAOutputBuffer<PIXEL_FORMAT>::GetReadbackPixels() {
if (!readbackActive)
return nullptr;
makeCurrent();
CUDA_CHECK(cudaEventSynchronize(readbackFinishedEvent));
readbackActive = false;
return m_host_pixels;
}
} // end namespace pbrt
#undef GL_CHECK
#undef GL_CHECK_ERRORS
#endif // PBRT_GPU_CUDAGL_H
......@@ -36,14 +36,14 @@ std::string PBRTOptions::ToString() const {
return StringPrintf(
"[ PBRTOptions seed: %s quiet: %s disablePixelJitter: %s "
"disableWavelengthJitter: %s disableTextureFiltering: %s forceDiffuse: %s "
"useGPU: %s wavefront: %s renderingSpace: %s nThreads: %s logLevel: %s logFile: "
"useGPU: %s wavefront: %s interactive: %s renderingSpace: %s nThreads: %s logLevel: %s logFile: "
"%s logUtilization: %s writePartialImages: %s recordPixelStatistics: %s "
"printStatistics: %s pixelSamples: %s gpuDevice: %s quickRender: %s upgrade: %s "
"imageFile: %s mseReferenceImage: %s mseReferenceOutput: %s debugStart: %s "
"displayServer: %s cropWindow: %s pixelBounds: %s pixelMaterial: %s "
"displacementEdgeScale: %f ]",
seed, quiet, disablePixelJitter, disableWavelengthJitter, disableTextureFiltering,
forceDiffuse, useGPU, wavefront, renderingSpace, nThreads, logLevel, logFile,
forceDiffuse, useGPU, wavefront, interactive, renderingSpace, nThreads, logLevel, logFile,
logUtilization, writePartialImages, recordPixelStatistics, printStatistics,
pixelSamples, gpuDevice, quickRender, upgrade, imageFile, mseReferenceImage,
mseReferenceOutput, debugStart, displayServer, cropWindow, pixelBounds,
......
......@@ -27,6 +27,7 @@ struct BasicPBRTOptions {
bool forceDiffuse = false;
bool useGPU = false;
bool wavefront = false;
bool interactive = false;
RenderingCoordinateSystem renderingSpace = RenderingCoordinateSystem::CameraWorld;
};
......
// pbrt is Copyright(c) 1998-2020 Matt Pharr, Wenzel Jakob, and Greg Humphreys.
// The pbrt source code is licensed under the Apache License, Version 2.0.
// SPDX: Apache-2.0
#include <pbrt/util/gui.h>
#include <pbrt/options.h>
#ifdef PBRT_BUILD_GPU_RENDERER
#include <pbrt/gpu/util.h>
#endif // PBRT_BUILD_GPU_RENDERER
#include <pbrt/util/error.h>
#include <pbrt/util/image.h>
#include <pbrt/util/parallel.h>
#define GL_CHECK(call) \
do { \
call; \
if (GLenum err = glGetError(); err != GL_NO_ERROR) \
LOG_FATAL("GL error: %s for " #call, getGLErrorString(err)); \
} while (0)
#define GL_CHECK_ERRORS() \
do { \
if (GLenum err = glGetError(); err != GL_NO_ERROR) \
LOG_FATAL("GL error: %s", getGLErrorString(err)); \
} while (0)
namespace pbrt {
const char *getGLErrorString(GLenum error) {
switch (error) {
case GL_NO_ERROR:
return "No error";
case GL_INVALID_ENUM:
return "Invalid enum";
case GL_INVALID_VALUE:
return "Invalid value";
case GL_INVALID_OPERATION:
return "Invalid operation";
case GL_OUT_OF_MEMORY:
return "Out of memory";
default:
return "Unknown GL error";
}
}
static void glfwErrorCallback(int error, const char *desc) {
LOG_ERROR("GLFW [%d]: %s", error, desc);
}
void GUI::keyboardCallback(GLFWwindow *window, int key, int scan, int action, int mods) {
if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
glfwSetWindowShouldClose(window, GLFW_TRUE);
auto doKey = [&](int k, char ch) {
if (key == k) {
if (action == GLFW_PRESS)
keysDown.insert(ch);
else if (action == GLFW_RELEASE) {
if (auto iter = keysDown.find(ch); iter != keysDown.end())
keysDown.erase(iter);
}
}
};
doKey(GLFW_KEY_A, 'a');
doKey(GLFW_KEY_D, 'd');
doKey(GLFW_KEY_S, 's');
doKey(GLFW_KEY_W, 'w');
doKey(GLFW_KEY_Q, 'q');
doKey(GLFW_KEY_E, 'e');
doKey(GLFW_KEY_B, (mods & GLFW_MOD_SHIFT) ? 'B' : 'b');
doKey(GLFW_KEY_C, 'c');
doKey(GLFW_KEY_EQUAL, '=');
doKey(GLFW_KEY_MINUS, '-');
doKey(GLFW_KEY_LEFT, 'L');
doKey(GLFW_KEY_RIGHT, 'R');
doKey(GLFW_KEY_UP, 'U');
doKey(GLFW_KEY_DOWN, 'D');
if (key == GLFW_KEY_R && action == GLFW_PRESS &&
glfwGetKey(window, GLFW_KEY_LEFT_CONTROL) == GLFW_PRESS)
recordFrames = !recordFrames;
else
doKey(GLFW_KEY_R, 'r');
}
bool GUI::processKeys() {
bool needsReset = false;
auto handleNeedsReset = [&](char key, std::function<Transform(Transform)> update) {
if (keysDown.find(key) != keysDown.end()) {
movingFromCamera = update(movingFromCamera);
needsReset = true;
}
};
handleNeedsReset(
'a', [&](Transform t) { return t * Translate(Vector3f(-moveScale, 0, 0)); });
handleNeedsReset(
'd', [&](Transform t) { return t * Translate(Vector3f(moveScale, 0, 0)); });
handleNeedsReset(
's', [&](Transform t) { return t * Translate(Vector3f(0, 0, -moveScale)); });
handleNeedsReset(
'w', [&](Transform t) { return t * Translate(Vector3f(0, 0, moveScale)); });
handleNeedsReset(
'q', [&](Transform t) { return t * Translate(Vector3f(0, -moveScale, 0)); });
handleNeedsReset(
'e', [&](Transform t) { return t * Translate(Vector3f(0, moveScale, 0)); });
handleNeedsReset('L',
[&](Transform t) { return t * Rotate(-.5f, Vector3f(0, 1, 0)); });
handleNeedsReset('R',
[&](Transform t) { return t * Rotate(.5f, Vector3f(0, 1, 0)); });
handleNeedsReset('U',
[&](Transform t) { return t * Rotate(-.5f, Vector3f(1, 0, 0)); });
handleNeedsReset('D',
[&](Transform t) { return t * Rotate(.5f, Vector3f(1, 0, 0)); });
handleNeedsReset('r', [&](Transform t) { return Transform(); });
// No reset needed for these.
if (keysDown.find('c') != keysDown.end()) {
keysDown.erase(keysDown.find('c'));
printCameraTransform = true;
}
if (keysDown.find('b') != keysDown.end()) {
keysDown.erase(keysDown.find('b'));
exposure *= 1.125f;
}
if (keysDown.find('B') != keysDown.end()) {
keysDown.erase(keysDown.find('B'));
exposure /= 1.125f;
}
if (keysDown.find('=') != keysDown.end()) {
keysDown.erase(keysDown.find('='));
moveScale *= 2;
}
if (keysDown.find('-') != keysDown.end()) {
keysDown.erase(keysDown.find('-'));
moveScale *= 0.5;
}
return needsReset;
}
static void glfwKeyCallback(GLFWwindow* window, int key, int scan, int action, int mods) {
GUI* gui = (GUI*)glfwGetWindowUserPointer(window);
gui->keyboardCallback(window, key, scan, action, mods);
}
GUI::GUI(std::string title, Vector2i resolution, Bounds3f sceneBounds)
: resolution(resolution) {
moveScale = Length(sceneBounds.Diagonal()) / 1000.f;
glfwSetErrorCallback(glfwErrorCallback);
if (!glfwInit())
LOG_FATAL("Unable to initialize GLFW");
window = glfwCreateWindow(resolution.x, resolution.y, "pbrt", NULL, NULL);
if (!window) {
glfwTerminate();
LOG_FATAL("Unable to create GLFW window");
}
glfwSetKeyCallback(window, glfwKeyCallback);
glfwSetWindowUserPointer(window, this);
glfwMakeContextCurrent(window);
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
LOG_FATAL("gladLoadGLLoader failed");
#ifdef PBRT_BUILD_GPU_RENDERER
if (Options->useGPU)
cudaFramebuffer = new CUDAOutputBuffer<RGB>(resolution.x, resolution.y);
else
#endif // PBRT_BUILD_GPU_RENDERER
cpuFramebuffer = new RGB[resolution.x * resolution.y];
}
GUI::~GUI() {
#ifdef PBRT_BUILD_GPU_RENDERER
delete cudaFramebuffer;
#endif // PBRT_BUILD_GPU_RENDERER
delete[] cpuFramebuffer;
glfwDestroyWindow(window);
glfwTerminate();
}
DisplayState GUI::RefreshDisplay() {
int width, height;
glfwGetFramebufferSize(window, &width, &height);
GL_CHECK(glViewport(0, 0, width, height));
#ifdef PBRT_BUILD_GPU_RENDERER
if (Options->useGPU)
cudaFramebuffer->Draw(width, height);
else
#endif // PBRT_BUILD_GPU_RENDERER
{
GL_CHECK(glEnable(GL_FRAMEBUFFER_SRGB));
GL_CHECK(glRasterPos2f(-1, 1));
GL_CHECK(glPixelZoom(1, -1));
GL_CHECK(
glDrawPixels(resolution.x, resolution.y, GL_RGB, GL_FLOAT, cpuFramebuffer));
}
glfwSwapBuffers(window);
glfwPollEvents();
if (recordFrames) {
const RGB *fb = nullptr;
#ifdef PBRT_BUILD_GPU_RENDERER
if (cudaFramebuffer)
fb = cudaFramebuffer->GetReadbackPixels();
else
#endif
fb = cpuFramebuffer;
if (fb) {
Image image(PixelFormat::Float, {width, height}, {"R", "G", "B"});
std::memcpy(image.RawPointer({0, 0}), fb, width * height * sizeof(RGB));
RunAsync([](Image image, int frameNumber) {
// TODO: set metadata for e.g. current camera position...
ImageMetadata metadata;
image.Write(StringPrintf("pbrt-frame%05d.exr", frameNumber), metadata);
return 0; // FIXME: RunAsync() doesn't like lambdas that return void..
}, std::move(image), frameNumber);
++frameNumber;
}
#ifdef PBRT_BUILD_GPU_RENDERER
if (cudaFramebuffer)
cudaFramebuffer->StartAsynchronousReadback();
#endif
}
if (glfwWindowShouldClose(window))
return DisplayState::EXIT;
else if (processKeys())
return DisplayState::RESET;
else
return DisplayState::NONE;
}
} // namespace pbrt
// pbrt is Copyright(c) 1998-2020 Matt Pharr, Wenzel Jakob, and Greg Humphreys.
// The pbrt source code is licensed under the Apache License, Version 2.0.
// SPDX: Apache-2.0
#ifndef PBRT_UTIL_GUI_H
#define PBRT_UTIL_GUI_H
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <pbrt/pbrt.h>
#ifdef PBRT_BUILD_GPU_RENDERER
#include <pbrt/gpu/cudagl.h>
#endif // PBRT_BUILD_GPU_RENDERER
#include <pbrt/util/color.h>
#include <pbrt/util/transform.h>
#include <pbrt/util/vecmath.h>
#include <set>
#include <string>
namespace pbrt {
enum DisplayState { EXIT, RESET, NONE };
class GUI {
public:
GUI(std::string title, Vector2i resolution, Bounds3f sceneBounds);
~GUI();
RGB *MapFramebuffer() {
#ifdef PBRT_BUILD_GPU_RENDERER
if (cudaFramebuffer)
return cudaFramebuffer->Map();
else
#endif // PBRT_BUILD_GPU_RENDERER
return cpuFramebuffer;
}
void UnmapFramebuffer() {
#ifdef PBRT_BUILD_GPU_RENDERER
if (cudaFramebuffer)
cudaFramebuffer->Unmap();
#endif // PBRT_BUILD_GPU_RENDERER
}
DisplayState RefreshDisplay();
// It's a little messy that the state of values controlled via the UI
// are just public variables here but it's probably not worth putting
// an abstraction layer on top of all this at this point.
Transform GetCameraTransform() const { return movingFromCamera; }
Float exposure = 1.f;
bool printCameraTransform = false;
void keyboardCallback(GLFWwindow *window, int key, int scan, int action, int mods);
private:
bool processKeys();
std::set<char> keysDown;
Float moveScale = 1.f;
Transform movingFromCamera;
Vector2i resolution;
bool recordFrames = false;
int frameNumber = 0;
#ifdef PBRT_BUILD_GPU_RENDERER
CUDAOutputBuffer<RGB> *cudaFramebuffer = nullptr;
#endif
RGB *cpuFramebuffer = nullptr;
GLFWwindow *window = nullptr;
};
} // namespace pbrt
#endif // PBRT_UTIL_GUI_H
......@@ -15,20 +15,22 @@
namespace pbrt {
// WavefrontPathIntegrator Camera Ray Methods
void WavefrontPathIntegrator::GenerateCameraRays(int y0, int sampleIndex) {
void WavefrontPathIntegrator::GenerateCameraRays(int y0, Transform movingFromCamera,
int sampleIndex) {
// Define _generateRays_ lambda function
auto generateRays = [=](auto sampler) {
using ConcreteSampler = std::remove_reference_t<decltype(*sampler)>;
if constexpr (!std::is_same_v<ConcreteSampler, MLTSampler> &&
!std::is_same_v<ConcreteSampler, DebugMLTSampler>)
GenerateCameraRays<ConcreteSampler>(y0, sampleIndex);
GenerateCameraRays<ConcreteSampler>(y0, movingFromCamera, sampleIndex);
};
sampler.DispatchCPU(generateRays);
}
template <typename ConcreteSampler>
void WavefrontPathIntegrator::GenerateCameraRays(int y0, int sampleIndex) {
void WavefrontPathIntegrator::GenerateCameraRays(int y0, Transform movingFromCamera,
int sampleIndex) {
RayQueue *rayQueue = CurrentRayQueue(0);
ParallelFor(
"Generate camera rays", maxQueueSize, PBRT_CPU_GPU_LAMBDA(int pixelIndex) {
......@@ -58,6 +60,8 @@ void WavefrontPathIntegrator::GenerateCameraRays(int y0, int sampleIndex) {
CameraSample cameraSample = GetCameraSample(pixelSampler, pPixel, filter);
pstd::optional<CameraRay> cameraRay =
camera.GenerateRay(cameraSample, lambda);
if (cameraRay)
cameraRay->ray = movingFromCamera(cameraRay->ray);
// Initialize remainder of _PixelSampleState_ for ray
pixelSampleState.L[pixelIndex] = SampledSpectrum(0.f);
......
......@@ -18,6 +18,7 @@
#include <pbrt/util/colorspace.h>
#include <pbrt/util/display.h>
#include <pbrt/util/file.h>
#include <pbrt/util/gui.h>
#include <pbrt/util/image.h>
#include <pbrt/util/log.h>
#include <pbrt/util/print.h>
......@@ -286,6 +287,18 @@ WavefrontPathIntegrator::WavefrontPathIntegrator(
Float WavefrontPathIntegrator::Render() {
Bounds2i pixelBounds = film.PixelBounds();
Vector2i resolution = pixelBounds.Diagonal();
GUI *gui = nullptr;
// FIXME: camera animation; whatever...
Transform renderFromCamera = camera.GetCameraTransform().RenderFromCamera().startTransform;
Transform cameraFromRender = Inverse(renderFromCamera);
Transform cameraFromWorld = camera.GetCameraTransform().CameraFromWorld(camera.SampleTime(0.f));
if (Options->interactive) {
if (!Options->displayServer.empty())
ErrorExit("--interactive and --display-server cannot be used at the same time.");
gui = new GUI(film.GetFilename(), resolution, aggregate->Bounds());
}
Timer timer;
// Prefetch allocations to GPU memory
#ifdef PBRT_BUILD_GPU_RENDERER
......@@ -313,9 +326,8 @@ Float WavefrontPathIntegrator::Render() {
}
ProgressReporter progress(lastSampleIndex - firstSampleIndex, "Rendering",
Options->quiet, Options->useGPU);
for (int sampleIndex = firstSampleIndex; sampleIndex < lastSampleIndex;
++sampleIndex) {
Options->quiet || Options->interactive, Options->useGPU);
for (int sampleIndex = firstSampleIndex; sampleIndex < lastSampleIndex; ++sampleIndex) {
// Attempt to work around issue #145.
#if !(defined(PBRT_IS_WINDOWS) && defined(PBRT_BUILD_GPU_RENDERER) && \
__CUDACC_VER_MAJOR__ == 11 && __CUDACC_VER_MINOR__ == 1)
......@@ -338,7 +350,11 @@ Float WavefrontPathIntegrator::Render() {
sampleIndex, samplesPerPixel);
cameraRayQueue->Reset();
});
GenerateCameraRays(y0, sampleIndex);
Transform cameraMotion;
if (gui)
cameraMotion = renderFromCamera * gui->GetCameraTransform() * cameraFromRender;
GenerateCameraRays(y0, cameraMotion, sampleIndex);
Do(
"Update camera ray stats",
PBRT_CPU_GPU_LAMBDA() { stats->cameraRays += cameraRayQueue->Size(); });
......@@ -396,7 +412,7 @@ Float WavefrontPathIntegrator::Render() {
if (wavefrontDepth == maxDepth)
break;
EvaluateMaterialsAndBSDFs(wavefrontDepth);
EvaluateMaterialsAndBSDFs(wavefrontDepth, cameraMotion);
// Do immediately so that we have space for shadow rays for subsurface..
TraceShadowRays(wavefrontDepth);
......@@ -405,12 +421,48 @@ Float WavefrontPathIntegrator::Render() {
}
UpdateFilm();
// Copy updated film pixels to buffer for display
UpdateDisplay(resolution);
}
// Copy updated film pixels to buffer for the display server.
if (Options->useGPU && !Options->displayServer.empty())
UpdateDisplayRGBFromFilm(pixelBounds);
if (gui) {
RGB *rgb = gui->MapFramebuffer();
UpdateFramebufferFromFilm(pixelBounds, gui->exposure, rgb);
gui->UnmapFramebuffer();
if (gui->printCameraTransform) {
SquareMatrix<4> cfw = (Inverse(gui->GetCameraTransform()) * cameraFromWorld).GetMatrix();
Printf("Current camera transform:\nTransform [ ");
for (int i = 0; i < 16; ++i)
Printf("%f ", cfw[i % 4][i / 4]);
Printf("]\n");
std::fflush(stdout);
gui->printCameraTransform = false;
}
DisplayState state = gui->RefreshDisplay();
if (state == DisplayState::EXIT)
break;
else if (state == DisplayState::RESET) {
sampleIndex = firstSampleIndex - 1;
ParallelFor("Reset pixels", resolution.x * resolution.y,
PBRT_CPU_GPU_LAMBDA(int i) {
int x = i % resolution.x, y = i / resolution.x;
film.ResetPixel(pixelBounds.pMin + Vector2i(x, y));
});
}
}
progress.Update();
}
if (gui) {
delete gui;
gui = nullptr;
}
progress.Done();
#ifdef PBRT_BUILD_GPU_RENDERER
......@@ -650,19 +702,15 @@ void WavefrontPathIntegrator::StartDisplayThread() {
});
}
void WavefrontPathIntegrator::UpdateDisplay(Vector2i resolution) {
void WavefrontPathIntegrator::UpdateDisplayRGBFromFilm(Bounds2i pixelBounds) {
#ifdef PBRT_BUILD_GPU_RENDERER
if (Options->useGPU && !Options->displayServer.empty())
GPUParallelFor(
"Update Display RGB Buffer", maxQueueSize,
PBRT_CPU_GPU_LAMBDA(int pixelIndex) {
Point2i pPixel = pixelSampleState.pPixel[pixelIndex];
if (!InsideExclusive(pPixel, film.PixelBounds()))
return;
Point2i p(pPixel - film.PixelBounds().pMin);
displayRGB[p.x + p.y * resolution.x] = film.GetPixelRGB(pPixel);
});
Vector2i resolution = pixelBounds.Diagonal();
GPUParallelFor(
"Update Display RGB Buffer", resolution.x * resolution.y,
PBRT_CPU_GPU_LAMBDA(int index) {
Point2i p(index % resolution.x, index / resolution.x);
displayRGB[index] = film.GetPixelRGB(p + pixelBounds.pMin);
});
#endif // PBRT_BUILD_GPU_RENDERER
}
......@@ -685,4 +733,14 @@ void WavefrontPathIntegrator::StopDisplayThread() {
#endif // PBRT_BUILD_GPU_RENDERER
}
void WavefrontPathIntegrator::UpdateFramebufferFromFilm(Bounds2i pixelBounds, Float exposure,
RGB *rgb) {
Vector2i resolution = pixelBounds.Diagonal();
ParallelFor("Update framebuffer", resolution.x * resolution.y,
PBRT_CPU_GPU_LAMBDA(int index) {
Point2i p(index % resolution.x, index / resolution.x);
rgb[index] = exposure * film.GetPixelRGB(p + film.PixelBounds().pMin);
});
}
} // namespace pbrt
......@@ -26,6 +26,7 @@
namespace pbrt {
class BasicScene;
class GUI;
// WavefrontAggregate Definition
class WavefrontAggregate {
......@@ -58,9 +59,10 @@ class WavefrontPathIntegrator {
// WavefrontPathIntegrator Public Methods
Float Render();
void GenerateCameraRays(int y0, int sampleIndex);
void GenerateCameraRays(int y0, Transform movingFromcamera,
int sampleIndex);
template <typename Sampler>
void GenerateCameraRays(int y0, int sampleIndex);
void GenerateCameraRays(int y0, Transform movingFromCamera, int sampleIndex);
void GenerateRaySamples(int wavefrontDepth, int sampleIndex);
template <typename Sampler>
......@@ -75,11 +77,12 @@ class WavefrontPathIntegrator {
void HandleEscapedRays();
void HandleEmissiveIntersection();
void EvaluateMaterialsAndBSDFs(int wavefrontDepth);
void EvaluateMaterialsAndBSDFs(int wavefrontDepth, Transform movingFromCamera);
template <typename ConcreteMaterial>
void EvaluateMaterialAndBSDF(int wavefrontDepth);
void EvaluateMaterialAndBSDF(int wavefrontDepth, Transform movingFromCamera);
template <typename ConcreteMaterial, typename TextureEvaluator>
void EvaluateMaterialAndBSDF(MaterialEvalQueue *evalQueue, int wavefrontDepth);
void EvaluateMaterialAndBSDF(MaterialEvalQueue *evalQueue, Transform movingFromCamera,
int wavefrontDepth);
void UpdateFilm();
......@@ -121,10 +124,14 @@ class WavefrontPathIntegrator {
void PrefetchGPUAllocations();
#endif // PBRT_BUILD_GPU_RENDERER
// --display-server methods
void StartDisplayThread();
void UpdateDisplay(Vector2i resolution);
void UpdateDisplayRGBFromFilm(Bounds2i pixelBounds);
void StopDisplayThread();
// --interactive support
void UpdateFramebufferFromFilm(Bounds2i pixelBounds, Float exposure, RGB *rgb);
// WavefrontPathIntegrator Member Variables
bool initializeVisibleSurface;
bool haveSubsurface;
......
......@@ -25,33 +25,35 @@ namespace pbrt {
struct EvaluateMaterialCallback {
int wavefrontDepth;
WavefrontPathIntegrator *integrator;
Transform movingFromCamera;
// EvaluateMaterialCallback Public Methods
template <typename ConcreteMaterial>
void operator()() {
if constexpr (!std::is_same_v<ConcreteMaterial, MixMaterial>)
integrator->EvaluateMaterialAndBSDF<ConcreteMaterial>(wavefrontDepth);
integrator->EvaluateMaterialAndBSDF<ConcreteMaterial>(wavefrontDepth, movingFromCamera);
}
};
// WavefrontPathIntegrator Surface Scattering Methods
void WavefrontPathIntegrator::EvaluateMaterialsAndBSDFs(int wavefrontDepth) {
ForEachType(EvaluateMaterialCallback{wavefrontDepth, this}, Material::Types());
void WavefrontPathIntegrator::EvaluateMaterialsAndBSDFs(int wavefrontDepth, Transform movingFromCamera) {
ForEachType(EvaluateMaterialCallback{wavefrontDepth, this, movingFromCamera},
Material::Types());
}
template <typename ConcreteMaterial>
void WavefrontPathIntegrator::EvaluateMaterialAndBSDF(int wavefrontDepth) {
void WavefrontPathIntegrator::EvaluateMaterialAndBSDF(int wavefrontDepth, Transform movingFromCamera) {
int index = Material::TypeIndex<ConcreteMaterial>();
if (haveBasicEvalMaterial[index])
EvaluateMaterialAndBSDF<ConcreteMaterial, BasicTextureEvaluator>(
basicEvalMaterialQueue, wavefrontDepth);
basicEvalMaterialQueue, movingFromCamera, wavefrontDepth);
if (haveUniversalEvalMaterial[index])
EvaluateMaterialAndBSDF<ConcreteMaterial, UniversalTextureEvaluator>(
universalEvalMaterialQueue, wavefrontDepth);
universalEvalMaterialQueue, movingFromCamera, wavefrontDepth);
}
template <typename ConcreteMaterial, typename TextureEvaluator>
void WavefrontPathIntegrator::EvaluateMaterialAndBSDF(MaterialEvalQueue *evalQueue,
int wavefrontDepth) {
Transform movingFromCamera, int wavefrontDepth) {
// Get BSDF for items in _evalQueue_ and sample illumination
// Construct _desc_ for material/texture evaluation kernel
std::string desc = StringPrintf(
......@@ -69,7 +71,9 @@ void WavefrontPathIntegrator::EvaluateMaterialAndBSDF(MaterialEvalQueue *evalQue
Vector3f dpdx, dpdy;
Float dudx = 0, dudy = 0, dvdx = 0, dvdy = 0;
if (!GetOptions().disableTextureFiltering) {
camera.Approximate_dp_dxy(Point3f(w.pi), w.n, w.time, samplesPerPixel,
Point3f pc = movingFromCamera.ApplyInverse(Point3f(w.pi));
Normal3f nc = movingFromCamera.ApplyInverse(w.n);
camera.Approximate_dp_dxy(pc, nc, w.time, samplesPerPixel,
&dpdx, &dpdy);
Vector3f dpdu = w.dpdu, dpdv = w.dpdv;
// Estimate screen-space change in $(u,v)$
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册