Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ jobs:
setenvs: export OSL_CMAKE_FLAGS="-DSANITIZE=address -DOSL_TEST_BIG_TIMEOUT=1200"
CMAKE_BUILD_TYPE=Debug
CTEST_TEST_TIMEOUT=1200
CTEST_EXCLUSIONS="broken|noise-reg.regress|noise-gabor-reg.regress|example-.*deformer|spline-reg.regress.batched|osl-imageio"
CTEST_EXCLUSIONS="broken|noise-reg.regress|noise-gabor-reg.regress|example-.*deformer|example-tinyrender|spline-reg.regress.batched|osl-imageio"
OSL_OPTIONS=llvm_debugging_symbols=1
TESTRENDER_AA=1
OSL_TESTSUITE_SKIP_DIFF=1
Expand Down
1 change: 1 addition & 0 deletions src/cmake/testing.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,7 @@ macro (osl_add_all_tests)
error-dupes error-malformed error-serialized
example-deformer
example-batched-deformer
example-tinyrender
exit exponential
filterwidth-reg
for-reg format-reg fprintf
Expand Down
60 changes: 60 additions & 0 deletions testsuite/example-tinyrender/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Copyright Contributors to the Open Shading Language project.
# SPDX-License-Identifier: BSD-3-Clause
# https://github.com/AcademySoftwareFoundation/OpenShadingLanguage

cmake_minimum_required (VERSION 3.23)
project (tinyrender
LANGUAGES CXX)

if (NOT CMAKE_BUILD_TYPE)
set (CMAKE_BUILD_TYPE "Release")
endif ()

message (STATUS "Building ${PROJECT_NAME}...")

# Make the build area layout look like we expect
set (CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
set (CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
set (CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)

set (CMAKE_CXX_STANDARD 17 CACHE STRING "C++ standard to prefer (17, 20, etc.)")
set (CMAKE_CXX_STANDARD_REQUIRED ON)
set (CMAKE_CXX_EXTENSIONS OFF)


# Make sure we have dependencies we need
find_package (OSL REQUIRED)


# Compile the shaders with the oslc that was installed along with OSL.
set (shader_src_dir "${CMAKE_CURRENT_SOURCE_DIR}/shaders")
set (shader_oso_dir "${CMAKE_BINARY_DIR}/shaders")
file (MAKE_DIRECTORY "${shader_oso_dir}")

set (compiled_shaders)
foreach (shader matte metal glass plastic)
set (oso "${shader_oso_dir}/${shader}.oso")
add_custom_command (
OUTPUT "${oso}"
COMMAND "$<TARGET_FILE:OSL::oslc>"
-I "${shader_src_dir}" -o "${oso}" "${shader_src_dir}/${shader}.osl"
MAIN_DEPENDENCY "${shader_src_dir}/${shader}.osl"
DEPENDS "${shader_src_dir}/rtiow.h" OSL::oslc
COMMENT "Compiling OSL shader ${shader}.osl"
VERBATIM)
list (APPEND compiled_shaders "${oso}")
endforeach ()
add_custom_target (tinyrender_shaders ALL DEPENDS ${compiled_shaders})


add_executable (tinyrender
tinyrender.cpp
osl/shading.cpp)
add_dependencies (tinyrender tinyrender_shaders)
target_include_directories (tinyrender
PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}")
target_compile_definitions (tinyrender
PRIVATE TINYRENDER_SHADER_DIR="${shader_oso_dir}")
# OSL::oslexec brings in OpenImageIO and Imath.
target_link_libraries (tinyrender
PRIVATE OSL::oslexec)
155 changes: 155 additions & 0 deletions testsuite/example-tinyrender/camera.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
// Copyright Contributors to the Open Shading Language project.
// SPDX-License-Identifier: BSD-3-Clause
// https://github.com/AcademySoftwareFoundation/OpenShadingLanguage

#pragma once

#include "osl/material.h"
#include "rtweekend.h"

#include <OpenImageIO/parallel.h>

#include <cstddef>
#include <cstdint>
#include <vector>

class camera {
public:
double aspect_ratio = 1.0;
int image_width = 100;
int samples_per_pixel = 10;
int max_depth = 10;

double vfov = 90; // Vertical field of view
point3 lookfrom = point3(0, 0, 0);
point3 lookat = point3(0, 0, -1);
vec3 vup = vec3(0, 1, 0); // Camera-relative "up" direction

int height() const { return image_height; }

// Linear color. The book's gamma correction is left out, since the
// output is an EXR and those hold linear values.
const std::vector<color>& pixels() const { return frameBuffer; }

void render(const hittable& world, OSL::ShadingSystem& shadingsys)
{
initialize();

OIIO::parallel_for_chunked(
0, image_height, 0,
[this, &world, &shadingsys](int64_t jbegin, int64_t jend) {
// A ShadingContext can only be used by one thread at a time.
OSL::PerThreadInfo* thread_info
= shadingsys.create_thread_info();
OSL::ShadingContext* ctx = shadingsys.get_context(thread_info);

for (int j = static_cast<int>(jbegin); j < jend; j++) {
// Reseed per row, so the image is the same for any number
// of threads.
seed_random_generator(static_cast<unsigned int>(j));

for (int i = 0; i < image_width; i++) {
color pixel_color(0, 0, 0);
for (int sample = 0; sample < samples_per_pixel;
sample++) {
ray r = get_ray(i, j);
pixel_color += ray_color(r, max_depth, world, *ctx);
}
frameBuffer[static_cast<std::size_t>(j) * image_width
+ i] = pixel_samples_scale * pixel_color;
}
}

shadingsys.release_context(ctx);
shadingsys.destroy_thread_info(thread_info);
});
}

private:
int image_height = 0;
double pixel_samples_scale = 0;
point3 center;
point3 pixel00_loc;
vec3 pixel_delta_u;
vec3 pixel_delta_v;
vec3 u, v, w; // Camera frame basis vectors
std::vector<color> frameBuffer;

void initialize()
{
image_height = static_cast<int>(image_width / aspect_ratio);
image_height = (image_height < 1) ? 1 : image_height;

frameBuffer.resize(static_cast<std::size_t>(image_width)
* image_height);
pixel_samples_scale = 1.0 / samples_per_pixel;

center = lookfrom;

// Determine viewport dimensions.
double focal_length = (lookfrom - lookat).length();
double theta = degrees_to_radians(vfov);
double h = std::tan(theta / 2);
double viewport_height = 2 * h * focal_length;
double viewport_width = viewport_height
* (static_cast<double>(image_width)
/ image_height);

// Calculate the u, v, w unit basis vectors for the camera frame.
w = unit_vector(lookfrom - lookat);
u = unit_vector(cross(vup, w));
v = cross(w, u);

vec3 viewport_u = viewport_width * u;
vec3 viewport_v = viewport_height * -v;

pixel_delta_u = viewport_u / image_width;
pixel_delta_v = viewport_v / image_height;

point3 viewport_upper_left = center - (focal_length * w)
- viewport_u / 2 - viewport_v / 2;
pixel00_loc = viewport_upper_left
+ 0.5 * (pixel_delta_u + pixel_delta_v);
}

ray get_ray(int i, int j) const
{
// A ray from the camera center to a random point around pixel i, j.
vec3 offset = sample_square();
point3 pixel_sample = pixel00_loc + ((i + offset.x()) * pixel_delta_u)
+ ((j + offset.y()) * pixel_delta_v);

return ray(center, pixel_sample - center);
}

static vec3 sample_square()
{
// A random point in the [-.5,-.5]-[+.5,+.5] unit square.
double x = random_double() - 0.5;
double y = random_double() - 0.5;
return vec3(x, y, 0);
}

color ray_color(const ray& r, int depth, const hittable& world,
OSL::ShadingContext& ctx) const
{
// Past the bounce limit, no more light is gathered.
if (depth <= 0)
return color(0, 0, 0);

hit_record rec;

if (world.hit(r, interval(0.001, infinity), rec)) {
ray scattered;
color attenuation;
if (rec.mat->scatter(r, rec, ctx, attenuation, scattered))
return attenuation
* ray_color(scattered, depth - 1, world, ctx);
return color(0, 0, 0);
}

vec3 unit_direction = unit_vector(r.direction());
double a = 0.5 * (unit_direction.y() + 1.0);
return (1.0 - a) * color(1.0, 1.0, 1.0) + a * color(0.5, 0.7, 1.0);
}
};
137 changes: 137 additions & 0 deletions testsuite/example-tinyrender/osl/material.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
// Copyright Contributors to the Open Shading Language project.
// SPDX-License-Identifier: BSD-3-Clause
// https://github.com/AcademySoftwareFoundation/OpenShadingLanguage

#pragma once

#include "rtweekend.h"
#include "shading.h"

#include <OSL/oslexec.h>

#include <cstring>
#include <memory>
#include <utility>

// The book has one material class each for matte, metal and glass. Here a
// single class covers all three: the OSL shader it holds decides which.
class material {
public:
material(OSL::ShadingSystem& shadingsys, OSL::ShaderGroupRef group)
: shadingsys(shadingsys), group(std::move(group))
{
}

[[nodiscard]] bool scatter(const ray& r_in, const hit_record& rec,
OSL::ShadingContext& ctx, color& attenuation,
ray& scattered) const
{
vec3 I = unit_vector(r_in.direction());

OSL::ShaderGlobals sg;
globals_from_hit(sg, I, rec);

// 0 is fine for the thread index: it is only read by renderers that
// collect shader messages per thread, and this one does not.
if (!shadingsys.execute(ctx, *group, /*thread_index=*/0,
/*shadeindex=*/0, sg, nullptr, nullptr))
return false;

if (sg.Ci == nullptr)
return false;

vec3 wi;
if (!sample_closure(sg.Ci, I, wi, attenuation))
return false;

scattered = ray(rec.p, wi);
return true;
}

private:
OSL::ShadingSystem& shadingsys;
OSL::ShaderGroupRef group;

// The book's vec3 holds doubles, and OSL's Vec3 holds floats.
static OSL::Vec3 to_osl(const vec3& v)
{
return OSL::Vec3(static_cast<float>(v.x()), static_cast<float>(v.y()),
static_cast<float>(v.z()));
}

static void globals_from_hit(OSL::ShaderGlobals& sg, const vec3& I,
const hit_record& rec)
{
// Imath vectors start uninitialized, so `{}` would not zero this.
std::memset(static_cast<void*>(&sg), 0, sizeof(sg));

sg.P = to_osl(rec.p);
sg.I = to_osl(I);

// rec.normal already faces the ray, which is what OSL wants.
sg.N = sg.Ng = to_osl(rec.normal);
sg.backfacing = rec.front_face ? 0 : 1;

sg.u = static_cast<float>(rec.u);
sg.v = static_cast<float>(rec.v);
sg.dPdu = to_osl(rec.dpdu);
sg.dPdv = to_osl(rec.dpdv);

// Only light shaders would read this, and there are none.
sg.surfacearea = 1;
}
};



// Each material is a shader group holding one surface shader.

inline std::shared_ptr<material>
make_matte(OSL::ShadingSystem& shadingsys, const OSL::Color3& albedo, bool& ok)
{
OSL::ShaderGroupRef group = shadingsys.ShaderGroupBegin("matte");
ok &= shadingsys.Parameter(*group, "Cs", OSL::TypeColor, &albedo);
ok &= shadingsys.Shader(*group, "surface", "matte", "layer");
ok &= shadingsys.ShaderGroupEnd(*group);
return std::make_shared<material>(shadingsys, group);
}



inline std::shared_ptr<material>
make_metal(OSL::ShadingSystem& shadingsys, const OSL::Color3& albedo,
float fuzz, bool& ok)
{
OSL::ShaderGroupRef group = shadingsys.ShaderGroupBegin("metal");
ok &= shadingsys.Parameter(*group, "Cs", OSL::TypeColor, &albedo);
ok &= shadingsys.Parameter(*group, "fuzz", OSL::TypeFloat, &fuzz);
ok &= shadingsys.Shader(*group, "surface", "metal", "layer");
ok &= shadingsys.ShaderGroupEnd(*group);
return std::make_shared<material>(shadingsys, group);
}



inline std::shared_ptr<material>
make_glass(OSL::ShadingSystem& shadingsys, float ior, bool& ok)
{
OSL::ShaderGroupRef group = shadingsys.ShaderGroupBegin("glass");
ok &= shadingsys.Parameter(*group, "ior", OSL::TypeFloat, &ior);
ok &= shadingsys.Shader(*group, "surface", "glass", "layer");
ok &= shadingsys.ShaderGroupEnd(*group);
return std::make_shared<material>(shadingsys, group);
}



// An addition to the book's three materials, to test a sum of closures.
inline std::shared_ptr<material>
make_plastic(OSL::ShadingSystem& shadingsys, const OSL::Color3& albedo,
bool& ok)
{
OSL::ShaderGroupRef group = shadingsys.ShaderGroupBegin("plastic");
ok &= shadingsys.Parameter(*group, "Cs", OSL::TypeColor, &albedo);
ok &= shadingsys.Shader(*group, "surface", "plastic", "layer");
ok &= shadingsys.ShaderGroupEnd(*group);
return std::make_shared<material>(shadingsys, group);
}
Loading
Loading