diff --git a/src/cmake/testing.cmake b/src/cmake/testing.cmake index fd30c4199..c4780abb5 100644 --- a/src/cmake/testing.cmake +++ b/src/cmake/testing.cmake @@ -355,7 +355,9 @@ macro (osl_add_all_tests) reparam reparam-arrays reparam-string testoptix-reparam render-background render-bumptest render-bunny - render-cornell render-furnace-diffuse + render-cornell + render-displacement + render-furnace-diffuse render-mx-furnace-burley-diffuse render-mx-furnace-oren-nayar render-mx-furnace-sheen diff --git a/src/testrender/simpleraytracer.cpp b/src/testrender/simpleraytracer.cpp index 7ad197547..49b51e1b2 100644 --- a/src/testrender/simpleraytracer.cpp +++ b/src/testrender/simpleraytracer.cpp @@ -471,9 +471,26 @@ SimpleRaytracer::parse_scene_xml(const std::string& scenefile) } } shadingsys->ShaderGroupEnd(*group); - if (name_attr) - shadermap.emplace(name_attr.value(), int(shaders().size())); - shaders().emplace_back(group); + if (name_attr) { + if (auto it = shadermap.find(name); it != shadermap.end()) { + int shaderID = it->second; + if (shaderID >= 0 && shaderID < int(shaders().size())) { + fprintf(stderr, "Updating shader %d - %s\n", shaderID, shadertype.c_str()); + // we already have a material under this name, + Material& m = shaders()[shaderID]; + // TODO: could we query the shadertype directly from the ShaderGroup? + if (shadertype == "displacement") + m.disp = group; + else if (shadertype == "surface") + m.surf = group; + // skip the rest which would add a new material + continue; + } + } else { + shadermap.emplace(name, int(shaders().size())); + } + } + shaders().emplace_back(Material { group, nullptr }); m_shader_is_light.emplace_back( is_light_attr ? strtobool(is_light_attr.value()) : false); } else { @@ -925,7 +942,7 @@ SimpleRaytracer::eval_background(const Dual2& dir, ShadingContext* ctx, if (bounce >= 0) sg.raytype = bounce > 0 ? Ray::DIFFUSE : Ray::CAMERA; #ifndef __CUDACC__ - shadingsys->execute(*ctx, *m_shaders[backgroundShaderID], sg); + shadingsys->execute(*ctx, *m_shaders[backgroundShaderID].surf, sg); #else alignas(8) char closure_pool[256]; execute_shader(sg, render_params.bg_id, closure_pool); @@ -1000,11 +1017,11 @@ SimpleRaytracer::subpixel_radiance(float x, float y, Sampler& sampler, int shaderID = scene.shaderid(hit.id); #ifndef __CUDACC__ - if (shaderID < 0 || !m_shaders[shaderID]) + if (shaderID < 0 || !m_shaders[shaderID].surf) break; // no shader attached? done // execute shader and process the resulting list of closures - shadingsys->execute(*ctx, *m_shaders[shaderID], sg); + shadingsys->execute(*ctx, *m_shaders[shaderID].surf, sg); #else if (shaderID < 0) break; // no shader attached? done @@ -1116,7 +1133,7 @@ SimpleRaytracer::subpixel_radiance(float x, float y, Sampler& sampler, sample.u, sample.v); #ifndef __CUDACC__ // execute the light shader (for emissive closures only) - shadingsys->execute(*ctx, *m_shaders[shaderID], + shadingsys->execute(*ctx, *m_shaders[shaderID].surf, light_sg); #else execute_shader(light_sg, shaderID, light_closure_pool); @@ -1205,6 +1222,122 @@ SimpleRaytracer::prepare_render() backgroundResolution = 0; } + bool have_displacement = false; + for (const Material& m : shaders()) { + if (m.disp) { + have_displacement = true; + break; + } + } + if (have_displacement) { + errhandler().infofmt("Evaluating displacement shaders"); + // Loop through all triangles and run displacement shader if there is one + // or copy the input point if there is none + std::vector disp_verts(scene.verts.size(), Vec3(0, 0, 0)); + std::vector valance(scene.verts.size(), 0); // number of times each vertex has been displaced + + OSL::PerThreadInfo* thread_info = shadingsys->create_thread_info(); + ShadingContext* ctx = shadingsys->get_context(thread_info); + + bool has_smooth_normals = false; + for (int primID = 0, nprims = scene.triangles.size(); primID < nprims; primID++) { + Vec3 p[3], n[3]; + Vec2 uv[3]; + + p[0] = scene.verts[scene.triangles[primID].a]; + p[1] = scene.verts[scene.triangles[primID].b]; + p[2] = scene.verts[scene.triangles[primID].c]; + + valance[scene.triangles[primID].a]++; + valance[scene.triangles[primID].b]++; + valance[scene.triangles[primID].c]++; + + int shaderID = scene.shaderid(primID); + if (shaderID < 0 || !m_shaders[shaderID].disp) { + disp_verts[scene.triangles[primID].a] += p[0]; + disp_verts[scene.triangles[primID].b] += p[1]; + disp_verts[scene.triangles[primID].c] += p[2]; + continue; + } + + + Vec3 Ng = (p[0] - p[1]).cross(p[0] - p[2]); + float area = 0.5f * Ng.length(); + Ng = Ng.normalize(); + if (scene.n_triangles[primID].a >= 0) { + n[0] = scene.normals[scene.n_triangles[primID].a]; + n[1] = scene.normals[scene.n_triangles[primID].b]; + n[2] = scene.normals[scene.n_triangles[primID].c]; + has_smooth_normals = true; + } else { + n[0] = n[1] = n[2] = Ng; + } + + if (scene.uv_triangles[primID].a >= 0) { + uv[0] = scene.uvs[scene.uv_triangles[primID].a]; + uv[1] = scene.uvs[scene.uv_triangles[primID].b]; + uv[2] = scene.uvs[scene.uv_triangles[primID].c]; + } else { + uv[0] = uv[1] = uv[2] = Vec2(0, 0); + } + + // displace each vertex + for (int i = 0; i < 3; i++) { + ShaderGlobals sg = {}; + sg.P = p[i]; + sg.Ng = Ng; + sg.N = n[i]; + sg.u = uv[i].x; + sg.v = uv[i].y; + sg.I = (p[i] - camera.eye).normalize(); + sg.surfacearea = area; + sg.renderstate = &sg; + + shadingsys->execute(*ctx, *m_shaders[shaderID].disp, sg); + + p[i] = sg.P; + } + disp_verts[scene.triangles[primID].a] += p[0]; + disp_verts[scene.triangles[primID].b] += p[1]; + disp_verts[scene.triangles[primID].c] += p[2]; + } + + // release context + shadingsys->release_context(ctx); + shadingsys->destroy_thread_info(thread_info); + + // average each vertex by the number of times it was displaced + for (int i = 0, n = scene.verts.size(); i < n; i++) { + if (valance[i] > 0) + disp_verts[i] /= float(valance[i]); + else + disp_verts[i] = scene.verts[i]; + } + // replace old data with the new + scene.verts = std::move(disp_verts); + + if (has_smooth_normals) { + // Recompute the vertex normals (if we had some) + std::vector disp_normals(scene.normals.size(), Vec3(0, 0, 0)); + for (int primID = 0, nprims = scene.triangles.size(); primID < nprims; primID++) { + if (scene.n_triangles[primID].a >= 0) { + Vec3 p[3]; + p[0] = scene.verts[scene.triangles[primID].a]; + p[1] = scene.verts[scene.triangles[primID].b]; + p[2] = scene.verts[scene.triangles[primID].c]; + // don't normalize to weight by area + Vec3 Ng = (p[0] - p[1]).cross(p[0] - p[2]); + disp_normals[scene.n_triangles[primID].a] += Ng; + disp_normals[scene.n_triangles[primID].b] += Ng; + disp_normals[scene.n_triangles[primID].c] += Ng; + } + } + for (Vec3& n : disp_normals) + n = n.normalize(); + scene.normals = std::move(disp_normals); + } + } + // build bvh and prepare triangles scene.prepare(errhandler()); prepare_lights(); @@ -1241,7 +1374,7 @@ SimpleRaytracer::prepare_lights() // collect all light emitting triangles for (unsigned t = 0, n = scene.num_prims(); t < n; t++) { int shaderID = scene.shaderid(t); - if (shaderID < 0 || !m_shaders[shaderID]) + if (shaderID < 0 || !m_shaders[shaderID].surf) continue; // no shader attached if (m_shader_is_light[shaderID]) m_lightprims.emplace_back(t); diff --git a/src/testrender/simpleraytracer.h b/src/testrender/simpleraytracer.h index 09b31380c..3778967a8 100644 --- a/src/testrender/simpleraytracer.h +++ b/src/testrender/simpleraytracer.h @@ -19,6 +19,12 @@ OSL_NAMESPACE_ENTER +struct Material { + ShaderGroupRef surf; + ShaderGroupRef disp; +}; + +using MaterialVec = std::vector; class SimpleRaytracer : public RendererServices { public: @@ -89,7 +95,7 @@ class SimpleRaytracer : public RendererServices { virtual void finalize_pixel_buffer() {} // ShaderGroupRef storage - std::vector& shaders() { return m_shaders; } + MaterialVec& shaders() { return m_shaders; } OIIO::ErrorHandler& errhandler() const { return *m_errhandler; } @@ -122,7 +128,7 @@ class SimpleRaytracer : public RendererServices { int rr_depth = 5; float show_albedo_scale = 0.0f; int show_globals = 0; - std::vector m_shaders; + MaterialVec m_shaders; std::vector m_shader_is_light; std::vector m_mesh_surfacearea; // surface area of all triangles in each mesh (one entry per mesh) diff --git a/testsuite/render-displacement/disp.osl b/testsuite/render-displacement/disp.osl new file mode 100644 index 000000000..cd81a7da6 --- /dev/null +++ b/testsuite/render-displacement/disp.osl @@ -0,0 +1,24 @@ +// Copyright Contributors to the Open Shading Language project. +// SPDX-License-Identifier: BSD-3-Clause +// https://github.com/AcademySoftwareFoundation/OpenShadingLanguage + + +displacement +disp +( + float scale = 1, + float amplitude = 1, + int octaves = 4, + color Cs = 1 + ) +{ + float amount = 0; + point b = P * scale; + float a = amplitude; + for (int i = 0; i < octaves; i++) { + amount += a * noise(b); + b *= 2.0; + a *= 0.5; + } + P += amount * N; +} diff --git a/testsuite/render-displacement/emitter.osl b/testsuite/render-displacement/emitter.osl new file mode 100644 index 000000000..b026b63e0 --- /dev/null +++ b/testsuite/render-displacement/emitter.osl @@ -0,0 +1,23 @@ +// Copyright Contributors to the Open Shading Language project. +// SPDX-License-Identifier: BSD-3-Clause +// https://github.com/AcademySoftwareFoundation/OpenShadingLanguage + + +surface +emitter + [[ string description = "Lambertian emitter material" ]] +( + float power = 1 + [[ string description = "Total power of the light", + float UImin = 0 ]], + color Cs = 1 + [[ string description = "Base color", + float UImin = 0, float UImax = 1 ]] + ) +{ + // Because emission() expects a weight in radiance, we must convert by dividing + // the power (in Watts) by the surface area and the factor of PI implied by + // uniform emission over the hemisphere. N.B.: The total power is BEFORE Cs + // filters the color! + Ci = (power / (M_PI * surfacearea())) * Cs * emission(); +} diff --git a/testsuite/render-displacement/matte.osl b/testsuite/render-displacement/matte.osl new file mode 100644 index 000000000..a8c6f187e --- /dev/null +++ b/testsuite/render-displacement/matte.osl @@ -0,0 +1,19 @@ +// Copyright Contributors to the Open Shading Language project. +// SPDX-License-Identifier: BSD-3-Clause +// https://github.com/AcademySoftwareFoundation/OpenShadingLanguage + + +surface +matte + [[ string description = "Lambertian diffuse material" ]] +( + float Kd = 1 + [[ string description = "Diffuse scaling", + float UImin = 0, float UIsoftmax = 1 ]], + color Cs = 1 + [[ string description = "Base color", + float UImin = 0, float UImax = 1 ]] + ) +{ + Ci = Kd * Cs * diffuse (N); +} diff --git a/testsuite/render-displacement/ref/out.exr b/testsuite/render-displacement/ref/out.exr new file mode 100644 index 000000000..fb504358a Binary files /dev/null and b/testsuite/render-displacement/ref/out.exr differ diff --git a/testsuite/render-displacement/run.py b/testsuite/render-displacement/run.py new file mode 100755 index 000000000..9552e6995 --- /dev/null +++ b/testsuite/render-displacement/run.py @@ -0,0 +1,10 @@ +#!/usr/bin/env python + +# Copyright Contributors to the Open Shading Language project. +# SPDX-License-Identifier: BSD-3-Clause +# https://github.com/AcademySoftwareFoundation/OpenShadingLanguage + +failthresh = 0.01 +failpercent = 1 +outputs = [ "out.exr" ] +command = testrender("-r 256 256 -aa 8 scene.xml out.exr") diff --git a/testsuite/render-displacement/scene.xml b/testsuite/render-displacement/scene.xml new file mode 100644 index 000000000..18681e763 --- /dev/null +++ b/testsuite/render-displacement/scene.xml @@ -0,0 +1,28 @@ + + + + color Cs 0.35 0.35 0.35; shader matte layer1; + + float scale 4; + float amplitude 0.3; + int octaves 8; + shader disp layer1; + + + + + color Cs 0.75 0.25 0.25; shader matte layer1; + + + color Cs 0.25 0.25 0.75; shader matte layer1; + + + color Cs 0.25 0.25 0.25; shader matte layer1; + + + + + + float power 100; shader emitter layer1 + +